mirror of
https://github.com/opencv/opencv.git
synced 2025-06-07 09:25:45 +08:00
add SoftNMS implementation
This commit is contained in:
parent
c832e62db0
commit
2221dcc9f2
@ -1130,6 +1130,39 @@ CV__DNN_INLINE_NS_BEGIN
|
||||
CV_OUT std::vector<int>& indices,
|
||||
const float eta = 1.f, const int top_k = 0);
|
||||
|
||||
/**
|
||||
* @brief Enum of Soft NMS methods.
|
||||
* @see softNMSBoxes
|
||||
*/
|
||||
enum class SoftNMSMethod
|
||||
{
|
||||
SOFTNMS_LINEAR = 1,
|
||||
SOFTNMS_GAUSSIAN = 2
|
||||
};
|
||||
|
||||
/** @brief Performs soft non maximum suppression given boxes and corresponding scores.
|
||||
* Reference: https://arxiv.org/abs/1704.04503
|
||||
* @param bboxes a set of bounding boxes to apply Soft NMS.
|
||||
* @param scores a set of corresponding confidences.
|
||||
* @param updated_scores a set of corresponding updated confidences.
|
||||
* @param score_threshold a threshold used to filter boxes by score.
|
||||
* @param nms_threshold a threshold used in non maximum suppression.
|
||||
* @param indices the kept indices of bboxes after NMS.
|
||||
* @param top_k keep at most @p top_k picked indices.
|
||||
* @param sigma parameter of Gaussian weighting.
|
||||
* @param method Gaussian or linear.
|
||||
* @see SoftNMSMethod
|
||||
*/
|
||||
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 = SoftNMSMethod::SOFTNMS_GAUSSIAN);
|
||||
|
||||
|
||||
/** @brief This class is presented high-level API for neural networks.
|
||||
*
|
||||
|
@ -58,6 +58,83 @@ void NMSBoxes(const std::vector<RotatedRect>& bboxes, const std::vector<float>&
|
||||
NMSFast_(bboxes, scores, score_threshold, nms_threshold, eta, top_k, indices, rotatedRectIOU);
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
CV_Assert_N(bboxes.size() == scores.size(), score_threshold >= 0,
|
||||
nms_threshold >= 0, sigma >= 0);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
const auto 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;
|
||||
};
|
||||
|
||||
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]);
|
||||
|
||||
switch (method)
|
||||
{
|
||||
case SoftNMSMethod::SOFTNMS_LINEAR:
|
||||
if (overlap > nms_threshold)
|
||||
{
|
||||
bscore_i *= 1.f - overlap;
|
||||
}
|
||||
break;
|
||||
case SoftNMSMethod::SOFTNMS_GAUSSIAN:
|
||||
bscore_i *= exp(-(overlap * overlap) / sigma);
|
||||
break;
|
||||
default:
|
||||
CV_Error(Error::StsBadArg, "Not supported SoftNMS method.");
|
||||
}
|
||||
}
|
||||
++start;
|
||||
}
|
||||
}
|
||||
|
||||
CV__DNN_INLINE_NS_END
|
||||
}// dnn
|
||||
}// cv
|
||||
|
@ -37,4 +37,41 @@ TEST(NMS, Accuracy)
|
||||
ASSERT_EQ(indices[i], ref_indices[i]);
|
||||
}
|
||||
|
||||
TEST(SoftNMS, Accuracy)
|
||||
{
|
||||
//reference results are obtained using TF v2.7 tf.image.non_max_suppression_with_scores
|
||||
std::string dataPath = findDataFile("dnn/soft_nms_reference.yml");
|
||||
FileStorage fs(dataPath, FileStorage::READ);
|
||||
|
||||
std::vector<Rect> bboxes;
|
||||
std::vector<float> scores;
|
||||
std::vector<int> ref_indices;
|
||||
std::vector<float> ref_updated_scores;
|
||||
|
||||
fs["boxes"] >> bboxes;
|
||||
fs["probs"] >> scores;
|
||||
fs["indices"] >> ref_indices;
|
||||
fs["updated_scores"] >> ref_updated_scores;
|
||||
|
||||
std::vector<float> updated_scores;
|
||||
const float score_thresh = .01f;
|
||||
const float nms_thresh = .5f;
|
||||
std::vector<int> indices;
|
||||
const size_t top_k = 0;
|
||||
const float sigma = 1.; // sigma in TF is being multiplied by 2, so 0.5 should be passed there
|
||||
cv::dnn::softNMSBoxes(bboxes, scores, updated_scores, score_thresh, nms_thresh, indices, top_k, sigma);
|
||||
|
||||
ASSERT_EQ(ref_indices.size(), indices.size());
|
||||
for(size_t i = 0; i < indices.size(); i++)
|
||||
{
|
||||
ASSERT_EQ(indices[i], ref_indices[i]);
|
||||
}
|
||||
|
||||
ASSERT_EQ(ref_updated_scores.size(), updated_scores.size());
|
||||
for(size_t i = 0; i < updated_scores.size(); i++)
|
||||
{
|
||||
EXPECT_NEAR(updated_scores[i], ref_updated_scores[i], 1e-7);
|
||||
}
|
||||
}
|
||||
|
||||
}} // namespace
|
||||
|
Loading…
Reference in New Issue
Block a user