Skip to content

Commit bc93775

Browse files
committed
Merge pull request opencv#9862 from sovrasov:dnn_nms
2 parents b4367c9 + 7e3e914 commit bc93775

File tree

5 files changed

+199
-70
lines changed

5 files changed

+199
-70
lines changed

modules/dnn/include/opencv2/dnn/dnn.hpp

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -734,6 +734,21 @@ CV__DNN_EXPERIMENTAL_NS_BEGIN
734734
*/
735735
CV_EXPORTS_W void shrinkCaffeModel(const String& src, const String& dst);
736736

737+
/** @brief Performs non maximum suppression given boxes and corresponding scores.
738+
739+
* @param bboxes a set of bounding boxes to apply NMS.
740+
* @param scores a set of corresponding confidences.
741+
* @param score_threshold a threshold used to filter boxes by score.
742+
* @param nms_threshold a threshold used in non maximum suppression.
743+
* @param indices the kept indices of bboxes after NMS.
744+
* @param eta a coefficient in adaptive threshold formula: \f$nms\_threshold_{i+1}=eta\cdot nms\_threshold_i\f$.
745+
* @param top_k if `>0`, keep at most @p top_k picked indices.
746+
*/
747+
CV_EXPORTS_W void NMSBoxes(const std::vector<Rect>& bboxes, const std::vector<float>& scores,
748+
const float score_threshold, const float nms_threshold,
749+
CV_OUT std::vector<int>& indices,
750+
const float eta = 1.f, const int top_k = 0);
751+
737752

738753
//! @}
739754
CV__DNN_EXPERIMENTAL_NS_END

modules/dnn/src/layers/detection_output_layer.cpp

Lines changed: 10 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
#include <float.h>
4646
#include <string>
4747
#include <caffe.pb.h>
48+
#include "../nms.inl.hpp"
4849

4950
namespace cv
5051
{
@@ -61,6 +62,8 @@ static inline bool SortScorePairDescend(const std::pair<float, T>& pair1,
6162
return pair1.first > pair2.first;
6263
}
6364

65+
static inline float caffe_box_overlap(const caffe::NormalizedBBox& a, const caffe::NormalizedBBox& b);
66+
6467
} // namespace
6568

6669
class DetectionOutputLayerImpl : public DetectionOutputLayer
@@ -308,7 +311,8 @@ class DetectionOutputLayerImpl : public DetectionOutputLayer
308311
LabelBBox::const_iterator label_bboxes = decodeBBoxes.find(label);
309312
if (label_bboxes == decodeBBoxes.end())
310313
CV_ErrorNoReturn_(cv::Error::StsError, ("Could not find location predictions for label %d", label));
311-
ApplyNMSFast(label_bboxes->second, scores, _confidenceThreshold, _nmsThreshold, 1.0, _topK, indices[c]);
314+
NMSFast_(label_bboxes->second, scores, _confidenceThreshold, _nmsThreshold, 1.0, _topK,
315+
indices[c], util::caffe_box_overlap);
312316
numDetections += indices[c].size();
313317
}
314318
if (_keepTopK > -1 && numDetections > (size_t)_keepTopK)
@@ -619,75 +623,6 @@ class DetectionOutputLayerImpl : public DetectionOutputLayer
619623
}
620624
}
621625

