博客地址:http://blog.csdn.net/qq_14845119/article/details/52064928
理论基础
说实话,讲理论基础实在不是我的强项,但是还是得硬着头皮来讲,希望我的讲解不至于晦涩难懂。
非极大值抑制,简称为NMS算法。是一种获取局部最大值的有效方法。在3领域中,假设一个行向量的长度为w,从左向右,由第一个到第w个和其3领域中的数值进行比对。
如果某个i大于i+1并且小于i-1,则其为一个绝不最大值,同时也就意味着i+1不是一个局部最大值,所以将i移动2个步长,从i+2开始继续向后进行比较判断。如果某个i不满足上述条件,则将i+1,继续对i+1进行比对。当比对到最后一个w时,直接将w设置为局部最大值。算法流程如下图所示。
![](https://img-blog.csdn.net/20160729144955641)
应用范围
非极大值抑制NMS在目标检测,定位等领域是一种被广泛使用的方法。对于目标具体位置定位过程,不管是使用sw(sliding Window)还是ss(selective search)方法,都会产生好多的候选区域。实际看到的情形就是好多区域的交叉重叠,难以满足实际的应用。如下图所示。
针对该问题有3种传统的解决思路。
第一种,选取好多矩形框的交集,即公共区域作为最后的目标区域。
第二种,选取好多矩形框的并集,即所有矩形框的最小外截矩作为目标区域。当然这里也不是只要相交就直接取并集,需要相交的框满足交集占最小框的面积达到一定比例(也就是阈值)才合并。
第三种,也就是本文的NMS,简单的说,对于有相交的就选取其中置信度最高的一个作为最后结果,对于没相交的就直接保留下来,作为最后结果。
总体来说,3种处理思路都各有千秋,不能一概评论哪种好坏。各种顶会论文也会选择不同的处理方法。
程序实现
本文提供了nonMaximumSuppression的C语言,c++,M语言,三个版本。
其中,c语言版本为OpenCV的源码这里摘出并进行相关的注释。sort为排序函数,这里是最基本的选择排序算法的实现。nonMaximumSuppression为具体非极大值抑制的实现。
- static void sort(int n, const float* x, int* indices)
- {
-
-
-
- int i, j;
- for (i = 0; i < n; i++)
- for (j = i + 1; j < n; j++)
- {
- if (x[indices[j]] > x[indices[i]])
- {
-
- int index_tmp = indices[i];
-
- indices[i] = indices[j];
-
- indices[j] = index_tmp;
- }
- }
- }
- int nonMaximumSuppression(int numBoxes, const CvPoint *points,
- const CvPoint *oppositePoints, const float *score,
- float overlapThreshold,
- int *numBoxesOut, CvPoint **pointsOut,
- CvPoint **oppositePointsOut, float **scoreOut)
- {
-
-
-
-
-
- int i, j, index;
- float* box_area = (float*)malloc(numBoxes * sizeof(float));
- int* indices = (int*)malloc(numBoxes * sizeof(int));
- int* is_suppressed = (int*)malloc(numBoxes * sizeof(int));
-
- for (i = 0; i < numBoxes; i++)
- {
- indices[i] = i;
- is_suppressed[i] = 0;
- box_area[i] = (float)( (oppositePoints[i].x - points[i].x + 1) *
- (oppositePoints[i].y - points[i].y + 1));
- }
-
- sort(numBoxes, score, indices);
- for (i = 0; i < numBoxes; i++)
- {
- if (!is_suppressed[indices[i]])
- {
- for (j = i + 1; j < numBoxes; j++)
- {
- if (!is_suppressed[indices[j]])
- {
- int x1max = max(points[indices[i]].x, points[indices[j]].x);
- int x2min = min(oppositePoints[indices[i]].x, oppositePoints[indices[j]].x);
- int y1max = max(points[indices[i]].y, points[indices[j]].y);
- int y2min = min(oppositePoints[indices[i]].y, oppositePoints[indices[j]].y);
- int overlapWidth = x2min - x1max + 1;
- int overlapHeight = y2min - y1max + 1;
- if (overlapWidth > 0 && overlapHeight > 0)
- {
- float overlapPart = (overlapWidth * overlapHeight) / box_area[indices[j]];
- if (overlapPart > overlapThreshold)
- {
- is_suppressed[indices[j]] = 1;
- }
- }
- }
- }
- }
- }
-
- *numBoxesOut = 0;
- for (i = 0; i < numBoxes; i++)
- {
- if (!is_suppressed[i]) (*numBoxesOut)++;
- }
-
- *pointsOut = (CvPoint *)malloc((*numBoxesOut) * sizeof(CvPoint));
- *oppositePointsOut = (CvPoint *)malloc((*numBoxesOut) * sizeof(CvPoint));
- *scoreOut = (float *)malloc((*numBoxesOut) * sizeof(float));
- index = 0;
- for (i = 0; i < numBoxes; i++)
- {
- if (!is_suppressed[indices[i]])
- {
- (*pointsOut)[index].x = points[indices[i]].x;
- (*pointsOut)[index].y = points[indices[i]].y;
- (*oppositePointsOut)[index].x = oppositePoints[indices[i]].x;
- (*oppositePointsOut)[index].y = oppositePoints[indices[i]].y;
- (*scoreOut)[index] = score[indices[i]];
- index++;
- }
-
- }
-
- free(indices);
- free(box_area);
- free(is_suppressed);
-
- return LATENT_SVM_OK;
- }
c++版本程序如下所示,根据opencv源码改编,vs2010实测运行完美。由于c和c++版本基本一个思路,因此将这两个的思路一起讲解。
整体程序分为两部分,sort函数主要实现候选框的置信度从高到低的排序,是基于基本的选择排序实现。nonMaximumSuppression主要实现非极大值抑制算法。算法思路为,先根据候选框的points 和oppositePoints 求出每个候选框的面积box_area,并将标签is_suppressed全部置为0。通过一个二重for循环将所有的候选框进行比对,这里的循环是从置信度最高的窗口进行比对,每层外循环中置信度最高的保留,其余的只要大于规定阈值overlapThreshold就舍弃,不大于阈值的保留下来。最终输出NMS处理后的结果。
- static void sort(int n, const vector<float> x, vector<int> indices)
- {
-
-
-
- int i, j;
- for (i = 0; i < n; i++)
- for (j = i + 1; j < n; j++)
- {
- if (x[indices[j]] > x[indices[i]])
- {
-
- int index_tmp = indices[i];
-
- indices[i] = indices[j];
-
- indices[j] = index_tmp;
- }
- }
- }
- int nonMaximumSuppression(int numBoxes, const vector<CvPoint> points,const vector<CvPoint> oppositePoints,
- const vector<float> score, float overlapThreshold,int& numBoxesOut, vector<CvPoint>& pointsOut,
- vector<CvPoint>& oppositePointsOut, vector<float> scoreOut)
- {
-
-
-
-
- int i, j, index;
- vector<float> box_area(numBoxes);
- vector<int> indices(numBoxes);
- vector<int> is_suppressed(numBoxes);
-
- for (i = 0; i < numBoxes; i++)
- {
- indices[i] = i;
- is_suppressed[i] = 0;
- box_area[i] = (float)( (oppositePoints[i].x - points[i].x + 1) *(oppositePoints[i].y - points[i].y + 1));
- }
-
- sort(numBoxes, score, indices);
- for (i = 0; i < numBoxes; i++)
- {
- if (!is_suppressed[indices[i]])
- {
- for (j = i + 1; j < numBoxes; j++)
- {
- if (!is_suppressed[indices[j]])
- {
- int x1max = max(points[indices[i]].x, points[indices[j]].x);
- int x2min = min(oppositePoints[indices[i]].x, oppositePoints[indices[j]].x);
- int y1max = max(points[indices[i]].y, points[indices[j]].y);
- int y2min = min(oppositePoints[indices[i]].y, oppositePoints[indices[j]].y);
- int overlapWidth = x2min - x1max + 1;
- int overlapHeight = y2min - y1max + 1;
- if (overlapWidth > 0 && overlapHeight > 0)
- {
- float overlapPart = (overlapWidth * overlapHeight) / box_area[indices[j]];
- if (overlapPart > overlapThreshold)
- {
- is_suppressed[indices[j]] = 1;
- }
- }
- }
- }
- }
- }
-
- numBoxesOut = 0;
- for (i = 0; i < numBoxes; i++)
- {
- if (!is_suppressed[i]) numBoxesOut++;
- }
- index = 0;
- for (i = 0; i < numBoxes; i++)
- {
- if (!is_suppressed[indices[i]])
- {
- pointsOut.push_back(Point(points[indices[i]].x,points[indices[i]].y));
- oppositePointsOut.push_back(Point(oppositePoints[indices[i]].x,oppositePoints[indices[i]].y));
- scoreOut.push_back(score[indices[i]]);
- index++;
- }
-
- }
-
- return true;
- }
2、更好的优化代码如下:
bool CompareBBox(const seeta::FaceInfo & a, const seeta::FaceInfo & b) {
return a.score > b.score;
}
void NonMaximumSuppression(std::vector<seeta::FaceInfo>* bboxes,
std::vector<seeta::FaceInfo>* bboxes_nms, float iou_thresh) {
bboxes_nms->clear();
std::sort(bboxes->begin(), bboxes->end(), seeta::fd::CompareBBox);
int32_t select_idx = 0;
int32_t num_bbox = static_cast<int32_t>(bboxes->size());
std::vector<int32_t> mask_merged(num_bbox, 0);
bool all_merged = false;
while (!all_merged) {
while (select_idx < num_bbox && mask_merged[select_idx] == 1)
select_idx++;
if (select_idx == num_bbox) {
all_merged = true;
continue;
}
bboxes_nms->push_back((*bboxes)[select_idx]);
mask_merged[select_idx] = 1;
seeta::Rect select_bbox = (*bboxes)[select_idx].bbox;
float area1 = static_cast<float>(select_bbox.width * select_bbox.height);
float x1 = static_cast<float>(select_bbox.x);
float y1 = static_cast<float>(select_bbox.y);
float x2 = static_cast<float>(select_bbox.x + select_bbox.width - 1);
float y2 = static_cast<float>(select_bbox.y + select_bbox.height - 1);
select_idx++;
for (int32_t i = select_idx; i < num_bbox; i++) {
if (mask_merged[i] == 1)
continue;
seeta::Rect & bbox_i = (*bboxes)[i].bbox;
float x = std::max<float>(x1, static_cast<float>(bbox_i.x));
float y = std::max<float>(y1, static_cast<float>(bbox_i.y));
float w = std::min<float>(x2, static_cast<float>(bbox_i.x + bbox_i.width - 1)) - x + 1;
float h = std::min<float>(y2, static_cast<float>(bbox_i.y + bbox_i.height - 1)) - y + 1;
if (w <= 0 || h <= 0)
continue;
float area2 = static_cast<float>(bbox_i.width * bbox_i.height);
float area_intersect = w * h;
float area_union = area1 + area2 - area_intersect;
if (static_cast<float>(area_intersect) / area_union > iou_thresh) {
mask_merged[i] = 1;
bboxes_nms->back().score += (*bboxes)[i].score;
}
}
}
}
实验结果
c++和Matlab的测试结果如下所示,其中,红色框为经过NMS处理后的结果,黄色框为原始的输入框。从图中可以看出,经过NMS处理后的候选框中,在重叠部分大于规定阈值的都被舍弃,只保留其中置信度最高的一个,而对于没有重叠的框,不管其置信度多少,都直接保留下来。
注意,在matlab,opencv里面图像左上角为坐标原点,而本文在matlab中是单纯画图,此时图像左下角为坐标原点,所以同样的坐标,两幅图效果有所区别。
本文所有程序github下载链接https://github.com/watersink/nonMaximumSuppression,