关于人工智能:深度学习应用篇计算机视觉目标检测4边界框锚框交并比非极大值抑制NMSSoftNMS

0次阅读

共计 18797 个字符,预计需要花费 47 分钟才能阅读完成。

深度学习利用篇 - 计算机视觉 - 指标检测[4]:综述、边界框 bounding box、锚框(Anchor box)、交并比、非极大值克制 NMS、SoftNMS

1. 指标检测综述

对计算机而言,可能“看到”的是图像被编码之后的数字,它很难了解高层语义概念,比方图像或者视频帧中呈现的指标是人还是物体,更无奈定位指标呈现在图像中哪个区域。指标检测的次要目标是让计算机能够自动识别图片或者视频帧中所有指标的类别,并在该指标四周绘制边界框,标示出每个指标的地位,如 图 1 所示。

图 1 图像分类和指标检测示意图

  • 图 1(a)是图像分类工作,只需对这张图片进行类别辨认。
  • 图 1(b)是指标检测工作,不仅要辨认出这一张图片中的类别为斑马,还要标出图中斑马的地位。

1.1 利用场景

图 2 所示,现在的指标检测不管在日常生活中还是工业生产中都有着十分多的利用场景。

  • 生产娱乐:智能手机的人脸解锁以及领取 APP 中的人脸领取;自动售货机应用的商品检测;视频网站中图片、视频审核等;
  • 智慧交通:主动驾驶中的行人检测、车辆检测、红绿灯检测等;
  • 工业生产:工业生产中的整机计数、缺点检测;设施巡检场景下的设施状态监控;厂区中的烟火检测、安全帽检测等;
  • 智慧医疗:眼底、肺部等器官病变检测;新冠疫情中的口罩检测等。

图 2 指标检测利用场景

1.2 指标检测倒退历程

在图像分类工作中,咱们会先应用卷积神经网络提取图像特色,而后再用这些特色预测分类概率,依据训练样本标签建设起分类损失函数,开启端到端的训练,如 图 3 所示。

图 3 图像分类流程示意图

但对于指标检测问题,依照 图 3 的流程则行不通。因为在对整张图提取特色的过程中无奈体现出不同指标之间的区别,最终也就没法别离标示出每个物体所在的地位。

为了解决这个问题,联合图片分类工作获得的成功经验,咱们能够将指标检测工作进行拆分。假如咱们应用某种形式在输出图片上生成一系列可能蕴含物体的区域,这些区域称为候选区域。对于每个候选区域,能够独自当成一幅图像来对待,应用图像分类模型对候选区域进行分类,看它属于哪个类别或者背景(即不蕴含任何物体的类别)。上一节咱们曾经学过如何解决图像分类工作,应用卷积神经网络对一幅图像进行分类不再是一件艰难的事件。

那么,当初问题的要害就是如何产生候选区域?比方咱们能够应用穷举法来产生候选区域,如 图 4 所示。

图 4 候选区域

A 为图像上的某个像素点,B 为 A 右下方另外一个像素点,A、B 两点能够确定一个矩形框,记作 AB。

  • 如图 4(a)所示:A 在图片左上角地位,B 遍历除 A 之外的所有地位,生成矩形框 $A_1B_1, …, A_1B_n, …$
  • 如图 4(b)所示:A 在图片两头某个地位,B 遍历 A 右下方所有地位,生成矩形框 $A_kB_1, …, A_kB_n, …$

当 A 遍历图像上所有像素点,B 则遍历它右下方所有的像素点,最终生成的矩形框汇合 ${A_iB_j}$ 将会蕴含图像上所有能够抉择的区域。

只有咱们对每个候选区域的分类足够的精确,则肯定能找到跟理论物体足够靠近的区域来。穷举法兴许能失去正确的预测后果,但其计算量也是十分微小的,其所生成的总候选区域数目约为 $\frac{W^2 H^2}{4}$,假如 $H=W=100$,总数将会达到 $2.5 \times 10^{7}$ 个,如此多的候选区域使得这种办法简直没有什么实用性。然而通过这种形式,咱们能够看出,假如分类工作实现的足够完满,从实践上来讲检测工作也是能够解决的,亟待解决的问题是如何设计出适合的办法来产生候选区域。

科学家们开始思考,是否能够利用传统图像算法先产生候选区域,而后再用卷积神经网络对这些区域进行分类?

  • 2013 年,Ross Girshick 等人于首次将 CNN 的办法利用在指标检测工作上,他们应用传统图像算法 Selective Search 产生候选区域,获得了极大的胜利,这就是对指标检测畛域影响深远的区域卷积神经网络 (R-CNN[1]) 模型。
  • 2015 年,Ross Girshick 对此办法进行了改良,提出了 Fast R-CNN[2]模型。通过将不同区域的物体共用卷积层的计算,大大缩减了计算量,进步了处理速度,而且还引入了调整指标物体地位的回归办法,进一步提高了地位预测的准确性。
  • 2015 年,Shaoqing Ren 等人提出了 Faster R-CNN[3]模型,提出了 RPN 的办法来产生物体的候选区域,这一办法不再须要应用传统的图像处理算法来产生候选区域,进一步晋升了处理速度。
  • 2017 年,Kaiming He 等人提出了 Mask R-CNN[4]模型,只须要在 Faster R-CNN 模型上增加比拟少的计算量,就能够同时实现目标检测和物体实例宰割两个工作。

