减少误报的最佳策略:谷歌新的卫星图像对象检测 API

Posted

技术标签:

【中文标题】减少误报的最佳策略:谷歌新的卫星图像对象检测 API【英文标题】:Best strategy to reduce false positives: Google's new Object Detection API on Satellite Imagery 【发布时间】:2018-01-21 19:39:13 【问题描述】:

我正在设置新的Tensorflow Object Detection API 以在大面积卫星图像中查找小物体。它工作得很好——它找到了我想要的所有 10 个对象,但我也得到了 50-100 个误报[看起来有点像目标对象但不是的东西]。

我正在使用'pets' tutorial 中的sample config 来微调他们提供的faster_rcnn_resnet101_coco 模型。我从小处着手,我的对象只有 100 个训练示例(只有 1 个类)。我的验证集中有 50 个示例。每个示例都是一个 200x200 像素的图像,中间有一个标记对象 (~40x40)。我训练直到我的精度和损失曲线达到稳定水平。

我对使用深度学习进行对象检测比较陌生。提高精度的最佳策略是什么?例如硬负挖掘?增加我的训练数据集大小?我还没有尝试他们提供的最准确的模型faster_rcnn_inception_resnet_v2_atrous_coco,因为我想保持一些速度,但如果需要的话会这样做。

硬否定挖掘似乎是一个合乎逻辑的步骤。如果您同意,我该如何实现它 w.r.t 为我的训练数据集设置 tfrecord 文件?假设我为 50-100 个误报中的每一个制作 200x200 图像:

我是否为每个创建“注释”xml 文件,没有“对象”元素? ...或者我应该将这些硬底片标记为第二类吗? 如果我的训练集中有 100 个负数对 100 个正数 - 这是一个健康的比例吗?我可以包含多少个否定词?

【问题讨论】:

【参考方案1】:

我认为我正在经历相同或接近的场景,值得与您分享。

我设法通过将不带注释的图像传递给培训师来解决它。

在我的场景中,我正在构建一个项目来实时检测客户产品的装配故障。 我通过对具有明确负模式的组件(例如,有螺丝开/关的螺丝(只是孔))使用检测+分类,并且只对没有的东西进行检测,成功地获得了非常稳健的结果(对于生产环境)负片(例如可以放在任何地方的胶带)。

在系统上,用户必须录制 2 个视频,一个包含正面场景,另一个包含负面场景(或 n 个视频,包含 n 个正面和负面模式,以便算法可以推广)。

经过一段时间的测试,我发现如果我注册只检测磁带,则检测器会给出非常有把握的 (0.999) 磁带误报检测。它正在学习插入磁带而不是磁带本身的模式。当我有另一个组件时(比如在它的负片格式上拧一个螺丝),我在没有明确意识到的情况下传递了磁带的负片图案,所以 FP 没有发生。

所以我发现,在这种情况下,我必须在没有磁带的情况下传递图像,以便区分磁带和无磁带。

我考虑了两种替代方案进行实验并尝试解决此问题:

    训练传递大量没有任何注释的图像(占我所有负样本的 10%)以及我有真实注释的所有图像。 在我没有注释的图像上,我创建了一个带有虚拟标签的虚拟注释,因此我可以强制检测器使用该图像进行训练(从而学习无胶带模式)。稍后,当得到虚拟预测时,忽略它们。

得出的结论是,这两种选择都非常适合我的方案。 训练损失有点混乱,但是对于我非常受控的场景(系统的摄像头有自己的盒子和照明来减少变量),预测具有鲁棒性。

我必须对第一个替代方案进行两个小修改:

    所有没有任何注释的图像我通过了一个虚拟注释 (class=None, xmin/ymin/xmax/ymax=-1) 在生成 tfrecord 文件时,我使用此信息(在本例中为 xmin == -1)为示例添加一个空列表:
