看论文觉得很好理解,但是一看源码,瞬间懵了,很正常的想法,到代码上就看不懂了,结合了下面博文讲解,终于看懂了caffe-ssd中nms的代码。
https://blog.csdn.net/HappyRocking/article/details/79970627
源码:
void ApplyNMSFast(const vector<NormalizedBBox>& bboxes,
const vector<float>& scores, const float score_threshold,
const float nms_threshold, const float eta, const int top_k,
vector<int>* indices) {
// Sanity check. 使用的是这个版本的重定义
CHECK_EQ(bboxes.size(), scores.size())
<< "bboxes and scores have different size.";
// Get top_k scores (with corresponding indices).
vector<pair<float, int> > score_index_vec;
//score_threshold 设置好的conf_threshold,找到得分最高的前top_k个
GetMaxScoreIndex(scores, score_threshold, top_k, &score_index_vec);
// Do nms.
float adaptive_threshold = nms_threshold;
indices->clear();
while (score_index_vec.size() != 0) {
// score_index_vec存放的是map<scores, idx>
// idx是最大的那个得分的框的序号
const int idx = score_index_vec.front().second;
bool keep = true;
// 刚开始的时候,indices->size()=0,也就是直接把最大的放进去
for (int k = 0; k < indices->size(); ++k) {
if (keep) {
const int kept_idx = (*indices)[k];
float overlap = JaccardOverlap(bboxes[idx], bboxes[kept_idx]);
// 对于本次的这个得分最大值序号idx,只要其他候选框的重叠度都小于adaptive_threshold
// 那就证明不是同一个目标,那就保留这个局部最大值,直到把得分top_k的框都遍历一遍
keep = overlap <= adaptive_threshold;
} else {
// 否则就不保留这个极大值。
break;
}
}
if (keep) { // 如果把所有的框都遍历一遍,没有跟idx有重叠超过阈值的,说明它是局部极大值,保留。
// 第一次肯定是把最大的放进去,然后
indices->push_back(idx);
}
// 然后将最大的那个去掉
score_index_vec.erase(score_index_vec.begin());
if (keep && eta < 1 && adaptive_threshold > 0.5) {
adaptive_threshold *= eta;
}
}
}
中间标的注释描述不太到位,建议大家先看开头推荐的博文。
个人感觉NMS就是个寻找聚类最中心的值的问题。像推荐博文中举的例子,一张图片中有一类目标:人脸,有两个instance:jack and rose。那他俩的脸肯定不能直接选得分最高的那个,不然jack就没了。这里面的5个框对应两个聚类中心jack and rose,NMS的任务就是把两个聚类里面得分最高的给找出来。我们人一眼就能看出来,但是计算机必须通过计算。
结合代码,函数首先通过GetMaxScoreIndex(scores, score_threshold, top_k, &score_index_vec)这个函数将得分前top_k做成map_pair<score, index>,这里score就是预测某一类目标的第index个框的得分,index的排序是在产生prior box时由那6层feature map对应的priorbox经过flatten和concatenate后的排序,这个不重要,重要的是index跟那8732个框一一对应,就可以了。通过这个函数得到的score_index_vec是经过排序的,也就是其score是从大到小排列的,共top_k=400个(默认参数)。然后进入while(score_index_vec.size() != 0)这个循环,先不去管为啥不等于就得一直进行,先看idx,idx首先得到的是得分最高的那个index,那必然是其中某一个聚类的最中心点,它必须保留(类似于推荐博文中的rose那个得分0.9多的那个框,那个必须保留)。代码也是这么实现的,令keep=1,第一次循环时,indices是个空的vector,因此, 循环等效为:for (int k = 0; k < 0; ++k) {。。。}里面的代码不执行,后面keep依然是1,那顺理成章的把idx推进indices 里面了:indices->push_back(idx);然后把idx从score_index_vec里面清掉。下面的if应该是个自适应阈值的衰减系数,暂且不理会。
当第一个循环搞定后,后面的就是从得分第二的开始,跟已经成为聚类中心的比较,如果重叠率高于既定阈值,那就将其erase。如果得分第二的这个index跟得分最高的重叠率也不超过阈值,那证明他是另外一个聚类中心,也就是第二Instance(类似于博文中的jack,当然jack那个中心的框并不是得分第二高的)。
非极大值抑制抑制的思想较为简单,但是代码实现还是很巧妙的,值得学习。