From acedb4a5792b2b7fcd9a88ce2cd3784b1a344fff Mon Sep 17 00:00:00 2001 From: Vladislav Sovrasov <sovrasov.vlad@gmail.com> Date: Mon, 16 Oct 2017 19:16:52 +0300 Subject: [PATCH] dnn: make NMS function public --- modules/dnn/include/opencv2/dnn/dnn.hpp | 13 ++ modules/dnn/include/opencv2/dnn/nms.inl.hpp | 114 ++++++++++++++++++ .../dnn/src/layers/detection_output_layer.cpp | 72 ++--------- modules/dnn/src/nms.cpp | 24 ++++ 4 files changed, 161 insertions(+), 62 deletions(-) create mode 100644 modules/dnn/include/opencv2/dnn/nms.inl.hpp create mode 100644 modules/dnn/src/nms.cpp diff --git a/modules/dnn/include/opencv2/dnn/dnn.hpp b/modules/dnn/include/opencv2/dnn/dnn.hpp index ff222d7232..d276abf099 100644 --- a/modules/dnn/include/opencv2/dnn/dnn.hpp +++ b/modules/dnn/include/opencv2/dnn/dnn.hpp @@ -734,6 +734,19 @@ CV__DNN_EXPERIMENTAL_NS_BEGIN */ CV_EXPORTS_W void shrinkCaffeModel(const String& src, const String& dst); + /** @brief + * @param bboxes + * @param scores + * @param score_threshold + * @param nms_threshold + * @param eta + * @param top_k + * @param indices + */ + CV_EXPORTS_W void NMSBoxes(const std::vector<Rect>& bboxes, const std::vector<float>& scores, + const float score_threshold, const float nms_threshold, + const float eta, const int top_k, CV_OUT std::vector<int>& indices); + //! @} CV__DNN_EXPERIMENTAL_NS_END diff --git a/modules/dnn/include/opencv2/dnn/nms.inl.hpp b/modules/dnn/include/opencv2/dnn/nms.inl.hpp new file mode 100644 index 0000000000..26183a083a --- /dev/null +++ b/modules/dnn/include/opencv2/dnn/nms.inl.hpp @@ -0,0 +1,114 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. +// +// Copyright (C) 2017, Intel Corporation, all rights reserved. +// Third party copyrights are property of their respective owners. + +#ifndef OPENCV_DNN_NMS_INL_HPP +#define OPENCV_DNN_NMS_INL_HPP + +#include <opencv2/dnn.hpp> + +namespace cv { +namespace dnn { + +namespace +{ + +template <typename T> +static inline bool SortScorePairDescend(const std::pair<float, T>& pair1, + const std::pair<float, T>& pair2) +{ + return pair1.first > pair2.first; +} + +} // namespace + +// 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) +{ + CV_DbgAssert(score_index_vec.empty()); + // 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<int>); + + // Keep top_k scores if needed. + if (top_k > -1 && top_k < (int)score_index_vec.size()) + { + score_index_vec.resize(top_k); + } +} + +template <typename BoxType> +struct NMSOverlap +{ + float operator() (const BoxType& a, const BoxType& b); +}; + +template <> +inline float NMSOverlap<Rect>::operator() (const Rect& a, const Rect& b) +{ + float rectIntersectionArea = (float)(a & b).area(); + return rectIntersectionArea / (a.area() + b.area() - rectIntersectionArea); +} + +// 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 -1, keep at most top_k picked indices. +// 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, NMSOverlap<BoxType> computeOverlap) +{ + CV_Assert(bboxes.size() == scores.size()); + + // 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(); + while (score_index_vec.size() != 0) { + const int idx = score_index_vec.front().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); + score_index_vec.erase(score_index_vec.begin()); + if (keep && eta < 1 && adaptive_threshold > 0.5) { + adaptive_threshold *= eta; + } + } +} + +}// dnn +}// cv + +#endif diff --git a/modules/dnn/src/layers/detection_output_layer.cpp b/modules/dnn/src/layers/detection_output_layer.cpp index 505b9c7b74..1312a81b13 100644 --- a/modules/dnn/src/layers/detection_output_layer.cpp +++ b/modules/dnn/src/layers/detection_output_layer.cpp @@ -45,6 +45,7 @@ #include <float.h> #include <string> #include <caffe.pb.h> +#include <opencv2/dnn/nms.inl.hpp> namespace cv { @@ -619,73 +620,14 @@ public: } } - // 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 -1, keep at most top_k picked indices. - // indices: the kept indices of bboxes after nms. + + static void ApplyNMSFast(const std::vector<caffe::NormalizedBBox>& 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) { - CV_Assert(bboxes.size() == scores.size()); - - // 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(); - while (score_index_vec.size() != 0) { - const int idx = score_index_vec.front().second; - bool keep = true; - for (int k = 0; k < (int)indices.size() && keep; ++k) { - const int kept_idx = indices[k]; - float overlap = JaccardOverlap<true>(bboxes[idx], bboxes[kept_idx]); - keep = overlap <= adaptive_threshold; - } - if (keep) - indices.push_back(idx); - score_index_vec.erase(score_index_vec.begin()); - if (keep && eta < 1 && adaptive_threshold > 0.5) { - adaptive_threshold *= eta; - } - } - } - - // 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. - static void GetMaxScoreIndex(const std::vector<float>& scores, const float threshold, const int top_k, - std::vector<std::pair<float, int> >& score_index_vec) - { - CV_DbgAssert(score_index_vec.empty()); - // 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(), - util::SortScorePairDescend<int>); - - // Keep top_k scores if needed. - if (top_k > -1 && top_k < (int)score_index_vec.size()) - { - score_index_vec.resize(top_k); - } + NMSFast_(bboxes, scores, score_threshold, nms_threshold, eta, top_k, indices, NMSOverlap<caffe::NormalizedBBox>()); } // Compute the jaccard (intersection over union IoU) overlap between two bboxes. @@ -733,6 +675,12 @@ public: } }; +template <> +float NMSOverlap<caffe::NormalizedBBox>::operator() (const caffe::NormalizedBBox& a, const caffe::NormalizedBBox& b) +{ + return DetectionOutputLayerImpl::JaccardOverlap<true>(a, b); +} + const std::string DetectionOutputLayerImpl::_layerName = std::string("DetectionOutput"); Ptr<DetectionOutputLayer> DetectionOutputLayer::create(const LayerParams ¶ms) diff --git a/modules/dnn/src/nms.cpp b/modules/dnn/src/nms.cpp new file mode 100644 index 0000000000..5f433dba22 --- /dev/null +++ b/modules/dnn/src/nms.cpp @@ -0,0 +1,24 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. +// +// Copyright (C) 2017, Intel Corporation, all rights reserved. +// Third party copyrights are property of their respective owners. + +#include "precomp.hpp" +#include <opencv2/dnn/nms.inl.hpp> + +namespace cv +{ +namespace dnn +{ + +void NMSBoxes(const std::vector<Rect>& 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) +{ + NMSFast_(bboxes, scores, score_threshold, nms_threshold, eta, top_k, indices, NMSOverlap<Rect>()); +} + +}// dnn +}// cv