写在前面
在上一篇博客:【计算机视觉——RCNN目标检测系列】二、边界框回归(Bounding-Box Regression)中我们主要讲解了R-CNN中边界框回归,接下来我们在这篇博客我们讲解R-CNN中另外一个比较种重要的模块——IoU与非极大抑制。
在这篇博客中,我们将重点RCNN论文中另一个比较重要模块——IoU与非极大抑制。IoU是描述两个矩形框之间重合程度的指标,在RCNN中常用于衡量边界框回归算法得到的预测目标框与真实目标框之间的重合程度。非极大抑制算法(Non-Maximum Suppression,NMS)则是用于去除大量重复的候选目标框。
一、IoU
首先来看下交并比(Intersection over Union,IoU)。假定两个目标框分别为 和 ,两个矩形框的示意图如下图所示。那么两个目标框的交并比计算公式为: I o U = A ∩ B A ∪ B IoU=\frac{A \cap B}{A \cup B} IoU=A∪BA∩B。也就是IoU是两个目标框交际与并集的比值。一般来说,在目标检测中, 可视为检测结果良好正确,如果预测器和实际边界框完美重叠,则 ,因为交集就等于并集。但一般来说只要 ,那么结果是可以接受。
python中实现IoU的代码如下:
import numpy as np
def IoU(vertice1, vertice2):
"""
这是计算两个矩形区域的交并比函数,verticle格式为:[xin,ymin,xmax,ymax]
:param vertice1: 目标框1
:param vertice2: 目标框1
:return: 两个矩形区域的交并比
"""
# 计算区域交集的左上与右下坐标
lu = np.maximum(vertice1[0:2], vertice2[0:2])
rd = np.minimum(vertice1[2:], vertice2[2:])
# 计算区域交集的面积
intersection = np.maximum(0.0, rd - lu)
inter_square = intersection[0] * intersection[1]
# 计算区域并集的面积
square1 = (vertice1[2] - vertice1[0]) * (vertice1[3] - vertice1[1])
square2 = (vertice2[2] - vertice2[0]) * (vertice2[3] - vertice2[1])
union_square = np.maximum(square1 + square2 - inter_square, 1e-10)
return np.clip(inter_square / union_square, 0.0, 1.0)
二、非极大抑制
接下来,我们重点介绍非极大抑制(Non-Maximum Suppression,NMS)。顾名思义,非极大抑制就是要抑制非极大值。也可以将非极大抑制理解成搜索局部最大值。
在此我们讨论的非极大抑制主要特指用于目标检测领域中搜索木分类概率最大的目标框的非极大抑制算法,而通用的非极大抑制请阅读论文《Efficient Non-Maximum Suppression》。下面我们来看下非极大抑制的示意图。
我们常会采用分类概率与IoU作为指标来实现目标框的非极大抑制。具体算法流程如下:
- 首先我们按照目标框对应的分类概率进行排序,选取分类概率最大的目标框,记作current_box。
- 计算current_box与剩余目标框之间的IoU
- 将IoU大于阈值的目标框舍弃
- 接下来在剩余的目标框中再选出最大分类概率的目标框。按照上述流程一直循环直至条件结束。
显然从算法流程可以看出,非极大抑制是一种贪心算法。它的主要目的就是消除多余重叠比例较高的目标框。在RCNN与Fast RCNN中,候选框主要是由选择性搜索算法获取的,为了涵盖每张图片中对各个目标,选择行搜索算法会返回将近2000个候选框,因此带来大量重叠率叫高的目标框,因此在分类和定位任务借宿后,利用非极大抑制算法进行淘汰多余重复候选框时一项十分重要的工作。
非极大抑制(NMS)算法的python实现如下:
import numpy as np
def py_cpu_nms(dets, thresh):
"""
这是NMS去除重复目标框的函数
:param dets: 目标框数组,目标框的格式为:[xin,ymin,xmax,ymax,score]
:param thresh: 阈值
:return: 不重复的目标框数组在元目标框数组中的下标数组
"""
vertices = dets[:, 0:4] # 目标框
scores = dets[:, 4] # bbox打分
#areas = (x2 - x1 + 1) * (y2 - y1 + 1)
# 打分从大到小排列,取index
order = scores.argsort()[::-1]
# keep为最后保留的边框
keep = []
while order.size > 0:
# order[0]是当前分数最大的窗口,肯定保留
i = order[0]
keep.append(i)
# 计算窗口i与其他所有窗口的交叠部分的面积
ious = np.array([IoU(vertices[i], vertices[j]) for j in order[1:]])
# inds为所有与窗口i的iou值小于threshold值的窗口的index,其他窗口此次都被窗口i吸收
inds = np.where(ious <= thresh)[0]
# order里面只保留与窗口i交叠面积小于threshold的那些窗口,由于ovr长度比order长度少1(不包 含i),所以inds+1对应到保留的窗口
order = order[inds + 1]
return keep