以上都是基于 R -CNN 系列的驰名模型,对指标检测方向的倒退有着较大的影响力。此外,还有一些其余模型,比方 SSD[5]、YOLO[6,7,8]、R-FCN[9]等也都是指标检测畛域风行的模型构造。图 5 为指标检测综述文章 [10] 中的一幅图,梳理了近些年指标检测算法的倒退流程。

图 5 指标检测算法倒退流程

其中,因为上文所述的 R -CNN 的系列算法将指标检测工作分成两个阶段,先在图像上产生候选区域,再对候选区域进行分类并预测指标物体地位,所以它们通常被叫做 两阶段检测算法 。而 SSD 和 YOLO 系列算法则是应用一个网络同时产生候选区域并预测出物体的类别和地位,所以它们通常被叫做 单阶段检测算法

上文中提到,穷举法来获取候选区域是不事实的。因而在起初的经典算法中,罕用的一个思路是应用 Anchor 提取候选指标框,Anchor 是事后设定好比例的一组候选框汇合,在图片上进行滑动就能够获取候选区域了。

因为这类算法都是应用 Anchor 提取候选指标框。在特色图的每一个点上,对 Anchor 进行分类和回归。所以这些算法也统称为基于 Anchor 的算法。

然而这种基于 Anchor 的办法,在理论利用中存在一些问题:

  • Anchor 是人为手工设计的,那咱们换个数据集,应该设置多少?设置多大?长宽比如何设置?
  • Anchor 这种密集框,数量多,训练时如何抉择正负样本?
  • Anchor 设置也导致超参数较多,理论业务扩大中,相对来说,就有点麻烦。

因为上述毛病的存在,近些年研究者们还提出了另外一类成果优异的算法,这些算法不再应用 anchor 回归预测框,因而也称作 Anchor-free 的算法,例如:CornerNet[11]和 CenterNet[12]等。图 6 为大家简略列举了经典的 Anchor-base 和 Anchor-free 的算法。

图 6 基于深度学习的指标检测算法倒退流程

Anchor-base 和 Anchor-free 的算法也各具劣势,下表为大家简略比照了几类算法各自的优缺点。

Anchor-Based 单阶段 Anchor-Based 两阶段 Anchor-Free
网络结构 简略 简单 简略
精度 更优 较优
预测速度 稍慢
超参数 较多 绝对少
扩展性 个别 个别 较好

1.3 罕用数据集

在指标检测畛域,罕用的开源数据集次要蕴含以下 4 个:Pascal VOC[13]、COCO[14]、Object365[15]、OpenImages[16]。这些数据集的类别数、图片数、指标框的总数量各不相同,因而难易也各不相同。这里整顿了 4 个数据集的具体情况,如下表所示。

数据集 类别数 train 图片数,box 数 val 图片数,box 数 boxes/Image
Pascal VOC-2012 20 5717, 1.3 万 + 5823,1.3 万 + 2.4
COCO 80 118287,4 万 + 5000,3.6 万 + 7.3
Object365 365 600k, 9623k 38k, 479k 16
OpenImages18 500 1643042, 86 万 + 100000,69.6 万 + 7.0
  • Pascal VOC-2012:VOC 数据集是 PASCAL VOC 挑战赛应用的数据集,蕴含了 20 种常见类别的图片,是指标检测畛域的经典学术数据集之一。
  • COCO:COCO 数据集是一个经典的大规模指标检测、宰割、姿势预计数据集,图片数据次要从简单的日常场景中截取,共 80 类。目前的学术论文常常会应用 COCO 数据集进行精度评测。
  • Object365:旷世科技公布的大规模通用物体检测数据集,共 365 类。
  • OpenImages18:谷歌公布的超大规模数据集,共 500 类。
  • 参考文献

[1] Rich feature hierarchies for accurate object detection and semantic segmentation

[2] Fast R-CNN

[3] Faster R-CNN: Towards Real-Time Object Detection with Region Proposal Networks

[4] Mask R-CNN

[5] SSD: Single Shot MultiBox Detector

[6] You Only Look Once: Unified, Real-Time Object Detection

[7] YOLO9000: Better, Faster, Stronger

[8] YOLOv3: An Incremental Improvement

[9] R-FCN: Object Detection via Region-based Fully Convolutional Networks

[10] Object Detection in 20 Years: A Survey

[11] CornerNet: Detecting Objects as Paired Keypoints

[12] Objects as Points

[13] Pascal VOC

[14] COCO

[15] Object365

[16] OpenImages

2. 边界框(bounding box)

在检测工作中,咱们须要同时预测物体的类别和地位,因而须要引入一些跟地位相干的概念。通常应用边界框(bounding box,bbox)来示意物体的地位,边界框是正好能蕴含物体的矩形框,如 图 1 所示,图中 3 集体别离对应 3 个边界框。

