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 <list>
5 #include <vector>
6 
7 #include "caffe2/utils/eigen_utils.h"
8 
9 #include "caffe2/core/logging.h"
10 #include "caffe2/utils/math.h"
11 
12 namespace caffe2 {
13 namespace utils {
14 
15 // Greedy non-maximum suppression for proposed bounding boxes
16 // Reject a bounding box if its region has an intersection-overunion (IoU)
17 // overlap with a higher scoring selected bounding box larger than a
18 // threshold.
19 // Reference: detectron/lib/utils/cython_nms.pyx
20 // proposals: pixel coordinates of proposed bounding boxes,
21 // size: (M, 4), format: [x1; y1; x2; y2]
22 // scores: scores for each bounding box, size: (M, 1)
23 // sorted_indices: indices that sorts the scores from high to low
24 // return: row indices of the selected proposals
25 template <class Derived1, class Derived2>
26 std::vector<int> nms_cpu(
27  const Eigen::ArrayBase<Derived1>& proposals,
28  const Eigen::ArrayBase<Derived2>& scores,
29  const std::vector<int>& sorted_indices,
30  float thresh,
31  int topN = -1) {
32  CAFFE_ENFORCE_EQ(proposals.rows(), scores.rows());
33  CAFFE_ENFORCE_EQ(proposals.cols(), 4);
34  CAFFE_ENFORCE_EQ(scores.cols(), 1);
35  CAFFE_ENFORCE_LE(sorted_indices.size(), proposals.rows());
36 
37  using EArrX = EArrXt<typename Derived1::Scalar>;
38 
39  auto x1 = proposals.col(0);
40  auto y1 = proposals.col(1);
41  auto x2 = proposals.col(2);
42  auto y2 = proposals.col(3);
43 
44  EArrX areas = (x2 - x1 + 1.0) * (y2 - y1 + 1.0);
45 
46  EArrXi order = AsEArrXt(sorted_indices);
47  std::vector<int> keep;
48  int ci = 0;
49  while (order.size() > 0) {
50  // exit if already enough proposals
51  if (topN >= 0 && keep.size() >= topN) {
52  break;
53  }
54 
55  int i = order[0];
56  keep.push_back(i);
57  ConstEigenVectorArrayMap<int> rest_indices(
58  order.data() + 1, order.size() - 1);
59  EArrX xx1 = GetSubArray(x1, rest_indices).cwiseMax(x1[i]);
60  EArrX yy1 = GetSubArray(y1, rest_indices).cwiseMax(y1[i]);
61  EArrX xx2 = GetSubArray(x2, rest_indices).cwiseMin(x2[i]);
62  EArrX yy2 = GetSubArray(y2, rest_indices).cwiseMin(y2[i]);
63 
64  EArrX w = (xx2 - xx1 + 1.0).cwiseMax(0.0);
65  EArrX h = (yy2 - yy1 + 1.0).cwiseMax(0.0);
66  EArrX inter = w * h;
67  EArrX ovr = inter / (areas[i] + GetSubArray(areas, rest_indices) - inter);
68 
69  // indices for sub array order[1:n]
70  auto inds = GetArrayIndices(ovr <= thresh);
71  order = GetSubArray(order, AsEArrXt(inds) + 1);
72  }
73 
74  return keep;
75 }
76 
77 // Greedy non-maximum suppression for proposed bounding boxes
78 // Reject a bounding box if its region has an intersection-overunion (IoU)
79 // overlap with a higher scoring selected bounding box larger than a
80 // threshold.
81 // Reference: detectron/lib/utils/cython_nms.pyx
82 // proposals: pixel coordinates of proposed bounding boxes,
83 // size: (M, 4), format: [x1; y1; x2; y2]
84 // scores: scores for each bounding box, size: (M, 1)
85 // return: row indices of the selected proposals
86 template <class Derived1, class Derived2>
87 std::vector<int> nms_cpu(
88  const Eigen::ArrayBase<Derived1>& proposals,
89  const Eigen::ArrayBase<Derived2>& scores,
90  float thres) {
91  std::vector<int> indices(proposals.rows());
92  std::iota(indices.begin(), indices.end(), 0);
93  std::sort(
94  indices.data(),
95  indices.data() + indices.size(),
96  [&scores](int lhs, int rhs) { return scores(lhs) > scores(rhs); });
97 
98  return nms_cpu(proposals, scores, indices, thres);
99 }
100 
116 template <class Derived1, class Derived2, class Derived3>
117 std::vector<int> soft_nms_cpu(
118  Eigen::ArrayBase<Derived3>* out_scores,
119  const Eigen::ArrayBase<Derived1>& proposals,
120  const Eigen::ArrayBase<Derived2>& scores,
121  const std::vector<int>& indices,
122  float sigma = 0.5,
123  float overlap_thresh = 0.3,
124  float score_thresh = 0.001,
125  unsigned int method = 1,
126  int topN = -1) {
127  CAFFE_ENFORCE_EQ(proposals.rows(), scores.rows());
128  CAFFE_ENFORCE_EQ(proposals.cols(), 4);
129  CAFFE_ENFORCE_EQ(scores.cols(), 1);
130 
131  using EArrX = EArrXt<typename Derived1::Scalar>;
132 
133  const auto& x1 = proposals.col(0);
134  const auto& y1 = proposals.col(1);
135  const auto& x2 = proposals.col(2);
136  const auto& y2 = proposals.col(3);
137 
138  EArrX areas = (x2 - x1 + 1.0) * (y2 - y1 + 1.0);
139 
140  // Initialize out_scores with original scores. Will be iteratively updated
141  // as Soft-NMS is applied.
142  *out_scores = scores;
143 
144  std::vector<int> keep;
145  EArrXi pending = AsEArrXt(indices);
146  while (pending.size() > 0) {
147  // Exit if already enough proposals
148  if (topN >= 0 && keep.size() >= topN) {
149  break;
150  }
151 
152  // Find proposal with max score among remaining proposals
153  int max_pos;
154  auto max_score = GetSubArray(*out_scores, pending).maxCoeff(&max_pos);
155  int i = pending[max_pos];
156  keep.push_back(i);
157 
158  // Compute IoU of the remaining boxes with the identified max box
159  std::swap(pending(0), pending(max_pos));
160  const auto& rest_indices = pending.tail(pending.size() - 1);
161  EArrX xx1 = GetSubArray(x1, rest_indices).cwiseMax(x1[i]);
162  EArrX yy1 = GetSubArray(y1, rest_indices).cwiseMax(y1[i]);
163  EArrX xx2 = GetSubArray(x2, rest_indices).cwiseMin(x2[i]);
164  EArrX yy2 = GetSubArray(y2, rest_indices).cwiseMin(y2[i]);
165 
166  EArrX w = (xx2 - xx1 + 1.0).cwiseMax(0.0);
167  EArrX h = (yy2 - yy1 + 1.0).cwiseMax(0.0);
168  EArrX inter = w * h;
169  EArrX ovr = inter / (areas[i] + GetSubArray(areas, rest_indices) - inter);
170 
171  // Update scores based on computed IoU, overlap threshold and NMS method
172  for (int j = 0; j < rest_indices.size(); ++j) {
173  typename Derived2::Scalar weight;
174  switch (method) {
175  case 1: // Linear
176  weight = (ovr(j) > overlap_thresh) ? (1.0 - ovr(j)) : 1.0;
177  break;
178  case 2: // Gaussian
179  weight = std::exp(-1.0 * ovr(j) * ovr(j) / sigma);
180  break;
181  default: // Original NMS
182  weight = (ovr(j) > overlap_thresh) ? 0.0 : 1.0;
183  }
184  (*out_scores)(rest_indices[j]) *= weight;
185  }
186 
187  // Discard boxes with new scores below min threshold and update pending
188  // indices
189  const auto& rest_scores = GetSubArray(*out_scores, rest_indices);
190  const auto& inds = GetArrayIndices(rest_scores >= score_thresh);
191  pending = GetSubArray(rest_indices, AsEArrXt(inds));
192  }
193 
194  return keep;
195 }
196 
197 template <class Derived1, class Derived2, class Derived3>
198 std::vector<int> soft_nms_cpu(
199  Eigen::ArrayBase<Derived3>* out_scores,
200  const Eigen::ArrayBase<Derived1>& proposals,
201  const Eigen::ArrayBase<Derived2>& scores,
202  float sigma = 0.5,
203  float overlap_thresh = 0.3,
204  float score_thresh = 0.001,
205  unsigned int method = 1,
206  int topN = -1) {
207  std::vector<int> indices(proposals.rows());
208  std::iota(indices.begin(), indices.end(), 0);
209  return soft_nms_cpu(
210  out_scores,
211  proposals,
212  scores,
213  indices,
214  sigma,
215  overlap_thresh,
216  score_thresh,
217  method,
218  topN);
219 }
220 
221 } // namespace utils
222 } // namespace caffe2
223 
224 #endif // CAFFE2_OPERATORS_UTILS_NMS_H_
A global dictionary that holds information about what Caffe2 modules have been loaded in the current ...