mirror of
https://github.com/nihui/opencv-mobile.git
synced 2025-09-27 04:46:17 +08:00
add dnn nms (#119)
This commit is contained in:
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
@@ -145,6 +145,7 @@ jobs:
|
|||||||
rm -rf modules/js
|
rm -rf modules/js
|
||||||
rm -rf modules/python
|
rm -rf modules/python
|
||||||
rm -rf modules/ts
|
rm -rf modules/ts
|
||||||
|
rm -rf modules/dnn
|
||||||
sed -e 's/__VERSION__/${{ env.opencv-version }}/g' ../patches/Info.plist > ./Info.plist
|
sed -e 's/__VERSION__/${{ env.opencv-version }}/g' ../patches/Info.plist > ./Info.plist
|
||||||
cp ../opencv3_cmake_options.txt ./options.txt
|
cp ../opencv3_cmake_options.txt ./options.txt
|
||||||
cd ..
|
cd ..
|
||||||
@@ -214,6 +215,7 @@ jobs:
|
|||||||
rm -rf modules/js
|
rm -rf modules/js
|
||||||
rm -rf modules/python
|
rm -rf modules/python
|
||||||
rm -rf modules/ts
|
rm -rf modules/ts
|
||||||
|
rm -rf modules/dnn
|
||||||
sed -e 's/__VERSION__/${{ env.opencv-version }}/g' ../patches/Info.plist > ./Info.plist
|
sed -e 's/__VERSION__/${{ env.opencv-version }}/g' ../patches/Info.plist > ./Info.plist
|
||||||
cp ../opencv4_cmake_options.txt ./options.txt
|
cp ../opencv4_cmake_options.txt ./options.txt
|
||||||
cd ..
|
cd ..
|
||||||
|
@@ -10,6 +10,9 @@ set(highgui_srcs
|
|||||||
${CMAKE_CURRENT_LIST_DIR}/src/highgui.cpp
|
${CMAKE_CURRENT_LIST_DIR}/src/highgui.cpp
|
||||||
${CMAKE_CURRENT_LIST_DIR}/src/kanna_rotate.cpp
|
${CMAKE_CURRENT_LIST_DIR}/src/kanna_rotate.cpp
|
||||||
${CMAKE_CURRENT_LIST_DIR}/src/videocapture.cpp
|
${CMAKE_CURRENT_LIST_DIR}/src/videocapture.cpp
|
||||||
|
|
||||||
|
# dnn
|
||||||
|
${CMAKE_CURRENT_LIST_DIR}/src/nms.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
if(WITH_CVI)
|
if(WITH_CVI)
|
||||||
@@ -40,7 +43,9 @@ endif()
|
|||||||
file(GLOB highgui_ext_hdrs
|
file(GLOB highgui_ext_hdrs
|
||||||
"${CMAKE_CURRENT_LIST_DIR}/include/opencv2/*.hpp"
|
"${CMAKE_CURRENT_LIST_DIR}/include/opencv2/*.hpp"
|
||||||
"${CMAKE_CURRENT_LIST_DIR}/include/opencv2/${name}/*.hpp"
|
"${CMAKE_CURRENT_LIST_DIR}/include/opencv2/${name}/*.hpp"
|
||||||
"${CMAKE_CURRENT_LIST_DIR}/include/opencv2/${name}/*.h")
|
"${CMAKE_CURRENT_LIST_DIR}/include/opencv2/${name}/*.h"
|
||||||
|
"${CMAKE_CURRENT_LIST_DIR}/include/opencv2/dnn/*.hpp"
|
||||||
|
"${CMAKE_CURRENT_LIST_DIR}/include/opencv2/dnn/*.h")
|
||||||
|
|
||||||
if(UNIX OR OPENCV_VERSION_MAJOR GREATER_EQUAL 3)
|
if(UNIX OR OPENCV_VERSION_MAJOR GREATER_EQUAL 3)
|
||||||
#these variables are set by CHECK_MODULE macro
|
#these variables are set by CHECK_MODULE macro
|
||||||
|
17
highgui/include/opencv2/dnn.hpp
Normal file
17
highgui/include/opencv2/dnn.hpp
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
//
|
||||||
|
// Copyright (C) 2024 nihui
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
//
|
||||||
|
|
||||||
|
#include "opencv2/dnn/dnn.hpp"
|
50
highgui/include/opencv2/dnn/dnn.hpp
Normal file
50
highgui/include/opencv2/dnn/dnn.hpp
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
//
|
||||||
|
// Copyright (C) 2024 nihui
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef OPENCV_DNN_HPP
|
||||||
|
#define OPENCV_DNN_HPP
|
||||||
|
|
||||||
|
#include "opencv2/core.hpp"
|
||||||
|
|
||||||
|
namespace cv {
|
||||||
|
namespace dnn {
|
||||||
|
|
||||||
|
enum SoftNMSMethod
|
||||||
|
{
|
||||||
|
SOFTNMS_LINEAR = 1,
|
||||||
|
SOFTNMS_GAUSSIAN = 2
|
||||||
|
};
|
||||||
|
|
||||||
|
CV_EXPORTS void NMSBoxes(const std::vector<Rect>& bboxes, const std::vector<float>& scores,
|
||||||
|
const float score_threshold, const float nms_threshold,
|
||||||
|
CV_OUT std::vector<int>& indices,
|
||||||
|
const float eta = 1.f, const int top_k = 0);
|
||||||
|
|
||||||
|
CV_EXPORTS void NMSBoxesBatched(const std::vector<Rect>& bboxes, const std::vector<float>& scores, const std::vector<int>& class_ids,
|
||||||
|
const float score_threshold, const float nms_threshold,
|
||||||
|
CV_OUT std::vector<int>& indices,
|
||||||
|
const float eta = 1.f, const int top_k = 0);
|
||||||
|
|
||||||
|
CV_EXPORTS_W void softNMSBoxes(const std::vector<Rect>& bboxes, const std::vector<float>& scores,
|
||||||
|
CV_OUT std::vector<float>& updated_scores,
|
||||||
|
const float score_threshold, const float nms_threshold,
|
||||||
|
CV_OUT std::vector<int>& indices,
|
||||||
|
size_t top_k = 0, const float sigma = 0.5, SoftNMSMethod method = SOFTNMS_GAUSSIAN);
|
||||||
|
|
||||||
|
} // namespace dnn
|
||||||
|
} // namespace cv
|
||||||
|
|
||||||
|
#endif // OPENCV_DNN_HPP
|
237
highgui/src/nms.cpp
Normal file
237
highgui/src/nms.cpp
Normal file
@@ -0,0 +1,237 @@
|
|||||||
|
//
|
||||||
|
// Copyright (C) 2024 nihui
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
//
|
||||||
|
|
||||||
|
#include <opencv2/core.hpp>
|
||||||
|
#include <opencv2/imgproc.hpp>
|
||||||
|
#include <opencv2/dnn.hpp>
|
||||||
|
#include <limits>
|
||||||
|
#include <vector>
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
namespace cv {
|
||||||
|
namespace dnn {
|
||||||
|
|
||||||
|
static inline bool SortScorePairDescend(const std::pair<float, int>& pair1, const std::pair<float, int>& pair2)
|
||||||
|
{
|
||||||
|
return pair1.first > pair2.first;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get max scores with corresponding indices.
|
||||||
|
// scores: a set of scores.
|
||||||
|
// threshold: only consider scores higher than the threshold.
|
||||||
|
// top_k: if -1, keep all; otherwise, keep at most top_k.
|
||||||
|
// score_index_vec: store the sorted (score, index) pair.
|
||||||
|
inline void GetMaxScoreIndex(const std::vector<float>& scores, const float threshold, const int top_k,
|
||||||
|
std::vector<std::pair<float, int> >& score_index_vec)
|
||||||
|
{
|
||||||
|
// Generate index score pairs.
|
||||||
|
for (size_t i = 0; i < scores.size(); ++i)
|
||||||
|
{
|
||||||
|
if (scores[i] > threshold)
|
||||||
|
{
|
||||||
|
score_index_vec.push_back(std::make_pair(scores[i], i));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort the score pair according to the scores in descending order
|
||||||
|
std::stable_sort(score_index_vec.begin(), score_index_vec.end(), SortScorePairDescend);
|
||||||
|
|
||||||
|
// Keep top_k scores if needed.
|
||||||
|
if (top_k > 0 && top_k < (int)score_index_vec.size())
|
||||||
|
{
|
||||||
|
score_index_vec.resize(top_k);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do non maximum suppression given bboxes and scores.
|
||||||
|
// Inspired by Piotr Dollar's NMS implementation in EdgeBox.
|
||||||
|
// https://goo.gl/jV3JYS
|
||||||
|
// bboxes: a set of bounding boxes.
|
||||||
|
// scores: a set of corresponding confidences.
|
||||||
|
// score_threshold: a threshold used to filter detection results.
|
||||||
|
// nms_threshold: a threshold used in non maximum suppression.
|
||||||
|
// top_k: if not > 0, keep at most top_k picked indices.
|
||||||
|
// limit: early terminate once the # of picked indices has reached it.
|
||||||
|
// indices: the kept indices of bboxes after nms.
|
||||||
|
template <typename BoxType>
|
||||||
|
inline void NMSFast_(const std::vector<BoxType>& bboxes,
|
||||||
|
const std::vector<float>& scores, const float score_threshold,
|
||||||
|
const float nms_threshold, const float eta, const int top_k,
|
||||||
|
std::vector<int>& indices,
|
||||||
|
float (*computeOverlap)(const BoxType&, const BoxType&),
|
||||||
|
int limit = std::numeric_limits<int>::max())
|
||||||
|
{
|
||||||
|
// Get top_k scores (with corresponding indices).
|
||||||
|
std::vector<std::pair<float, int> > score_index_vec;
|
||||||
|
GetMaxScoreIndex(scores, score_threshold, top_k, score_index_vec);
|
||||||
|
|
||||||
|
// Do nms.
|
||||||
|
float adaptive_threshold = nms_threshold;
|
||||||
|
indices.clear();
|
||||||
|
for (size_t i = 0; i < score_index_vec.size(); ++i)
|
||||||
|
{
|
||||||
|
const int idx = score_index_vec[i].second;
|
||||||
|
bool keep = true;
|
||||||
|
for (int k = 0; k < (int)indices.size() && keep; ++k)
|
||||||
|
{
|
||||||
|
const int kept_idx = indices[k];
|
||||||
|
float overlap = computeOverlap(bboxes[idx], bboxes[kept_idx]);
|
||||||
|
keep = overlap <= adaptive_threshold;
|
||||||
|
}
|
||||||
|
if (keep)
|
||||||
|
{
|
||||||
|
indices.push_back(idx);
|
||||||
|
if ((int)indices.size() >= limit) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (keep && eta < 1 && adaptive_threshold > 0.5) {
|
||||||
|
adaptive_threshold *= eta;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline float rectOverlap(const Rect& a, const Rect& b)
|
||||||
|
{
|
||||||
|
int Aa = a.area();
|
||||||
|
int Ab = b.area();
|
||||||
|
|
||||||
|
if (Aa + Ab == 0)
|
||||||
|
return 0.f;
|
||||||
|
|
||||||
|
int intersect = (a & b).area();
|
||||||
|
|
||||||
|
return (float)intersect / (Aa + Ab - intersect);
|
||||||
|
}
|
||||||
|
|
||||||
|
void NMSBoxes(const std::vector<Rect>& bboxes, const std::vector<float>& scores,
|
||||||
|
const float score_threshold, const float nms_threshold,
|
||||||
|
std::vector<int>& indices, const float eta, const int top_k)
|
||||||
|
{
|
||||||
|
NMSFast_(bboxes, scores, score_threshold, nms_threshold, eta, top_k, indices, rectOverlap);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void NMSBoxesBatchedImpl(const std::vector<Rect>& bboxes,
|
||||||
|
const std::vector<float>& scores, const std::vector<int>& class_ids,
|
||||||
|
const float score_threshold, const float nms_threshold,
|
||||||
|
std::vector<int>& indices, const float eta, const int top_k)
|
||||||
|
{
|
||||||
|
int x1, y1, x2, y2, max_coord = 0;
|
||||||
|
for (size_t i = 0; i < bboxes.size(); i++)
|
||||||
|
{
|
||||||
|
x1 = bboxes[i].x;
|
||||||
|
y1 = bboxes[i].y;
|
||||||
|
x2 = x1 + bboxes[i].width;
|
||||||
|
y2 = y1 + bboxes[i].height;
|
||||||
|
|
||||||
|
max_coord = std::max(x1, max_coord);
|
||||||
|
max_coord = std::max(y1, max_coord);
|
||||||
|
max_coord = std::max(x2, max_coord);
|
||||||
|
max_coord = std::max(y2, max_coord);
|
||||||
|
}
|
||||||
|
|
||||||
|
// calculate offset and add offset to each bbox
|
||||||
|
std::vector<Rect> bboxes_offset;
|
||||||
|
for (size_t i = 0; i < bboxes.size(); i++)
|
||||||
|
{
|
||||||
|
int offset = class_ids[i] * (max_coord + 1);
|
||||||
|
bboxes_offset.push_back(Rect(bboxes[i].x + offset, bboxes[i].y + offset, bboxes[i].width, bboxes[i].height));
|
||||||
|
}
|
||||||
|
|
||||||
|
NMSFast_(bboxes_offset, scores, score_threshold, nms_threshold, eta, top_k, indices, rectOverlap);
|
||||||
|
}
|
||||||
|
|
||||||
|
void NMSBoxesBatched(const std::vector<Rect>& bboxes,
|
||||||
|
const std::vector<float>& scores, const std::vector<int>& class_ids,
|
||||||
|
const float score_threshold, const float nms_threshold,
|
||||||
|
std::vector<int>& indices, const float eta, const int top_k)
|
||||||
|
{
|
||||||
|
NMSBoxesBatchedImpl(bboxes, scores, class_ids, score_threshold, nms_threshold, indices, eta, top_k);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline bool score_cmp(const std::pair<float, size_t>& a, const std::pair<float, size_t>& b)
|
||||||
|
{
|
||||||
|
return a.first == b.first ? a.second > b.second : a.first < b.first;
|
||||||
|
}
|
||||||
|
|
||||||
|
void softNMSBoxes(const std::vector<Rect>& bboxes,
|
||||||
|
const std::vector<float>& scores,
|
||||||
|
std::vector<float>& updated_scores,
|
||||||
|
const float score_threshold,
|
||||||
|
const float nms_threshold,
|
||||||
|
std::vector<int>& indices,
|
||||||
|
size_t top_k,
|
||||||
|
const float sigma,
|
||||||
|
SoftNMSMethod method)
|
||||||
|
{
|
||||||
|
indices.clear();
|
||||||
|
updated_scores.clear();
|
||||||
|
|
||||||
|
std::vector<std::pair<float, size_t> > score_index_vec(scores.size());
|
||||||
|
for (size_t i = 0; i < scores.size(); i++)
|
||||||
|
{
|
||||||
|
score_index_vec[i].first = scores[i];
|
||||||
|
score_index_vec[i].second = i;
|
||||||
|
}
|
||||||
|
|
||||||
|
top_k = top_k == 0 ? scores.size() : std::min(top_k, scores.size());
|
||||||
|
ptrdiff_t start = 0;
|
||||||
|
while (indices.size() < top_k)
|
||||||
|
{
|
||||||
|
auto it = std::max_element(score_index_vec.begin() + start, score_index_vec.end(), score_cmp);
|
||||||
|
|
||||||
|
float bscore = it->first;
|
||||||
|
size_t bidx = it->second;
|
||||||
|
|
||||||
|
if (bscore < score_threshold)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
indices.push_back(static_cast<int>(bidx));
|
||||||
|
updated_scores.push_back(bscore);
|
||||||
|
std::swap(score_index_vec[start], *it); // first start elements are chosen
|
||||||
|
|
||||||
|
for (size_t i = start + 1; i < scores.size(); ++i)
|
||||||
|
{
|
||||||
|
float& bscore_i = score_index_vec[i].first;
|
||||||
|
const size_t bidx_i = score_index_vec[i].second;
|
||||||
|
|
||||||
|
if (bscore_i < score_threshold)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
float overlap = rectOverlap(bboxes[bidx], bboxes[bidx_i]);
|
||||||
|
|
||||||
|
if (method == SOFTNMS_LINEAR)
|
||||||
|
{
|
||||||
|
if (overlap > nms_threshold)
|
||||||
|
{
|
||||||
|
bscore_i *= 1.f - overlap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else // if (method == SOFTNMS_GAUSSIAN)
|
||||||
|
{
|
||||||
|
bscore_i *= exp(-(overlap * overlap) / sigma);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
++start;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace dnn
|
||||||
|
} // namespace cv
|
Reference in New Issue
Block a user