自测好用的C++实现的NMS算法片段,一看就能回忆起原理。本次整理原理加上手撸代码共花费2个小时,这大概是我第三次全流程撸nms算法吧,希望以后再来撸它的时候效率能大大提高。
对一组bounding boxes进行nms,输入iou阈值去除重复的目标框,经过nms算法后返回实际的目标框
计算IOU的时候bbox的坐标加上分类id乘以一个经验值,将不同分类id的bbox离散到不同的数据域做IOU,避免了数据的拷贝,提升算法性能。
std::vector<Bbox> nms(std::vector<Bbox>&boxes, float threshold)
{
int wh_max = 7680;
std::vector<Bbox> resluts;
std::sort(boxes.begin(), boxes.end(), sort_confidence);
while (boxes.size() > 0)
{
//最上面的框肯定是要放到结果中的,因为它的置信度最大,要删也是删置信度小的框
resluts.push_back(boxes[0]);
int index = 1;
while (index < boxes.size()) {
//目标框坐标按分类id分簇,因为做IOU的时候只对分类id相同的框进行比较
float aleft = boxes[0].left + boxes[0].clas * wh_max;
float atop = boxes[0].top + boxes[0].clas * wh_max;
float aright = boxes[0].right + boxes[0].clas * wh_max;
float abottom = boxes[0].bottom + boxes[0].clas * wh_max;
float bleft = boxes[index].left + boxes[index].clas * wh_max;
float btop = boxes[index].top + boxes[index].clas * wh_max;
float bright = boxes[index].right + boxes[index].clas * wh_max;
float bbottom = boxes[index].bottom + boxes[index].clas * wh_max;
float iou_value = iou(aleft, atop, aright, abottom,
bleft, btop, bright, bbottom);
if (iou_value > threshold) {
//IOU大于阈值时,删除遍历的框,删除后index刚好指向下一个遍历的框,巧妙
boxes.erase(boxes.begin() + index);
}
else {
//不删,那就继续遍历下一个框
index++;
}
}
//当前置信度最高的框,和其他目标框遍历比较结束,最后删除它
boxes.erase(boxes.begin());
}
return resluts;
}
输入NMS算法的数据首先要根据置信度阈值从大到小排序:
CPU实现可以用std::sort实现
//std::sort的函数定义
template< class RandomIt, class Compare >
void sort( RandomIt first, RandomIt last, Compare comp );
Compare参数是一个用来比较元素大小的函数或者函数对象。它接受两个元素作为参数,返回一个布尔值,表示第一个元素是否应该排在第二个元素之前。如果没有提供Compare参数,默认non-descending(从小到大)排序。
此处我们需要提供一个从大到小排序的Compare函数对象。
bool sort_confidence(Bbox bbox1,Bbox bbox2)
{
return bbox1.confidence > bbox2.confidence ? true : false;
}
Compare函数在nms中使用:
std::sort(boxes.begin(), boxes.end(), sort_confidence);
iou算法使用的函数,记录一下这次自己手写的:
//IOU算法, 输入两个box坐标,计算交并比
float intersection_over_union(Bbox box1, Bbox box2)
{
float _bottom = std::fmin(box1.bottom, box2.bottom);
float _top = std::fmax(box1.top, box2.top);
float _right = std::fmin(box1.right, box2.right);
float _left = std::fmax(box2.left,box2.left);
float inter_area = std::fmax(_bottom-_top, 0.0) * std::fmax(_right-_left, 0.0);
if(inter_area == 0.0)
return 0.0;
float union_area = std::fmax(bbox1.bottom - bbox1.top, 0.0) * std::fmax(bbox1.right - bbox1.top, 0.0) + /
std::fmax(bbox1.bottom - bbox1.top, 0.0) * std::fmax(bbox1.right - bbox1.top, 0.0) - inter_area;
return inter_area / union_area;
}