1、定义:

非极大值抑制算法NMS广泛应用于目标检测算法,其目的是为了消除多余的候选框,找到最佳的物体检测位置。

2、原理:

使用深度学习模型检测出的目标都有多个框,如下图,针对每一个被检测目标,为了得到效果最好的那一个,需要使用一定的过滤技术把多余的框过滤掉。NMS应运而生。

现,假设有一个候选BOXES的集合B和其对应的SCORES集合S:

1、找出分数最高的那个框M;

2、将M对应的BOX从B中删除;

3、将删除的BOX添加到集合D中;

4、从B中删除与M对应的BOX重叠区域大于阈值Nt的其他框;

5、重复上述步骤1到4。

伪代码如下:

其中Si可表述成:

源代码如下:

1、在FastRCNN中的python实现:

  1. def nms(dets,thresh):
  2. x1 = dets[:, 0]
  3. y1 = dets[:, 1]
  4. x2 = dets[:, 2]
  5. y2 = dets[:, 3]
  6.  
  7. scores = dets[:, 4]
  8. areas = (x2 - x1 + 1) * (y2 - y1 + 1)
  9. order = scores.argsort()[::-1]
  10.  
  11. keep = []
  12. while order.size>0:
  13. i=order[0]
  14. keep.append(i)
  15. xx1=np.maximum(x1[i],x1[order[1:]])
  16. yy1=np.maximum(y1[i],y1[order[1:]])
  17. xx2=np.minimum(x2[i],x2[order[1:]])
  18. yy2=np.minimum(y2[i],y2[order[1:]])
  19.  
  20. w=np.maximum(0.,xx2-xx1+1)
  21. h=np.maximum(0.,yy2-yy1+1)
  22. inter=w*h
  23. iou=inter/(areas[i]+areas[order[1:]]-inter)
  24.  
  25. inds=np.where(iou<=thresh)[0]
  26. order=order[inds+1]
  27.  
  28. return keep

2、在MaskRCNN中的python实现:

  1. def non_max_suppression(boxes,scores,threshold):
  2. '''
  3. 保留boxes的索引
  4. boxes:[N,(y1,x1,y2,x2)],(y2,x2)可能会超过box的边界
  5. scores:box分数的一数组
  6. threshold:Float型,用于过滤IoU的阈值
  7. '''
  8. assert boxes.shape[0]>0
  9. if boxes.dtpye.kind!='f':
  10. boxes=boxes.astype(np.float32)
  11.  
  12. #计算box面积
  13. y1=boxes[:,0]
  14. x1=boxes[:,1]
  15. y2=boxes[:,2]
  16. y3=boxes[:,3]
  17. area=(y2-y1)*(x2-x1)
  18.  
  19. #获取根据分数排序的boxes的索引(最高的排在对前面)
  20. ixs=scores.argsort()[::-]
  21.  
  22. pick=[]
  23. while len(ixs)>0:
  24. i=ixs[0]
  25. pick.append(i)
  26. iou=compute_iou(boxes[i],boxes[ixs[1:]],area[i],area[ixs[1:]])
  27. remove_ixs=np.where(iou>threshold)[0]+1
  28. ixs=np.delete(ixs,remove_ixs)
  29. ixs=np.delete(ixs,0)
  30.  
  31. return np.array(pick,dtype=np.int32)