622-
// Do non maximum suppression given bboxes and scores.
623-
// Inspired by Piotr Dollar's NMS implementation in EdgeBox.
624-
// https://goo.gl/jV3JYS
625-
// bboxes: a set of bounding boxes.
626-
// scores: a set of corresponding confidences.
627-
// score_threshold: a threshold used to filter detection results.
628-
// nms_threshold: a threshold used in non maximum suppression.
629-
// top_k: if not -1, keep at most top_k picked indices.
630-
// indices: the kept indices of bboxes after nms.
631-
static void ApplyNMSFast(const std::vector<caffe::NormalizedBBox>& bboxes,
632-
const std::vector<float>& scores, const float score_threshold,
633-
const float nms_threshold, const float eta, const int top_k,
634-
std::vector<int>& indices)
635-
{
636-
CV_Assert(bboxes.size() == scores.size());
637-
638-
// Get top_k scores (with corresponding indices).
639-
std::vector<std::pair<float, int> > score_index_vec;
640-
GetMaxScoreIndex(scores, score_threshold, top_k, score_index_vec);
641-
642-
// Do nms.
643-
float adaptive_threshold = nms_threshold;
644-
indices.clear();
645-
while (score_index_vec.size() != 0) {
646-
const int idx = score_index_vec.front().second;
647-
bool keep = true;
648-
for (int k = 0; k < (int)indices.size() && keep; ++k) {
649-
const int kept_idx = indices[k];
650-
float overlap = JaccardOverlap<true>(bboxes[idx], bboxes[kept_idx]);
651-
keep = overlap <= adaptive_threshold;
652-
}
653-
if (keep)
654-
indices.push_back(idx);
655-
score_index_vec.erase(score_index_vec.begin());
656-
if (keep && eta < 1 && adaptive_threshold > 0.5) {
657-
adaptive_threshold *= eta;
658-
}
659-
}
660-
}
661-
662-
// Get max scores with corresponding indices.
663-
// scores: a set of scores.
664-
// threshold: only consider scores higher than the threshold.
665-
// top_k: if -1, keep all; otherwise, keep at most top_k.
666-
// score_index_vec: store the sorted (score, index) pair.
667-
static void GetMaxScoreIndex(const std::vector<float>& scores, const float threshold, const int top_k,
668-
std::vector<std::pair<float, int> >& score_index_vec)
669-
{
670-
CV_DbgAssert(score_index_vec.empty());
671-
// Generate index score pairs.
672-
for (size_t i = 0; i < scores.size(); ++i)
673-
{
674-
if (scores[i] > threshold)
675-
{
676-
score_index_vec.push_back(std::make_pair(scores[i], i));
677-
}
678-
}
679-
680-
// Sort the score pair according to the scores in descending order
681-
std::stable_sort(score_index_vec.begin(), score_index_vec.end(),
682-
util::SortScorePairDescend<int>);
683-
684-
// Keep top_k scores if needed.
685-
if (top_k > -1 && top_k < (int)score_index_vec.size())
686-
{
687-
score_index_vec.resize(top_k);
688-
}
689-
}
690-
691626
// Compute the jaccard (intersection over union IoU) overlap between two bboxes.
692627
template<bool normalized>
693628
static float JaccardOverlap(const caffe::NormalizedBBox& bbox1,
@@ -733,6 +668,11 @@ class DetectionOutputLayerImpl : public DetectionOutputLayer
733668
}
734669
};
735670

671+
float util::caffe_box_overlap(const caffe::NormalizedBBox& a, const caffe::NormalizedBBox& b)
672+
{
673+
return DetectionOutputLayerImpl::JaccardOverlap<true>(a, b);
674+
}
675+
736676
const std::string DetectionOutputLayerImpl::_layerName = std::string("DetectionOutput");
737677

738678
Ptr<DetectionOutputLayer> DetectionOutputLayer::create(const LayerParams &params)

modules/dnn/src/nms.cpp

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
// This file is part of OpenCV project.
2+
// It is subject to the license terms in the LICENSE file found in the top-level directory
3+
// of this distribution and at http://opencv.org/license.html.
4+
//
5+
// Copyright (C) 2017, Intel Corporation, all rights reserved.
6+
// Third party copyrights are property of their respective owners.
7+
8+
#include "precomp.hpp"
9+
#include <nms.inl.hpp>
10+
11+
namespace cv
12+
{
13+
namespace dnn
14+
{
15+
CV__DNN_EXPERIMENTAL_NS_BEGIN
16+
17+
static inline float rectOverlap(const Rect& a, const Rect& b)
18+
{
19+
return 1.f - static_cast<float>(jaccardDistance(a, b));
20+
}
21+
22+
void NMSBoxes(const std::vector<Rect>& bboxes, const std::vector<float>& scores,
23+
const float score_threshold, const float nms_threshold,
24+
std::vector<int>& indices, const float eta, const int top_k)
25+
{
26+
CV_Assert(bboxes.size() == scores.size(), score_threshold >= 0,
27+
nms_threshold >= 0, eta > 0);
28+
NMSFast_(bboxes, scores, score_threshold, nms_threshold, eta, top_k, indices, rectOverlap);
29+
}
30+
31+
CV__DNN_EXPERIMENTAL_NS_END
32+
}// dnn
33+
}// cv

