Caffe2 - C++ API
A deep learning, cross platform ML framework
generate_proposals_op_util_nms.h
1 #ifndef CAFFE2_OPERATORS_UTILS_NMS_H_
2 #define CAFFE2_OPERATORS_UTILS_NMS_H_
3 
4 #include <vector>
5 
6 #include "caffe2/core/logging.h"
7 #include "caffe2/core/macros.h"
8 #include "caffe2/utils/eigen_utils.h"
9 #include "caffe2/utils/math.h"
10 
11 namespace caffe2 {
12 namespace utils {
13 
14 // Greedy non-maximum suppression for proposed bounding boxes
15 // Reject a bounding box if its region has an intersection-overunion (IoU)
16 // overlap with a higher scoring selected bounding box larger than a
17 // threshold.
18 // Reference: facebookresearch/Detectron/detectron/utils/cython_nms.pyx
19 // proposals: pixel coordinates of proposed bounding boxes,
20 // size: (M, 4), format: [x1; y1; x2; y2]
21 // scores: scores for each bounding box, size: (M, 1)
22 // sorted_indices: indices that sorts the scores from high to low
23 // return: row indices of the selected proposals
24 template <class Derived1, class Derived2>
25 std::vector<int> nms_cpu_upright(
26  const Eigen::ArrayBase<Derived1>& proposals,
27  const Eigen::ArrayBase<Derived2>& scores,
28  const std::vector<int>& sorted_indices,
29  float thresh,
30  int topN = -1) {
31  CAFFE_ENFORCE_EQ(proposals.rows(), scores.rows());
32  CAFFE_ENFORCE_EQ(proposals.cols(), 4);
33  CAFFE_ENFORCE_EQ(scores.cols(), 1);
34  CAFFE_ENFORCE_LE(sorted_indices.size(), proposals.rows());
35 
36  using EArrX = EArrXt<typename Derived1::Scalar>;
37 
38  auto x1 = proposals.col(0);
39  auto y1 = proposals.col(1);
40  auto x2 = proposals.col(2);
41  auto y2 = proposals.col(3);
42 
43  EArrX areas = (x2 - x1 + 1.0) * (y2 - y1 + 1.0);
44 
45  EArrXi order = AsEArrXt(sorted_indices);
46  std::vector<int> keep;
47  while (order.size() > 0) {
48  // exit if already enough proposals
49  if (topN >= 0 && keep.size() >= topN) {
50  break;
51  }
52 
53  int i = order[0];
54  keep.push_back(i);
55  ConstEigenVectorArrayMap<int> rest_indices(
56  order.data() + 1, order.size() - 1);
57  EArrX xx1 = GetSubArray(x1, rest_indices).cwiseMax(x1[i]);
58  EArrX yy1 = GetSubArray(y1, rest_indices).cwiseMax(y1[i]);
59  EArrX xx2 = GetSubArray(x2, rest_indices).cwiseMin(x2[i]);
60  EArrX yy2 = GetSubArray(y2, rest_indices).cwiseMin(y2[i]);
61 
62  EArrX w = (xx2 - xx1 + 1.0).cwiseMax(0.0);
63  EArrX h = (yy2 - yy1 + 1.0).cwiseMax(0.0);
64  EArrX inter = w * h;
65  EArrX ovr = inter / (areas[i] + GetSubArray(areas, rest_indices) - inter);
66 
67  // indices for sub array order[1:n]
68  auto inds = GetArrayIndices(ovr <= thresh);
69  order = GetSubArray(order, AsEArrXt(inds) + 1);
70  }
71 
72  return keep;
73 }
74 
91 template <class Derived1, class Derived2, class Derived3>
92 std::vector<int> soft_nms_cpu_upright(
93  Eigen::ArrayBase<Derived3>* out_scores,
94  const Eigen::ArrayBase<Derived1>& proposals,
95  const Eigen::ArrayBase<Derived2>& scores,
96  const std::vector<int>& indices,
97  float sigma = 0.5,
98  float overlap_thresh = 0.3,
99  float score_thresh = 0.001,
100  unsigned int method = 1,
101  int topN = -1) {
102  CAFFE_ENFORCE_EQ(proposals.rows(), scores.rows());
103  CAFFE_ENFORCE_EQ(proposals.cols(), 4);
104  CAFFE_ENFORCE_EQ(scores.cols(), 1);
105 
106  using EArrX = EArrXt<typename Derived1::Scalar>;
107 
108  const auto& x1 = proposals.col(0);
109  const auto& y1 = proposals.col(1);
110  const auto& x2 = proposals.col(2);
111  const auto& y2 = proposals.col(3);
112 
113  EArrX areas = (x2 - x1 + 1.0) * (y2 - y1 + 1.0);
114 
115  // Initialize out_scores with original scores. Will be iteratively updated
116  // as Soft-NMS is applied.
117  *out_scores = scores;
118 
119  std::vector<int> keep;
120  EArrXi pending = AsEArrXt(indices);
121  while (pending.size() > 0) {
122  // Exit if already enough proposals
123  if (topN >= 0 && keep.size() >= topN) {
124  break;
125  }
126 
127  // Find proposal with max score among remaining proposals
128  int max_pos;
129  auto max_score = GetSubArray(*out_scores, pending).maxCoeff(&max_pos);
130  int i = pending[max_pos];
131  keep.push_back(i);
132 
133  // Compute IoU of the remaining boxes with the identified max box
134  std::swap(pending(0), pending(max_pos));
135  const auto& rest_indices = pending.tail(pending.size() - 1);
136  EArrX xx1 = GetSubArray(x1, rest_indices).cwiseMax(x1[i]);
137  EArrX yy1 = GetSubArray(y1, rest_indices).cwiseMax(y1[i]);
138  EArrX xx2 = GetSubArray(x2, rest_indices).cwiseMin(x2[i]);
139  EArrX yy2 = GetSubArray(y2, rest_indices).cwiseMin(y2[i]);
140 
141  EArrX w = (xx2 - xx1 + 1.0).cwiseMax(0.0);
142  EArrX h = (yy2 - yy1 + 1.0).cwiseMax(0.0);
143  EArrX inter = w * h;
144  EArrX ovr = inter / (areas[i] + GetSubArray(areas, rest_indices) - inter);
145 
146  // Update scores based on computed IoU, overlap threshold and NMS method
147  for (int j = 0; j < rest_indices.size(); ++j) {
148  typename Derived2::Scalar weight;
149  switch (method) {
150  case 1: // Linear
151  weight = (ovr(j) > overlap_thresh) ? (1.0 - ovr(j)) : 1.0;
152  break;
153  case 2: // Gaussian
154  weight = std::exp(-1.0 * ovr(j) * ovr(j) / sigma);
155  break;
156  default: // Original NMS
157  weight = (ovr(j) > overlap_thresh) ? 0.0 : 1.0;
158  }
159  (*out_scores)(rest_indices[j]) *= weight;
160  }
161 
162  // Discard boxes with new scores below min threshold and update pending
163  // indices
164  const auto& rest_scores = GetSubArray(*out_scores, rest_indices);
165  const auto& inds = GetArrayIndices(rest_scores >= score_thresh);
166  pending = GetSubArray(rest_indices, AsEArrXt(inds));
167  }
168 
169  return keep;
170 }
171 
172 #if defined(CV_MAJOR_VERSION) && (CV_MAJOR_VERSION >= 3)
173 namespace {
174 
175 template <class Derived>
176 cv::RotatedRect bbox_to_rotated_rect(const Eigen::ArrayBase<Derived>& box) {
177  CAFFE_ENFORCE_EQ(box.size(), 5);
178  // cv::RotatedRect takes angle to mean clockwise rotation, but RRPN bbox
179  // representation means counter-clockwise rotation.
180  return cv::RotatedRect(
181  cv::Point2f(box[0], box[1]), cv::Size2f(box[2], box[3]), -box[4]);
182 }
183 
184 // TODO: cvfix_rotatedRectangleIntersection is a replacement function for
185 // cv::rotatedRectangleIntersection, which has a bug due to float underflow
186 // When OpenCV version is upgraded to be >= 4.0,
187 // we can remove this replacement function.
188 // For anyone interested, here're the PRs on OpenCV:
189 // https://github.com/opencv/opencv/issues/12221
190 // https://github.com/opencv/opencv/pull/12222
191 int cvfix_rotatedRectangleIntersection(
192  const cv::RotatedRect& rect1,
193  const cv::RotatedRect& rect2,
194  cv::OutputArray intersectingRegion) {
195  // Used to test if two points are the same
196  const float samePointEps = 0.00001f;
197  const float EPS = 1e-14;
198 
199  cv::Point2f vec1[4], vec2[4];
200  cv::Point2f pts1[4], pts2[4];
201 
202  std::vector<cv::Point2f> intersection;
203 
204  rect1.points(pts1);
205  rect2.points(pts2);
206 
207  int ret = cv::INTERSECT_FULL;
208 
209  // Specical case of rect1 == rect2
210  {
211  bool same = true;
212 
213  for (int i = 0; i < 4; i++) {
214  if (fabs(pts1[i].x - pts2[i].x) > samePointEps ||
215  (fabs(pts1[i].y - pts2[i].y) > samePointEps)) {
216  same = false;
217  break;
218  }
219  }
220 
221  if (same) {
222  intersection.resize(4);
223 
224  for (int i = 0; i < 4; i++) {
225  intersection[i] = pts1[i];
226  }
227 
228  cv::Mat(intersection).copyTo(intersectingRegion);
229 
230  return cv::INTERSECT_FULL;
231  }
232  }
233 
234  // Line vector
235  // A line from p1 to p2 is: p1 + (p2-p1)*t, t=[0,1]
236  for (int i = 0; i < 4; i++) {
237  vec1[i].x = pts1[(i + 1) % 4].x - pts1[i].x;
238  vec1[i].y = pts1[(i + 1) % 4].y - pts1[i].y;
239 
240  vec2[i].x = pts2[(i + 1) % 4].x - pts2[i].x;
241  vec2[i].y = pts2[(i + 1) % 4].y - pts2[i].y;
242  }
243 
244  // Line test - test all line combos for intersection
245  for (int i = 0; i < 4; i++) {
246  for (int j = 0; j < 4; j++) {
247  // Solve for 2x2 Ax=b
248  float x21 = pts2[j].x - pts1[i].x;
249  float y21 = pts2[j].y - pts1[i].y;
250 
251  const auto& l1 = vec1[i];
252  const auto& l2 = vec2[j];
253 
254  // This takes care of parallel lines
255  float det = l2.x * l1.y - l1.x * l2.y;
256  if (std::fabs(det) <= EPS) {
257  continue;
258  }
259 
260  float t1 = (l2.x * y21 - l2.y * x21) / det;
261  float t2 = (l1.x * y21 - l1.y * x21) / det;
262 
263  if (t1 >= 0.0f && t1 <= 1.0f && t2 >= 0.0f && t2 <= 1.0f) {
264  float xi = pts1[i].x + vec1[i].x * t1;
265  float yi = pts1[i].y + vec1[i].y * t1;
266  intersection.push_back(cv::Point2f(xi, yi));
267  }
268  }
269  }
270 
271  if (!intersection.empty()) {
272  ret = cv::INTERSECT_PARTIAL;
273  }
274 
275  // Check for vertices from rect1 inside rect2
276  for (int i = 0; i < 4; i++) {
277  // We do a sign test to see which side the point lies.
278  // If the point all lie on the same sign for all 4 sides of the rect,
279  // then there's an intersection
280  int posSign = 0;
281  int negSign = 0;
282 
283  float x = pts1[i].x;
284  float y = pts1[i].y;
285 
286  for (int j = 0; j < 4; j++) {
287  // line equation: Ax + By + C = 0
288  // see which side of the line this point is at
289 
290  // float causes underflow!
291  // Original version:
292  // float A = -vec2[j].y;
293  // float B = vec2[j].x;
294  // float C = -(A * pts2[j].x + B * pts2[j].y);
295  // float s = A * x + B * y + C;
296 
297  double A = -vec2[j].y;
298  double B = vec2[j].x;
299  double C = -(A * pts2[j].x + B * pts2[j].y);
300  double s = A * x + B * y + C;
301 
302  if (s >= 0) {
303  posSign++;
304  } else {
305  negSign++;
306  }
307  }
308 
309  if (posSign == 4 || negSign == 4) {
310  intersection.push_back(pts1[i]);
311  }
312  }
313 
314  // Reverse the check - check for vertices from rect2 inside rect1
315  for (int i = 0; i < 4; i++) {
316  // We do a sign test to see which side the point lies.
317  // If the point all lie on the same sign for all 4 sides of the rect,
318  // then there's an intersection
319  int posSign = 0;
320  int negSign = 0;
321 
322  float x = pts2[i].x;
323  float y = pts2[i].y;
324 
325  for (int j = 0; j < 4; j++) {
326  // line equation: Ax + By + C = 0
327  // see which side of the line this point is at
328 
329  // float causes underflow!
330  // Original version:
331  // float A = -vec1[j].y;
332  // float B = vec1[j].x;
333  // float C = -(A * pts1[j].x + B * pts1[j].y);
334  // float s = A*x + B*y + C;
335 
336  double A = -vec1[j].y;
337  double B = vec1[j].x;
338  double C = -(A * pts1[j].x + B * pts1[j].y);
339  double s = A * x + B * y + C;
340 
341  if (s >= 0) {
342  posSign++;
343  } else {
344  negSign++;
345  }
346  }
347 
348  if (posSign == 4 || negSign == 4) {
349  intersection.push_back(pts2[i]);
350  }
351  }
352 
353  // Get rid of dupes
354  for (int i = 0; i < (int)intersection.size() - 1; i++) {
355  for (size_t j = i + 1; j < intersection.size(); j++) {
356  float dx = intersection[i].x - intersection[j].x;
357  float dy = intersection[i].y - intersection[j].y;
358  // can be a really small number, need double here
359  double d2 = dx * dx + dy * dy;
360 
361  if (d2 < samePointEps * samePointEps) {
362  // Found a dupe, remove it
363  std::swap(intersection[j], intersection.back());
364  intersection.pop_back();
365  j--; // restart check
366  }
367  }
368  }
369 
370  if (intersection.empty()) {
371  return cv::INTERSECT_NONE;
372  }
373 
374  // If this check fails then it means we're getting dupes
375  // CV_Assert(intersection.size() <= 8);
376 
377  // At this point, there might still be some edge cases failing the check above
378  // However, it doesn't affect the result of polygon area,
379  // even if the number of intersections is greater than 8.
380  // Therefore, we just print out these cases for now instead of assertion.
381  // TODO: These cases should provide good reference for improving the accuracy
382  // for intersection computation above (for example, we should use
383  // cross-product/dot-product of vectors instead of line equation to
384  // judge the relationships between the points and line segments)
385 
386  if (intersection.size() > 8) {
387  LOG(ERROR) << "Intersection size = " << intersection.size();
388  LOG(ERROR) << "Rect 1:";
389  for (int i = 0; i < 4; i++) {
390  LOG(ERROR) << " (" << pts1[i].x << " ," << pts1[i].y << "),";
391  }
392  LOG(ERROR) << "Rect 2:";
393  for (int i = 0; i < 4; i++) {
394  LOG(ERROR) << " (" << pts2[i].x << " ," << pts2[i].y << "),";
395  }
396  LOG(ERROR) << "Intersections:";
397  for (auto& p : intersection) {
398  LOG(ERROR) << " (" << p.x << " ," << p.y << "),";
399  }
400  }
401 
402  cv::Mat(intersection).copyTo(intersectingRegion);
403 
404  return ret;
405 }
406 
410 double rotated_rect_intersection(
411  const cv::RotatedRect& rect1,
412  const cv::RotatedRect& rect2) {
413  std::vector<cv::Point2f> intersectPts, orderedPts;
414 
415  // Find points of intersection
416 
417  // TODO: cvfix_rotatedRectangleIntersection is a replacement function for
418  // cv::rotatedRectangleIntersection, which has a bug due to float underflow
419  // When OpenCV version is upgraded to be >= 4.0,
420  // we can remove this replacement function and use the following instead:
421  // auto ret = cv::rotatedRectangleIntersection(rect1, rect2, intersectPts);
422  // For anyone interested, here're the PRs on OpenCV:
423  // https://github.com/opencv/opencv/issues/12221
424  // https://github.com/opencv/opencv/pull/12222
425  auto ret = cvfix_rotatedRectangleIntersection(rect1, rect2, intersectPts);
426  if (intersectPts.size() <= 2) {
427  return 0.0;
428  }
429 
430  // If one rectangle is fully enclosed within another, return the area
431  // of the smaller one early.
432  if (ret == cv::INTERSECT_FULL) {
433  return std::min(rect1.size.area(), rect2.size.area());
434  }
435 
436  // Convex Hull to order the intersection points in clockwise or
437  // counter-clockwise order and find the countour area.
438  cv::convexHull(intersectPts, orderedPts);
439  return cv::contourArea(orderedPts);
440 }
441 
442 } // namespace
443 
449 template <class Derived1, class Derived2>
450 double bbox_intersection_rotated(
451  const Eigen::ArrayBase<Derived1>& box1,
452  const Eigen::ArrayBase<Derived2>& box2) {
453  CAFFE_ENFORCE(box1.size() == 5 && box2.size() == 5);
454  const auto& rect1 = bbox_to_rotated_rect(box1);
455  const auto& rect2 = bbox_to_rotated_rect(box2);
456  return rotated_rect_intersection(rect1, rect2);
457 }
458 
465 template <class Derived1, class Derived2>
466 Eigen::ArrayXXf bbox_overlaps_rotated(
467  const Eigen::ArrayBase<Derived1>& boxes,
468  const Eigen::ArrayBase<Derived2>& query_boxes) {
469  CAFFE_ENFORCE(boxes.cols() == 5 && query_boxes.cols() == 5);
470 
471  const auto& boxes_areas = boxes.col(2) * boxes.col(3);
472  const auto& query_boxes_areas = query_boxes.col(2) * query_boxes.col(3);
473 
474  Eigen::ArrayXXf overlaps(boxes.rows(), query_boxes.rows());
475  for (int i = 0; i < boxes.rows(); ++i) {
476  for (int j = 0; j < query_boxes.rows(); ++j) {
477  auto inter = bbox_intersection_rotated(boxes.row(i), query_boxes.row(j));
478  overlaps(i, j) = (inter == 0.0)
479  ? 0.0
480  : inter / (boxes_areas[i] + query_boxes_areas[j] - inter);
481  }
482  }
483  return overlaps;
484 }
485 
486 // Similar to nms_cpu_upright, but handles rotated proposal boxes
487 // in the format:
488 // size (M, 5), format [ctr_x; ctr_y; width; height; angle (in degrees)].
489 //
490 // For now, we only consider IoU as the metric for suppression. No angle info
491 // is used yet.
492 template <class Derived1, class Derived2>
493 std::vector<int> nms_cpu_rotated(
494  const Eigen::ArrayBase<Derived1>& proposals,
495  const Eigen::ArrayBase<Derived2>& scores,
496  const std::vector<int>& sorted_indices,
497  float thresh,
498  int topN = -1) {
499  CAFFE_ENFORCE_EQ(proposals.rows(), scores.rows());
500  CAFFE_ENFORCE_EQ(proposals.cols(), 5);
501  CAFFE_ENFORCE_EQ(scores.cols(), 1);
502  CAFFE_ENFORCE_LE(sorted_indices.size(), proposals.rows());
503 
504  using EArrX = EArrXt<typename Derived1::Scalar>;
505 
506  auto widths = proposals.col(2);
507  auto heights = proposals.col(3);
508  EArrX areas = widths * heights;
509 
510  std::vector<cv::RotatedRect> rotated_rects(proposals.rows());
511  for (int i = 0; i < proposals.rows(); ++i) {
512  rotated_rects[i] = bbox_to_rotated_rect(proposals.row(i));
513  }
514 
515  EArrXi order = AsEArrXt(sorted_indices);
516  std::vector<int> keep;
517  while (order.size() > 0) {
518  // exit if already enough proposals
519  if (topN >= 0 && keep.size() >= topN) {
520  break;
521  }
522 
523  int i = order[0];
524  keep.push_back(i);
525  ConstEigenVectorArrayMap<int> rest_indices(
526  order.data() + 1, order.size() - 1);
527 
528  EArrX inter(rest_indices.size());
529  for (int j = 0; j < rest_indices.size(); ++j) {
530  inter[j] = rotated_rect_intersection(
531  rotated_rects[i], rotated_rects[rest_indices[j]]);
532  }
533  EArrX ovr = inter / (areas[i] + GetSubArray(areas, rest_indices) - inter);
534 
535  // indices for sub array order[1:n].
536  // TODO (viswanath): Should angle info be included as well while filtering?
537  auto inds = GetArrayIndices(ovr <= thresh);
538  order = GetSubArray(order, AsEArrXt(inds) + 1);
539  }
540 
541  return keep;
542 }
543 
544 // Similar to soft_nms_cpu_upright, but handles rotated proposal boxes
545 // in the format:
546 // size (M, 5), format [ctr_x; ctr_y; width; height; angle (in degrees)].
547 //
548 // For now, we only consider IoU as the metric for suppression. No angle info
549 // is used yet.
550 template <class Derived1, class Derived2, class Derived3>
551 std::vector<int> soft_nms_cpu_rotated(
552  Eigen::ArrayBase<Derived3>* out_scores,
553  const Eigen::ArrayBase<Derived1>& proposals,
554  const Eigen::ArrayBase<Derived2>& scores,
555  const std::vector<int>& indices,
556  float sigma = 0.5,
557  float overlap_thresh = 0.3,
558  float score_thresh = 0.001,
559  unsigned int method = 1,
560  int topN = -1) {
561  CAFFE_ENFORCE_EQ(proposals.rows(), scores.rows());
562  CAFFE_ENFORCE_EQ(proposals.cols(), 5);
563  CAFFE_ENFORCE_EQ(scores.cols(), 1);
564 
565  using EArrX = EArrXt<typename Derived1::Scalar>;
566 
567  auto widths = proposals.col(2);
568  auto heights = proposals.col(3);
569  EArrX areas = widths * heights;
570 
571  std::vector<cv::RotatedRect> rotated_rects(proposals.rows());
572  for (int i = 0; i < proposals.rows(); ++i) {
573  rotated_rects[i] = bbox_to_rotated_rect(proposals.row(i));
574  }
575 
576  // Initialize out_scores with original scores. Will be iteratively updated
577  // as Soft-NMS is applied.
578  *out_scores = scores;
579 
580  std::vector<int> keep;
581  EArrXi pending = AsEArrXt(indices);
582  while (pending.size() > 0) {
583  // Exit if already enough proposals
584  if (topN >= 0 && keep.size() >= topN) {
585  break;
586  }
587 
588  // Find proposal with max score among remaining proposals
589  int max_pos;
590  auto max_score = GetSubArray(*out_scores, pending).maxCoeff(&max_pos);
591  int i = pending[max_pos];
592  keep.push_back(i);
593 
594  // Compute IoU of the remaining boxes with the identified max box
595  std::swap(pending(0), pending(max_pos));
596  const auto& rest_indices = pending.tail(pending.size() - 1);
597  EArrX inter(rest_indices.size());
598  for (int j = 0; j < rest_indices.size(); ++j) {
599  inter[j] = rotated_rect_intersection(
600  rotated_rects[i], rotated_rects[rest_indices[j]]);
601  }
602  EArrX ovr = inter / (areas[i] + GetSubArray(areas, rest_indices) - inter);
603 
604  // Update scores based on computed IoU, overlap threshold and NMS method
605  // TODO (viswanath): Should angle info be included as well while filtering?
606  for (int j = 0; j < rest_indices.size(); ++j) {
607  typename Derived2::Scalar weight;
608  switch (method) {
609  case 1: // Linear
610  weight = (ovr(j) > overlap_thresh) ? (1.0 - ovr(j)) : 1.0;
611  break;
612  case 2: // Gaussian
613  weight = std::exp(-1.0 * ovr(j) * ovr(j) / sigma);
614  break;
615  default: // Original NMS
616  weight = (ovr(j) > overlap_thresh) ? 0.0 : 1.0;
617  }
618  (*out_scores)(rest_indices[j]) *= weight;
619  }
620 
621  // Discard boxes with new scores below min threshold and update pending
622  // indices
623  const auto& rest_scores = GetSubArray(*out_scores, rest_indices);
624  const auto& inds = GetArrayIndices(rest_scores >= score_thresh);
625  pending = GetSubArray(rest_indices, AsEArrXt(inds));
626  }
627 
628  return keep;
629 }
630 #endif // CV_MAJOR_VERSION >= 3
631 
632 template <class Derived1, class Derived2>
633 std::vector<int> nms_cpu(
634  const Eigen::ArrayBase<Derived1>& proposals,
635  const Eigen::ArrayBase<Derived2>& scores,
636  const std::vector<int>& sorted_indices,
637  float thresh,
638  int topN = -1) {
639 #if defined(CV_MAJOR_VERSION) && (CV_MAJOR_VERSION >= 3)
640  CAFFE_ENFORCE(proposals.cols() == 4 || proposals.cols() == 5);
641  if (proposals.cols() == 4) {
642  // Upright boxes
643  return nms_cpu_upright(proposals, scores, sorted_indices, thresh, topN);
644  } else {
645  // Rotated boxes with angle info
646  return nms_cpu_rotated(proposals, scores, sorted_indices, thresh, topN);
647  }
648 #else
649  return nms_cpu_upright(proposals, scores, sorted_indices, thresh, topN);
650 #endif // CV_MAJOR_VERSION >= 3
651 }
652 
653 // Greedy non-maximum suppression for proposed bounding boxes
654 // Reject a bounding box if its region has an intersection-overunion (IoU)
655 // overlap with a higher scoring selected bounding box larger than a
656 // threshold.
657 // Reference: facebookresearch/Detectron/detectron/lib/utils/cython_nms.pyx
658 // proposals: pixel coordinates of proposed bounding boxes,
659 // size: (M, 4), format: [x1; y1; x2; y2]
660 // size: (M, 5), format: [ctr_x; ctr_y; w; h; angle (degrees)] for RRPN
661 // scores: scores for each bounding box, size: (M, 1)
662 // return: row indices of the selected proposals
663 template <class Derived1, class Derived2>
664 std::vector<int> nms_cpu(
665  const Eigen::ArrayBase<Derived1>& proposals,
666  const Eigen::ArrayBase<Derived2>& scores,
667  float thres) {
668  std::vector<int> indices(proposals.rows());
669  std::iota(indices.begin(), indices.end(), 0);
670  std::sort(
671  indices.data(),
672  indices.data() + indices.size(),
673  [&scores](int lhs, int rhs) { return scores(lhs) > scores(rhs); });
674 
675  return nms_cpu(proposals, scores, indices, thres);
676 }
677 
678 template <class Derived1, class Derived2, class Derived3>
679 std::vector<int> soft_nms_cpu(
680  Eigen::ArrayBase<Derived3>* out_scores,
681  const Eigen::ArrayBase<Derived1>& proposals,
682  const Eigen::ArrayBase<Derived2>& scores,
683  const std::vector<int>& indices,
684  float sigma = 0.5,
685  float overlap_thresh = 0.3,
686  float score_thresh = 0.001,
687  unsigned int method = 1,
688  int topN = -1) {
689 #if defined(CV_MAJOR_VERSION) && (CV_MAJOR_VERSION >= 3)
690  CAFFE_ENFORCE(proposals.cols() == 4 || proposals.cols() == 5);
691  if (proposals.cols() == 4) {
692  // Upright boxes
693  return soft_nms_cpu_upright(
694  out_scores,
695  proposals,
696  scores,
697  indices,
698  sigma,
699  overlap_thresh,
700  score_thresh,
701  method,
702  topN);
703  } else {
704  // Rotated boxes with angle info
705  return soft_nms_cpu_rotated(
706  out_scores,
707  proposals,
708  scores,
709  indices,
710  sigma,
711  overlap_thresh,
712  score_thresh,
713  method,
714  topN);
715  }
716 #else
717  return soft_nms_cpu_upright(
718  out_scores,
719  proposals,
720  scores,
721  indices,
722  sigma,
723  overlap_thresh,
724  score_thresh,
725  method,
726  topN);
727 #endif // CV_MAJOR_VERSION >= 3
728 }
729 
730 template <class Derived1, class Derived2, class Derived3>
731 std::vector<int> soft_nms_cpu(
732  Eigen::ArrayBase<Derived3>* out_scores,
733  const Eigen::ArrayBase<Derived1>& proposals,
734  const Eigen::ArrayBase<Derived2>& scores,
735  float sigma = 0.5,
736  float overlap_thresh = 0.3,
737  float score_thresh = 0.001,
738  unsigned int method = 1,
739  int topN = -1) {
740  std::vector<int> indices(proposals.rows());
741  std::iota(indices.begin(), indices.end(), 0);
742  return soft_nms_cpu(
743  out_scores,
744  proposals,
745  scores,
746  indices,
747  sigma,
748  overlap_thresh,
749  score_thresh,
750  method,
751  topN);
752 }
753 
754 } // namespace utils
755 } // namespace caffe2
756 
757 #endif // CAFFE2_OPERATORS_UTILS_NMS_H_
Definition: static.cpp:52
A global dictionary that holds information about what Caffe2 modules have been loaded in the current ...
Definition: blob.h:13
Definition: static.cpp:64
Definition: static.cpp:58