Skip to content

Commit 4393a3d

Browse files
authored
add dnn nms (#119)
1 parent a4c17fc commit 4393a3d

File tree

5 files changed

+312
-1
lines changed

5 files changed

+312
-1
lines changed

.github/workflows/release.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,7 @@ jobs:
145145
rm -rf modules/js
146146
rm -rf modules/python
147147
rm -rf modules/ts
148+
rm -rf modules/dnn
148149
sed -e 's/__VERSION__/${{ env.opencv-version }}/g' ../patches/Info.plist > ./Info.plist
149150
cp ../opencv3_cmake_options.txt ./options.txt
150151
cd ..
@@ -214,6 +215,7 @@ jobs:
214215
rm -rf modules/js
215216
rm -rf modules/python
216217
rm -rf modules/ts
218+
rm -rf modules/dnn
217219
sed -e 's/__VERSION__/${{ env.opencv-version }}/g' ../patches/Info.plist > ./Info.plist
218220
cp ../opencv4_cmake_options.txt ./options.txt
219221
cd ..

highgui/CMakeLists.txt

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ set(highgui_srcs
1010
${CMAKE_CURRENT_LIST_DIR}/src/highgui.cpp
1111
${CMAKE_CURRENT_LIST_DIR}/src/kanna_rotate.cpp
1212
${CMAKE_CURRENT_LIST_DIR}/src/videocapture.cpp
13+
14+
# dnn
15+
${CMAKE_CURRENT_LIST_DIR}/src/nms.cpp
1316
)
1417

1518
if(WITH_CVI)
@@ -40,7 +43,9 @@ endif()
4043
file(GLOB highgui_ext_hdrs
4144
"${CMAKE_CURRENT_LIST_DIR}/include/opencv2/*.hpp"
4245
"${CMAKE_CURRENT_LIST_DIR}/include/opencv2/${name}/*.hpp"
43-
"${CMAKE_CURRENT_LIST_DIR}/include/opencv2/${name}/*.h")
46+
"${CMAKE_CURRENT_LIST_DIR}/include/opencv2/${name}/*.h"
47+
"${CMAKE_CURRENT_LIST_DIR}/include/opencv2/dnn/*.hpp"
48+
"${CMAKE_CURRENT_LIST_DIR}/include/opencv2/dnn/*.h")
4449

4550
if(UNIX OR OPENCV_VERSION_MAJOR GREATER_EQUAL 3)
4651
#these variables are set by CHECK_MODULE macro

highgui/include/opencv2/dnn.hpp

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
//
2+
// Copyright (C) 2024 nihui
3+
//
4+
// Licensed under the Apache License, Version 2.0 (the "License");
5+
// you may not use this file except in compliance with the License.
6+
// You may obtain a copy of the License at
7+
//
8+
// http://www.apache.org/licenses/LICENSE-2.0
9+
//
10+
// Unless required by applicable law or agreed to in writing, software
11+
// distributed under the License is distributed on an "AS IS" BASIS,
12+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
// See the License for the specific language governing permissions and
14+
// limitations under the License.
15+
//
16+
17+
#include "opencv2/dnn/dnn.hpp"
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
//
2+
// Copyright (C) 2024 nihui
3+
//
4+
// Licensed under the Apache License, Version 2.0 (the "License");
5+
// you may not use this file except in compliance with the License.
6+
// You may obtain a copy of the License at
7+
//
8+
// http://www.apache.org/licenses/LICENSE-2.0
9+
//
10+
// Unless required by applicable law or agreed to in writing, software
11+
// distributed under the License is distributed on an "AS IS" BASIS,
12+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
// See the License for the specific language governing permissions and
14+
// limitations under the License.
15+
//
16+
17+
#ifndef OPENCV_DNN_HPP
18+
#define OPENCV_DNN_HPP
19+
20+
#include "opencv2/core.hpp"
21+
22+
namespace cv {
23+
namespace dnn {
24+
25+
enum SoftNMSMethod
26+
{
27+
SOFTNMS_LINEAR = 1,
28+
SOFTNMS_GAUSSIAN = 2
29+
};
30+
31+
CV_EXPORTS void NMSBoxes(const std::vector<Rect>& bboxes, const std::vector<float>& scores,
32+
const float score_threshold, const float nms_threshold,
33+
CV_OUT std::vector<int>& indices,
34+
const float eta = 1.f, const int top_k = 0);
35+
36+
CV_EXPORTS void NMSBoxesBatched(const std::vector<Rect>& bboxes, const std::vector<float>& scores, const std::vector<int>& class_ids,
37+
const float score_threshold, const float nms_threshold,
38+
CV_OUT std::vector<int>& indices,
39+
const float eta = 1.f, const int top_k = 0);
40+
41+
CV_EXPORTS_W void softNMSBoxes(const std::vector<Rect>& bboxes, const std::vector<float>& scores,
42+
CV_OUT std::vector<float>& updated_scores,
43+
const float score_threshold, const float nms_threshold,
44+
CV_OUT std::vector<int>& indices,
45+
size_t top_k = 0, const float sigma = 0.5, SoftNMSMethod method = SOFTNMS_GAUSSIAN);
46+
47+
} // namespace dnn
48+
} // namespace cv
49+
50+
#endif // OPENCV_DNN_HPP

