【导读】前面我们详细介绍了目标检测领域常用的一些评价指标。详情见:【目标检测基础积累】常用的评价指标本文我们来讨论一下在目标检测算法中必须掌握的两个基本概念:边框回归和NMS(非极大值抑制)。
边框回归的背景
如下图所示:
对于上图,绿色的框表示Ground Truth, 红色的框为Selective Search提取的Region Proposal。那么即便红色的框被分类器识别为飞机,但是由于红色的框定位不准(IoU<0.5), 那么这张图相当于没有正确的检测出飞机。如果我们能对红色的框进行微调, 使得经过微调后的窗口跟Ground Truth 更接近, 这样岂不是定位会更准确。确实,Bounding-box regression 就是用来微调这个窗口的。
边框回归的含义
对于窗口一般使用四维向量(x,y,w,h)来表示, 分别表示窗口的中心点坐标和宽高。边框回归的目的就是:在给定一组候选目标框 ,寻找到一个
映射 ,使得 。边界框回归过程图像表示如下图所示。在图2中红色框代表候选目标框,绿色框代表真实目标框,蓝色框代表边界框回归算法预测目标框,红色圆圈代表选候选目标框的中心点,绿色圆圈代表选真实目标框的中心点,蓝色圆圈代表选边界框回归算法预测目标框的中心点。
边界框回归中的变换
RCNN论文里指出,边界框回归是利用平移变换和尺度变换来实现映射 。平移变换的计算公式如下:
尺度变换的计算公式如下:
其中
(
代表
),接下来要做的就是求解这4个变换。在边界框回归中,我们利用了线性回归在RCNN论文代表这AlexNet第5个池化层得到的特征即将送入全连接层的输入特征的线型函数。在这里,我们将特征记作
,那么
。因此,我们可以利用最小二乘法或者梯度下降算法进行求解 ,RCNN论文中给出了的求解表达式:
其中:
可以看出,上述模型就是一个Ridge回归模型。在RCNN中,边界框回归要设计4个不同的Ridge回归模型分别求解
。
宽高尺度设计
由于CNN具有尺度不变性,以下图为例:
那么我们假设经过CNN提取得到的特征分别为
和
。同时,我们假设
为第
个真实目标框的
坐标,
为第
个候选目标框的
坐标,边界框回归的映射关系为
。那么我们可以得出:
由于CNN具有尺度不变性,因此
。那么理论上
。但是观察上图就可明显得出
,显然由于尺寸的变化,候选目标框和真实目标框坐标之间的偏移量也随着尺寸而成比例缩放,即这个比例值是恒定不变的。因此,我们必须对
坐标的偏移量除以候选目标框的宽,
坐标的偏移量除以候选目标框的高。只有这样才能得到候选目标框与真实目标框之间坐标偏移量值的相对值。同时使用相对偏移量的好处可以自由选择输入图像的尺寸,使得模型灵活多变。也就说,对坐标偏移量除以宽高就是在做尺度归一化,即尺寸较大的目标框的坐标偏移量较大,尺寸较小的目标框的坐标偏移量较小。
我们想要得到一个放缩的尺度,也就是说这里限制尺度必须大于0。我们学习的tw,th怎么保证满足大于0呢?直观的想法就是EXP函数,如公式(2)所示,那么反过来推导就是Log函数的来源了。
为什么IoU较大时边界框回归可视为线性变换?
在这里我们需要回顾下在高等数学中有关等价无穷小的结论:
也就是说当
趋向于0时,我们可有
,即可将
近似看成线型变换。接下来我们将式(4)的后两个公式进行重写:
也就是说,当
和
趋向于0时,即
和
时,上式(6)可以近似将对数变换看成线性变换。
和
时候选目标框和真实目标框非常接近,即IoU值较大。按照RCNN论文的说法,IoU大于0.6时,边界框回归可视为线型变换。
NMS的定义
NMS(Non-maximum suppression),即非极大值抑制,在目标检测中的出镜率也很高呀。在目标检测中,不论是最初的region proposal,还是后来的anchor box,不可避免的一个问题就是对于同一个物体,会预测出多个bounding box,如下左图所示。而NMS所做的就是去除掉多余的bounding box,只保留和ground truth重叠度最高的bounding box,如下右图所示。
在目标检测中,分类器会给每个bounding box(bbox)计算出一个class score,就是这个bbox属于每一类的概率,NMS就是根据这些值来进行的,主要流程:
- 对于每一类,首先把所有score<threshold的bbox的score设为0
- 之后,将所有的bbox按照得分排序,选中最高分及其对应的bbox
- 遍历其余的bbox,如果和当前最高分bb的重叠面积(IoU)大于一定的阀值,便将该bbox删除
- 从未处理的bbox中继续选择一个最高分的bb,重复上述过程
- 重复上述过程,直到找到全部保留的bbox
- 然后根据所有保留bbox的class score和class color画出最后的预测结果
NMS的实现(Pytorch代码)
from __future__ import absolute_import
import numpy as np
import torch
def nms(dets, thresh):
dets = dets.numpy()
x1 = dets[:, 0]
y1 = dets[:, 1]
x2 = dets[:, 2]
y2 = dets[:, 3]
scores = dets[:, 4]
areas = (x2 - x1 + 1) * (y2 - y1 + 1)
order = scores.argsort()[::-1]
keep = []
while order.size > 0:
i = order.item(0)
keep.append(i)
xx1 = np.maximum(x1[i], x1[order[1:]])
yy1 = np.maximum(y1[i], y1[order[1:]])
xx2 = np.minimum(x2[i], x2[order[1:]])
yy2 = np.minimum(y2[i], y2[order[1:]])
w = np.maximum(0.0, xx2 - xx1 + 1)
h = np.maximum(0.0, yy2 - yy1 + 1)
inter = w * h
ovr = inter / (areas[i] + areas[order[1:]] - inter)
inds = np.where(ovr <= thresh)[0]
order = order[inds + 1]
return torch.IntTensor(keep)