图 1 边界框

通常示意边界框的地位有两种形式:

  1. 即 $(x_1, y_1, x_2, y_2)$,其中 $(x_1, y_1)$ 是矩形框左上角的坐标,$(x_2, y_2)$ 是矩形框右下角的坐标。图 1 中 3 个红色矩形框用 $xyxy$ 格局示意如下:
  • 左:$(40.93, 141.1, 226.99, 515.73)$。
  • 中:$(214.29, 325.03, 399.82, 631.37)$。
  • 右:$(247.2, 131.62, 480.0, 639.32)$。
  1. $xywh$,即 $(x, y, w, h)$,其中 $(x, y)$ 是矩形框中心点的坐标,$w$ 是矩形框的宽度,$h$ 是矩形框的高度。

在检测工作中,训练数据集的标签里会给出指标物体实在边界框所对应的 $(x_1, y_1, x_2, y_2)$,这样的边界框也被称为实在框(ground truth box),图 1 画出了 3 集体像所对应的实在框。模型会对指标物体可能呈现的地位进行预测,由模型预测出的边界框则称为预测框(prediction box)。

要实现一项检测工作,咱们通常心愿模型可能依据输出的图片,输入一些预测的边界框,以及边界框中所蕴含的物体的类别或者说属于某个类别的概率,例如这种格局: $[L, P, x_1, y_1, x_2, y_2]$,其中 $L$ 是预测出的类别标签,$P$ 是预测物体属于该类别的概率。一张输出图片可能会产生多个预测框,接下来让咱们一起学习如何实现这项工作。


留神:

  1. 在浏览代码时,请留神应用的是哪一种格局的示意形式。
  2. 图片坐标的原点在左上角,$x$ 轴向右为正方向,$y$ 轴向下为正方向。

3. 锚框(Anchor box)

指标检测算法通常会在输出图像中采样大量的区域,而后判断这些区域中是否蕴含咱们感兴趣的指标,并调整区域边缘从而更精确地预测指标的实在边界框(ground-truth bounding box)。不同的模型应用的区域采样办法可能不同。这里咱们介绍其中的一种办法:它以每个像素为核心生成多个大小和宽高比(aspect ratio)不同的边界框。这些边界框被称为锚框(anchor box)。

在指标检测工作中,咱们会先设定好锚框的大小和形态,再以图像上某一个点为核心画出这些锚框,将这些锚框当成可能的候选区域。


目前,罕用的锚框尺寸抉择办法有:

  1. 人为教训选取
  2. k-means 聚类
  3. 作为超参数进行学习

模型对这些候选区域是否蕴含物体进行预测,如果蕴含指标物体,则还须要进一步预测出物体所属的类别。还有更为重要的一点是,模型须要预测出微调的幅度。这是因为锚框地位是固定的,它不大可能刚好跟物体边界框重合,所以须要在锚框的根底上进行微调以造成能精确形容物体地位的预测框。

在训练过程中,模型通过学习一直的调整参数,最终能学会如何判断出锚框所代表的候选区域是否蕴含物体,如果蕴含物体的话,物体属于哪个类别,以及物体边界框绝对于锚框地位须要调整的幅度。而不同的模型往往有着不同的生成锚框的形式。

在下图中,以像素点 [300, 500] 为核心能够应用上面的程序生成 3 个框,如 图 2 中蓝色框所示,其中锚框 A1 跟人像区域十分靠近。

图 2 锚框

# 画图展现如何绘制边界框和锚框
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.patches as patches
from matplotlib.image import imread
import math

#定义画矩形框的程序    
def draw_rectangle(currentAxis, bbox, edgecolor = 'k', facecolor = 'y', fill=False, linestyle='-'):
    # currentAxis,坐标轴,通过 plt.gca()获取
    # bbox,边界框,蕴含四个数值的 list,[x1, y1, x2, y2]
    # edgecolor,边框线条色彩
    # facecolor,填充色彩
    # fill, 是否填充
    # linestype,边框线型

    # patches.Rectangle(xy, width, height,linewidth,edgecolor,facecolor,fill, linestyle)
    # xy: 左下角坐标; width: 矩形框的宽; height: 矩形框的高; linewidth: 线宽; edgecolor: 边界色彩; facecolor: 填充色彩; fill: 是否填充; linestyle: 线断类型
    rect=patches.Rectangle((bbox[0], bbox[1]), bbox[2]-bbox[0]+1, bbox[3]-bbox[1]+1, linewidth=1,
                           edgecolor=edgecolor,facecolor=facecolor,fill=fill, linestyle=linestyle)
    currentAxis.add_patch(rect)

    
plt.figure(figsize=(10, 10))
#传入图片门路
filename = '/home/aistudio/work/images/section3/000000086956.jpg'
im = imread(filename)
plt.imshow(im)

#应用 xyxy 格局示意物体实在框
bbox1 = [214.29, 325.03, 399.82, 631.37]
bbox2 = [40.93, 141.1, 226.99, 515.73]
bbox3 = [247.2, 131.62, 480.0, 639.32]

