NMS流程及示例代码

devtools/2024/10/19 23:48:21/

NMS在目标检测中的作用不再赘述,现在就该算法的方法和流程进行总结。
以某yolo模型输出的61440*6的数据为例,总共输出61440的bbox(实际只有3个目标),每个bbox的格式为[cx,cy,w,h,conf,cls_score],分别代表bbox的4个值,置信度以及类别分类得分。在该任务中只有1个类,故cls_score≈1,在多类别中,bbox格式为[cx,cy,w,h,conf,cls1_score,cls2_score,…],所有的类别得到之和等于1。

  • step 1.过滤
    在61440个bbox中大部分都是在背景位置的bbox,其置信度很低,所以首先需要过滤这些低置信度的bbox。比如在ndarray格式中,可以通过下面的代码筛选出置信度大于conf_thresh(一般设为0.5)的bbox,最终filtered_data只有18个bbox
# 筛选conf大于或等于0.5的行
filtered_indices = output[:, 4] >= conf_thresh  # data[:, 4]是获取conf列
filtered_data = output[filtered_indices, :]  # 根据筛选结果保留符合条件的行
  • step 2. 聚类整理
    在同一个位置,不同的类别的bbox可能IOU很高,但是这不属于NMS过滤的对象。所以在所有bbox中,需要按照类别将所有的bbox分类,然后在每一个类中进行IOU过滤。比如通过字典,以类名为key,所属类的bbox在value中:
for bbox in filtered_data:if class_bbox_dict.get(round(bbox[5])):class_bbox_dict[round(bbox[5])].append(bbox)  # 如果有类名key则追加新的bboxelse:class_bbox_dict[round(bbox[5])] = [bbox]  # 如果没有类名key则新建list,并存储当前bbox

结果如图,因为该任务重只有类别1,所以全部bbox都归并到key=1中:
在这里插入图片描述

  • step3. 类内IOU过滤
    在这里插入图片描述

类内过滤如上图所示,假设某类别有6个bbox,按conf降序排序。第一轮首先以第一个为标准,计算剩余bbox与该bbox的IOU,超过阈值则舍弃(红色bbox,在下方的python代码中用None标记)。在该过程中有bbox2和bbox3和bbox1的IOU较高,被舍弃。

第二轮以第二个有效bbox(即bbox3)为标准,计算剩余的有效box与该bbox的IOU,即bbox3和bbox5、bbox6计算IOU,发现bbox6和bbox3的IOU超过阈值,所以被舍弃。

在上面的过程中,标准bbox即绿色的bbox,在代码中会加入到结果result中,最后有3个bbox(bbox1、bbox3、bbox5)加入到result,其余的则被舍弃。

python的实现代码如下:

result = []
for classid, bboxes in class_bbox_dict.items():  # 遍历每一个类,依此对类内bbox进行NMS处理bboxes = sorted(list(bboxes), key=lambda x: -x[4])  # 置信度降序排序for i in range(bboxes.__len__()):if not bboxes[i] is None:result.append(bboxes[i])for j in range(i + 1, bboxes.__len__()):if not bboxes[j] is None:if compute_iou(bboxes[i][:4], bboxes[j][:4]) > iou_thresh:bboxes[j] = None	# 该bbox和标准bbox的IOU较高,被舍弃,这里用None标记
  • 总结
    详细来看NMS并不难,上面只是一个类,在多类中NMS会有所不同,但是流程一样。不同之处在于conf会和cls_score进行乘法运算。在这里插入图片描述
    比如上方是一个4类的bbox,类别就是4个概率中最大的位置代表的类。在NMS是conf=conf*max(cls_score),即新的conf综合考虑了检测框的conf和分类概率。NMS的其他过程不变

完成的python代码如下:

import numpy as np
import cv2def cxcywh2xyxy(bbox):"""将yolo输出格式转为xyxy格式@param bbox:@return:"""cx, cy, w, h = bboxx1, y1, x2, y2 = cx - w / 2, cy - h / 2, cx + w / 2, cy + h / 2return [x1, y1, x2, y2]def compute_iou(box1, box2):"""计算两个矩形框的IoU:param box1: 第一个矩形框,格式为(x1, y1, x2, y2):param box2: 第二个矩形框,格式为(x1, y1, x2, y2):return: 两个矩形框的IoU"""# 计算交集box1 = cxcywh2xyxy(box1)box2 = cxcywh2xyxy(box2)xi1 = max(box1[0], box2[0])yi1 = max(box1[1], box2[1])xi2 = min(box1[2], box2[2])yi2 = min(box1[3], box2[3])inter_area = max(xi2 - xi1, 0) * max(yi2 - yi1, 0)# 计算并集box1_area = (box1[2] - box1[0]) * (box1[3] - box1[1])box2_area = (box2[2] - box2[0]) * (box2[3] - box2[1])union_area = box1_area + box2_area - inter_area# 计算IoUiou = inter_area / union_areareturn ioudef NMS(img, output, conf_thresh, iou_thresh):"""根据置信度过滤bbox---》按类别聚集bbox并在类内按conf进行排序---》每一个类中计算bbox之间的置信度@param img:@param output:@param conf_thresh:@param iou_thresh:@return:"""# 筛选conf大于或等于0.5的行filtered_indices = output[:, 4] >= conf_thresh  # data[:, 4]是获取conf列filtered_data = output[filtered_indices, :]  # 根据筛选结果保留符合条件的行class_bbox_dict = {}for bbox in filtered_data:if class_bbox_dict.get(round(bbox[5])):class_bbox_dict[round(bbox[5])].append(bbox)else:class_bbox_dict[round(bbox[5])] = [bbox]result = []for classid, bboxes in class_bbox_dict.items():bboxes = sorted(list(bboxes), key=lambda x: -x[4])  # 置信度降序排序for i in range(bboxes.__len__()):if not bboxes[i] is None:result.append(bboxes[i])for j in range(i + 1, bboxes.__len__()):if not bboxes[j] is None:if compute_iou(bboxes[i][:4], bboxes[j][:4]) > iou_thresh:print(f"del {j}")bboxes[j] = Noneprint(result)if __name__ == "__main__":output = np.load("sky500_coarse_2024_08_15_00_22_31_34.npy") # 加载一个yolo输出的数据,对齐进行NMSimg = cv2.imread("sky500_coarse_2024_08_15_00_22_31_34.jpg")conf_thresh = 0.5iou_thresh = 0.5NMS(img, output, conf_thresh, iou_thresh)