modules/dnn/src/nms.inl.hpp

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
// This file is part of OpenCV project.
2+
// It is subject to the license terms in the LICENSE file found in the top-level directory
3+
// of this distribution and at http://opencv.org/license.html.
4+
//
5+
// Copyright (C) 2017, Intel Corporation, all rights reserved.
6+
// Third party copyrights are property of their respective owners.
7+
8+
#ifndef OPENCV_DNN_NMS_INL_HPP
9+
#define OPENCV_DNN_NMS_INL_HPP
10+
11+
#include <opencv2/dnn.hpp>
12+
13+
namespace cv {
14+
namespace dnn {
15+
16+
namespace
17+
{
18+
19+
template <typename T>
20+
static inline bool SortScorePairDescend(const std::pair<float, T>& pair1,
21+
const std::pair<float, T>& pair2)
22+
{
23+
return pair1.first > pair2.first;
24+
}
25+
26+
} // namespace
27+
28+
// Get max scores with corresponding indices.
29+
// scores: a set of scores.
30+
// threshold: only consider scores higher than the threshold.
31+
// top_k: if -1, keep all; otherwise, keep at most top_k.
32+
// score_index_vec: store the sorted (score, index) pair.
33+
inline void GetMaxScoreIndex(const std::vector<float>& scores, const float threshold, const int top_k,
34+
std::vector<std::pair<float, int> >& score_index_vec)
35+
{
36+
CV_DbgAssert(score_index_vec.empty());
37+
// Generate index score pairs.
38+
for (size_t i = 0; i < scores.size(); ++i)
39+
{
40+
if (scores[i] > threshold)
41+
{
42+
score_index_vec.push_back(std::make_pair(scores[i], i));
43+
}
44+
}
45+
46+
// Sort the score pair according to the scores in descending order
47+
std::stable_sort(score_index_vec.begin(), score_index_vec.end(),
48+
SortScorePairDescend<int>);
49+
50+
// Keep top_k scores if needed.
51+
if (top_k > 0 && top_k < (int)score_index_vec.size())
52+
{
53+
score_index_vec.resize(top_k);
54+
}
55+
}
56+
57+
// Do non maximum suppression given bboxes and scores.
58+
// Inspired by Piotr Dollar's NMS implementation in EdgeBox.
59+
// https://goo.gl/jV3JYS
60+
// bboxes: a set of bounding boxes.
61+
// scores: a set of corresponding confidences.
62+
// score_threshold: a threshold used to filter detection results.
63+
// nms_threshold: a threshold used in non maximum suppression.
64+
// top_k: if not > 0, keep at most top_k picked indices.
65+
// indices: the kept indices of bboxes after nms.
66+
template <typename BoxType>
67+
inline void NMSFast_(const std::vector<BoxType>& bboxes,
68+
const std::vector<float>& scores, const float score_threshold,
69+
const float nms_threshold, const float eta, const int top_k,
70+
std::vector<int>& indices, float (*computeOverlap)(const BoxType&, const BoxType&))
71+
{
72+
CV_Assert(bboxes.size() == scores.size());
73+
74+
// Get top_k scores (with corresponding indices).
75+
std::vector<std::pair<float, int> > score_index_vec;
76+
GetMaxScoreIndex(scores, score_threshold, top_k, score_index_vec);
77+
78+
// Do nms.
79+
float adaptive_threshold = nms_threshold;
80+
indices.clear();
81+
for (size_t i = 0; i < score_index_vec.size(); ++i) {
82+
const int idx = score_index_vec[i].second;
83+
bool keep = true;
84+
for (int k = 0; k < (int)indices.size() && keep; ++k) {
85+
const int kept_idx = indices[k];
86+
float overlap = computeOverlap(bboxes[idx], bboxes[kept_idx]);
87+
keep = overlap <= adaptive_threshold;
88+
}
89+
if (keep)
90+
indices.push_back(idx);
91+
if (keep && eta < 1 && adaptive_threshold > 0.5) {
92+
adaptive_threshold *= eta;
93+
}
94+
}
95+
}
96+
97+
}// dnn
98+
}// cv
99+
100+
#endif

modules/dnn/test/test_nms.cpp

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
// This file is part of OpenCV project.
2+
// It is subject to the license terms in the LICENSE file found in the top-level directory
3+
// of this distribution and at http://opencv.org/license.html.
4+
//
5+
// Copyright (C) 2017, Intel Corporation, all rights reserved.
6+
// Third party copyrights are property of their respective owners.
7+
8+
#include "test_precomp.hpp"
9+
10+
namespace cvtest
11+
{
12+
13+
TEST(NMS, Accuracy)
14+
{
15+
//reference results obtained using tf.image.non_max_suppression with iou_threshold=0.5
16+
std::string dataPath = findDataFile("dnn/nms_reference.yml");
17+
FileStorage fs(dataPath, FileStorage::READ);
18+
19+
std::vector<Rect> bboxes;
20+
std::vector<float> scores;
21+
std::vector<int> ref_indices;
22+
23+
fs["boxes"] >> bboxes;
24+
fs["probs"] >> scores;
25+
fs["output"] >> ref_indices;
26+
27+
const float nms_thresh = .5f;
28+
const float score_thresh = .01f;
29+
std::vector<int> indices;
30+
cv::dnn::NMSBoxes(bboxes, scores, score_thresh, nms_thresh, indices);
31+
32+
ASSERT_EQ(ref_indices.size(), indices.size());
33+
34+
std::sort(indices.begin(), indices.end());
35+
std::sort(ref_indices.begin(), ref_indices.end());
36+
37+
for(size_t i = 0; i < indices.size(); i++)
38+
ASSERT_EQ(indices[i], ref_indices[i]);
39+
}
40+
41+
}//cvtest

0 commit comments

Comments
 (0)