3、C++实现

  1. static void sort(int n, const float* x, int* indices)
  2. {
  3. // 排序函数(降序排序),排序后进行交换的是indices中的数据
  4. // n:排序总数// x:带排序数// indices:初始为0~n-1数目
  5.  
  6. int i, j;
  7. for (i = ; i < n; i++)
  8. for (j = i + ; j < n; j++)
  9. {
  10. if (x[indices[j]] > x[indices[i]])
  11. {
  12. //float x_tmp = x[i];
  13. int index_tmp = indices[i];
  14. //x[i] = x[j];
  15. indices[i] = indices[j];
  16. //x[j] = x_tmp;
  17. indices[j] = index_tmp;
  18. }
  19. }
  20. }
  21.  
  22. int nonMaximumSuppression(int numBoxes, const CvPoint *points,
  23. const CvPoint *oppositePoints, const float *score,
  24. float overlapThreshold,
  25. int *numBoxesOut, CvPoint **pointsOut,
  26. CvPoint **oppositePointsOut, float **scoreOut)
  27. {
  28.  
  29. // numBoxes:窗口数目// points:窗口左上角坐标点// oppositePoints:窗口右下角坐标点
  30. // score:窗口得分// overlapThreshold:重叠阈值控制// numBoxesOut:输出窗口数目
  31. // pointsOut:输出窗口左上角坐标点// oppositePoints:输出窗口右下角坐标点
  32. // scoreOut:输出窗口得分
  33. int i, j, index;
  34. float* box_area = (float*)malloc(numBoxes * sizeof(float)); // 定义窗口面积变量并分配空间
  35. int* indices = (int*)malloc(numBoxes * sizeof(int)); // 定义窗口索引并分配空间
  36. int* is_suppressed = (int*)malloc(numBoxes * sizeof(int)); // 定义是否抑制表标志并分配空间
  37. // 初始化indices、is_supperssed、box_area信息
  38. for (i = ; i < numBoxes; i++)
  39. {
  40. indices[i] = i;
  41. is_suppressed[i] = ;
  42. box_area[i] = (float)( (oppositePoints[i].x - points[i].x + ) *
  43. (oppositePoints[i].y - points[i].y + ));
  44. }
  45. // 对输入窗口按照分数比值进行排序,排序后的编号放在indices中
  46. sort(numBoxes, score, indices);
  47. for (i = ; i < numBoxes; i++) // 循环所有窗口
  48. {
  49. if (!is_suppressed[indices[i]]) // 判断窗口是否被抑制
  50. {
  51. for (j = i + ; j < numBoxes; j++) // 循环当前窗口之后的窗口
  52. {
  53. if (!is_suppressed[indices[j]]) // 判断窗口是否被抑制
  54. {
  55. int x1max = max(points[indices[i]].x, points[indices[j]].x); // 求两个窗口左上角x坐标最大值
  56. int x2min = min(oppositePoints[indices[i]].x, oppositePoints[indices[j]].x); // 求两个窗口右下角x坐标最小值
  57. int y1max = max(points[indices[i]].y, points[indices[j]].y); // 求两个窗口左上角y坐标最大值
  58. int y2min = min(oppositePoints[indices[i]].y, oppositePoints[indices[j]].y); // 求两个窗口右下角y坐标最小值
  59. int overlapWidth = x2min - x1max + ; // 计算两矩形重叠的宽度
  60. int overlapHeight = y2min - y1max + ; // 计算两矩形重叠的高度
  61. if (overlapWidth > && overlapHeight > )
  62. {
  63. float overlapPart = (overlapWidth * overlapHeight) / box_area[indices[j]]; // 计算重叠的比率
  64. if (overlapPart > overlapThreshold) // 判断重叠比率是否超过重叠阈值
  65. {
  66. is_suppressed[indices[j]] = ; // 将窗口j标记为抑制
  67. }
  68. }
  69. }
  70. }
  71. }
  72. }
  73.  
  74. *numBoxesOut = ; // 初始化输出窗口数目0
  75. for (i = ; i < numBoxes; i++)
  76. {
  77. if (!is_suppressed[i]) (*numBoxesOut)++; // 统计输出窗口数目
  78. }
  79.  
  80. *pointsOut = (CvPoint *)malloc((*numBoxesOut) * sizeof(CvPoint)); // 分配输出窗口左上角坐标空间
  81. *oppositePointsOut = (CvPoint *)malloc((*numBoxesOut) * sizeof(CvPoint)); // 分配输出窗口右下角坐标空间
  82. *scoreOut = (float *)malloc((*numBoxesOut) * sizeof(float)); // 分配输出窗口得分空间
  83. index = ;
  84. for (i = ; i < numBoxes; i++) // 遍历所有输入窗口
  85. {
  86. if (!is_suppressed[indices[i]]) // 将未发生抑制的窗口信息保存到输出信息中
  87. {
  88. (*pointsOut)[index].x = points[indices[i]].x;
  89. (*pointsOut)[index].y = points[indices[i]].y;
  90. (*oppositePointsOut)[index].x = oppositePoints[indices[i]].x;
  91. (*oppositePointsOut)[index].y = oppositePoints[indices[i]].y;
  92. (*scoreOut)[index] = score[indices[i]];
  93. index++;
  94. }
  95.  
  96. }
  97.  
  98. free(indices); // 释放indices空间
  99. free(box_area); // 释放box_area空间
  100. free(is_suppressed); // 释放is_suppressed空间
  101.  
  102. return LATENT_SVM_OK;
  103. }