currentAxis=plt.gca()
#绘制 3 个实在框
draw_rectangle(currentAxis, bbox1, edgecolor='r')
draw_rectangle(currentAxis, bbox2, edgecolor='r')
draw_rectangle(currentAxis, bbox3,edgecolor='r')

#绘制锚框
def draw_anchor_box(center, length, scales, ratios, img_height, img_width):
    """
    以 center 为核心,产生一系列锚框
    其中 length 指定了一个基准的长度
    scales 是蕴含多种尺寸比例的 list
    ratios 是蕴含多种长宽比的 list
    img_height 和 img_width 是图片的尺寸,生成的锚框范畴不能超出图片尺寸之外
    """
    bboxes = []
    for scale in scales:
        for ratio in ratios:
            h = length*scale*math.sqrt(ratio)
            w = length*scale/math.sqrt(ratio) 
            x1 = max(center[0] - w/2., 0.)
            y1 = max(center[1] - h/2., 0.)
            x2 = min(center[0] + w/2. - 1.0, img_width - 1.0)
            y2 = min(center[1] + h/2. - 1.0, img_height - 1.0)
            print(center[0], center[1], w, h)
            bboxes.append([x1, y1, x2, y2])

    for bbox in bboxes:
        draw_rectangle(currentAxis, bbox, edgecolor = 'b')

img_height = im.shape[0]
img_width = im.shape[1] 
#绘制锚框
draw_anchor_box([300., 500.], 100., [2.0], [0.5, 1.0, 2.0], img_height, img_width)

################# 以下为增加上图中的文字说明和箭头 ###############################
plt.text(285, 285, 'G1', color='red', fontsize=20)
plt.arrow(300, 288, 30, 40, color='red', width=0.001, length_includes_head=True, \
         head_width=5, head_length=10, shape='full')

plt.text(190, 320, 'A1', color='blue', fontsize=20)
plt.arrow(200, 320, 30, 40, color='blue', width=0.001, length_includes_head=True, \
         head_width=5, head_length=10, shape='full')

plt.text(160, 370, 'A2', color='blue', fontsize=20)
plt.arrow(170, 370, 30, 40, color='blue', width=0.001, length_includes_head=True, \
         head_width=5, head_length=10, shape='full')

plt.text(115, 420, 'A3', color='blue', fontsize=20)
plt.arrow(127, 420, 30, 40, color='blue', width=0.001, length_includes_head=True, \
         head_width=5, head_length=10, shape='full')

plt.show()

锚框的概念最早在 Faster rcnn[1]指标检测算法中被提出,起初被 YOLOv2[2]等各种指标检测算法借鉴。比照于晚期指标检测算法中应用的滑动窗口或 Selective Search 办法,应用锚框来提取候选区域大大减少了工夫开销。而比照 YOLOv1[3]中间接回归坐标值来计算检测框,应用锚框能够简化指标检测问题,使得网络仅仅学习锚框的地位偏移量即可,从而使得网络模型更容易学习。

[1] Faster R-CNN: Towards Real-Time Object Detection with Region Proposal Networks

[2] YOLO9000: Better, Faster, Stronger

[3] You Only Look Once: Unified, Real-Time Object Detection

4. 交并比

在指标检测工作中,通常会应用交并比(Intersection of Union,IoU)作为掂量指标,来掂量两个矩形框之间的关系。例如在基于锚框的指标检测算法中,咱们晓得当锚框中蕴含物体时,咱们须要预测物体类别并微调锚框的坐标,从而取得最终的预测框。此时,判断锚框中是否蕴含物体就须要用到交并比,当锚框与实在框交并比足够大时,咱们就能够认为锚框中蕴含了该物体;而锚框与实在框交并比很小时,咱们就能够认为锚框中不蕴含该物体。此外,在前面 NMS 的计算过程中,同样也要应用交并比来判断不同矩形框是否重叠。

交并比这一概念来源于数学中的汇合,用来形容两个汇合 $A$ 和 $B$ 之间的关系,它等于两个汇合的交加外面所蕴含的元素个数,除以它们的并集外面所蕴含的元素个数,具体计算公式如下:

$$IoU = \frac{A\cap B}{A \cup B}$$

咱们将用这个概念来形容两个框之间的重合度。两个框能够看成是两个像素的汇合,它们的交并比等于两个框重合局部的面积除以它们合并起来的面积。下图“交加”中青色区域是两个框的重合面积,下图“并集”中蓝色区域是两个框的相并面积。用这两个面积相除即可失去它们之间的交并比,如 图 1 所示。

图 1 交并比

假如两个矩形框 A 和 B 的地位别离为:

$$A: [x_{a1}, y_{a1}, x_{a2}, y_{a2}]$$

$$B: [x_{b1}, y_{b1}, x_{b2}, y_{b2}]$$

如果地位关系如 图 2 所示:

图 2 计算交并比

如果二者有相交局部,则相交局部左上角坐标为:

