目标跟踪(3)MultiTracker : 基于 OpenCV (C++/Python) 的多目标跟踪

在这篇文章中,我们将介绍如何使用通过 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 模型。
在下面的代码中,给定跟踪器类的名称,我们返回跟踪器对象。这将在稍后用于多目标跟踪器。
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

C++
**注意:**除了包含opencv2/opencv.hpp,还需要包含opencv2/tracking.hpp

#include 
#include 

using namespace cv;
using namespace std;

vector<string> trackerTypes = {"BOOSTING", "MIL", "KCF", "TLD", "MEDIANFLOW", "GOTURN", "MOSSE", "CSRT"}; 

// create tracker by name
Ptr<Tracker> createTrackerByName(string trackerType)
{
  Ptr<Tracker> 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<string>::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<Rect> 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 << "\n==========================================================\n";
cout << "OpenCV says press c to cancel objects selection process" << endl;
cout << "It doesn't work. Press Escape to exit selection process" << endl;
cout << "\n==========================================================\n";
cv::selectROIs("MultiTracker", frame, bboxes, showCrosshair, fromCenter);

// quit if there are no objects to track
if(bboxes.size() < 1)
  return 0;

vector<Scalar> colors;
getRandomColors(colors, bboxes.size());

getRandomColors 函数相当简单

// Fill the vector with random colors
void getRandomColors(vector<Scalar>& 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> 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; i<multiTracker->getObjects().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<string> trackerTypes = {"BOOSTING", "MIL", "KCF", "TLD", "MEDIANFLOW", "GOTURN", "MOSSE", "CSRT"}; 

// 按名称创建跟踪器
Ptr<Tracker> createTrackerByName(string trackerType) 
{
  Ptr<Tracker> 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<string>::iterator it = trackerTypes.begin() ; it != trackerTypes.end(); ++it)
      std::cout << " " << *it << endl;
  }
  return tracker;
}

// 用随机颜色填充vector
void getRandomColors(vector<Scalar> &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<string>::iterator it = trackerTypes.begin() ; it != trackerTypes.end(); ++it)
    std::cout << " " << *it << endl;
  
  // 设置跟踪器类型。更改此项以尝试不同的跟踪器。
  string trackerType = "CSRT";

  // 设置跟踪算法和视频的默认值
  string videoPath = "videos/run.mp4";
  
  // 使用跟踪算法初始化 MultiTracker
  vector<Rect> 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 << "\n==========================================================\n";
  cout << "OpenCV says press c to cancel objects selection process" << endl;
  cout << "It doesn't work. Press Escape to exit selection process" << endl;
  cout << "\n==========================================================\n";
  cv::selectROIs("MultiTracker", frame, bboxes, showCrosshair, fromCenter);
  
  // 如果没有要跟踪的对象,则退出
  if(bboxes.size() < 1)
    return 0;
  
  vector<Scalar> colors;  
  getRandomColors(colors, bboxes.size()); 
  
  // 创建 multitracker
  Ptr<MultiTracker> multiTracker = cv::MultiTracker::create();

  // 初始化 multitracker
  for(int i=0; i < bboxes.size(); i++)
    multiTracker->add(createTrackerByName(trackerType), frame, Rect2d(bboxes[i]));  
  
  // 处理视频和跟踪对象
  cout << "\n==========================================================\n";
  cout << "Started tracking, press ESC to quit." << endl;
  while(cap.isOpened()) 
  {
    // 从视频中获取帧
    cap >> frame;
  
    // 如果到达视频结尾,则停止程序
    if (frame.empty()) break;

    // 用新帧更新跟踪结果
    multiTracker->update(frame);

    // 绘制跟踪对象
    for(unsigned i=0; i<multiTracker->getObjects().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 \n"
        "Available tracking algorithms are:\n")
  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/

你可能感兴趣的:(OpenCV,目标跟踪,opencv,目标跟踪)