优化版:SoftNMS

NMS能解决大部分的重叠问题,但如下图的情况就无法解决,红色框和绿色框是当前的检测结果,二者的得分分别是0.95和0.80。如果按照传统的NMS进行处理,首先选中得分最高的红色框,然后绿色框就会因为与之重叠面积过大而被删掉。另一方面,NMS的阈值也不太容易确定,设小了会出现下图的情况(绿色框因为和红色框重叠面积较大而被删掉),设置过高又容易增大误检。

思路:不要简单粗暴地删除所有IOU大于阈值的框,而是降低其置信度。

伪代码如下:

NMS可以描述如下:将IOU大于阈值的窗口的得分全部置为0。

SoftNMS改进有两种形式

一种是线性加权的:

一种是高斯加权的:

两种方法的思路都是:M为当前得分最高框,Bi是待处理框,和M的IOU越大,Bi的得分就下降的越厉害。

  1. def cpu_soft_nms(np.ndarray[float, ndim=2] boxes, float sigma=0.5, float Nt=0.3, float threshold=0.001, unsigned int method=0):
  2. cdef unsigned int N = boxes.shape[0]
  3. cdef float iw, ih, box_area
  4. cdef float ua
  5. cdef int pos = 0
  6. cdef float maxscore = 0
  7. cdef int maxpos = 0
  8. cdef float x1,x2,y1,y2,tx1,tx2,ty1,ty2,ts,area,weight,ov
  9.  
  10. for i in range(N):
  11. maxscore = boxes[i, 4]
  12. maxpos = i
  13.  
  14. tx1 = boxes[i,0]
  15. ty1 = boxes[i,1]
  16. tx2 = boxes[i,2]
  17. ty2 = boxes[i,3]
  18. ts = boxes[i,4]
  19.  
  20. pos = i + 1
  21. # get max box
  22. while pos < N:
  23. if maxscore < boxes[pos, 4]:
  24. maxscore = boxes[pos, 4]
  25. maxpos = pos
  26. pos = pos + 1
  27.  
  28. # add max box as a detection
  29. boxes[i,0] = boxes[maxpos,0]
  30. boxes[i,1] = boxes[maxpos,1]
  31. boxes[i,2] = boxes[maxpos,2]
  32. boxes[i,3] = boxes[maxpos,3]
  33. boxes[i,4] = boxes[maxpos,4]
  34.  
  35. # swap ith box with position of max box
  36. boxes[maxpos,0] = tx1
  37. boxes[maxpos,1] = ty1
  38. boxes[maxpos,2] = tx2
  39. boxes[maxpos,3] = ty2
  40. boxes[maxpos,4] = ts
  41.  
  42. tx1 = boxes[i,0]
  43. ty1 = boxes[i,1]
  44. tx2 = boxes[i,2]
  45. ty2 = boxes[i,3]
  46. ts = boxes[i,4]
  47.  
  48. pos = i + 1
  49. # NMS iterations, note that N changes if detection boxes fall below threshold
  50. while pos < N:
  51. x1 = boxes[pos, 0]
  52. y1 = boxes[pos, 1]
  53. x2 = boxes[pos, 2]
  54. y2 = boxes[pos, 3]
  55. s = boxes[pos, 4]
  56.  
  57. area = (x2 - x1 + 1) * (y2 - y1 + 1)
  58. iw = (min(tx2, x2) - max(tx1, x1) + 1)
  59. if iw > 0:
  60. ih = (min(ty2, y2) - max(ty1, y1) + 1)
  61. if ih > 0:
  62. ua = float((tx2 - tx1 + 1) * (ty2 - ty1 + 1) + area - iw * ih)
  63. ov = iw * ih / ua #iou between max box and detection box
  64.  
  65. if method == 1: # linear
  66. if ov > Nt:
  67. weight = 1 - ov
  68. else:
  69. weight = 1
  70. elif method == 2: # gaussian
  71. weight = np.exp(-(ov * ov)/sigma)
  72. else: # original NMS
  73. if ov > Nt:
  74. weight = 0
  75. else:
  76. weight = 1
  77.  
  78. boxes[pos, 4] = weight*boxes[pos, 4]
  79.  
  80. # if box score falls below threshold, discard the box by swapping with last box
  81. # update N
  82. if boxes[pos, 4] < threshold:
  83. boxes[pos,0] = boxes[N-1, 0]
  84. boxes[pos,1] = boxes[N-1, 1]
  85. boxes[pos,2] = boxes[N-1, 2]
  86. boxes[pos,3] = boxes[N-1, 3]
  87. boxes[pos,4] = boxes[N-1, 4]
  88. N = N - 1
  89. pos = pos - 1
  90.  
  91. pos = pos + 1
  92.  
  93. keep = [i for i in range(N)]
  94. return keep