highgui/src/nms.cpp

Lines changed: 237 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,237 @@
1+
//
2+
// Copyright (C) 2024 nihui
3+
//
4+
// Licensed under the Apache License, Version 2.0 (the "License");
5+
// you may not use this file except in compliance with the License.
6+
// You may obtain a copy of the License at
7+
//
8+
// http://www.apache.org/licenses/LICENSE-2.0
9+
//
10+
// Unless required by applicable law or agreed to in writing, software
11+
// distributed under the License is distributed on an "AS IS" BASIS,
12+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
// See the License for the specific language governing permissions and
14+
// limitations under the License.
15+
//
16+
17+
#include <opencv2/core.hpp>
18+
#include <opencv2/imgproc.hpp>
19+
#include <opencv2/dnn.hpp>
20+
#include <limits>
21+
#include <vector>
22+
#include <algorithm>
23+
24+
namespace cv {
25+
namespace dnn {
26+
27+
static inline bool SortScorePairDescend(const std::pair<float, int>& pair1, const std::pair<float, int>& pair2)
28+
{
29+
return pair1.first > pair2.first;
30+
}
31+
32+
// Get max scores with corresponding indices.
33+
// scores: a set of scores.
34+
// threshold: only consider scores higher than the threshold.
35+
// top_k: if -1, keep all; otherwise, keep at most top_k.
36+
// score_index_vec: store the sorted (score, index) pair.
37+
inline void GetMaxScoreIndex(const std::vector<float>& scores, const float threshold, const int top_k,
38+
std::vector<std::pair<float, int> >& score_index_vec)
39+
{
40+
// Generate index score pairs.
41+
for (size_t i = 0; i < scores.size(); ++i)
42+
{
43+
if (scores[i] > threshold)
44+
{
45+
score_index_vec.push_back(std::make_pair(scores[i], i));
46+
}
47+
}
48+
49+
// Sort the score pair according to the scores in descending order
50+
std::stable_sort(score_index_vec.begin(), score_index_vec.end(), SortScorePairDescend);
51+
52+
// Keep top_k scores if needed.
53+
if (top_k > 0 && top_k < (int)score_index_vec.size())
54+
{
55+
score_index_vec.resize(top_k);
56+
}
57+
}
58+
59+
// Do non maximum suppression given bboxes and scores.
60+
// Inspired by Piotr Dollar's NMS implementation in EdgeBox.
61+
// https://goo.gl/jV3JYS
62+
// bboxes: a set of bounding boxes.
63+
// scores: a set of corresponding confidences.
64+
// score_threshold: a threshold used to filter detection results.
65+
// nms_threshold: a threshold used in non maximum suppression.
66+
// top_k: if not > 0, keep at most top_k picked indices.
67+
// limit: early terminate once the # of picked indices has reached it.
68+
// indices: the kept indices of bboxes after nms.
69+
template <typename BoxType>
70+
inline void NMSFast_(const std::vector<BoxType>& bboxes,
71+
const std::vector<float>& scores, const float score_threshold,
72+
const float nms_threshold, const float eta, const int top_k,
73+
std::vector<int>& indices,
74+
float (*computeOverlap)(const BoxType&, const BoxType&),
75+
int limit = std::numeric_limits<int>::max())
76+
{
77+
// Get top_k scores (with corresponding indices).
78+
std::vector<std::pair<float, int> > score_index_vec;
79+
GetMaxScoreIndex(scores, score_threshold, top_k, score_index_vec);
80+
81+
// Do nms.
82+
float adaptive_threshold = nms_threshold;
83+
indices.clear();
84+
for (size_t i = 0; i < score_index_vec.size(); ++i)
85+
{
86+
const int idx = score_index_vec[i].second;
87+
bool keep = true;
88+
for (int k = 0; k < (int)indices.size() && keep; ++k)
89+
{
90+
const int kept_idx = indices[k];
91+
float overlap = computeOverlap(bboxes[idx], bboxes[kept_idx]);
92+
keep = overlap <= adaptive_threshold;
93+
}
94+
if (keep)
95+
{
96+
indices.push_back(idx);
97+
if ((int)indices.size() >= limit) {
98+
break;
99+
}
100+
}
101+
if (keep && eta < 1 && adaptive_threshold > 0.5) {
102+
adaptive_threshold *= eta;
103+
}
104+
}
105+
}
106+
107+
static inline float rectOverlap(const Rect& a, const Rect& b)
108+
{
109+
int Aa = a.area();
110+
int Ab = b.area();
111+
112+
if (Aa + Ab == 0)
113+
return 0.f;
114+
115+
int intersect = (a & b).area();
116+
117+
return (float)intersect / (Aa + Ab - intersect);
118+
}
119+
120+
void NMSBoxes(const std::vector<Rect>& bboxes, const std::vector<float>& scores,
121+
const float score_threshold, const float nms_threshold,
122+
std::vector<int>& indices, const float eta, const int top_k)
123+
{
124+
NMSFast_(bboxes, scores, score_threshold, nms_threshold, eta, top_k, indices, rectOverlap);
125+
}
126+
127+
static inline void NMSBoxesBatchedImpl(const std::vector<Rect>& bboxes,
128+
const std::vector<float>& scores, const std::vector<int>& class_ids,
129+
const float score_threshold, const float nms_threshold,
130+
std::vector<int>& indices, const float eta, const int top_k)
131+
{
132+
int x1, y1, x2, y2, max_coord = 0;
133+
for (size_t i = 0; i < bboxes.size(); i++)
134+
{
135+
x1 = bboxes[i].x;
136+
y1 = bboxes[i].y;
137+
x2 = x1 + bboxes[i].width;
138+
y2 = y1 + bboxes[i].height;
139+
140+
max_coord = std::max(x1, max_coord);
141+
max_coord = std::max(y1, max_coord);
142+
max_coord = std::max(x2, max_coord);
143+
max_coord = std::max(y2, max_coord);
144+
}
145+
146+
// calculate offset and add offset to each bbox
147+
std::vector<Rect> bboxes_offset;
148+
for (size_t i = 0; i < bboxes.size(); i++)
149+
{
150+
int offset = class_ids[i] * (max_coord + 1);
151+
bboxes_offset.push_back(Rect(bboxes[i].x + offset, bboxes[i].y + offset, bboxes[i].width, bboxes[i].height));
152+
}
153+
154+
NMSFast_(bboxes_offset, scores, score_threshold, nms_threshold, eta, top_k, indices, rectOverlap);
155+
}
156+
157+
void NMSBoxesBatched(const std::vector<Rect>& bboxes,
158+
const std::vector<float>& scores, const std::vector<int>& class_ids,
159+
const float score_threshold, const float nms_threshold,
160+
std::vector<int>& indices, const float eta, const int top_k)
161+
{
162+
NMSBoxesBatchedImpl(bboxes, scores, class_ids, score_threshold, nms_threshold, indices, eta, top_k);
163+
}
164+
165+
static inline bool score_cmp(const std::pair<float, size_t>& a, const std::pair<float, size_t>& b)
166+
{
167+
return a.first == b.first ? a.second > b.second : a.first < b.first;
168+
}
169+
170+
void softNMSBoxes(const std::vector<Rect>& bboxes,
171+
const std::vector<float>& scores,
172+
std::vector<float>& updated_scores,
173+
const float score_threshold,
174+
const float nms_threshold,
175+
std::vector<int>& indices,
176+
size_t top_k,
177+
const float sigma,
178+
SoftNMSMethod method)
179+
{
180+
indices.clear();
181+
updated_scores.clear();
182+
183+
std::vector<std::pair<float, size_t> > score_index_vec(scores.size());
184+
for (size_t i = 0; i < scores.size(); i++)
185+
{
186+
score_index_vec[i].first = scores[i];
187+
score_index_vec[i].second = i;
188+
}
189+
190+
top_k = top_k == 0 ? scores.size() : std::min(top_k, scores.size());
191+
ptrdiff_t start = 0;
192+
while (indices.size() < top_k)
193+
{
194+
auto it = std::max_element(score_index_vec.begin() + start, score_index_vec.end(), score_cmp);
195+
196+
float bscore = it->first;
197+
size_t bidx = it->second;
198+
199+
if (bscore < score_threshold)
200+
{
201+
break;
202+
}
203+
204+
indices.push_back(static_cast<int>(bidx));
205+
updated_scores.push_back(bscore);
206+
std::swap(score_index_vec[start], *it); // first start elements are chosen
207+
208+
for (size_t i = start + 1; i < scores.size(); ++i)
209+
{
210+
float& bscore_i = score_index_vec[i].first;
211+
const size_t bidx_i = score_index_vec[i].second;
212+
213+
if (bscore_i < score_threshold)
214+
{
215+
continue;
216+
}
217+
218+
float overlap = rectOverlap(bboxes[bidx], bboxes[bidx_i]);
219+
220+
if (method == SOFTNMS_LINEAR)
221+
{
222+
if (overlap > nms_threshold)
223+
{
224+
bscore_i *= 1.f - overlap;
225+
}
226+
}
227+
else // if (method == SOFTNMS_GAUSSIAN)
228+
{
229+
bscore_i *= exp(-(overlap * overlap) / sigma);
230+
}
231+
}
232+
++start;
233+
}
234+
}
235+
236+
} // namespace dnn
237+
} // namespace cv

0 commit comments

Comments
 (0)