$$x_1 = max(x_{a1}, x_{b1}), \ \ \ \ \ y_1 = max(y_{a1}, y_{b1})$$

相交局部右下角坐标为:

$$x_2 = min(x_{a2}, x_{b2}), \ \ \ \ \ y_2 = min(y_{a2}, y_{b2})$$

计算先交局部面积:

$$intersection = max(x_2 – x_1 + 1.0, 0) \cdot max(y_2 – y_1 + 1.0, 0)$$

矩形框 A 和 B 的面积别离是:

$$S_A = (x_{a2} – x_{a1} + 1.0) \cdot (y_{a2} – y_{a1} + 1.0)$$

$$S_B = (x_{b2} – x_{b1} + 1.0) \cdot (y_{b2} – y_{b1} + 1.0)$$

计算相并局部面积:

$$union = S_A + S_B – intersection$$

计算交并比:

$$IoU = \frac{intersection}{union}$$

交并比实现代码如下:

  • 当矩形框的坐标模式为 xyxy 时
import numpy as np

#计算 IoU,矩形框的坐标模式为 xyxy
def box_iou_xyxy(box1, box2):
    # 获取 box1 左上角和右下角的坐标
    x1min, y1min, x1max, y1max = box1[0], box1[1], box1[2], box1[3]
    # 计算 box1 的面积
    s1 = (y1max - y1min + 1.) * (x1max - x1min + 1.)
    # 获取 box2 左上角和右下角的坐标
    x2min, y2min, x2max, y2max = box2[0], box2[1], box2[2], box2[3]
    # 计算 box2 的面积
    s2 = (y2max - y2min + 1.) * (x2max - x2min + 1.)
    
    # 计算相交矩形框的坐标
    xmin = np.maximum(x1min, x2min)
    ymin = np.maximum(y1min, y2min)
    xmax = np.minimum(x1max, x2max)
    ymax = np.minimum(y1max, y2max)
    # 计算相交矩形行的高度、宽度、面积
    inter_h = np.maximum(ymax - ymin + 1., 0.)
    inter_w = np.maximum(xmax - xmin + 1., 0.)
    intersection = inter_h * inter_w
    # 计算相并面积
    union = s1 + s2 - intersection
    # 计算交并比
    iou = intersection / union
    return iou


bbox1 = [100., 100., 200., 200.]
bbox2 = [120., 120., 220., 220.]
iou = box_iou_xyxy(bbox1, bbox2)
print('IoU is {}'.format(iou))  
  • 当矩形框的坐标模式为 xywh 时
import numpy as np

#计算 IoU,矩形框的坐标模式为 xywh
def box_iou_xywh(box1, box2):
    x1min, y1min = box1[0] - box1[2]/2.0, box1[1] - box1[3]/2.0
    x1max, y1max = box1[0] + box1[2]/2.0, box1[1] + box1[3]/2.0
    s1 = box1[2] * box1[3]

    x2min, y2min = box2[0] - box2[2]/2.0, box2[1] - box2[3]/2.0
    x2max, y2max = box2[0] + box2[2]/2.0, box2[1] + box2[3]/2.0
    s2 = box2[2] * box2[3]

    xmin = np.maximum(x1min, x2min)
    ymin = np.maximum(y1min, y2min)
    xmax = np.minimum(x1max, x2max)
    ymax = np.minimum(y1max, y2max)
    inter_h = np.maximum(ymax - ymin, 0.)
    inter_w = np.maximum(xmax - xmin, 0.)
    intersection = inter_h * inter_w

    union = s1 + s2 - intersection
    iou = intersection / union
    return iou

bbox1 = [100., 100., 200., 200.]
bbox2 = [120., 120., 220., 220.]
iou = box_iou_xywh(bbox1, bbox2)
print('IoU is {}'.format(iou))  

为了直观的展现交并比的大小跟重合水平之间的关系,图 3 示意了不同交并比下两个框之间的绝对地位关系,从 IoU = 0.95 到 IoU = 0。

图 3 不同交并比下两个框之间绝对地位示意图


问题:

  1. 什么状况下两个矩形框的 IoU 等于 1?

    答案:两个矩形框齐全重合。

  2. 什么状况下两个矩形框的 IoU 等于 0?

    答案:两个矩形框齐全不相交。

5. 非极大值克制 NMS

在理论的指标检测过程中,不论是用什么形式获取候选区域,都会存在一个通用的问题,那就是网络对同一个指标可能会进行屡次检测。这也就导致对于同一个物体,会产生多个预测框。因而须要打消重叠较大的冗余预测框。具体的解决办法就是非极大值克制(NMS)。

假如应用模型对图片进行预测,一共输入了 11 个预测框及其得分,在图上画出预测框如 图 1 所示。在每个人像四周,都呈现了多个预测框,须要打消冗余的预测框以失去最终的预测后果。

图 1 预测框示意图

输入 11 个预测框及其得分的代码实现如下:

# 画图展现指标物体边界框
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.patches as patches
from matplotlib.image import imread
import math