解释如下:

如上图,假如还检测出了3号框,而我们的最终目标是检测出1号和2号框,并且剔除3号框,原始的nms只会检测出一个1号框并剔除2号框和3号框,而softnms算法可以对1、2、3号检测狂进行置信度排序,可以知道这三个框的置信度从大到小的顺序依次为:1-》2-》3(由于是使用了惩罚,所有可以获得这种大小关系),如果我们再选择了合适的置信度阈值,就可以保留1号和2号,同时剔除3号,实现我们的功能。

遗留问题:

置信度的阈值设置目前还是手工设置,这依然存在很大局限性,所以还有改进的空间。

参考链接:

1、https://www.cnblogs.com/zf-blog/p/8532228.html

2、https://blog.csdn.net/heiheiya/article/details/81169758

目标检测后处理之NMS(非极大值抑制算法)的更多相关文章

  1. NMS(非极大值抑制算法)

    目的:为了消除多余的框,找到最佳的物体检测的位置 思想: 选取那些领域里分数最高的窗口,同时抑制那些分数低的窗口 Soft-NMS

  2. 【56】目标检测之NMS非极大值抑制

    非极大值抑制(Non-max suppression) 到目前为止你们学到的对象检测中的一个问题是,你的算法可能对同一个对象做出多次检测,所以算法不是对某个对象检测出一次,而是检测出多次.非极大值抑制 ...

  3. Non-Maximum Suppression,NMS非极大值抑制

    Non-Maximum Suppression,NMS非极大值抑制概述非极大值抑制(Non-Maximum Suppression,NMS),顾名思义就是抑制不是极大值的元素,可以理解为局部最大搜索. ...

  4. 非极大值抑制算法(Python实现)

    date: 2017-07-21 16:48:02 非极大值抑制算法(Non-maximum suppression, NMS) 算法原理 非极大值抑制算法的本质是搜索局部极大值,抑制非极大值元素. ...

  5. 输出预测边界框,NMS非极大值抑制

    我们预测阶段时: 生成多个锚框 每个锚框预测类别和偏移量 但是,当同一个目标上可能输出较多的相似的预测边界框.我们可以移除相似的预测边界框.——NMS(非极大值抑制). 对于一个预测边界框B,模型会计 ...

  6. Non-maximum suppression(非极大值抑制算法)

    在RCNN系列目标检测中,有一个重要的算法,用于消除一些冗余的bounding box,这就是non-maximum suppression算法. 这里有一篇博客写的挺好的: http://www.c ...

  7. 3分钟理解NMS非极大值抑制

    1. NMS被广泛用到目标检测技术中,正如字面意思,抑制那些分数低的目标,使最终框的位置更准: 2. 假如图片上实际有10张人脸,但目标检测过程中,检测到有30个框的位置,并且模型都认为它们是人脸,造 ...

  8. NMS(非极大值抑制)实现

    1.IOU计算 设两个边界框分别为A,B.A的坐标为Ax1,Ax2,Ay1,Ay2,且Ax1 < Ax2,Ay1 < Ay2.B和A类似. 则IOU为A∩B除以A∪B. 当两个边界框有重叠 ...

  9. [DeeplearningAI笔记]卷积神经网络3.6-3.9交并比/非极大值抑制/Anchor boxes/YOLO算法

    4.3目标检测 觉得有用的话,欢迎一起讨论相互学习~Follow Me 3.6交并比intersection over union 交并比函数(loU)可以用来评价对象检测算法,可以被用来进一步改善对 ...

