OpenShot Library | libopenshot  0.7.0
sort.cpp
Go to the documentation of this file.
1 // © OpenShot Studios, LLC
2 //
3 // SPDX-License-Identifier: LGPL-3.0-or-later
4 
5 #include "sort.hpp"
6 #include <cmath>
7 
8 using namespace std;
9 
10 namespace {
11 struct DetectionBox : public TrackingBox
12 {
13  std::vector<ClassScore> classScores;
14 };
15 
16 double box_diagonal(const cv::Rect_<float>& box)
17 {
18  return std::sqrt(box.width * box.width + box.height * box.height);
19 }
20 
21 double box_area(const cv::Rect_<float>& box)
22 {
23  return std::max(0.0f, box.width) * std::max(0.0f, box.height);
24 }
25 
26 double aspect_ratio(const cv::Rect_<float>& box)
27 {
28  if (box.width <= 0.0f || box.height <= 0.0f)
29  return 0.0;
30  return static_cast<double>(box.width) / static_cast<double>(box.height);
31 }
32 
33 bool box_shape_matches(const cv::Rect_<float>& predicted_box, const cv::Rect_<float>& detection_box, double iou)
34 {
35  const double predicted_area = box_area(predicted_box);
36  const double detection_area = box_area(detection_box);
37  if (predicted_area <= 1.0 || detection_area <= 1.0)
38  return false;
39 
40  const double area_ratio = detection_area / predicted_area;
41  const double predicted_aspect = aspect_ratio(predicted_box);
42  const double detection_aspect = aspect_ratio(detection_box);
43  const double aspect_ratio_delta = (predicted_aspect > 0.0 && detection_aspect > 0.0)
44  ? std::max(predicted_aspect / detection_aspect, detection_aspect / predicted_aspect)
45  : 999.0;
46 
47  if (iou >= 0.70)
48  return area_ratio >= 0.20 && area_ratio <= 5.00 && aspect_ratio_delta <= 4.00;
49  return area_ratio >= 0.35 && area_ratio <= 2.80 && aspect_ratio_delta <= 2.75;
50 }
51 
52 bool detection_matches_track_gate(
53  const KalmanTracker& tracker,
54  const cv::Rect_<float>& predicted_box,
55  const DetectionBox& detection,
56  double iou,
57  double centroid_distance)
58 {
59  if (!box_shape_matches(predicted_box, detection.box, iou))
60  return false;
61 
62  const double scale = std::max(box_diagonal(predicted_box), box_diagonal(detection.box));
63  const bool missed_previous_frame = tracker.m_time_since_update > 1;
64 
65  if (missed_previous_frame) {
66  const double reacquire_distance = std::max(12.0, scale * 0.25);
67  return iou >= 0.35 || centroid_distance <= reacquire_distance;
68  }
69 
70  const double local_distance = std::max(12.0, scale * 0.22);
71  return iou >= 0.20 || centroid_distance <= local_distance;
72 }
73 }
74 
75 // Constructor
76 SortTracker::SortTracker(int max_age, int min_hits, int max_missed, double min_iou, double nms_iou_thresh, double min_conf)
77 {
78  _min_hits = min_hits;
79  _max_age = max_age;
80  _max_missed = max_missed;
81  _min_iou = min_iou;
82  _nms_iou_thresh = nms_iou_thresh;
83  _min_conf = min_conf;
84  _next_id = 0;
85  alive_tracker = true;
86 }
87 
88 // Computes IOU between two bounding boxes
89 double SortTracker::GetIOU(cv::Rect_<float> bb_test, cv::Rect_<float> bb_gt)
90 {
91  float in = (bb_test & bb_gt).area();
92  float un = bb_test.area() + bb_gt.area() - in;
93 
94  if (un < DBL_EPSILON)
95  return 0;
96 
97  return (double)(in / un);
98 }
99 
100 // Computes centroid distance between two bounding boxes
102  cv::Rect_<float> bb_test,
103  cv::Rect_<float> bb_gt)
104 {
105  float bb_test_centroid_x = (bb_test.x + bb_test.width / 2);
106  float bb_test_centroid_y = (bb_test.y + bb_test.height / 2);
107 
108  float bb_gt_centroid_x = (bb_gt.x + bb_gt.width / 2);
109  float bb_gt_centroid_y = (bb_gt.y + bb_gt.height / 2);
110 
111  double distance = (double)sqrt(pow(bb_gt_centroid_x - bb_test_centroid_x, 2) + pow(bb_gt_centroid_y - bb_test_centroid_y, 2));
112 
113  return distance;
114 }
115 
116 // Function to apply NMS on detections
117 void apply_nms(vector<DetectionBox>& detections, double nms_iou_thresh) {
118  if (detections.empty()) return;
119 
120  // Sort detections by confidence descending
121  std::sort(detections.begin(), detections.end(), [](const DetectionBox& a, const DetectionBox& b) {
122  return a.confidence > b.confidence;
123  });
124 
125  vector<bool> suppressed(detections.size(), false);
126 
127  for (size_t i = 0; i < detections.size(); ++i) {
128  if (suppressed[i]) continue;
129 
130  for (size_t j = i + 1; j < detections.size(); ++j) {
131  if (suppressed[j]) continue;
132 
133  double iou = SortTracker::GetIOU(detections[i].box, detections[j].box);
134  if ((detections[i].classId == detections[j].classId && iou > nms_iou_thresh) ||
135  iou > 0.85) {
136  suppressed[j] = true;
137  }
138  }
139  }
140 
141  // Remove suppressed detections
142  vector<DetectionBox> filtered;
143  for (size_t i = 0; i < detections.size(); ++i) {
144  if (!suppressed[i]) {
145  filtered.push_back(detections[i]);
146  }
147  }
148  detections = filtered;
149 }
150 
151 void SortTracker::update(vector<cv::Rect> detections_cv, int frame_count, double image_diagonal, std::vector<float> confidences, std::vector<int> classIds, std::vector<std::vector<ClassScore>> classScores)
152 {
153  vector<DetectionBox> detections;
154  dead_trackers_id.clear();
155  if (classScores.size() != detections_cv.size())
156  classScores.resize(detections_cv.size());
157  if (trackers.size() == 0) // the first frame met
158  {
159  alive_tracker = false;
160  // initialize kalman trackers using first detections.
161  for (unsigned int i = 0; i < detections_cv.size(); i++)
162  {
163  if (confidences[i] < _min_conf) continue; // filter low conf
164 
165  DetectionBox tb;
166 
167  tb.box = cv::Rect_<float>(detections_cv[i]);
168  tb.classId = classIds[i];
169  tb.confidence = confidences[i];
170  tb.classScores = classScores[i];
171  detections.push_back(tb);
172  }
173 
174  apply_nms(detections, _nms_iou_thresh);
175 
176  for (const auto& detection : detections)
177  {
178  KalmanTracker trk = KalmanTracker(detection.box, detection.confidence, detection.classId, _next_id++, detection.classScores);
179  trackers.push_back(trk);
180  }
181  return;
182  }
183  else
184  {
185  for (unsigned int i = 0; i < detections_cv.size(); i++)
186  {
187  if (confidences[i] < _min_conf) continue; // filter low conf
188 
189  DetectionBox tb;
190  tb.box = cv::Rect_<float>(detections_cv[i]);
191  tb.classId = classIds[i];
192  tb.confidence = confidences[i];
193  tb.classScores = classScores[i];
194  detections.push_back(tb);
195  }
196 
197  // Apply NMS to remove duplicates
198  apply_nms(detections, _nms_iou_thresh);
199  }
200 
202  // 3.1. get predicted locations from existing trackers.
203  predictedBoxes.clear();
204  for (unsigned int i = 0; i < trackers.size();)
205  {
206  cv::Rect_<float> pBox = trackers[i].predict();
207  if (pBox.x >= 0 && pBox.y >= 0)
208  {
209  predictedBoxes.push_back(pBox);
210  i++;
211  continue;
212  }
213  trackers.erase(trackers.begin() + i);
214  }
215 
216  trkNum = predictedBoxes.size();
217  detNum = detections.size();
218 
219  cost_matrix.clear();
220  cost_matrix.resize(trkNum, vector<double>(detNum, 0));
221  assignment.clear();
222  matchedPairs.clear();
223  unmatchedTrajectories.clear();
224  unmatchedDetections.clear();
225  allItems.clear();
226  matchedItems.clear();
227 
228  if (trkNum == 0)
229  {
230  for (auto& detection : detections)
231  {
232  KalmanTracker tracker = KalmanTracker(detection.box, detection.confidence, detection.classId, _next_id++, detection.classScores);
233  trackers.push_back(tracker);
234  }
235  }
236  else if (detNum == 0)
237  {
238  for (unsigned int i = 0; i < trkNum; ++i)
239  unmatchedTrajectories.insert(i);
240  }
241  else
242  {
243 
244  for (unsigned int i = 0; i < trkNum; i++) // compute cost matrix using 1 - IOU with gating
245  {
246  for (unsigned int j = 0; j < detNum; j++)
247  {
248  double iou = GetIOU(predictedBoxes[i], detections[j].box);
249  double centroid_distance = GetCentroidsDistance(predictedBoxes[i], detections[j].box);
250  if (!detection_matches_track_gate(trackers[i], predictedBoxes[i], detections[j], iou, centroid_distance))
251  {
252  cost_matrix[i][j] = 1e9; // large cost for gating
253  }
254  else
255  {
256  const double scale = std::max(1.0, std::max(box_diagonal(predictedBoxes[i]), box_diagonal(detections[j].box)));
257  const double distance_penalty = std::min(1.0, centroid_distance / scale) * 0.35;
258  cost_matrix[i][j] = 1 - iou + distance_penalty + (1 - detections[j].confidence) * 0.1;
259  }
260  }
261  }
262 
263  HungarianAlgorithm HungAlgo;
264  HungAlgo.Solve(cost_matrix, assignment);
265 
266  // find matches, unmatched_detections and unmatched_predictions
267  if (detNum > trkNum) // there are unmatched detections
268  {
269  for (unsigned int n = 0; n < detNum; n++)
270  allItems.insert(n);
271 
272  for (unsigned int i = 0; i < trkNum; ++i)
273  matchedItems.insert(assignment[i]);
274 
275  set_difference(allItems.begin(), allItems.end(),
276  matchedItems.begin(), matchedItems.end(),
277  insert_iterator<set<int>>(unmatchedDetections, unmatchedDetections.begin()));
278  }
279  else if (detNum < trkNum) // there are unmatched trajectory/predictions
280  {
281  for (unsigned int i = 0; i < trkNum; ++i)
282  if (assignment[i] == -1) // unassigned label will be set as -1 in the assignment algorithm
283  unmatchedTrajectories.insert(i);
284  }
285  else
286  ;
287 
288  // filter out matched with low IOU
289  for (unsigned int i = 0; i < trkNum; ++i)
290  {
291  if (assignment[i] == -1) // pass over invalid values
292  continue;
293  if (cost_matrix[i][assignment[i]] >= 1e8)
294  {
295  unmatchedTrajectories.insert(i);
296  unmatchedDetections.insert(assignment[i]);
297  }
298  else
299  matchedPairs.push_back(cv::Point(i, assignment[i]));
300  }
301  }
302 
303  for (unsigned int i = 0; i < matchedPairs.size(); i++)
304  {
305  int trkIdx = matchedPairs[i].x;
306  int detIdx = matchedPairs[i].y;
307  trackers[trkIdx].update(detections[detIdx].box);
308  trackers[trkIdx].update_class_scores(detections[detIdx].classScores, detections[detIdx].classId, detections[detIdx].confidence);
309  trackers[trkIdx].confidence = detections[detIdx].confidence;
310  }
311 
312  // create and initialise new trackers for unmatched detections
313  for (auto umd : unmatchedDetections)
314  {
315  KalmanTracker tracker = KalmanTracker(detections[umd].box, detections[umd].confidence, detections[umd].classId, _next_id++, detections[umd].classScores);
316  trackers.push_back(tracker);
317  }
318 
319  // get trackers' output
320  frameTrackingResult.clear();
321  for (unsigned int i = 0; i < trackers.size();)
322  {
323  if ((trackers[i].m_hits >= _min_hits && trackers[i].m_time_since_update <= _max_missed) ||
324  frame_count <= _min_hits)
325  {
326  alive_tracker = true;
327  TrackingBox res;
328  if (i < predictedBoxes.size() && trackers[i].m_time_since_update > 0)
329  res.box = predictedBoxes[i];
330  else
331  res.box = trackers[i].get_state();
332  res.id = trackers[i].m_id;
333  res.frame = frame_count;
334  res.classId = trackers[i].classId;
335  res.confidence = trackers[i].confidence;
336  frameTrackingResult.push_back(res);
337  }
338 
339  // remove dead tracklet
340  if (trackers[i].m_time_since_update >= _max_age)
341  {
342  trackers.erase(trackers.begin() + i);
343  continue;
344  }
345  i++;
346  }
347 }
TrackingBox::confidence
float confidence
Definition: sort.hpp:25
HungarianAlgorithm
Definition: Hungarian.h:22
TrackingBox
Definition: sort.hpp:22
TrackingBox::frame
int frame
Definition: sort.hpp:24
SortTracker::GetCentroidsDistance
double GetCentroidsDistance(cv::Rect_< float > bb_test, cv::Rect_< float > bb_gt)
Definition: sort.cpp:101
KalmanTracker
This class represents the internel state of individual tracked objects observed as bounding box.
Definition: KalmanTracker.h:29
TrackingBox::classId
int classId
Definition: sort.hpp:26
SortTracker::GetIOU
static double GetIOU(cv::Rect_< float > bb_test, cv::Rect_< float > bb_gt)
Definition: sort.cpp:89
apply_nms
void apply_nms(vector< DetectionBox > &detections, double nms_iou_thresh)
Definition: sort.cpp:117
TrackingBox::box
cv::Rect_< float > box
Definition: sort.hpp:28
TrackingBox::id
int id
Definition: sort.hpp:27
HungarianAlgorithm::Solve
double Solve(std::vector< std::vector< double >> &DistMatrix, std::vector< int > &Assignment)
Definition: Hungarian.cpp:26
KalmanTracker::m_time_since_update
int m_time_since_update
Definition: KalmanTracker.h:65
sort.hpp
SortTracker::update
void update(std::vector< cv::Rect > detection, int frame_count, double image_diagonal, std::vector< float > confidences, std::vector< int > classIds, std::vector< std::vector< ClassScore >> classScores={})
Definition: sort.cpp:151
SortTracker::SortTracker
SortTracker(int max_age=50, int min_hits=5, int max_missed=3, double min_iou=0.1, double nms_iou_thresh=0.5, double min_conf=0.3)
Definition: sort.cpp:76