#定义画矩形框的程序    
def draw_rectangle(currentAxis, bbox, edgecolor = 'k', facecolor = 'y', fill=False, linestyle='-'):
    # currentAxis,坐标轴,通过 plt.gca()获取
    # bbox,边界框,蕴含四个数值的 list,[x1, y1, x2, y2]
    # edgecolor,边框线条色彩
    # facecolor,填充色彩
    # fill, 是否填充
    # linestype,边框线型
    
    # patches.Rectangle(xy, width, height,linewidth,edgecolor,facecolor,fill, linestyle)
    # xy: 左下角坐标; width: 矩形框的宽; height: 矩形框的高; linewidth: 线宽; edgecolor: 边界色彩; facecolor: 填充色彩; fill: 是否填充; linestyle: 线断类型
    rect=patches.Rectangle((bbox[0], bbox[1]), bbox[2]-bbox[0]+1, bbox[3]-bbox[1]+1, linewidth=1,
                           edgecolor=edgecolor,facecolor=facecolor,fill=fill, linestyle=linestyle)
    currentAxis.add_patch(rect)

    
plt.figure(figsize=(10, 10))
#传入图片门路
filename = '/home/aistudio/work/images/section3/000000086956.jpg'
im = imread(filename)
plt.imshow(im)

currentAxis=plt.gca()

#预测框地位,由网络预测失去
boxes = np.array([[4.21716537e+01, 1.28230896e+02, 2.26547668e+02, 6.00434631e+02],
       [3.18562988e+02, 1.23168472e+02, 4.79000000e+02, 6.05688416e+02],
       [2.62704697e+01, 1.39430557e+02, 2.20587097e+02, 6.38959656e+02],
       [4.24965363e+01, 1.42706665e+02, 2.25955185e+02, 6.35671204e+02],
       [2.37462646e+02, 1.35731537e+02, 4.79000000e+02, 6.31451294e+02],
       [3.19390472e+02, 1.29295090e+02, 4.79000000e+02, 6.33003845e+02],
       [3.28933838e+02, 1.22736115e+02, 4.79000000e+02, 6.39000000e+02],
       [4.44292603e+01, 1.70438187e+02, 2.26841858e+02, 6.39000000e+02],
       [2.17988785e+02, 3.02472412e+02, 4.06062927e+02, 6.29106628e+02],
       [2.00241089e+02, 3.23755096e+02, 3.96929321e+02, 6.36386108e+02],
       [2.14310303e+02, 3.23443665e+02, 4.06732849e+02, 6.35775269e+02]])

#预测框得分,由网络预测失去
scores = np.array([0.5247661 , 0.51759845, 0.86075854, 0.9910175 , 0.39170712,
       0.9297706 , 0.5115228 , 0.270992  , 0.19087596, 0.64201415, 0.879036])

#画出所有预测框
for box in boxes:
    draw_rectangle(currentAxis, box)

这里应用非极大值克制(Non-Maximum Suppression, NMS)来打消冗余框。根本思维是,如果有多个预测框都对应同一个物体,则只选出得分最高的那个预测框,剩下的预测框被抛弃掉。

如何判断两个预测框对应的是同一个物体呢,规范该怎么设置?

如果两个预测框的类别一样,而且他们的地位重合度比拟大,则能够认为他们是在预测同一个指标。非极大值克制的做法是,选出某个类别得分最高的预测框,而后看哪些预测框跟它的 IoU 大于阈值,就把这些预测框给抛弃掉。这里 IoU 的阈值是超参数,须要提前设置,这里咱们参考 YOLOv3 算法,外面设置的是 0.5。

比方在下面的程序中,boxes 外面一共对应 11 个预测框,scores 给出了它们预测 ” 人 ” 这一类别的得分,NMS 的具体做法如下。

  • Step0:创立选中列表,keep_list = []
  • Step1:对得分进行排序,remain_list = [3, 5, 10, 2, 9, 0, 1, 6, 4, 7, 8],
  • Step2:选出 boxes[3],此时 keep_list 为空,不须要计算 IoU,间接将其放入 keep_list,keep_list = [3],remain_list=[5, 10, 2, 9, 0, 1, 6, 4, 7, 8]
  • Step3:选出 boxes[5],此时 keep_list 中曾经存在 boxes[3],计算出 IoU(boxes[3], boxes[5]) = 0.0,显然小于阈值,则 keep_list=[3, 5], remain_list = [10, 2, 9, 0, 1, 6, 4, 7, 8]
  • Step4:选出 boxes[10],此时 keep_list=[3, 5],计算 IoU(boxes[3], boxes[10])=0.0268,IoU(boxes[5], boxes[10])=0.0268 = 0.24,都小于阈值,则 keep_list = [3, 5, 10],remain_list=[2, 9, 0, 1, 6, 4, 7, 8]
  • Step5:选出 boxes[2],此时 keep_list = [3, 5, 10],计算 IoU(boxes[3], boxes[2]) = 0.88,超过了阈值,间接将 boxes[2]抛弃,keep_list=[3, 5, 10],remain_list=[9, 0, 1, 6, 4, 7, 8]
  • Step6:选出 boxes[9],此时 keep_list = [3, 5, 10],计算 IoU(boxes[3], boxes[9]) = 0.0577,IoU(boxes[5], boxes[9]) = 0.205,IoU(boxes[10], boxes[9]) = 0.88,超过了阈值,将 boxes[9]抛弃掉。keep_list=[3, 5, 10],remain_list=[0, 1, 6, 4, 7, 8]
  • Step7:反复上述 Step6 直到 remain_list 为空。