随机推荐

  1. 极客时间-左耳听风-程序员攻略-Java底层知识

    Java 字节码相关 字节码编程,也就是动态修改或是动态生成 Java 字节码.Java 的字节码相当于汇编,其中的一些细节. Java Zone: Introduction to Java Byte ...

  2. 退出virtual box 独占键盘和鼠标

    先按住右边的Alt键,然后按一下(右边)ctrl键

  3. Git速成学习第六课:Bug分支

    Git速成学习笔记整理于廖雪峰老师的官网网站:https://www.liaoxuefeng.com/ 当你接到一个修复代码为101的任务的时候,很自然的你想创建一个分支issue-101来修复它,但 ...

  4. 通用 spring cloud 微服务模板

    说明文档 功能 1. 基于映射数据库一键生成 spring cloud 微服务 2. 通用 Controller ,无需编写代码即可完成基于数据库的服务 3. 动态多条件 CRUD + 分页 使用说明 ...

  5. Android之makefile

    在Android的源代码中,随处可见Makefile,那么Makefile到底是用来干嘛的呢?其实Makefile和Maven.ANT.Gradle一样,属于构建工具,当项目比较庞大的时候,就可以使用 ...

  6. container_of宏

    title: container_of宏 date: 2019/7/24 15:49:26 toc: true --- container_of宏 解析 在linux链表结构中有这样一个宏,通过成员变 ...

  7. 巧用 Class Extension 隐藏属性

    一般来说,Extension用来给Class增加私有属性和方法,写在 Class 的.m文件.但是Extension不是必须要写在.m文件,你可以写在任何地方,只要在 @implementation  ...

  8. poj 2362:square

    题目大意:给你T组数据,每组数据有n个棍子,问你能不能用这些棍子拼成一个正方形(所有都要用上,而且不能截断棍子). Sample Input 34 1 1 1 15 10 20 30 40 508 1 ...

  9. PTA(Advanced Level)1041.Be Unique

    Being unique is so important to people on Mars that even their lottery is designed in a unique way. ...

  10. kafka producer consumer demo(三)

    我们在前面把集群搭建起来了,也设置了kafka broker的配置,下面我们用代码来实现一下客户端向kafka发送消息,consumer端从kafka消费数据.大家先不要着急着了解 各种参数的配置,先 ...