一个c++版本:

void nmsDet(std::vector<Detection> &src, std::vector<Detection> &res, float nms_thresh)
{int det_size = sizeof(Detection) / sizeof(float);std::map<float, std::vector<Detection>> m;for (int i = 0; i < src.size() && i < kMaxNumOutputBbox; i++){Detection det = src[i];// 先查询map中key为det类别的个数,如果为0证明还没有创建该类的容器,否则直接push_backif (m.count(det.class_id) == 0)   // count 返回与特定key匹配的元素的数量{m.emplace(det.class_id, std::vector<Detection>());  // 插入一个新的类别子容器}m[det.class_id].push_back(det);   // 向类别子容器中插入det}for (auto it = m.begin(); it != m.end(); it++)  // 遍历每一个类别子容器{auto &dets = it->second;           // 获取存放目标det的Vetor,并按照置信度排序std::sort(dets.begin(), dets.end(), cmp); // 保证结果中,任意两个目标的IOU都符合条件,for (size_t m = 0; m < dets.size(); ++m)    // 遍历每一个Detection{auto &item = dets[m];res.push_back(item);        //将当前最高Conf存入结果容器for (size_t n = m + 1; n < dets.size(); ++n)  // 然后剩余的Detection与当前Detection进行IOU计算,如果IOU大于nms_thresh,则删除当前Detection{if (iou(item.bbox, dets[n].bbox) > nms_thresh){dets.erase(dets.begin() + n);  // 删除当前Detection--n;}}}}
}

http://www.ppmy.cn/devtools/100404.html

相关文章

python怎么去除换行符

在Python的编写过程中&#xff0c;获取到的字符串进场存在不明原因的换行和空格&#xff0c;如何整合成一个单句&#xff0c;成为问题。 方法&#xff1a; 一、去除空格 “ ”代表的为空格 "xyz".strip() # returns "xyz" "xyz".ls…

人社大赛算法赛题解题思路分享+第五名

关联比赛: [国家社保]全国社会保险大数据应用创新大赛 赛题背景分析及理解 本次比赛&#xff0c;“精准社保”的赛题为“基本医疗保险医疗服务智能监控”&#xff0c;由参赛队完成数据算法模型的开发设计&#xff0c;实现对各类医疗保险基金欺诈违规行为的准确识别。 在进行了…

【C#】【EXCEL】Bumblebee/Classes/ExEnums.cs

文章目录 Bumblebee/Classes/ExEnums.csFlow diagramDescriptionCode Bumblebee/Classes/ExEnums.cs Flow diagram #mermaid-svg-FB98N7ZCCccQ4Z38 {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-FB98N7ZCCccQ4Z38…

建筑楼宇电气安全与能效管理

随着建筑业的发展&#xff0c;配电系统在楼宇建筑特别是高层建筑中的比重也随之加大。现代的建筑的功能越来越完善&#xff0c;变配电工程、空调工程、机电工程、电梯工程、消防工程等工程设施设备与建筑体相结合&#xff0c;敷设的电气线路变得更为复杂&#xff0c;火灾隐患明…

leetcode 数组+哈希+双指针+子串+滑动窗口

——————双指针 283. 移动零 给定一个数组 nums&#xff0c;编写一个函数将所有 0 移动到数组的末尾&#xff0c;同时保持非零元素的相对顺序。 请注意 &#xff0c;必须在不复制数组的情况下原地对数组进行操作。 示例 1: 输入: nums [0,1,0,3,12] 输出: [1,3,12,0,0] …

如何利用命令模式实现一个手游后端架构

命令模式&#xff08;Command Pattern&#xff09;是一种行为设计模式&#xff0c;它允许将请求封装为对象&#xff0c;从而使用不同的请求、队列、日志来参数化其他对象。命令模式也支持可撤销的操作。虽然命令模式在图形用户界面&#xff08;GUI&#xff09;编程中最为常见&a…

【精选】基于django柚子校园影院(咨询+解答+辅导)

博主介绍&#xff1a; ✌我是阿龙&#xff0c;一名专注于Java技术领域的程序员&#xff0c;全网拥有10W粉丝。作为CSDN特邀作者、博客专家、新星计划导师&#xff0c;我在计算机毕业设计开发方面积累了丰富的经验。同时&#xff0c;我也是掘金、华为云、阿里云、InfoQ等平台…

C语言与Python的区别

一、言语类型Python是一种基于解说器的言语&#xff0c;解说器会逐行读取代码&#xff1b;首先将Python编译为字节码&#xff0c;然后由大型C程序解说&#xff1b;C是一种编译言语&#xff0c;完好的源代码将直接编译为机器代码&#xff0c;由CPU直接履行。 二、内存办理Python…