非极大值克制的具体实现代码如上面的 nms 函数的定义。

# 非极大值克制
def nms(bboxes, scores, score_thresh, nms_thresh):
    """nms"""
    inds = np.argsort(scores)
    inds = inds[::-1]
    keep_inds = []
    while(len(inds) > 0):
        cur_ind = inds[0]
        cur_score = scores[cur_ind]
        # if score of the box is less than score_thresh, just drop it
        if cur_score < score_thresh:
            break

        keep = True
        for ind in keep_inds:
            current_box = bboxes[cur_ind]
            remain_box = bboxes[ind]
            iou = box_iou_xyxy(current_box, remain_box)
            if iou > nms_thresh:
                keep = False
                break
        if keep:
            keep_inds.append(cur_ind)
        inds = inds[1:]

    return np.array(keep_inds)

最终失去 keep_list=[3, 5, 10],也就是预测框 3、5、10 被最终筛选进去了,如 图 2 所示。

图 2 NMS 后果示意图

整个过程的实现代码如下:

# 画图展现指标物体边界框
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.patches as patches
from matplotlib.image import imread
import math

#定义画矩形框的程序    
def draw_rectangle(currentAxis, bbox, edgecolor = 'k', facecolor = 'y', fill=False, linestyle='-'):
    # currentAxis,坐标轴,通过 plt.gca()获取
    # bbox,边界框,蕴含四个数值的 list,[x1, y1, x2, y2]
    # edgecolor,边框线条色彩
    # facecolor,填充色彩
    # fill, 是否填充
    # linestype,边框线型
    # patches.Rectangle 须要传入左上角坐标、矩形区域的宽度、高度等参数
    rect=patches.Rectangle((bbox[0], bbox[1]), bbox[2]-bbox[0]+1, bbox[3]-bbox[1]+1, linewidth=1,
                           edgecolor=edgecolor,facecolor=facecolor,fill=fill, linestyle=linestyle)
    currentAxis.add_patch(rect)

    
plt.figure(figsize=(10, 10))

filename = '/home/aistudio/work/images/section3/000000086956.jpg'
im = imread(filename)
plt.imshow(im)

currentAxis=plt.gca()

boxes = np.array([[4.21716537e+01, 1.28230896e+02, 2.26547668e+02, 6.00434631e+02],
       [3.18562988e+02, 1.23168472e+02, 4.79000000e+02, 6.05688416e+02],
       [2.62704697e+01, 1.39430557e+02, 2.20587097e+02, 6.38959656e+02],
       [4.24965363e+01, 1.42706665e+02, 2.25955185e+02, 6.35671204e+02],
       [2.37462646e+02, 1.35731537e+02, 4.79000000e+02, 6.31451294e+02],
       [3.19390472e+02, 1.29295090e+02, 4.79000000e+02, 6.33003845e+02],
       [3.28933838e+02, 1.22736115e+02, 4.79000000e+02, 6.39000000e+02],
       [4.44292603e+01, 1.70438187e+02, 2.26841858e+02, 6.39000000e+02],
       [2.17988785e+02, 3.02472412e+02, 4.06062927e+02, 6.29106628e+02],
       [2.00241089e+02, 3.23755096e+02, 3.96929321e+02, 6.36386108e+02],
       [2.14310303e+02, 3.23443665e+02, 4.06732849e+02, 6.35775269e+02]])
 
scores = np.array([0.5247661 , 0.51759845, 0.86075854, 0.9910175 , 0.39170712,
       0.9297706 , 0.5115228 , 0.270992  , 0.19087596, 0.64201415, 0.879036])

left_ind = np.where((boxes[:, 0]<60) * (boxes[:, 0]>20))
left_boxes = boxes[left_ind]
left_scores = scores[left_ind]

colors = ['r', 'g', 'b', 'k']

# 画出最终保留的预测框
inds = nms(boxes, scores, score_thresh=0.01, nms_thresh=0.5)
# 打印最终保留的预测框是哪几个
print(inds)
for i in range(len(inds)):
    box = boxes[inds[i]]
    draw_rectangle(currentAxis, box, edgecolor=colors[i])

须要阐明的是当数据集中含有多个类别的物体时,须要做多分类非极大值克制,其实现原理与非极大值克制雷同,区别在于须要对每个类别都做非极大值克制,实现代码如上面的 multiclass_nms 所示。

# 多分类非极大值克制
def multiclass_nms(bboxes, scores, score_thresh=0.01, nms_thresh=0.45, pre_nms_topk=1000, pos_nms_topk=100):
    """This is for multiclass_nms"""
    batch_size = bboxes.shape[0]
    class_num = scores.shape[1]
    rets = []
    for i in range(batch_size):
        bboxes_i = bboxes[i]
        scores_i = scores[i]
        ret = []
        # 对每个类别都进行 NMS 操作
        for c in range(class_num):
            scores_i_c = scores_i
            keep_inds = nms(bboxes_i, scores_i_c, score_thresh, nms_thresh)
            if len(keep_inds) < 1:
                continue
            keep_bboxes = bboxes_i[keep_inds]
            keep_scores = scores_i_c[keep_inds]
            keep_results = np.zeros([keep_scores.shape[0], 6])
            keep_results[:, 0] = c
            keep_results[:, 1] = keep_scores[:]
            keep_results[:, 2:6] = keep_bboxes[:, :]
            ret.append(keep_results)
        if len(ret) < 1:
            rets.append(ret)
            continue
        ret_i = np.concatenate(ret, axis=0)
        scores_i = ret_i[:, 1]
        if len(scores_i) > pos_nms_topk:
            inds = np.argsort(scores_i)[::-1]
            inds = inds[:pos_nms_topk]
            ret_i = ret_i[inds]

        rets.append(ret_i)

    return rets

6.Soft NMS

6.1Soft NMS 提出背景

NMS(非极大值克制)办法是指标检测工作中罕用的后处理办法,其根本思维是:如果有多个预测框都对应同一个物体,则只选出得分最高的那个预测框,剩下的预测框被抛弃掉。在这种办法的解决下,能够无效地缩小冗余的检测框。然而,传统的 NMS 算法会存在以下毛病:IOU 阈值难以确定,阈值太小,则容易产生漏检景象,当两个雷同类别的物体重叠十分多的时候,类别得分较低的物体则会被舍弃;阈值太大,则难以打消大部分冗余框。

因而,在《Improving Object Detection With One Line of Code》[1]论文中,作者提出了 Soft NMS 办法来无效加重上述问题。

6.2 Soft NMS 算法流程

假如以后得分最高的检测框为 $M$,对于另一个类别得分为 $s_i$ 的检测框 $b_i$,传统的 NMS 算法的计算形式能够示意为下式:

$$
s_i = \{\begin{matrix}
s_i,iou(M,b_i)<N_t\\
0,iou(M,b_i)\ge N_t
\end{matrix}
$$

其中,$N_t$ 为设定好的 IOU 阈值。

而 Soft NMS 算法的计算形式能够示意为下式:

$$
s_i = \{\begin{matrix}
s_i,iou(M,b_i)<N_t\\
s_i(1-iou(M,b_i)),iou(M,b_i)\ge N_t
\end{matrix}
$$

这里其实咱们就能够看出两个办法的区别了。传统的 NMS 算法中,如果得分较低的检测框与得分最高的检测框的 IOU 大于阈值,则得分较低的检测框就会间接被舍弃掉;而 Soft NMS 算法中,没有将得分较低的检测框得分间接置 0,而是将其升高。具体来说,Soft NMS 算法中,最终的边框得分是依赖原始得分与 IOU 后果独特决定的,对原始得分进行了线性衰减。

然而,如果应用上述公式进行 Soft NMS 的计算,当 IOU 大于阈值时,边框得分会产生一个较大的变动。此时,检测后果有可能会也就会因而受到较大的影响。因而,Soft NMS 算法中,还提出了另一种边框得分的计算形式,如下式所示。

$$
s_i = s_ie^{-\frac {{iou(M,b_i)^2}}{\sigma}},\forall b_i\notin D
$$

此时,新的边界框得分变动较小,在后续的计算过程中也就又有了被计算为正确检测框的机会。

6.3 Soft NMS 算法示例

这里应用一个简略示例来阐明 Soft NMS 算法的计算过程以及其与规范 NMS 算法的差别。

图 1 SoftNMS 算法示例

假如应用马匹检测模型对上述图像进行预测,失去如上的两个检测后果。其中红色检测框中的马匹类别得分为 0.95,绿色虚线检测框中的马匹类别得分为 0.8。能够看到,间隔镜头更近的马匹简直将间隔镜头远的马匹齐全遮挡住了,此时,两个检测框的 IOU 是十分大的。

在传统 NMS 算法中,对于这种检测框的 IOU 十分大,超过事后设定的阈值的状况,会仅仅保留得分最大的检测框,将得分较小的检测框的得分间接置 0。此时,绿色虚线框中的马匹也就间接被舍弃掉了。然而,这两个检测框自身别离对应了两个不同的马匹,因而,这种 NMS 的办法会造成漏检的景象。

而在 SoftNMS 算法中,绿色虚线的检测框对应的新得分则不会被置 0,而是应用上文中提到的两种计算形式进行计算。此时,绿色虚线框中的马匹不会间接被舍弃掉,而是升高了类别得分,持续参加后续计算。对应原图中的状况,两个马匹则有很大的概率在最初同时被保留,防止了漏检景象的产生。

更多文章请关注公重号:汀丶人工智能

  • 参考文献

[1]《Improving Object Detection With One Line of Code》

正文完
 0