def create_tf_example(group, path, label_map):
    with tf.gfile.GFile(os.path.join(path, ''.format(group.filename)), 'rb') as fid:
        encoded_jpg = fid.read()
    encoded_jpg_io = io.BytesIO(encoded_jpg)
    image = Image.open(encoded_jpg_io)
    width, height = image.size

    filename = group.filename.encode('utf8')
    image_format = b'jpg'

    xmins = []
    xmaxs = []
    ymins = []
    ymaxs = []
    classes_text = []
    classes = []

    for index, row in group.object.iterrows():
        if not pd.isnull(row.xmin):
            if not row.xmin == -1:
                xmins.append(row['xmin'] / width)
                xmaxs.append(row['xmax'] / width)
                ymins.append(row['ymin'] / height)
                ymaxs.append(row['ymax'] / height)
                classes_text.append(row['class'].encode('utf8'))
                classes.append(label_map[row['class']])

    tf_example = tf.train.Example(features=tf.train.Features(feature=
        'image/height': dataset_util.int64_feature(height),
        'image/width': dataset_util.int64_feature(width),
        'image/filename': dataset_util.bytes_feature(filename),
        'image/source_id': dataset_util.bytes_feature(filename),
        'image/encoded': dataset_util.bytes_feature(encoded_jpg),
        'image/format': dataset_util.bytes_feature(image_format),
        'image/object/bbox/xmin': dataset_util.float_list_feature(xmins),
        'image/object/bbox/xmax': dataset_util.float_list_feature(xmaxs),
        'image/object/bbox/ymin': dataset_util.float_list_feature(ymins),
        'image/object/bbox/ymax': dataset_util.float_list_feature(ymaxs),
        'image/object/class/text': dataset_util.bytes_list_feature(classes_text),
        'image/object/class/label': dataset_util.int64_list_feature(classes),
    ))
    return tf_example

部分训练进度:

目前我正在使用 tensorflow 对象检测和 tensorflow==1.15,使用 faster_rcnn_resnet101_coco.config。

希望它能解决某人的问题,因为我在互联网上没有找到任何解决方案。我读到很多人告诉我,faster_rcnn 不适合用于减少 FP 的负面训练,但我的测试证明相反。

【讨论】:

@denisbf411:感谢您与我们分享您的观察结果。我为没有真实情况的图像分配了“0”边界框坐标和“0”类(反例)。您用“-1”分配坐标是否有任何具体原因?另外,您是否观察到负图像的损失,并且您是否反向传播了负图像的损失? @marc_s 不,这只是一种懒惰的方式来标记数据,以使 tfrecord 生成器脚本生成空样本(它不会触发 xmin == -1 因此注释最终会出现在空列表中)。这个脚本只是https://github.com/Tony607/object_detection_demo/blob/master/generate_tfrecord.py的修改版【参考方案2】:

我最近在工作中重新审视了这个主题,并认为我会更新我目前的学习情况,以供将来访问的任何人参考。

话题出现在Tensorflow's Models repo issue tracker。 SSD 允许您设置要挖掘的负例:正例的比率 (max_negatives_per_positive: 3),但您也可以为没有正例的图像设置最小数量 (min_negatives_per_image: 3)。这两个都在 model-ssd-loss 配置部分中定义。

也就是说,我在 Faster-RCNN 的模型配置中看不到相同的选项。问题中提到models/research/object_detection/core/balanced_positive_negative_sampler.py包含用于Faster-RCNN的代码。

本期讨论的另一个选项是专门为相似对象创建第二个类。在训练期间,模型将尝试学习类别差异,这将有助于达到您的目的。

最后,我在 Filter Amplifier Networks (FAN) 上看到了这个 article,它可能对您在航拍图像方面的工作有所帮助。

================================================ =====================

以下论文描述了与您描述的相同目的的硬负挖掘: Training Region-based Object Detectors with Online Hard Example Mining

在第 3.1 节中,他们描述了使用前景和背景类:

背景投资回报率。一个区域被标记为背景(bg),如果它的最大值 具有基本事实的 IoU 在区间 [bg lo, 0.5) 中。一个较低的 FRCN 和 SPPnet 都使用 bg lo = 0.1 的阈值,并且是 在[14]中假设粗略地近似硬负挖掘;这 假设与基本事实有一些重叠的区域是 更有可能是令人困惑或困难的。我们在 5.4 节中展示 尽管这种启发式有助于收敛和检测精度, 它是次优的,因为它忽略了一些不常见但重要的, 困难的背景区域。我们的方法去除了 bg lo 阈值。

