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