目标跟踪(3)MultiTracker基于OpenCV(CPython)的多目标跟踪
在这篇文章中,我们将介绍如何使用通过 MultiTracker 类实现的 OpenCV 的多对象跟踪 API。我们将共享C++ 和 Python 代码。
1.为什么我们需要多目标跟踪
大多数计算机视觉和机器学习的初学者都学习对象检测。如果您是初学者,您可能会想为什么我们需要对象跟踪。我们不能只检测每一帧中的对象吗?
让我们来探究一下跟踪是有用的几个原因。
首先,当在视频帧中检测到多个对象(例如人)时,跟踪有助于跨帧建立对象的身份。
其次,在某些情况下,对象检测可能会失败,但仍可能跟踪对象,因为跟踪考虑了对象在前一帧中的位置和外观。
第三,一些跟踪算法非常快,因为它们做的是局部搜索,而不是全局搜索。因此,我们可以通过每n帧进行目标检测,并在中间帧中跟踪目标,从而为我们的系统获得很高的帧率。
那么,为什么不在第一次检测后无限期地跟踪对象呢?跟踪算法有时可能会丢失它正在跟踪的对象。例如,当对象的运动太大时,跟踪算法可能跟不上。许多现实世界的应用程序同时使用检测和跟踪。
在本教程中,我们只关注跟踪部分。我们想要跟踪的对象将通过拖动它们周围的包围框来指定。2.MultiTracker: OpenCV的多对象跟踪器
OpenCV 中的 MultiTracker 类提供了多目标跟踪的实现。它是一个简单的实现,因为它独立处理跟踪对象,而不对跟踪对象进行任何优化。
让我们逐步查看代码,了解如何使用 OpenCV 的多目标跟踪 API。
2.1 第 1 步:创建单一对象跟踪器
多目标跟踪器只是单目标跟踪器的集合。我们首先定义一个函数,该函数接受一个跟踪器类型作为输入,并创建一个跟踪器对象。OpenCV有8种不同的跟踪器类型:BOOSTING, MIL, KCF,TLD, MEDIANFLOW, GOTURN, MOSSE, CSRT。
如果您想使用 GOTURN 跟踪器,请务必阅读这篇文章并下载 caffe 模型。
在下面的代码中,给定跟踪器类的名称,我们返回跟踪器对象。这将在稍后用于多目标跟踪器。
Pythonfrom __future__ import print_function import sys import cv2 from random import randint trackerTypes = ["BOOSTING", "MIL", "KCF","TLD", "MEDIANFLOW", "GOTURN", "MOSSE", "CSRT"] def createTrackerByName(trackerType): # Create a tracker based on tracker name if trackerType == trackerTypes[0]: tracker = cv2.TrackerBoosting_create() elif trackerType == trackerTypes[1]: tracker = cv2.TrackerMIL_create() elif trackerType == trackerTypes[2]: tracker = cv2.TrackerKCF_create() elif trackerType == trackerTypes[3]: tracker = cv2.TrackerTLD_create() elif trackerType == trackerTypes[4]: tracker = cv2.TrackerMedianFlow_create() elif trackerType == trackerTypes[5]: tracker = cv2.TrackerGOTURN_create() elif trackerType == trackerTypes[6]: tracker = cv2.TrackerMOSSE_create() elif trackerType == trackerTypes[7]: tracker = cv2.TrackerCSRT_create() else: tracker = None print("Incorrect tracker name") print("Available trackers are:") for t in trackerTypes: print(t) return tracker
C++**注意:**除了包含opencv2/opencv.hpp,还需要包含opencv2/tracking.hpp。 #include #include using namespace cv; using namespace std; vector trackerTypes = {"BOOSTING", "MIL", "KCF", "TLD", "MEDIANFLOW", "GOTURN", "MOSSE", "CSRT"}; // create tracker by name Ptr createTrackerByName(string trackerType) { Ptr tracker; if (trackerType == trackerTypes[0]) tracker = TrackerBoosting::create(); else if (trackerType == trackerTypes[1]) tracker = TrackerMIL::create(); else if (trackerType == trackerTypes[2]) tracker = TrackerKCF::create(); else if (trackerType == trackerTypes[3]) tracker = TrackerTLD::create(); else if (trackerType == trackerTypes[4]) tracker = TrackerMedianFlow::create(); else if (trackerType == trackerTypes[5]) tracker = TrackerGOTURN::create(); else if (trackerType == trackerTypes[6]) tracker = TrackerMOSSE::create(); else if (trackerType == trackerTypes[7]) tracker = TrackerCSRT::create(); else { cout << "Incorrect tracker name" << endl; cout << "Available trackers are: " << endl; for (vector::iterator it = trackerTypes.begin() ; it != trackerTypes.end(); ++it) std::cout << " " << *it << endl; } return tracker; }
2.2 第 2 步:读取视频的第一帧
多目标跟踪器需要两个输入一个视频帧我们要跟踪的所有对象的位置(边界框)。
给定这些信息,跟踪器在所有后续帧中跟踪这些指定对象的位置。 在下面的代码中,我们首先使用 VideoCapture 类加载视频并读取第一帧。这将在稍后用于初始化 MultiTracker。
Python# Set video to load videoPath = "videos/run.mp4" # Create a video capture object to read videos cap = cv2.VideoCapture(videoPath) # Read first frame success, frame = cap.read() # quit if unable to read the video file if not success: print("Failed to read video") sys.exit(1)
C++// set default values for tracking algorithm and video string videoPath = "videos/run.mp4"; // Initialize MultiTracker with tracking algo vector bboxes; // create a video capture object to read videos cv::VideoCapture cap(videoPath); Mat frame; // quit if unabke to read video file if(!cap.isOpened()) { cout << "Error opening video file " << videoPath << endl; return -1; } // read first frame cap >> frame;
2.3 第 3 步:在第一帧中定位对象
接下来,我们需要在第一帧中定位我们想要跟踪的对象。该位置只是一个边界框。 OpenCV 提供了一个名为 selectROI 的函数,该函数会弹出一个 GUI 来选择边界框(也称为感兴趣区域 (ROI))。 在 C++ 版本中,selectROI 允许您获取多个边界框,但在 Python 版本中,它只返回一个边界框。所以,在 Python 版本中,我们需要一个循环来获取多个边界框。 对于每个对象,我们还选择一种随机颜色来显示边界框。 代码如下所示。
Python## Select boxes bboxes = [] colors = [] # OpenCV 的 selectROI 函数不适用于在 Python 中选择多个对象 # 所以我们将循环调用这个函数,直到我们完成选择所有对象 while True: # 在对象上绘制边界框 # selectROI 的默认行为是从中心开始绘制框 # 当fromCenter设置为false时,可以从左上角开始画框 bbox = cv2.selectROI("MultiTracker", frame) bboxes.append(bbox) colors.append((randint(0, 255), randint(0, 255), randint(0, 255))) print("Press q to quit selecting boxes and start tracking") print("Press any other key to select next object") k = cv2.waitKey(0) & 0xFF if (k == 113): # q is pressed break print("Selected bounding boxes {}".format(bboxes))
C++// Get bounding boxes for first frame // selectROI"s default behaviour is to draw box starting from the center // when fromCenter is set to false, you can draw box starting from top left corner bool showCrosshair = true; bool fromCenter = false; cout << " ========================================================== "; cout << "OpenCV says press c to cancel objects selection process" << endl; cout << "It doesn"t work. Press Escape to exit selection process" << endl; cout << " ========================================================== "; cv::selectROIs("MultiTracker", frame, bboxes, showCrosshair, fromCenter); // quit if there are no objects to track if(bboxes.size() < 1) return 0; vector colors; getRandomColors(colors, bboxes.size());
getRandomColors 函数相当简单// Fill the vector with random colors void getRandomColors(vector& colors, int numColors) { RNG rng(0); for(int i=0; i < numColors; i++) colors.push_back(Scalar(rng.uniform(0,255), rng.uniform(0, 255), rng.uniform(0, 255))); }
2.4 第 3 步:初始化 MultiTracker
到目前为止,我们已经读取了第一帧并获得了对象周围的边界框。这就是我们初始化多目标跟踪器所需的所有信息。
我们首先创建一个 MultiTracker 对象,并向其中添加与边界框一样多的单个对象跟踪器。在此示例中,我们使用 CSRT 单对象跟踪器,但您可以通过将下面的 trackerType 变量更改为本文开头提到的 8 个跟踪器之一来尝试其他跟踪器类型。 CSRT 跟踪器不是最快的,但在我们尝试的许多情况下它产生了最好的结果。
您还可以使用包裹在同一个 MultiTracker 中的不同跟踪器,但当然,这没什么意义。
MultiTracker 类只是这些单个对象跟踪器的包装器。正如我们从上一篇文章中知道的那样,单个对象跟踪器是使用第一帧初始化的,并且边界框指示我们想要跟踪的对象的位置。 MultiTracker 将此信息传递给它在内部包装的单个对象跟踪器。
Python# Specify the tracker type trackerType = "CSRT" # Create MultiTracker object multiTracker = cv2.MultiTracker_create() # Initialize MultiTracker for bbox in bboxes: multiTracker.add(createTrackerByName(trackerType), frame, bbox)
C++// Specify the tracker type string trackerType = "CSRT"; // Create multitracker Ptr multiTracker = cv::MultiTracker::create(); // Initialize multitracker for(int i=0; i < bboxes.size(); i++) multiTracker->add(createTrackerByName(trackerType), frame, Rect2d(bboxes[i]));
2.5 第 4 步:更新 MultiTracker 并显示结果
最后,我们的 MultiTracker 已准备就绪,我们可以在新帧中跟踪多个对象。我们使用 MultiTracker 类的 update 方法来定位新框架中的对象。每个跟踪对象的每个边界框都使用不同的颜色绘制。
Python# Process video and track objects while cap.isOpened(): success, frame = cap.read() if not success: break # get updated location of objects in subsequent frames success, boxes = multiTracker.update(frame) # draw tracked objects for i, newbox in enumerate(boxes): p1 = (int(newbox[0]), int(newbox[1])) p2 = (int(newbox[0] + newbox[2]), int(newbox[1] + newbox[3])) cv2.rectangle(frame, p1, p2, colors[i], 2, 1) # show frame cv2.imshow("MultiTracker", frame) # quit on ESC button if cv2.waitKey(1) & 0xFF == 27: # Esc pressed break
C++while(cap.isOpened()) { // get frame from the video cap >> frame; // Stop the program if reached end of video if (frame.empty()) break; //Update the tracking result with new frame multiTracker->update(frame); // Draw tracked objects for(unsigned i=0; igetObjects().size(); i++) { rectangle(frame, multiTracker->getObjects()[i], colors[i], 2, 1); } // Show frame imshow("MultiTracker", frame); // quit on x button if (waitKey(1) == 27) break; }
3.完整代码
C++#include #include using namespace cv; using namespace std; vector trackerTypes = {"BOOSTING", "MIL", "KCF", "TLD", "MEDIANFLOW", "GOTURN", "MOSSE", "CSRT"}; // 按名称创建跟踪器 Ptr createTrackerByName(string trackerType) { Ptr tracker; if (trackerType == trackerTypes[0]) tracker = TrackerBoosting::create(); else if (trackerType == trackerTypes[1]) tracker = TrackerMIL::create(); else if (trackerType == trackerTypes[2]) tracker = TrackerKCF::create(); else if (trackerType == trackerTypes[3]) tracker = TrackerTLD::create(); else if (trackerType == trackerTypes[4]) tracker = TrackerMedianFlow::create(); else if (trackerType == trackerTypes[5]) tracker = TrackerGOTURN::create(); else if (trackerType == trackerTypes[6]) tracker = TrackerMOSSE::create(); else if (trackerType == trackerTypes[7]) tracker = TrackerCSRT::create(); else { cout << "Incorrect tracker name" << endl; cout << "Available trackers are: " << endl; for (vector::iterator it = trackerTypes.begin() ; it != trackerTypes.end(); ++it) std::cout << " " << *it << endl; } return tracker; } // 用随机颜色填充vector void getRandomColors(vector &colors, int numColors) { RNG rng(0); for(int i=0; i < numColors; i++) colors.push_back(Scalar(rng.uniform(0,255), rng.uniform(0, 255), rng.uniform(0, 255))); } int main(int argc, char * argv[]) { cout << "Default tracking algoritm is CSRT" << endl; cout << "Available tracking algorithms are:" << endl; for (vector::iterator it = trackerTypes.begin() ; it != trackerTypes.end(); ++it) std::cout << " " << *it << endl; // 设置跟踪器类型。更改此项以尝试不同的跟踪器。 string trackerType = "CSRT"; // 设置跟踪算法和视频的默认值 string videoPath = "videos/run.mp4"; // 使用跟踪算法初始化 MultiTracker vector bboxes; // 创建一个视频捕获对象来读取视频 cv::VideoCapture cap(videoPath); Mat frame; // 如果无法读取视频文件则退出 if(!cap.isOpened()) { cout << "Error opening video file " << videoPath << endl; return -1; } // 读取第一帧 cap >> frame; // 在对象上绘制边界框 // selectROI 的默认行为是从中心开始绘制框 // 当fromCenter设置为false时,可以从左上角开始画框 bool showCrosshair = true; bool fromCenter = false; cout << " ========================================================== "; cout << "OpenCV says press c to cancel objects selection process" << endl; cout << "It doesn"t work. Press Escape to exit selection process" << endl; cout << " ========================================================== "; cv::selectROIs("MultiTracker", frame, bboxes, showCrosshair, fromCenter); // 如果没有要跟踪的对象,则退出 if(bboxes.size() < 1) return 0; vector colors; getRandomColors(colors, bboxes.size()); // 创建 multitracker Ptr multiTracker = cv::MultiTracker::create(); // 初始化 multitracker for(int i=0; i < bboxes.size(); i++) multiTracker->add(createTrackerByName(trackerType), frame, Rect2d(bboxes[i])); // 处理视频和跟踪对象 cout << " ========================================================== "; cout << "Started tracking, press ESC to quit." << endl; while(cap.isOpened()) { // 从视频中获取帧 cap >> frame; // 如果到达视频结尾,则停止程序 if (frame.empty()) break; // 用新帧更新跟踪结果 multiTracker->update(frame); // 绘制跟踪对象 for(unsigned i=0; igetObjects().size(); i++) { rectangle(frame, multiTracker->getObjects()[i], colors[i], 2, 1); } // 显示 frame imshow("MultiTracker", frame); // 按ESC退出 if (waitKey(1) == 27) break; } }
Python#!/usr/bin/python from __future__ import print_function import sys import cv2 from random import randint trackerTypes = ["BOOSTING", "MIL", "KCF","TLD", "MEDIANFLOW", "GOTURN", "MOSSE", "CSRT"] def createTrackerByName(trackerType): # Create a tracker based on tracker name if trackerType == trackerTypes[0]: tracker = cv2.TrackerBoosting_create() elif trackerType == trackerTypes[1]: tracker = cv2.TrackerMIL_create() elif trackerType == trackerTypes[2]: tracker = cv2.TrackerKCF_create() elif trackerType == trackerTypes[3]: tracker = cv2.TrackerTLD_create() elif trackerType == trackerTypes[4]: tracker = cv2.TrackerMedianFlow_create() elif trackerType == trackerTypes[5]: tracker = cv2.TrackerGOTURN_create() elif trackerType == trackerTypes[6]: tracker = cv2.TrackerMOSSE_create() elif trackerType == trackerTypes[7]: tracker = cv2.TrackerCSRT_create() else: tracker = None print("Incorrect tracker name") print("Available trackers are:") for t in trackerTypes: print(t) return tracker if __name__ == "__main__": print("Default tracking algoritm is CSRT " "Available tracking algorithms are: ") for t in trackerTypes: print(t) trackerType = "CSRT" # Set video to load videoPath = "videos/run.mp4" # Create a video capture object to read videos cap = cv2.VideoCapture(videoPath) # Read first frame success, frame = cap.read() # quit if unable to read the video file if not success: print("Failed to read video") sys.exit(1) ## Select boxes bboxes = [] colors = [] # OpenCV"s selectROI function doesn"t work for selecting multiple objects in Python # So we will call this function in a loop till we are done selecting all objects while True: # draw bounding boxes over objects # selectROI"s default behaviour is to draw box starting from the center # when fromCenter is set to false, you can draw box starting from top left corner bbox = cv2.selectROI("MultiTracker", frame) bboxes.append(bbox) colors.append((randint(64, 255), randint(64, 255), randint(64, 255))) print("Press q to quit selecting boxes and start tracking") print("Press any other key to select next object") k = cv2.waitKey(0) & 0xFF if (k == 113): # q is pressed break print("Selected bounding boxes {}".format(bboxes)) ## Initialize MultiTracker # There are two ways you can initialize multitracker # 1. tracker = cv2.MultiTracker("CSRT") # All the trackers added to this multitracker # will use CSRT algorithm as default # 2. tracker = cv2.MultiTracker() # No default algorithm specified # Initialize MultiTracker with tracking algo # Specify tracker type # Create MultiTracker object multiTracker = cv2.MultiTracker_create() # Initialize MultiTracker for bbox in bboxes: multiTracker.add(createTrackerByName(trackerType), frame, bbox) # Process video and track objects while cap.isOpened(): success, frame = cap.read() if not success: break # get updated location of objects in subsequent frames success, boxes = multiTracker.update(frame) # draw tracked objects for i, newbox in enumerate(boxes): p1 = (int(newbox[0]), int(newbox[1])) p2 = (int(newbox[0] + newbox[2]), int(newbox[1] + newbox[3])) cv2.rectangle(frame, p1, p2, colors[i], 2, 1) # show frame cv2.imshow("MultiTracker", frame) # quit on ESC button if cv2.waitKey(1) & 0xFF == 27: # Esc pressed break
参考目录
https://learnopencv.com/multitracker-multiple-object-tracking-using-opencv-c-python/