其实是参考了这篇论文,其思想用在了Tensorflow的object detection loss.py代码中进行硬挖掘:

class HardExampleMiner(object):
"""Hard example mining for regions in a list of images.
Implements hard example mining to select a subset of regions to be
back-propagated. For each image, selects the regions with highest losses,
subject to the condition that a newly selected region cannot have
an IOU > iou_threshold with any of the previously selected regions.
This can be achieved by re-using a greedy non-maximum suppression algorithm.
A constraint on the number of negatives mined per positive region can also be
enforced.
Reference papers: "Training Region-based Object Detectors with Online
Hard Example Mining" (CVPR 2016) by Srivastava et al., and
"SSD: Single Shot MultiBox Detector" (ECCV 2016) by Liu et al.
"""

根据您的模型配置文件,HardMinerObject 由 loss_builder.py 在这段代码中返回:

def build_hard_example_miner(config,
                            classification_weight,
                            localization_weight):
"""Builds hard example miner based on the config.
Args:
    config: A losses_pb2.HardExampleMiner object.
    classification_weight: Classification loss weight.
    localization_weight: Localization loss weight.
Returns:
    Hard example miner.
"""
loss_type = None
if config.loss_type == losses_pb2.HardExampleMiner.BOTH:
    loss_type = 'both'
if config.loss_type == losses_pb2.HardExampleMiner.CLASSIFICATION:
    loss_type = 'cls'
if config.loss_type == losses_pb2.HardExampleMiner.LOCALIZATION:
    loss_type = 'loc'

max_negatives_per_positive = None
num_hard_examples = None
if config.max_negatives_per_positive > 0:
    max_negatives_per_positive = config.max_negatives_per_positive
if config.num_hard_examples > 0:
    num_hard_examples = config.num_hard_examples
hard_example_miner = losses.HardExampleMiner(
    num_hard_examples=num_hard_examples,
    iou_threshold=config.iou_threshold,
    loss_type=loss_type,
    cls_loss_weight=classification_weight,
    loc_loss_weight=localization_weight,
    max_negatives_per_positive=max_negatives_per_positive,
    min_negatives_per_image=config.min_negatives_per_image)
return hard_example_miner

由model_builder.py返回并由train.py调用。所以基本上,在我看来,简单地生成真正的正标签(使用 LabelImg 或 RectLabel 之类的工具)就足以让训练算法在同一图像中找到硬底片。相关问题给出了很好的walkthrough。

如果您想输入没有真正正面的数据(即图像中不应分类任何内容),只需将负面图像添加到您的 tfrecord 中,不带边界框。

【讨论】:

"如果您想输入没有真正正面的数据(即图像中不应该分类任何内容),只需将负面图像添加到您的 tfrecord 中,没有边界框。"您认为这样做与为相似对象创建一个指定的类并将它们标记为不是我的对象之间有区别吗?你会推荐哪一个?我目前被确切讨论的问题所困扰。 @cheekybanana 我认为您为相似类添加困难示例的方法应该会更好一些。如果您只是将其作为一个软示例(即让算法自行解决),您可能会在最终模型中获得更多噪音。你试一试了吗?你的结果是什么? 请评论您的结果@cheekybanana。不必精确。

以上是关于减少误报的最佳策略:谷歌新的卫星图像对象检测 API的主要内容,如果未能解决你的问题,请参考以下文章

在鼓励使用特殊字符的密码等字段的 REST API 中,避免 WAF 误报的最佳编码实践是啥

数据缩至1/5000,模型准确率却翻倍,谷歌新“蒸馏法”火了 | ICLR&NeurIPS

Windows 上的 Python 脚本分发 - 避免病毒误报的选项?

开源无止境,谷歌新的移动UI框架—Flutter落地

使用 OpenCV 和 C++ 进行全身检测

要变天了,谷歌新政策,真的对华为“不讲武德”吗?