Commit 0a2179b3 authored by Leonid Beynenson's avatar Leonid Beynenson Committed by Alexander Alekhin

Merge pull request #2182 from LeonidBeynenson:lb/tracking_by_matching

* Add tracking-by_matching code and sample

* Make interface for PedestrianTracker

* Replace PedestrianTracker -> TrackerByMatching

* Make proper filtering by class id in tracking_by_matching

Also make the sample build in the case when opencv_dnn module is not
built.
Also help is added.

* Remove TODO-s from tracking_by_matching code

* Add parameter frame_step, add copyrights, fix warnings

* Remove copyright from tracking_by_matching

* Rename check macros and remove obsolete mentions of pedestrians

* Tune default thresholds in tracking_by_matching sample

* Add description of classes and factories

* Remove unrequired EOL-s at the end of files

* Replace pointers by references for output parameters

* Fix some warnings found by buildbot

* Fix warning from buildbot, tune some thresholds in tracking_by_matching

* Replace pragma once by ifndef-define clause

* Fix more Windows warnings

* Change case of methods of TrackerByMatching class

* Change name of methods to CamelCase in TrackerByMatching

* Make more convenient check macros in tracking_by_matching.cpp

* Simplify tracking_by_matching sample

* Fix Mac error in tracking_by_matching
parent 1b636e72
#ifndef __OPENCV_TRACKING_TRACKING_BY_MATCHING_HPP__
#define __OPENCV_TRACKING_TRACKING_BY_MATCHING_HPP__
#include <deque>
#include <iostream>
#include <string>
#include <unordered_map>
#include <vector>
#include <memory>
#include <map>
#include <tuple>
#include <set>
#include "opencv2/core.hpp"
#include "opencv2/imgproc.hpp"
namespace cv {
namespace tbm { //Tracking-by-Matching
///
/// \brief The TrackedObject struct defines properties of detected object.
///
struct CV_EXPORTS TrackedObject {
cv::Rect rect; ///< Detected object ROI (zero area if N/A).
double confidence; ///< Detection confidence level (-1 if N/A).
int frame_idx; ///< Frame index where object was detected (-1 if N/A).
int object_id; ///< Unique object identifier (-1 if N/A).
uint64_t timestamp; ///< Timestamp in milliseconds.
///
/// \brief Default constructor.
///
TrackedObject()
: confidence(-1),
frame_idx(-1),
object_id(-1),
timestamp(0) {}
///
/// \brief Constructor with parameters.
/// \param rect Bounding box of detected object.
/// \param confidence Confidence of detection.
/// \param frame_idx Index of frame.
/// \param object_id Object ID.
///
TrackedObject(const cv::Rect &rect, float confidence, int frame_idx,
int object_id)
: rect(rect),
confidence(confidence),
frame_idx(frame_idx),
object_id(object_id),
timestamp(0) {}
};
using TrackedObjects = std::deque<TrackedObject>;
bool operator==(const TrackedObject& first, const TrackedObject& second);
bool operator!=(const TrackedObject& first, const TrackedObject& second);
/// (object id, detected objects) pairs collection.
using ObjectTracks = std::unordered_map<int, TrackedObjects>;
///
/// \brief The IImageDescriptor class declares base class for image
/// descriptor.
///
class CV_EXPORTS IImageDescriptor {
public:
///
/// \brief Descriptor size getter.
/// \return Descriptor size.
///
virtual cv::Size size() const = 0;
///
/// \brief Computes image descriptor.
/// \param[in] mat Color image.
/// \param[out] descr Computed descriptor.
///
virtual void compute(const cv::Mat &mat, CV_OUT cv::Mat& descr) = 0;
///
/// \brief Computes image descriptors in batches.
/// \param[in] mats Images of interest.
/// \param[out] descrs Matrices to store the computed descriptors.
///
virtual void compute(const std::vector<cv::Mat> &mats,
CV_OUT std::vector<cv::Mat>& descrs) = 0;
virtual ~IImageDescriptor() {}
};
///
/// \brief Uses resized image as descriptor.
///
class CV_EXPORTS ResizedImageDescriptor : public IImageDescriptor {
public:
///
/// \brief Constructor.
/// \param[in] descr_size Size of the descriptor (resized image).
/// \param[in] interpolation Interpolation algorithm.
///
explicit ResizedImageDescriptor(const cv::Size &descr_size,
const cv::InterpolationFlags interpolation)
: descr_size_(descr_size), interpolation_(interpolation) {
CV_Assert(descr_size.width > 0);
CV_Assert(descr_size.height > 0);
}
///
/// \brief Returns descriptor size.
/// \return Number of elements in the descriptor.
///
cv::Size size() const override { return descr_size_; }
///
/// \brief Computes image descriptor.
/// \param[in] mat Frame containing the image of interest.
/// \param[out] descr Matrix to store the computed descriptor.
///
void compute(const cv::Mat &mat, CV_OUT cv::Mat& descr) override {
CV_Assert(!mat.empty());
cv::resize(mat, descr, descr_size_, 0, 0, interpolation_);
}
///
/// \brief Computes images descriptors.
/// \param[in] mats Frames containing images of interest.
/// \param[out] descrs Matrices to store the computed descriptors.
//
void compute(const std::vector<cv::Mat> &mats,
CV_OUT std::vector<cv::Mat>& descrs) override {
descrs.resize(mats.size());
for (size_t i = 0; i < mats.size(); i++) {
compute(mats[i], descrs[i]);
}
}
private:
cv::Size descr_size_;
cv::InterpolationFlags interpolation_;
};
///
/// \brief The IDescriptorDistance class declares an interface for distance
/// computation between reidentification descriptors.
///
class CV_EXPORTS IDescriptorDistance {
public:
///
/// \brief Computes distance between two descriptors.
/// \param[in] descr1 First descriptor.
/// \param[in] descr2 Second descriptor.
/// \return Distance between two descriptors.
///
virtual float compute(const cv::Mat &descr1, const cv::Mat &descr2) = 0;
///
/// \brief Computes distances between two descriptors in batches.
/// \param[in] descrs1 Batch of first descriptors.
/// \param[in] descrs2 Batch of second descriptors.
/// \return Distances between descriptors.
///
virtual std::vector<float> compute(const std::vector<cv::Mat> &descrs1,
const std::vector<cv::Mat> &descrs2) = 0;
virtual ~IDescriptorDistance() {}
};
///
/// \brief The CosDistance class allows computing cosine distance between two
/// reidentification descriptors.
///
class CV_EXPORTS CosDistance : public IDescriptorDistance {
public:
///
/// \brief CosDistance constructor.
/// \param[in] descriptor_size Descriptor size.
///
explicit CosDistance(const cv::Size &descriptor_size);
///
/// \brief Computes distance between two descriptors.
/// \param descr1 First descriptor.
/// \param descr2 Second descriptor.
/// \return Distance between two descriptors.
///
float compute(const cv::Mat &descr1, const cv::Mat &descr2) override;
///
/// \brief Computes distances between two descriptors in batches.
/// \param[in] descrs1 Batch of first descriptors.
/// \param[in] descrs2 Batch of second descriptors.
/// \return Distances between descriptors.
///
std::vector<float> compute(
const std::vector<cv::Mat> &descrs1,
const std::vector<cv::Mat> &descrs2) override;
private:
cv::Size descriptor_size_;
};
///
/// \brief Computes distance between images
/// using MatchTemplate function from OpenCV library
/// and its cross-correlation computation method in particular.
///
class CV_EXPORTS MatchTemplateDistance : public IDescriptorDistance {
public:
///
/// \brief Constructs the distance object.
///
/// \param[in] type Method of MatchTemplate function computation.
/// \param[in] scale Scale parameter for the distance.
/// Final distance is computed as:
/// scale * distance + offset.
/// \param[in] offset Offset parameter for the distance.
/// Final distance is computed as:
/// scale * distance + offset.
///
MatchTemplateDistance(int type = cv::TemplateMatchModes::TM_CCORR_NORMED,
float scale = -1, float offset = 1)
: type_(type), scale_(scale), offset_(offset) {}
///
/// \brief Computes distance between image descriptors.
/// \param[in] descr1 First image descriptor.
/// \param[in] descr2 Second image descriptor.
/// \return Distance between image descriptors.
///
float compute(const cv::Mat &descr1, const cv::Mat &descr2) override;
///
/// \brief Computes distances between two descriptors in batches.
/// \param[in] descrs1 Batch of first descriptors.
/// \param[in] descrs2 Batch of second descriptors.
/// \return Distances between descriptors.
///
std::vector<float> compute(const std::vector<cv::Mat> &descrs1,
const std::vector<cv::Mat> &descrs2) override;
virtual ~MatchTemplateDistance() {}
private:
int type_; ///< Method of MatchTemplate function computation.
float scale_; ///< Scale parameter for the distance. Final distance is
/// computed as: scale * distance + offset.
float offset_; ///< Offset parameter for the distance. Final distance is
/// computed as: scale * distance + offset.
};
///
/// \brief The TrackerParams struct stores parameters of TrackerByMatching
///
struct CV_EXPORTS TrackerParams {
size_t min_track_duration; ///< Min track duration in milliseconds.
size_t forget_delay; ///< Forget about track if the last bounding box in
/// track was detected more than specified number of
/// frames ago.
float aff_thr_fast; ///< Affinity threshold which is used to determine if
/// tracklet and detection should be combined (fast
/// descriptor is used).
float aff_thr_strong; ///< Affinity threshold which is used to determine if
/// tracklet and detection should be combined(strong
/// descriptor is used).
float shape_affinity_w; ///< Shape affinity weight.
float motion_affinity_w; ///< Motion affinity weight.
float time_affinity_w; ///< Time affinity weight.
float min_det_conf; ///< Min confidence of detection.
cv::Vec2f bbox_aspect_ratios_range; ///< Bounding box aspect ratios range.
cv::Vec2f bbox_heights_range; ///< Bounding box heights range.
int predict; ///< How many frames are used to predict bounding box in case
/// of lost track.
float strong_affinity_thr; ///< If 'fast' confidence is greater than this
/// threshold then 'strong' Re-ID approach is
/// used.
float reid_thr; ///< Affinity threshold for re-identification.
bool drop_forgotten_tracks; ///< Drop forgotten tracks. If it's enabled it
/// disables an ability to get detection log.
int max_num_objects_in_track; ///< The number of objects in track is
/// restricted by this parameter. If it is negative or zero, the max number of
/// objects in track is not restricted.
///
/// Default constructor.
///
TrackerParams();
};
///
/// \brief The Track class describes tracks.
///
class CV_EXPORTS Track {
public:
///
/// \brief Track constructor.
/// \param objs Detected objects sequence.
/// \param last_image Image of last image in the detected object sequence.
/// \param descriptor_fast Fast descriptor.
/// \param descriptor_strong Strong descriptor (reid embedding).
///
Track(const TrackedObjects &objs, const cv::Mat &last_image,
const cv::Mat &descriptor_fast, const cv::Mat &descriptor_strong)
: objects(objs),
predicted_rect(!objs.empty() ? objs.back().rect : cv::Rect()),
last_image(last_image),
descriptor_fast(descriptor_fast),
descriptor_strong(descriptor_strong),
lost(0),
length(1) {
CV_Assert(!objs.empty());
first_object = objs[0];
}
///
/// \brief empty returns if track does not contain objects.
/// \return true if track does not contain objects.
///
bool empty() const { return objects.empty(); }
///
/// \brief size returns number of detected objects in a track.
/// \return number of detected objects in a track.
///
size_t size() const { return objects.size(); }
///
/// \brief operator [] return const reference to detected object with
/// specified index.
/// \param i Index of object.
/// \return const reference to detected object with specified index.
///
const TrackedObject &operator[](size_t i) const { return objects[i]; }
///
/// \brief operator [] return non-const reference to detected object with
/// specified index.
/// \param i Index of object.
/// \return non-const reference to detected object with specified index.
///
TrackedObject &operator[](size_t i) { return objects[i]; }
///
/// \brief back returns const reference to last object in track.
/// \return const reference to last object in track.
///
const TrackedObject &back() const {
CV_Assert(!empty());
return objects.back();
}
///
/// \brief back returns non-const reference to last object in track.
/// \return non-const reference to last object in track.
///
TrackedObject &back() {
CV_Assert(!empty());
return objects.back();
}
TrackedObjects objects; ///< Detected objects;
cv::Rect predicted_rect; ///< Rectangle that represents predicted position
/// and size of bounding box if track has been lost.
cv::Mat last_image; ///< Image of last detected object in track.
cv::Mat descriptor_fast; ///< Fast descriptor.
cv::Mat descriptor_strong; ///< Strong descriptor (reid embedding).
size_t lost; ///< How many frames ago track has been lost.
TrackedObject first_object; ///< First object in track.
size_t length; ///< Length of a track including number of objects that were
/// removed from track in order to avoid memory usage growth.
};
///
/// \brief Tracker-by-Matching algorithm interface.
///
/// This class is implementation of tracking-by-matching system. It uses two
/// different appearance measures to compute affinity between bounding boxes:
/// some fast descriptor and some strong descriptor. Each time the assignment
/// problem is solved. The assignment problem in our case is how to establish
/// correspondence between existing tracklets and recently detected objects.
/// First step is to compute an affinity matrix between tracklets and
/// detections. The affinity equals to
/// appearance_affinity * motion_affinity * shape_affinity.
/// Where appearance is 1 - distance(tracklet_fast_dscr, detection_fast_dscr).
/// Second step is to solve the assignment problem using Kuhn-Munkres
/// algorithm. If correspondence between some tracklet and detection is
/// established with low confidence (affinity) then the strong descriptor is
/// used to determine if there is correspondence between tracklet and detection.
///
class CV_EXPORTS ITrackerByMatching {
public:
using Descriptor = std::shared_ptr<IImageDescriptor>;
using Distance = std::shared_ptr<IDescriptorDistance>;
///
/// \brief Destructor for the tracker
///
virtual ~ITrackerByMatching() {}
///
/// \brief Process given frame.
/// \param[in] frame Colored image (CV_8UC3).
/// \param[in] detections Detected objects on the frame.
/// \param[in] timestamp Timestamp must be positive and measured in
/// milliseconds
///
virtual void process(const cv::Mat &frame, const TrackedObjects &detections,
uint64_t timestamp) = 0;
///
/// \brief Pipeline parameters getter.
/// \return Parameters of pipeline.
///
virtual const TrackerParams &params() const = 0;
///
/// \brief Pipeline parameters setter.
/// \param[in] params Parameters of pipeline.
///
virtual void setParams(const TrackerParams &params) = 0;
///
/// \brief Fast descriptor getter.
/// \return Fast descriptor used in pipeline.
///
virtual const Descriptor &descriptorFast() const = 0;
///
/// \brief Fast descriptor setter.
/// \param[in] val Fast descriptor used in pipeline.
///
virtual void setDescriptorFast(const Descriptor &val) = 0;
///
/// \brief Strong descriptor getter.
/// \return Strong descriptor used in pipeline.
///
virtual const Descriptor &descriptorStrong() const = 0;
///
/// \brief Strong descriptor setter.
/// \param[in] val Strong descriptor used in pipeline.
///
virtual void setDescriptorStrong(const Descriptor &val) = 0;
///
/// \brief Fast distance getter.
/// \return Fast distance used in pipeline.
///
virtual const Distance &distanceFast() const = 0;
///
/// \brief Fast distance setter.
/// \param[in] val Fast distance used in pipeline.
///
virtual void setDistanceFast(const Distance &val) = 0;
///
/// \brief Strong distance getter.
/// \return Strong distance used in pipeline.
///
virtual const Distance &distanceStrong() const = 0;
///
/// \brief Strong distance setter.
/// \param[in] val Strong distance used in pipeline.
///
virtual void setDistanceStrong(const Distance &val) = 0;
///
/// \brief Returns number of counted people.
/// \return a number of counted people.
///
virtual size_t count() const = 0;
///
/// \brief Get active tracks to draw
/// \return Active tracks.
///
virtual std::unordered_map<size_t, std::vector<cv::Point> > getActiveTracks() const = 0;
///
/// \brief Get tracked detections.
/// \return Tracked detections.
///
virtual TrackedObjects trackedDetections() const = 0;
///
/// \brief Draws active tracks on a given frame.
/// \param[in] frame Colored image (CV_8UC3).
/// \return Colored image with drawn active tracks.
///
virtual cv::Mat drawActiveTracks(const cv::Mat &frame) = 0;
///
/// \brief isTrackForgotten returns true if track is forgotten.
/// \param id Track ID.
/// \return true if track is forgotten.
///
virtual bool isTrackForgotten(size_t id) const = 0;
///
/// \brief tracks Returns all tracks including forgotten (lost too many frames
/// ago).
/// \return Set of tracks {id, track}.
///
virtual const std::unordered_map<size_t, Track> &tracks() const = 0;
///
/// \brief isTrackValid Checks whether track is valid (duration > threshold).
/// \param track_id Index of checked track.
/// \return True if track duration exceeds some predefined value.
///
virtual bool isTrackValid(size_t track_id) const = 0;
///
/// \brief dropForgottenTracks Removes tracks from memory that were lost too
/// many frames ago.
///
virtual void dropForgottenTracks() = 0;
///
/// \brief dropForgottenTrack Check that the track was lost too many frames
/// ago
/// and removes it frm memory.
///
virtual void dropForgottenTrack(size_t track_id) = 0;
};
///
/// \brief The factory to create Tracker-by-Matching algorithm implementation.
///
CV_EXPORTS cv::Ptr<ITrackerByMatching> createTrackerByMatching(const TrackerParams &params = TrackerParams());
} // namespace tbm
} // namespace cv
#endif // #ifndef __OPENCV_TRACKING_TRACKING_BY_MATCHING_HPP__
#include <opencv2/core.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/tracking/tracking_by_matching.hpp>
#include <iostream>
#ifdef HAVE_OPENCV_DNN
#include <opencv2/dnn.hpp>
using namespace std;
using namespace cv;
using namespace cv::tbm;
static const char* keys =
{ "{video_name | | video name }"
"{start_frame |0| Start frame }"
"{frame_step |1| Frame step }"
"{detector_model | | Path to detector's Caffe model }"
"{detector_weights | | Path to detector's Caffe weights }"
"{desired_class_id |-1| The desired class that should be tracked }"
};
static void help()
{
cout << "\nThis example shows the functionality of \"Tracking-by-Matching\" approach:"
" detector is used to detect objects on frames, \n"
"matching is used to find correspondences between new detections and tracked objects.\n"
"Detection is made by DNN detection network every `--frame_step` frame.\n"
"Point a .prototxt file of the network as the parameter `--detector_model`, and a .caffemodel file"
" as the parameter `--detector_weights`.\n"
"(As an example of such detection network is a popular MobileNet_SSD network trained on VOC dataset.)\n"
"If `--desired_class_id` parameter is set, the detection result is filtered by class id,"
" returned by the detection network.\n"
"(That is, if a detection net was trained on VOC dataset, then to track pedestrians point --desired_class_id=15)\n"
"Example of <video_name> is in opencv_extra/testdata/cv/tracking/\n"
"Call:\n"
"./example_tracking_tracking_by_matching --video_name=<video_name> --detector_model=<detector_model_path> --detector_weights=<detector_weights_path> \\\n"
" [--start_frame=<start_frame>] \\\n"
" [--frame_step=<frame_step>] \\\n"
" [--desired_class_id=<desired_class_id>]\n"
<< endl;
cout << "\n\nHot keys: \n"
"\tq - quit the program\n"
"\tp - pause/resume video\n";
}
cv::Ptr<ITrackerByMatching> createTrackerByMatchingWithFastDescriptor();
class DnnObjectDetector
{
public:
DnnObjectDetector(const String& net_caffe_model_path, const String& net_caffe_weights_path,
int desired_class_id=-1,
float confidence_threshold = 0.2,
//the following parameters are default for popular MobileNet_SSD caffe model
const String& net_input_name="data",
const String& net_output_name="detection_out",
double net_scalefactor=0.007843,
const Size& net_size = Size(300,300),
const Scalar& net_mean = Scalar(127.5, 127.5, 127.5),
bool net_swapRB=false)
:desired_class_id(desired_class_id),
confidence_threshold(confidence_threshold),
net_input_name(net_input_name),
net_output_name(net_output_name),
net_scalefactor(net_scalefactor),
net_size(net_size),
net_mean(net_mean),
net_swapRB(net_swapRB)
{
net = dnn::readNetFromCaffe(net_caffe_model_path, net_caffe_weights_path);
if (net.empty())
CV_Error(Error::StsError, "Cannot read Caffe net");
}
TrackedObjects detect(const cv::Mat& frame, int frame_idx)
{
Mat resized_frame;
resize(frame, resized_frame, net_size);
Mat inputBlob = cv::dnn::blobFromImage(resized_frame, net_scalefactor, net_size, net_mean, net_swapRB);
net.setInput(inputBlob, net_input_name);
Mat detection = net.forward(net_output_name);
Mat detection_as_mat(detection.size[2], detection.size[3], CV_32F, detection.ptr<float>());
TrackedObjects res;
for (int i = 0; i < detection_as_mat.rows; i++)
{
float cur_confidence = detection_as_mat.at<float>(i, 2);
int cur_class_id = static_cast<int>(detection_as_mat.at<float>(i, 1));
int x_left = static_cast<int>(detection_as_mat.at<float>(i, 3) * frame.cols);
int y_bottom = static_cast<int>(detection_as_mat.at<float>(i, 4) * frame.rows);
int x_right = static_cast<int>(detection_as_mat.at<float>(i, 5) * frame.cols);
int y_top = static_cast<int>(detection_as_mat.at<float>(i, 6) * frame.rows);
Rect cur_rect(x_left, y_bottom, (x_right - x_left), (y_top - y_bottom));
if (cur_confidence < confidence_threshold)
continue;
if ((desired_class_id >= 0) && (cur_class_id != desired_class_id))
continue;
//clipping by frame size
cur_rect = cur_rect & Rect(Point(), frame.size());
if (cur_rect.empty())
continue;
TrackedObject cur_obj(cur_rect, cur_confidence, frame_idx, -1);
res.push_back(cur_obj);
}
return res;
}
private:
cv::dnn::Net net;
int desired_class_id;
float confidence_threshold;
String net_input_name;
String net_output_name;
double net_scalefactor;
Size net_size;
Scalar net_mean;
bool net_swapRB;
};
cv::Ptr<ITrackerByMatching>
createTrackerByMatchingWithFastDescriptor() {
cv::tbm::TrackerParams params;
cv::Ptr<ITrackerByMatching> tracker = createTrackerByMatching(params);
std::shared_ptr<IImageDescriptor> descriptor_fast =
std::make_shared<ResizedImageDescriptor>(
cv::Size(16, 32), cv::InterpolationFlags::INTER_LINEAR);
std::shared_ptr<IDescriptorDistance> distance_fast =
std::make_shared<MatchTemplateDistance>();
tracker->setDescriptorFast(descriptor_fast);
tracker->setDistanceFast(distance_fast);
return tracker;
}
int main( int argc, char** argv ){
CommandLineParser parser( argc, argv, keys );
cv::Ptr<ITrackerByMatching> tracker = createTrackerByMatchingWithFastDescriptor();
String video_name = parser.get<String>("video_name");
int start_frame = parser.get<int>("start_frame");
int frame_step = parser.get<int>("frame_step");
String detector_model = parser.get<String>("detector_model");
String detector_weights = parser.get<String>("detector_weights");
int desired_class_id = parser.get<int>("desired_class_id");
if( video_name.empty() || detector_model.empty() || detector_weights.empty() )
{
help();
return -1;
}
//open the capture
VideoCapture cap;
cap.open( video_name );
cap.set( CAP_PROP_POS_FRAMES, start_frame );
if( !cap.isOpened() )
{
help();
cout << "***Could not initialize capturing...***\n";
cout << "Current parameter's value: \n";
parser.printMessage();
return -1;
}
// If you use the popular MobileNet_SSD detector, the default parameters may be used.
// Otherwise, set your own parameters (net_mean, net_scalefactor, etc).
DnnObjectDetector detector(detector_model, detector_weights, desired_class_id);
Mat frame;
namedWindow( "Tracking by Matching", 1 );
int frame_counter = -1;
int64 time_total = 0;
bool paused = false;
for ( ;; )
{
if( paused )
{
char c = (char) waitKey(30);
if (c == 'p')
paused = !paused;
if (c == 'q')
break;
continue;
}
cap >> frame;
if(frame.empty()){
break;
}
frame_counter++;
if (frame_counter < start_frame)
continue;
if (frame_counter % frame_step != 0)
continue;
int64 frame_time = getTickCount();
TrackedObjects detections = detector.detect(frame, frame_counter);
// timestamp in milliseconds
uint64_t cur_timestamp = static_cast<uint64_t>(1000.0 / 30 * frame_counter);
tracker->process(frame, detections, cur_timestamp);
frame_time = getTickCount() - frame_time;
time_total += frame_time;
// Drawing colored "worms" (tracks).
frame = tracker->drawActiveTracks(frame);
// Drawing all detected objects on a frame by BLUE COLOR
for (const auto &detection : detections) {
cv::rectangle(frame, detection.rect, cv::Scalar(255, 0, 0), 3);
}
// Drawing tracked detections only by RED color and print ID and detection
// confidence level.
for (const auto &detection : tracker->trackedDetections()) {
cv::rectangle(frame, detection.rect, cv::Scalar(0, 0, 255), 3);
std::string text = std::to_string(detection.object_id) +
" conf: " + std::to_string(detection.confidence);
cv::putText(frame, text, detection.rect.tl(), cv::FONT_HERSHEY_COMPLEX,
1.0, cv::Scalar(0, 0, 255), 3);
}
imshow( "Tracking by Matching", frame );
char c = (char) waitKey( 2 );
if (c == 'q')
break;
if (c == 'p')
paused = !paused;
}
double s = frame_counter / (time_total / getTickFrequency());
printf("FPS: %f\n", s);
return 0;
}
#else // #ifdef HAVE_OPENCV_DNN
int main(int, char**){
CV_Error(cv::Error::StsNotImplemented, "At the moment the sample 'tracking_by_matching' can work only when opencv_dnn module is built.");
}
#endif // #ifdef HAVE_OPENCV_DNN
#include "kuhn_munkres.hpp"
#include <algorithm>
#include <limits>
#include <vector>
KuhnMunkres::KuhnMunkres() : n_() {}
std::vector<size_t> KuhnMunkres::Solve(const cv::Mat& dissimilarity_matrix) {
CV_Assert(dissimilarity_matrix.type() == CV_32F);
double min_val;
cv::minMaxLoc(dissimilarity_matrix, &min_val);
CV_Assert(min_val >= 0);
n_ = std::max(dissimilarity_matrix.rows, dissimilarity_matrix.cols);
dm_ = cv::Mat(n_, n_, CV_32F, cv::Scalar(0));
marked_ = cv::Mat(n_, n_, CV_8S, cv::Scalar(0));
points_ = std::vector<cv::Point>(n_ * 2);
dissimilarity_matrix.copyTo(dm_(
cv::Rect(0, 0, dissimilarity_matrix.cols, dissimilarity_matrix.rows)));
is_row_visited_ = std::vector<int>(n_, 0);
is_col_visited_ = std::vector<int>(n_, 0);
Run();
std::vector<size_t> results(static_cast<size_t>(marked_.rows), static_cast<size_t>(-1));
for (int i = 0; i < marked_.rows; i++) {
const auto ptr = marked_.ptr<char>(i);
for (int j = 0; j < marked_.cols; j++) {
if (ptr[j] == kStar) {
results[i] = j;
}
}
}
return results;
}
void KuhnMunkres::TrySimpleCase() {
auto is_row_visited = std::vector<int>(n_, 0);
auto is_col_visited = std::vector<int>(n_, 0);
for (int row = 0; row < n_; row++) {
auto ptr = dm_.ptr<float>(row);
auto marked_ptr = marked_.ptr<char>(row);
auto min_val = *std::min_element(ptr, ptr + n_);
for (int col = 0; col < n_; col++) {
ptr[col] -= min_val;
if (ptr[col] == 0 && !is_col_visited[col] && !is_row_visited[row]) {
marked_ptr[col] = kStar;
is_col_visited[col] = 1;
is_row_visited[row] = 1;
}
}
}
}
bool KuhnMunkres::CheckIfOptimumIsFound() {
int count = 0;
for (int i = 0; i < n_; i++) {
const auto marked_ptr = marked_.ptr<char>(i);
for (int j = 0; j < n_; j++) {
if (marked_ptr[j] == kStar) {
is_col_visited_[j] = 1;
count++;
}
}
}
return count >= n_;
}
cv::Point KuhnMunkres::FindUncoveredMinValPos() {
auto min_val = std::numeric_limits<float>::max();
cv::Point min_val_pos(-1, -1);
for (int i = 0; i < n_; i++) {
if (!is_row_visited_[i]) {
auto dm_ptr = dm_.ptr<float>(i);
for (int j = 0; j < n_; j++) {
if (!is_col_visited_[j] && dm_ptr[j] < min_val) {
min_val = dm_ptr[j];
min_val_pos = cv::Point(j, i);
}
}
}
}
return min_val_pos;
}
void KuhnMunkres::UpdateDissimilarityMatrix(float val) {
for (int i = 0; i < n_; i++) {
auto dm_ptr = dm_.ptr<float>(i);
for (int j = 0; j < n_; j++) {
if (is_row_visited_[i]) dm_ptr[j] += val;
if (!is_col_visited_[j]) dm_ptr[j] -= val;
}
}
}
int KuhnMunkres::FindInRow(int row, int what) {
for (int j = 0; j < n_; j++) {
if (marked_.at<char>(row, j) == what) {
return j;
}
}
return -1;
}
int KuhnMunkres::FindInCol(int col, int what) {
for (int i = 0; i < n_; i++) {
if (marked_.at<char>(i, col) == what) {
return i;
}
}
return -1;
}
void KuhnMunkres::Run() {
TrySimpleCase();
while (!CheckIfOptimumIsFound()) {
while (true) {
auto point = FindUncoveredMinValPos();
auto min_val = dm_.at<float>(point.y, point.x);
if (min_val > 0) {
UpdateDissimilarityMatrix(min_val);
} else {
marked_.at<char>(point.y, point.x) = kPrime;
int col = FindInRow(point.y, kStar);
if (col >= 0) {
is_row_visited_[point.y] = 1;
is_col_visited_[col] = 0;
} else {
int count = 0;
points_[count] = point;
while (true) {
int row = FindInCol(points_[count].x, kStar);
if (row >= 0) {
count++;
points_[count] = cv::Point(points_[count - 1].x, row);
int col1 = FindInRow(points_[count].y, kPrime);
count++;
points_[count] = cv::Point(col1, points_[count - 1].y);
} else {
break;
}
}
for (int i = 0; i < count + 1; i++) {
auto& mark = marked_.at<char>(points_[i].y, points_[i].x);
mark = mark == kStar ? 0 : kStar;
}
is_row_visited_ = std::vector<int>(n_, 0);
is_col_visited_ = std::vector<int>(n_, 0);
marked_.setTo(0, marked_ == kPrime);
break;
}
}
}
}
}
#ifndef __OPENCV_TRACKING_KUHN_MUNKRES_HPP__
#define __OPENCV_TRACKING_KUHN_MUNKRES_HPP__
#include "opencv2/core.hpp"
#include <memory>
#include <vector>
///
/// \brief The KuhnMunkres class
///
/// Solves the assignment problem.
///
class KuhnMunkres {
public:
KuhnMunkres();
///
/// \brief Solves the assignment problem for given dissimilarity matrix.
/// It returns a vector that where each element is a column index for
/// corresponding row (e.g. result[0] stores optimal column index for very
/// first row in the dissimilarity matrix).
/// \param dissimilarity_matrix CV_32F dissimilarity matrix.
/// \return Optimal column index for each row. -1 means that there is no
/// column for row.
///
std::vector<size_t> Solve(const cv::Mat &dissimilarity_matrix);
private:
static constexpr int kStar = 1;
static constexpr int kPrime = 2;
cv::Mat dm_;
cv::Mat marked_;
std::vector<cv::Point> points_;
std::vector<int> is_row_visited_;
std::vector<int> is_col_visited_;
int n_;
void TrySimpleCase();
bool CheckIfOptimumIsFound();
cv::Point FindUncoveredMinValPos();
void UpdateDissimilarityMatrix(float val);
int FindInRow(int row, int what);
int FindInCol(int col, int what);
void Run();
};
#endif // #ifndef __OPENCV_TRACKING_KUHN_MUNKRES_HPP__
#include <map>
#include <set>
#include <string>
#include <tuple>
#include <unordered_map>
#include <vector>
#include <utility>
#include <limits>
#include <algorithm>
#include "opencv2/tracking/tracking_by_matching.hpp"
#include "opencv2/core/check.hpp"
#include "kuhn_munkres.hpp"
#define TBM_CHECK(cond) CV_Assert(cond)
#define TBM_CHECK_EQ(actual, expected) CV_CheckEQ(actual, expected, "Assertion error:")
#define TBM_CHECK_NE(actual, expected) CV_CheckNE(actual, expected, "Assertion error:")
#define TBM_CHECK_LT(actual, expected) CV_CheckLT(actual, expected, "Assertion error:")
#define TBM_CHECK_GT(actual, expected) CV_CheckGT(actual, expected, "Assertion error:")
#define TBM_CHECK_LE(actual, expected) CV_CheckLE(actual, expected, "Assertion error:")
#define TBM_CHECK_GE(actual, expected) CV_CheckGE(actual, expected, "Assertion error:")
using namespace cv::tbm;
CosDistance::CosDistance(const cv::Size &descriptor_size)
: descriptor_size_(descriptor_size) {
TBM_CHECK(descriptor_size.area() != 0);
}
float CosDistance::compute(const cv::Mat &descr1, const cv::Mat &descr2) {
TBM_CHECK(!descr1.empty());
TBM_CHECK(!descr2.empty());
TBM_CHECK(descr1.size() == descriptor_size_);
TBM_CHECK(descr2.size() == descriptor_size_);
double xy = descr1.dot(descr2);
double xx = descr1.dot(descr1);
double yy = descr2.dot(descr2);
double norm = sqrt(xx * yy) + 1e-6;
return 0.5f * static_cast<float>(1.0 - xy / norm);
}
std::vector<float> CosDistance::compute(const std::vector<cv::Mat> &descrs1,
const std::vector<cv::Mat> &descrs2) {
TBM_CHECK(descrs1.size() != 0);
TBM_CHECK(descrs1.size() == descrs2.size());
std::vector<float> distances(descrs1.size(), 1.f);
for (size_t i = 0; i < descrs1.size(); i++) {
distances.at(i) = compute(descrs1.at(i), descrs2.at(i));
}
return distances;
}
float MatchTemplateDistance::compute(const cv::Mat &descr1,
const cv::Mat &descr2) {
TBM_CHECK(!descr1.empty() && !descr2.empty());
TBM_CHECK_EQ(descr1.size(), descr2.size());
TBM_CHECK_EQ(descr1.type(), descr2.type());
cv::Mat res;
cv::matchTemplate(descr1, descr2, res, type_);
TBM_CHECK(res.size() == cv::Size(1, 1));
float dist = res.at<float>(0, 0);
return scale_ * dist + offset_;
}
std::vector<float> MatchTemplateDistance::compute(const std::vector<cv::Mat> &descrs1,
const std::vector<cv::Mat> &descrs2) {
std::vector<float> result;
for (size_t i = 0; i < descrs1.size(); i++) {
result.push_back(compute(descrs1[i], descrs2[i]));
}
return result;
}
namespace {
cv::Point Center(const cv::Rect& rect) {
return cv::Point((int)(rect.x + rect.width * .5), (int)(rect.y + rect.height * .5));
}
std::vector<cv::Point> Centers(const TrackedObjects &detections) {
std::vector<cv::Point> centers(detections.size());
for (size_t i = 0; i < detections.size(); i++) {
centers[i] = Center(detections[i].rect);
}
return centers;
}
inline bool IsInRange(float val, float min, float max) {
return min <= val && val <= max;
}
inline bool IsInRange(float val, cv::Vec2f range) {
return IsInRange(val, range[0], range[1]);
}
std::vector<cv::Scalar> GenRandomColors(int colors_num) {
std::vector<cv::Scalar> colors(colors_num);
for (int i = 0; i < colors_num; i++) {
colors[i] = cv::Scalar(static_cast<uchar>(255. * rand() / RAND_MAX), // NOLINT
static_cast<uchar>(255. * rand() / RAND_MAX), // NOLINT
static_cast<uchar>(255. * rand() / RAND_MAX)); // NOLINT
}
return colors;
}
///
/// \brief Draws a polyline on a frame.
/// \param[in] polyline Vector of points (polyline).
/// \param[in] color Color (BGR).
/// \param[in,out] image Frame.
/// \param[in] lwd Line width.
///
void DrawPolyline(const std::vector<cv::Point>& polyline,
const cv::Scalar& color, CV_OUT cv::Mat& image,
int lwd = 5) {
TBM_CHECK(!image.empty());
TBM_CHECK_EQ(image.type(), CV_8UC3);
TBM_CHECK_GT(lwd, 0);
TBM_CHECK_LT(lwd, 20);
for (size_t i = 1; i < polyline.size(); i++) {
cv::line(image, polyline[i - 1], polyline[i], color, lwd);
}
}
void ValidateParams(const TrackerParams &p) {
TBM_CHECK_GE(p.min_track_duration, static_cast<size_t>(500));
TBM_CHECK_LE(p.min_track_duration, static_cast<size_t>(10000));
TBM_CHECK_LE(p.forget_delay, static_cast<size_t>(10000));
TBM_CHECK_GE(p.aff_thr_fast, 0.0f);
TBM_CHECK_LE(p.aff_thr_fast, 1.0f);
TBM_CHECK_GE(p.aff_thr_strong, 0.0f);
TBM_CHECK_LE(p.aff_thr_strong, 1.0f);
TBM_CHECK_GE(p.shape_affinity_w, 0.0f);
TBM_CHECK_LE(p.shape_affinity_w, 100.0f);
TBM_CHECK_GE(p.motion_affinity_w, 0.0f);
TBM_CHECK_LE(p.motion_affinity_w, 100.0f);
TBM_CHECK_GE(p.time_affinity_w, 0.0f);
TBM_CHECK_LE(p.time_affinity_w, 100.0f);
TBM_CHECK_GE(p.min_det_conf, 0.0f);
TBM_CHECK_LE(p.min_det_conf, 1.0f);
TBM_CHECK_GE(p.bbox_aspect_ratios_range[0], 0.0f);
TBM_CHECK_LE(p.bbox_aspect_ratios_range[1], 10.0f);
TBM_CHECK_LT(p.bbox_aspect_ratios_range[0], p.bbox_aspect_ratios_range[1]);
TBM_CHECK_GE(p.bbox_heights_range[0], 10.0f);
TBM_CHECK_LE(p.bbox_heights_range[1], 1080.0f);
TBM_CHECK_LT(p.bbox_heights_range[0], p.bbox_heights_range[1]);
TBM_CHECK_GE(p.predict, 0);
TBM_CHECK_LE(p.predict, 10000);
TBM_CHECK_GE(p.strong_affinity_thr, 0.0f);
TBM_CHECK_LE(p.strong_affinity_thr, 1.0f);
TBM_CHECK_GE(p.reid_thr, 0.0f);
TBM_CHECK_LE(p.reid_thr, 1.0f);
if (p.max_num_objects_in_track > 0) {
int min_required_track_length = static_cast<int>(p.forget_delay);
TBM_CHECK_GE(p.max_num_objects_in_track, min_required_track_length);
TBM_CHECK_LE(p.max_num_objects_in_track, 10000);
}
}
} // anonymous namespace
///
/// \brief Tracker-by-Matching algorithm implementation.
///
/// This class is implementation of tracking-by-matching system. It uses two
/// different appearance measures to compute affinity between bounding boxes:
/// some fast descriptor and some strong descriptor. Each time the assignment
/// problem is solved. The assignment problem in our case is how to establish
/// correspondence between existing tracklets and recently detected objects.
/// First step is to compute an affinity matrix between tracklets and
/// detections. The affinity equals to
/// appearance_affinity * motion_affinity * shape_affinity.
/// Where appearance is 1 - distance(tracklet_fast_dscr, detection_fast_dscr).
/// Second step is to solve the assignment problem using Kuhn-Munkres
/// algorithm. If correspondence between some tracklet and detection is
/// established with low confidence (affinity) then the strong descriptor is
/// used to determine if there is correspondence between tracklet and detection.
///
class TrackerByMatching: public ITrackerByMatching {
public:
using Descriptor = std::shared_ptr<IImageDescriptor>;
using Distance = std::shared_ptr<IDescriptorDistance>;
///
/// \brief Constructor that creates an instance of the tracker with
/// parameters.
/// \param[in] params - the tracker parameters.
///
explicit TrackerByMatching(const TrackerParams &params = TrackerParams());
virtual ~TrackerByMatching() {}
///
/// \brief Process given frame.
/// \param[in] frame Colored image (CV_8UC3).
/// \param[in] detections Detected objects on the frame.
/// \param[in] timestamp Timestamp must be positive and measured in
/// milliseconds
///
void process(const cv::Mat &frame, const TrackedObjects &detections,
uint64_t timestamp) override;
///
/// \brief Pipeline parameters getter.
/// \return Parameters of pipeline.
///
const TrackerParams &params() const override;
///
/// \brief Pipeline parameters setter.
/// \param[in] params Parameters of pipeline.
///
void setParams(const TrackerParams &params) override;
///
/// \brief Fast descriptor getter.
/// \return Fast descriptor used in pipeline.
///
const Descriptor &descriptorFast() const override;
///
/// \brief Fast descriptor setter.
/// \param[in] val Fast descriptor used in pipeline.
///
void setDescriptorFast(const Descriptor &val) override;
///
/// \brief Strong descriptor getter.
/// \return Strong descriptor used in pipeline.
///
const Descriptor &descriptorStrong() const override;
///
/// \brief Strong descriptor setter.
/// \param[in] val Strong descriptor used in pipeline.
///
void setDescriptorStrong(const Descriptor &val) override;
///
/// \brief Fast distance getter.
/// \return Fast distance used in pipeline.
///
const Distance &distanceFast() const override;
///
/// \brief Fast distance setter.
/// \param[in] val Fast distance used in pipeline.
///
void setDistanceFast(const Distance &val) override;
///
/// \brief Strong distance getter.
/// \return Strong distance used in pipeline.
///
const Distance &distanceStrong() const override;
///
/// \brief Strong distance setter.
/// \param[in] val Strong distance used in pipeline.
///
void setDistanceStrong(const Distance &val) override;
///
/// \brief Returns number of counted people.
/// \return a number of counted people.
///
size_t count() const override;
///
/// \brief Get active tracks to draw
/// \return Active tracks.
///
std::unordered_map<size_t, std::vector<cv::Point> > getActiveTracks() const override;
///
/// \brief Get tracked detections.
/// \return Tracked detections.
///
TrackedObjects trackedDetections() const override;
///
/// \brief Draws active tracks on a given frame.
/// \param[in] frame Colored image (CV_8UC3).
/// \return Colored image with drawn active tracks.
///
cv::Mat drawActiveTracks(const cv::Mat &frame) override;
///
/// \brief Print confusion matrices of data association classifiers.
/// It works only in case of loaded detection logs instead of native
/// detectors.
///
void PrintConfusionMatrices() const;
///
/// \brief isTrackForgotten returns true if track is forgotten.
/// \param id Track ID.
/// \return true if track is forgotten.
///
bool isTrackForgotten(size_t id) const override;
///
/// \brief tracks Returns all tracks including forgotten (lost too many frames
/// ago).
/// \return Set of tracks {id, track}.
///
const std::unordered_map<size_t, Track> &tracks() const override;
///
/// \brief isTrackValid Checks whether track is valid (duration > threshold).
/// \param track_id Index of checked track.
/// \return True if track duration exceeds some predefined value.
///
bool isTrackValid(size_t track_id) const override;
///
/// \brief dropForgottenTracks Removes tracks from memory that were lost too
/// many frames ago.
///
void dropForgottenTracks() override;
///
/// \brief dropForgottenTrack Check that the track was lost too many frames
/// ago
/// and removes it frm memory.
///
void dropForgottenTrack(size_t track_id) override;
private:
struct Match {
int frame_idx1;
int frame_idx2;
cv::Rect rect1;
cv::Rect rect2;
cv::Rect pr_rect1;
bool pr_label;
bool gt_label;
Match() {}
Match(const TrackedObject &a, const cv::Rect &a_pr_rect,
const TrackedObject &b, bool pr_label)
: frame_idx1(a.frame_idx),
frame_idx2(b.frame_idx),
rect1(a.rect),
rect2(b.rect),
pr_rect1(a_pr_rect),
pr_label(pr_label),
gt_label(a.object_id == b.object_id) {
CV_Assert(frame_idx1 != frame_idx2);
}
};
const ObjectTracks all_tracks(bool valid_only) const;
// Returns shape affinity.
static float ShapeAffinity(float w, const cv::Rect &trk, const cv::Rect &det);
// Returns motion affinity.
static float MotionAffinity(float w, const cv::Rect &trk,
const cv::Rect &det);
// Returns time affinity.
static float TimeAffinity(float w, const float &trk, const float &det);
cv::Rect PredictRect(size_t id, size_t k, size_t s) const;
cv::Rect PredictRectSmoothed(size_t id, size_t k, size_t s) const;
cv::Rect PredictRectSimple(size_t id, size_t k, size_t s) const;
void SolveAssignmentProblem(
const std::set<size_t> &track_ids, const TrackedObjects &detections,
const std::vector<cv::Mat> &descriptors,
CV_OUT std::set<size_t>& unmatched_tracks,
CV_OUT std::set<size_t>& unmatched_detections,
CV_OUT std::set<std::tuple<size_t, size_t, float>>& matches);
void ComputeFastDesciptors(const cv::Mat &frame,
const TrackedObjects &detections,
CV_OUT std::vector<cv::Mat>& desriptors);
void ComputeDissimilarityMatrix(const std::set<size_t> &active_track_ids,
const TrackedObjects &detections,
const std::vector<cv::Mat> &fast_descriptors,
CV_OUT cv::Mat& dissimilarity_matrix);
std::vector<float> ComputeDistances(
const cv::Mat &frame,
const TrackedObjects& detections,
const std::vector<std::pair<size_t, size_t>> &track_and_det_ids,
CV_OUT std::map<size_t, cv::Mat>& det_id_to_descriptor);
std::map<size_t, std::pair<bool, cv::Mat>> StrongMatching(
const cv::Mat &frame,
const TrackedObjects& detections,
const std::vector<std::pair<size_t, size_t>> &track_and_det_ids);
std::vector<std::pair<size_t, size_t>> GetTrackToDetectionIds(
const std::set<std::tuple<size_t, size_t, float>> &matches);
float AffinityFast(const cv::Mat &descriptor1, const TrackedObject &obj1,
const cv::Mat &descriptor2, const TrackedObject &obj2);
float Affinity(const TrackedObject &obj1, const TrackedObject &obj2);
void AddNewTrack(const cv::Mat &frame, const TrackedObject &detection,
const cv::Mat &fast_descriptor,
const cv::Mat &descriptor_strong = cv::Mat());
void AddNewTracks(const cv::Mat &frame, const TrackedObjects &detections,
const std::vector<cv::Mat> &descriptors_fast);
void AddNewTracks(const cv::Mat &frame, const TrackedObjects &detections,
const std::vector<cv::Mat> &descriptors_fast,
const std::set<size_t> &ids);
void AppendToTrack(const cv::Mat &frame, size_t track_id,
const TrackedObject &detection,
const cv::Mat &descriptor_fast,
const cv::Mat &descriptor_strong);
bool EraseTrackIfBBoxIsOutOfFrame(size_t track_id);
bool EraseTrackIfItWasLostTooManyFramesAgo(size_t track_id);
bool UpdateLostTrackAndEraseIfItsNeeded(size_t track_id);
void UpdateLostTracks(const std::set<size_t> &track_ids);
static cv::Mat ConfusionMatrix(const std::vector<Match> &matches);
const std::set<size_t> &active_track_ids() const;
// Returns decisions made by heuristic based on fast distance/descriptor and
// shape, motion and time affinity.
const std::vector<Match> & base_classifier_matches() const;
// Returns decisions made by heuristic based on strong distance/descriptor
// and
// shape, motion and time affinity.
const std::vector<Match> &reid_based_classifier_matches() const;
// Returns decisions made by strong distance/descriptor affinity.
const std::vector<Match> &reid_classifier_matches() const;
TrackedObjects FilterDetections(const TrackedObjects &detections) const;
bool isTrackForgotten(const Track &track) const;
// Parameters of the pipeline.
TrackerParams params_;
// Indexes of active tracks.
std::set<size_t> active_track_ids_;
// Descriptor fast (base classifer).
Descriptor descriptor_fast_;
// Distance fast (base classifer).
Distance distance_fast_;
// Descriptor strong (reid classifier).
Descriptor descriptor_strong_;
// Distance strong (reid classifier).
Distance distance_strong_;
// All tracks.
std::unordered_map<size_t, Track> tracks_;
// Previous frame image.
cv::Size prev_frame_size_;
struct pair_hash {
std::size_t operator()(const std::pair<size_t, size_t> &p) const {
CV_Assert(p.first < 1e6 && p.second < 1e6);
return static_cast<size_t>(p.first * 1e6 + p.second);
}
};
// Distance between current active tracks.
std::unordered_map<std::pair<size_t, size_t>, float, pair_hash> tracks_dists_;
// Whether collect matches and compute confusion matrices for
// track-detection
// association task (base classifier, reid-based classifier,
// reid-classiifer).
bool collect_matches_;
// This vector contains decisions made by
// fast_apperance-motion-shape affinity model.
std::vector<Match> base_classifier_matches_;
// This vector contains decisions made by
// strong_apperance(cnn-reid)-motion-shape affinity model.
std::vector<Match> reid_based_classifier_matches_;
// This vector contains decisions made by
// strong_apperance(cnn-reid) affinity model only.
std::vector<Match> reid_classifier_matches_;
// Number of all current tracks.
size_t tracks_counter_;
// Number of dropped valid tracks.
size_t valid_tracks_counter_;
cv::Size frame_size_;
std::vector<cv::Scalar> colors_;
uint64_t prev_timestamp_;
};
cv::Ptr<ITrackerByMatching> cv::tbm::createTrackerByMatching(const TrackerParams &params)
{
ITrackerByMatching* ptr = new TrackerByMatching(params);
return cv::Ptr<ITrackerByMatching>(ptr);
}
TrackerParams::TrackerParams()
: min_track_duration(1000),
forget_delay(150),
aff_thr_fast(0.8f),
aff_thr_strong(0.75f),
shape_affinity_w(0.5f),
motion_affinity_w(0.2f),
time_affinity_w(0.0f),
min_det_conf(0.1f),
bbox_aspect_ratios_range(0.666f, 5.0f),
bbox_heights_range(40.f, 1000.f),
predict(25),
strong_affinity_thr(0.2805f),
reid_thr(0.61f),
drop_forgotten_tracks(true),
max_num_objects_in_track(300) {}
// Returns confusion matrix as:
// |tp fn|
// |fp tn|
cv::Mat TrackerByMatching::ConfusionMatrix(const std::vector<Match> &matches) {
const bool kNegative = false;
cv::Mat conf_mat(2, 2, CV_32F, cv::Scalar(0));
for (const auto &m : matches) {
conf_mat.at<float>(m.gt_label == kNegative, m.pr_label == kNegative)++;
}
return conf_mat;
}
TrackerByMatching::TrackerByMatching(const TrackerParams &params)
: params_(params),
descriptor_strong_(nullptr),
distance_strong_(nullptr),
collect_matches_(true),
tracks_counter_(0),
valid_tracks_counter_(0),
frame_size_(0, 0),
prev_timestamp_(std::numeric_limits<uint64_t>::max()) {
ValidateParams(params);
}
// Pipeline parameters getter.
const TrackerParams &TrackerByMatching::params() const { return params_; }
// Pipeline parameters setter.
void TrackerByMatching::setParams(const TrackerParams &params) {
ValidateParams(params);
params_ = params;
}
// Descriptor fast getter.
const TrackerByMatching::Descriptor &TrackerByMatching::descriptorFast() const {
return descriptor_fast_;
}
// Descriptor fast setter.
void TrackerByMatching::setDescriptorFast(const Descriptor &val) {
descriptor_fast_ = val;
}
// Descriptor strong getter.
const TrackerByMatching::Descriptor &TrackerByMatching::descriptorStrong() const {
return descriptor_strong_;
}
// Descriptor strong setter.
void TrackerByMatching::setDescriptorStrong(const Descriptor &val) {
descriptor_strong_ = val;
}
// Distance fast getter.
const TrackerByMatching::Distance &TrackerByMatching::distanceFast() const { return distance_fast_; }
// Distance fast setter.
void TrackerByMatching::setDistanceFast(const Distance &val) { distance_fast_ = val; }
// Distance strong getter.
const TrackerByMatching::Distance &TrackerByMatching::distanceStrong() const { return distance_strong_; }
// Distance strong setter.
void TrackerByMatching::setDistanceStrong(const Distance &val) { distance_strong_ = val; }
// Returns all tracks including forgotten (lost too many frames ago).
const std::unordered_map<size_t, Track> &
TrackerByMatching::tracks() const {
return tracks_;
}
// Returns indexes of active tracks only.
const std::set<size_t> &TrackerByMatching::active_track_ids() const {
return active_track_ids_;
}
// Returns decisions made by heuristic based on fast distance/descriptor and
// shape, motion and time affinity.
const std::vector<TrackerByMatching::Match> &
TrackerByMatching::base_classifier_matches() const {
return base_classifier_matches_;
}
// Returns decisions made by heuristic based on strong distance/descriptor
// and
// shape, motion and time affinity.
const std::vector<TrackerByMatching::Match> &TrackerByMatching::reid_based_classifier_matches() const {
return reid_based_classifier_matches_;
}
// Returns decisions made by strong distance/descriptor affinity.
const std::vector<TrackerByMatching::Match> &TrackerByMatching::reid_classifier_matches() const {
return reid_classifier_matches_;
}
TrackedObjects TrackerByMatching::FilterDetections(
const TrackedObjects &detections) const {
TrackedObjects filtered_detections;
for (const auto &det : detections) {
float aspect_ratio = static_cast<float>(det.rect.height) / det.rect.width;
if (det.confidence > params_.min_det_conf &&
IsInRange(aspect_ratio, params_.bbox_aspect_ratios_range) &&
IsInRange(static_cast<float>(det.rect.height), params_.bbox_heights_range)) {
filtered_detections.emplace_back(det);
}
}
return filtered_detections;
}
void TrackerByMatching::SolveAssignmentProblem(
const std::set<size_t> &track_ids, const TrackedObjects &detections,
const std::vector<cv::Mat> &descriptors,
std::set<size_t>& unmatched_tracks, std::set<size_t>& unmatched_detections,
std::set<std::tuple<size_t, size_t, float>>& matches) {
unmatched_tracks.clear();
unmatched_detections.clear();
TBM_CHECK(!track_ids.empty());
TBM_CHECK(!detections.empty());
TBM_CHECK(descriptors.size() == detections.size());
matches.clear();
cv::Mat dissimilarity;
ComputeDissimilarityMatrix(track_ids, detections, descriptors,
dissimilarity);
auto res = KuhnMunkres().Solve(dissimilarity);
for (size_t i = 0; i < detections.size(); i++) {
unmatched_detections.insert(i);
}
int i = 0;
for (auto id : track_ids) {
if (res[i] < detections.size()) {
matches.emplace(id, res[i], 1 - dissimilarity.at<float>(i, static_cast<int>(res[i])));
} else {
unmatched_tracks.insert(id);
}
i++;
}
}
const ObjectTracks TrackerByMatching::all_tracks(bool valid_only) const {
ObjectTracks all_objects;
int counter = 0;
std::set<size_t> sorted_ids;
for (const auto &pair : tracks()) {
sorted_ids.emplace(pair.first);
}
for (size_t id : sorted_ids) {
if (!valid_only || isTrackValid(id)) {
TrackedObjects filtered_objects;
for (const auto &object : tracks().at(id).objects) {
filtered_objects.emplace_back(object);
filtered_objects.back().object_id = counter;
}
all_objects.emplace(counter++, filtered_objects);
}
}
return all_objects;
}
cv::Rect TrackerByMatching::PredictRect(size_t id, size_t k,
size_t s) const {
const auto &track = tracks_.at(id);
TBM_CHECK(!track.empty());
if (track.size() == 1) {
return track[0].rect;
}
size_t start_i = track.size() > k ? track.size() - k : 0;
float width = 0, height = 0;
for (size_t i = start_i; i < track.size(); i++) {
width += track[i].rect.width;
height += track[i].rect.height;
}
TBM_CHECK(track.size() - start_i > 0);
width /= (track.size() - start_i);
height /= (track.size() - start_i);
float delim = 0;
cv::Point2f d(0, 0);
for (size_t i = start_i + 1; i < track.size(); i++) {
d += cv::Point2f(Center(track[i].rect) - Center(track[i - 1].rect));
delim += (track[i].frame_idx - track[i - 1].frame_idx);
}
if (delim) {
d /= delim;
}
s += 1;
cv::Point c = Center(track.back().rect);
return cv::Rect(static_cast<int>(c.x - width / 2 + d.x * s),
static_cast<int>(c.y - height / 2 + d.y * s),
static_cast<int>(width),
static_cast<int>(height));
}
bool TrackerByMatching::EraseTrackIfBBoxIsOutOfFrame(size_t track_id) {
if (tracks_.find(track_id) == tracks_.end()) return true;
auto c = Center(tracks_.at(track_id).predicted_rect);
if (!prev_frame_size_.empty() &&
(c.x < 0 || c.y < 0 || c.x > prev_frame_size_.width ||
c.y > prev_frame_size_.height)) {
tracks_.at(track_id).lost = params_.forget_delay + 1;
for (auto id : active_track_ids()) {
size_t min_id = std::min(id, track_id);
size_t max_id = std::max(id, track_id);
tracks_dists_.erase(std::pair<size_t, size_t>(min_id, max_id));
}
active_track_ids_.erase(track_id);
return true;
}
return false;
}
bool TrackerByMatching::EraseTrackIfItWasLostTooManyFramesAgo(
size_t track_id) {
if (tracks_.find(track_id) == tracks_.end()) return true;
if (tracks_.at(track_id).lost > params_.forget_delay) {
for (auto id : active_track_ids()) {
size_t min_id = std::min(id, track_id);
size_t max_id = std::max(id, track_id);
tracks_dists_.erase(std::pair<size_t, size_t>(min_id, max_id));
}
active_track_ids_.erase(track_id);
return true;
}
return false;
}
bool TrackerByMatching::UpdateLostTrackAndEraseIfItsNeeded(
size_t track_id) {
tracks_.at(track_id).lost++;
tracks_.at(track_id).predicted_rect =
PredictRect(track_id, params().predict, tracks_.at(track_id).lost);
bool erased = EraseTrackIfBBoxIsOutOfFrame(track_id);
if (!erased) erased = EraseTrackIfItWasLostTooManyFramesAgo(track_id);
return erased;
}
void TrackerByMatching::UpdateLostTracks(
const std::set<size_t> &track_ids) {
for (auto track_id : track_ids) {
UpdateLostTrackAndEraseIfItsNeeded(track_id);
}
}
void TrackerByMatching::process(const cv::Mat &frame,
const TrackedObjects &input_detections,
uint64_t timestamp) {
if (prev_timestamp_ != std::numeric_limits<uint64_t>::max())
TBM_CHECK_LT(static_cast<size_t>(prev_timestamp_), static_cast<size_t>(timestamp));
if (frame_size_ == cv::Size(0, 0)) {
frame_size_ = frame.size();
} else {
TBM_CHECK_EQ(frame_size_, frame.size());
}
TrackedObjects detections = FilterDetections(input_detections);
for (auto &obj : detections) {
obj.timestamp = timestamp;
}
std::vector<cv::Mat> descriptors_fast;
ComputeFastDesciptors(frame, detections, descriptors_fast);
auto active_tracks = active_track_ids_;
if (!active_tracks.empty() && !detections.empty()) {
std::set<size_t> unmatched_tracks, unmatched_detections;
std::set<std::tuple<size_t, size_t, float>> matches;
SolveAssignmentProblem(active_tracks, detections, descriptors_fast,
unmatched_tracks,
unmatched_detections, matches);
std::map<size_t, std::pair<bool, cv::Mat>> is_matching_to_track;
if (distance_strong_) {
std::vector<std::pair<size_t, size_t>> reid_track_and_det_ids =
GetTrackToDetectionIds(matches);
is_matching_to_track = StrongMatching(
frame, detections, reid_track_and_det_ids);
}
for (const auto &match : matches) {
size_t track_id = std::get<0>(match);
size_t det_id = std::get<1>(match);
float conf = std::get<2>(match);
auto last_det = tracks_.at(track_id).objects.back();
last_det.rect = tracks_.at(track_id).predicted_rect;
if (collect_matches_ && last_det.object_id >= 0 &&
detections[det_id].object_id >= 0) {
base_classifier_matches_.emplace_back(
tracks_.at(track_id).objects.back(), last_det.rect,
detections[det_id], conf > params_.aff_thr_fast);
}
if (conf > params_.aff_thr_fast) {
AppendToTrack(frame, track_id, detections[det_id],
descriptors_fast[det_id], cv::Mat());
unmatched_detections.erase(det_id);
} else {
if (conf > params_.strong_affinity_thr) {
if (distance_strong_ && is_matching_to_track[track_id].first) {
AppendToTrack(frame, track_id, detections[det_id],
descriptors_fast[det_id],
is_matching_to_track[track_id].second.clone());
} else {
if (UpdateLostTrackAndEraseIfItsNeeded(track_id)) {
AddNewTrack(frame, detections[det_id], descriptors_fast[det_id],
distance_strong_
? is_matching_to_track[track_id].second.clone()
: cv::Mat());
}
}
unmatched_detections.erase(det_id);
} else {
unmatched_tracks.insert(track_id);
}
}
}
AddNewTracks(frame, detections, descriptors_fast, unmatched_detections);
UpdateLostTracks(unmatched_tracks);
for (size_t id : active_tracks) {
EraseTrackIfBBoxIsOutOfFrame(id);
}
} else {
AddNewTracks(frame, detections, descriptors_fast);
UpdateLostTracks(active_tracks);
}
prev_frame_size_ = frame.size();
if (params_.drop_forgotten_tracks) dropForgottenTracks();
tracks_dists_.clear();
prev_timestamp_ = timestamp;
}
void TrackerByMatching::dropForgottenTracks() {
std::unordered_map<size_t, Track> new_tracks;
std::set<size_t> new_active_tracks;
size_t max_id = 0;
if (!active_track_ids_.empty())
max_id =
*std::max_element(active_track_ids_.begin(), active_track_ids_.end());
const size_t kMaxTrackID = 10000;
bool reassign_id = max_id > kMaxTrackID;
size_t counter = 0;
for (const auto &pair : tracks_) {
if (!isTrackForgotten(pair.first)) {
new_tracks.emplace(reassign_id ? counter : pair.first, pair.second);
new_active_tracks.emplace(reassign_id ? counter : pair.first);
counter++;
} else {
if (isTrackValid(pair.first)) {
valid_tracks_counter_++;
}
}
}
tracks_.swap(new_tracks);
active_track_ids_.swap(new_active_tracks);
tracks_counter_ = reassign_id ? counter : tracks_counter_;
}
void TrackerByMatching::dropForgottenTrack(size_t track_id) {
TBM_CHECK(isTrackForgotten(track_id));
TBM_CHECK(active_track_ids_.count(track_id) == 0);
tracks_.erase(track_id);
}
float TrackerByMatching::ShapeAffinity(float weight, const cv::Rect &trk,
const cv::Rect &det) {
float w_dist = static_cast<float>(std::fabs(trk.width - det.width) / (trk.width + det.width));
float h_dist = static_cast<float>(std::fabs(trk.height - det.height) / (trk.height + det.height));
return exp(-weight * (w_dist + h_dist));
}
float TrackerByMatching::MotionAffinity(float weight, const cv::Rect &trk,
const cv::Rect &det) {
float x_dist = static_cast<float>(trk.x - det.x) * (trk.x - det.x) /
(det.width * det.width);
float y_dist = static_cast<float>(trk.y - det.y) * (trk.y - det.y) /
(det.height * det.height);
return exp(-weight * (x_dist + y_dist));
}
float TrackerByMatching::TimeAffinity(float weight, const float &trk_time,
const float &det_time) {
return exp(-weight * std::fabs(trk_time - det_time));
}
void TrackerByMatching::ComputeFastDesciptors(
const cv::Mat &frame, const TrackedObjects &detections,
std::vector<cv::Mat>& desriptors) {
desriptors = std::vector<cv::Mat>(detections.size(), cv::Mat());
for (size_t i = 0; i < detections.size(); i++) {
descriptor_fast_->compute(frame(detections[i].rect).clone(),
desriptors[i]);
}
}
void TrackerByMatching::ComputeDissimilarityMatrix(
const std::set<size_t> &active_tracks, const TrackedObjects &detections,
const std::vector<cv::Mat> &descriptors_fast,
cv::Mat& dissimilarity_matrix) {
cv::Mat am(static_cast<int>(active_tracks.size()), static_cast<int>(detections.size()), CV_32F, cv::Scalar(0));
int i = 0;
for (auto id : active_tracks) {
auto ptr = am.ptr<float>(i);
for (size_t j = 0; j < descriptors_fast.size(); j++) {
auto last_det = tracks_.at(id).objects.back();
last_det.rect = tracks_.at(id).predicted_rect;
ptr[j] = AffinityFast(tracks_.at(id).descriptor_fast, last_det,
descriptors_fast[j], detections[j]);
}
i++;
}
dissimilarity_matrix = 1.0 - am;
}
std::vector<float> TrackerByMatching::ComputeDistances(
const cv::Mat &frame,
const TrackedObjects& detections,
const std::vector<std::pair<size_t, size_t>> &track_and_det_ids,
std::map<size_t, cv::Mat>& det_id_to_descriptor) {
std::map<size_t, size_t> det_to_batch_ids;
std::map<size_t, size_t> track_to_batch_ids;
std::vector<cv::Mat> images;
std::vector<cv::Mat> descriptors;
for (size_t i = 0; i < track_and_det_ids.size(); i++) {
size_t track_id = track_and_det_ids[i].first;
size_t det_id = track_and_det_ids[i].second;
if (tracks_.at(track_id).descriptor_strong.empty()) {
images.push_back(tracks_.at(track_id).last_image);
descriptors.push_back(cv::Mat());
track_to_batch_ids[track_id] = descriptors.size() - 1;
}
images.push_back(frame(detections[det_id].rect));
descriptors.push_back(cv::Mat());
det_to_batch_ids[det_id] = descriptors.size() - 1;
}
descriptor_strong_->compute(images, descriptors);
std::vector<cv::Mat> descriptors1;
std::vector<cv::Mat> descriptors2;
for (size_t i = 0; i < track_and_det_ids.size(); i++) {
size_t track_id = track_and_det_ids[i].first;
size_t det_id = track_and_det_ids[i].second;
if (tracks_.at(track_id).descriptor_strong.empty()) {
tracks_.at(track_id).descriptor_strong =
descriptors[track_to_batch_ids[track_id]].clone();
}
det_id_to_descriptor[det_id] = descriptors[det_to_batch_ids[det_id]];
descriptors1.push_back(descriptors[det_to_batch_ids[det_id]]);
descriptors2.push_back(tracks_.at(track_id).descriptor_strong);
}
std::vector<float> distances =
distance_strong_->compute(descriptors1, descriptors2);
return distances;
}
std::vector<std::pair<size_t, size_t>>
TrackerByMatching::GetTrackToDetectionIds(
const std::set<std::tuple<size_t, size_t, float>> &matches) {
std::vector<std::pair<size_t, size_t>> track_and_det_ids;
for (const auto &match : matches) {
size_t track_id = std::get<0>(match);
size_t det_id = std::get<1>(match);
float conf = std::get<2>(match);
if (conf < params_.aff_thr_fast && conf > params_.strong_affinity_thr) {
track_and_det_ids.emplace_back(track_id, det_id);
}
}
return track_and_det_ids;
}
std::map<size_t, std::pair<bool, cv::Mat>>
TrackerByMatching::StrongMatching(
const cv::Mat &frame,
const TrackedObjects& detections,
const std::vector<std::pair<size_t, size_t>> &track_and_det_ids) {
std::map<size_t, std::pair<bool, cv::Mat>> is_matching;
if (track_and_det_ids.size() == 0) {
return is_matching;
}
std::map<size_t, cv::Mat> det_ids_to_descriptors;
std::vector<float> distances =
ComputeDistances(frame, detections,
track_and_det_ids, det_ids_to_descriptors);
for (size_t i = 0; i < track_and_det_ids.size(); i++) {
auto reid_affinity = 1.0 - distances[i];
size_t track_id = track_and_det_ids[i].first;
size_t det_id = track_and_det_ids[i].second;
const auto& track = tracks_.at(track_id);
const auto& detection = detections[det_id];
auto last_det = track.objects.back();
last_det.rect = track.predicted_rect;
float affinity = static_cast<float>(reid_affinity * Affinity(last_det, detection));
if (collect_matches_ && last_det.object_id >= 0 &&
detection.object_id >= 0) {
reid_classifier_matches_.emplace_back(track.objects.back(), last_det.rect,
detection,
reid_affinity > params_.reid_thr);
reid_based_classifier_matches_.emplace_back(
track.objects.back(), last_det.rect, detection,
affinity > params_.aff_thr_strong);
}
bool is_detection_matching =
reid_affinity > params_.reid_thr && affinity > params_.aff_thr_strong;
is_matching[track_id] = std::pair<bool, cv::Mat>(
is_detection_matching, det_ids_to_descriptors[det_id]);
}
return is_matching;
}
void TrackerByMatching::AddNewTracks(
const cv::Mat &frame, const TrackedObjects &detections,
const std::vector<cv::Mat> &descriptors_fast) {
TBM_CHECK(detections.size() == descriptors_fast.size());
for (size_t i = 0; i < detections.size(); i++) {
AddNewTrack(frame, detections[i], descriptors_fast[i]);
}
}
void TrackerByMatching::AddNewTracks(
const cv::Mat &frame, const TrackedObjects &detections,
const std::vector<cv::Mat> &descriptors_fast, const std::set<size_t> &ids) {
TBM_CHECK(detections.size() == descriptors_fast.size());
for (size_t i : ids) {
TBM_CHECK(i < detections.size());
AddNewTrack(frame, detections[i], descriptors_fast[i]);
}
}
void TrackerByMatching::AddNewTrack(const cv::Mat &frame,
const TrackedObject &detection,
const cv::Mat &descriptor_fast,
const cv::Mat &descriptor_strong) {
auto detection_with_id = detection;
detection_with_id.object_id = static_cast<int>(tracks_counter_);
tracks_.emplace(std::pair<size_t, Track>(
tracks_counter_,
Track({detection_with_id}, frame(detection.rect).clone(),
descriptor_fast.clone(), descriptor_strong.clone())));
for (size_t id : active_track_ids_) {
tracks_dists_.emplace(std::pair<size_t, size_t>(id, tracks_counter_),
std::numeric_limits<float>::max());
}
active_track_ids_.insert(tracks_counter_);
tracks_counter_++;
}
void TrackerByMatching::AppendToTrack(const cv::Mat &frame,
size_t track_id,
const TrackedObject &detection,
const cv::Mat &descriptor_fast,
const cv::Mat &descriptor_strong) {
TBM_CHECK(!isTrackForgotten(track_id));
auto detection_with_id = detection;
detection_with_id.object_id = static_cast<int>(track_id);
auto &cur_track = tracks_.at(track_id);
cur_track.objects.emplace_back(detection_with_id);
cur_track.predicted_rect = detection.rect;
cur_track.lost = 0;
cur_track.last_image = frame(detection.rect).clone();
cur_track.descriptor_fast = descriptor_fast.clone();
cur_track.length++;
if (cur_track.descriptor_strong.empty()) {
cur_track.descriptor_strong = descriptor_strong.clone();
} else if (!descriptor_strong.empty()) {
cur_track.descriptor_strong =
0.5 * (descriptor_strong + cur_track.descriptor_strong);
}
if (params_.max_num_objects_in_track > 0) {
while (cur_track.size() >
static_cast<size_t>(params_.max_num_objects_in_track)) {
cur_track.objects.erase(cur_track.objects.begin());
}
}
}
float TrackerByMatching::AffinityFast(const cv::Mat &descriptor1,
const TrackedObject &obj1,
const cv::Mat &descriptor2,
const TrackedObject &obj2) {
const float eps = static_cast<float>(1e-6);
float shp_aff = ShapeAffinity(params_.shape_affinity_w, obj1.rect, obj2.rect);
if (shp_aff < eps) return 0.0;
float mot_aff =
MotionAffinity(params_.motion_affinity_w, obj1.rect, obj2.rect);
if (mot_aff < eps) return 0.0;
float time_aff =
TimeAffinity(params_.time_affinity_w, static_cast<float>(obj1.frame_idx), static_cast<float>(obj2.frame_idx));
if (time_aff < eps) return 0.0;
float app_aff = static_cast<float>(1.0 - distance_fast_->compute(descriptor1, descriptor2));
return shp_aff * mot_aff * app_aff * time_aff;
}
float TrackerByMatching::Affinity(const TrackedObject &obj1,
const TrackedObject &obj2) {
float shp_aff = ShapeAffinity(params_.shape_affinity_w, obj1.rect, obj2.rect);
float mot_aff =
MotionAffinity(params_.motion_affinity_w, obj1.rect, obj2.rect);
float time_aff =
TimeAffinity(params_.time_affinity_w, static_cast<float>(obj1.frame_idx), static_cast<float>(obj2.frame_idx));
return shp_aff * mot_aff * time_aff;
}
bool TrackerByMatching::isTrackValid(size_t id) const {
const auto& track = tracks_.at(id);
const auto &objects = track.objects;
if (objects.empty()) {
return false;
}
int64_t duration_ms = objects.back().timestamp - track.first_object.timestamp;
if (duration_ms < static_cast<int64_t>(params_.min_track_duration))
return false;
return true;
}
bool TrackerByMatching::isTrackForgotten(size_t id) const {
return isTrackForgotten(tracks_.at(id));
}
bool TrackerByMatching::isTrackForgotten(const Track &track) const {
return (track.lost > params_.forget_delay);
}
size_t TrackerByMatching::count() const {
size_t count = valid_tracks_counter_;
for (const auto &pair : tracks_) {
count += (isTrackValid(pair.first) ? 1 : 0);
}
return count;
}
std::unordered_map<size_t, std::vector<cv::Point>>
TrackerByMatching::getActiveTracks() const {
std::unordered_map<size_t, std::vector<cv::Point>> active_tracks;
for (size_t idx : active_track_ids()) {
auto track = tracks().at(idx);
if (isTrackValid(idx) && !isTrackForgotten(idx)) {
active_tracks.emplace(idx, Centers(track.objects));
}
}
return active_tracks;
}
TrackedObjects TrackerByMatching::trackedDetections() const {
TrackedObjects detections;
for (size_t idx : active_track_ids()) {
auto track = tracks().at(idx);
if (isTrackValid(idx) && !track.lost) {
detections.emplace_back(track.objects.back());
}
}
return detections;
}
cv::Mat TrackerByMatching::drawActiveTracks(const cv::Mat &frame) {
cv::Mat out_frame = frame.clone();
if (colors_.empty()) {
int num_colors = 100;
colors_ = GenRandomColors(num_colors);
}
auto active_tracks = getActiveTracks();
for (auto active_track : active_tracks) {
size_t idx = active_track.first;
auto centers = active_track.second;
DrawPolyline(centers, colors_[idx % colors_.size()], out_frame);
std::stringstream ss;
ss << idx;
cv::putText(out_frame, ss.str(), centers.back(), cv::FONT_HERSHEY_SCRIPT_COMPLEX, 2.0,
colors_[idx % colors_.size()], 3);
auto track = tracks().at(idx);
if (track.lost) {
cv::line(out_frame, active_track.second.back(),
Center(track.predicted_rect), cv::Scalar(0, 0, 0), 4);
}
}
return out_frame;
}
const cv::Size kMinFrameSize = cv::Size(320, 240);
const cv::Size kMaxFrameSize = cv::Size(1920, 1080);
void TrackerByMatching::PrintConfusionMatrices() const {
std::cout << "Base classifier quality: " << std::endl;
{
auto cm = ConfusionMatrix(base_classifier_matches());
std::cout << cm << std::endl;
std::cout << "or" << std::endl;
cm.row(0) = cm.row(0) / std::max(1.0, cv::sum(cm.row(0))[0]);
cm.row(1) = cm.row(1) / std::max(1.0, cv::sum(cm.row(1))[0]);
std::cout << cm << std::endl << std::endl;
}
std::cout << "Reid-based classifier quality: " << std::endl;
{
auto cm = ConfusionMatrix(reid_based_classifier_matches());
std::cout << cm << std::endl;
std::cout << "or" << std::endl;
cm.row(0) = cm.row(0) / std::max(1.0, cv::sum(cm.row(0))[0]);
cm.row(1) = cm.row(1) / std::max(1.0, cv::sum(cm.row(1))[0]);
std::cout << cm << std::endl << std::endl;
}
std::cout << "Reid only classifier quality: " << std::endl;
{
auto cm = ConfusionMatrix(reid_classifier_matches());
std::cout << cm << std::endl;
std::cout << "or" << std::endl;
cm.row(0) = cm.row(0) / std::max(1.0, cv::sum(cm.row(0))[0]);
cm.row(1) = cm.row(1) / std::max(1.0, cv::sum(cm.row(1))[0]);
std::cout << cm << std::endl << std::endl;
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment