专栏首页AI研习社多目标追踪器:用OpenCV实现多目标追踪(C++/Python)

多目标追踪器:用OpenCV实现多目标追踪(C++/Python)

本文为 AI 研习社编译的技术博客,原标题 : MultiTracker : Multiple Object Tracking using OpenCV (C++/Python) 翻译 | 燕婕 校对 | 酱番梨 整理 | 菠萝妹 原文链接: https://www.learnopencv.com/multitracker-multiple-object-tracking-using-opencv-c-python/

在本文中,我们将谈到如何用 OpenCV 的多目标追踪API,通过使用 MultiTracker 类来实现。我们将分享用 C++ 和用 Python 实现的代码。

在我们深入探讨细节之前,请检查下边列出来的之前的关于目标追踪的帖子,理解用 OpenCV 实现的单目标追踪器的基本原理。

  1. 用 OpenCV 实现目标追踪
  2. GOTURN: 基于深度学习的目标追踪

为什么我们需要多目标追踪

大多数计算机视觉和机器学习的入门者都学习目标识别。如果你是一个初学者,你可能会思考我们到底为什么需要目标追踪。我们为什么不能只是在每一帧检测目标?

然我们来探索追踪很有用的几个原因。

首先,当视频的一帧中有多个目标(这里指人)时,追踪帮助建立帧之间的目标同一性。

第二,在一些情况下,目标检测可能失败但是仍有可能追踪目标,因为追踪考虑到目标在前一帧中的位置和外观。

第三,一些追踪算法是非常快的,因为他们做局部搜索而非全局搜索。所以通过每隔 n 帧做目标检测并在中间的帧做目标追踪,我们的系统可以获得很高的帧速。

所以,为什么不在第一次检测后无限期地跟踪这个对象呢?一个追踪算法有时可能会丢掉它所追踪的目标的轨迹。例如,当目标的移动太大,一个追踪算法可能不能保持一直追踪。所以现实中许多应用将检测和追踪一起使用。

在这个教程中,我们将只关注追踪这部分,我们将通过在对象周围放置一个边界框来指定想要跟踪的对象。

多目标追踪:OpenCV 的多目标追踪器

OpenCV 中的 MultiTracker 类提供了多目标追踪的实施方法。他是一个简单的实施方法因为他独立地处理被追踪的目标,不需要在多个被追踪对象之间做任何优化。

让我们一步步查看代码,学习我们如何用 OpenCV 的多目标追踪 API。

下载代码

为了能容易地跟着这个教程学习,请点击下面的按钮,下载代码。代码是免费的!

代码下载链接:https://bigvisionllc.leadpages.net/leadbox/143948b73f72a2%3A173c9390c346dc/5649050225344512/

第一步:创建单目标追踪器

一个多目标追踪器是由一系列简单的单目标追踪器组成的。一开始,我们先定义一个函数,用追踪器类型作为输入并创建一个追踪器对象。OpenCV 有八个不同的追踪器类型:BOOSTING, MTL, 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++

注意:除了需要 include opencv2 / opencv.hpp 之外,你还需要 include opencv2 / tracking.hpp 。

#include <opencv2/opencv.hpp>
#include <opencv2/tracking.hpp>

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;
}

第二步:读取视频的第一帧

一个多目标追踪器需要两个输入

  1. 视频的一帧
  2. 你想要追踪的所有目标的位置(边界框)

给定这些信息,追踪器会在多有子序列帧中追踪这些特定目标的位置。

在下面的代码中,我们先用 VidoeCapture 类加载视频,读取第一帧。这一帧将会用于之后的 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;

第三步:在第一帧中定位物体

接下来,我们需要在第一帧中定位我们想要追踪的物体。位置是一个简单的边界框。

OpenCV 提供了一个叫做 selectROI 的功能,它可以弹出一个 GUI 来选择边界框(也叫做感兴趣的区域(ROI))。

在C++版本中,selectROI允许你得到多个边界框,但在 Python 版本中,它会只返回一个边界框。所以,在 Python 版本中,我们需要一个循环来得到多个边界框。

对于每个目标,我们还会选择随机的颜色来显示边界框。

下面就是实现代码。

Python

## 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(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))); 
}

第三步:初始化多目标追踪器

直到目前,我们读到了第一帧并且得到了目标周围的边界框。这些是我们需要初始化多目标追踪器所需的全部信息。

我们首先创建一个 MuliTracker 对象并且增加和单个目标追踪器一样多的边界框。在这个例子中,我们用 CSRT 单目标追踪器,但是你尝试可以通过将 trackerTyper 变量改变为这篇文章一开始提到的8种追踪器中的一种,来尝试其使用他类型的追踪器。CSRT 追踪器不是最快的,但它在我们尝试的许多情况下都能生成最好的结果。

你可以用封装在同一个 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
PtrmultiTracker = cv::MultiTracker::create();
 
// Initialize multitracker
for(int i=0; i < bboxes.size(); i++)
  multiTracker->add(createTrackerByName(trackerType), frame, Rect2d(bboxes[i]));

第四步:更新多目标追踪器并展示结果

最后,我们的多目标追踪器已经准备好了,我们可以在新的帧中追踪多个目标。我们用 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;
   
 }

本文分享自微信公众号 - AI研习社(okweiwu)

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2018-10-22

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 赵丽颖公布婚讯致微博瘫痪, 新浪程序员转发:能否提前打个招呼!

    我们都知道,在10月16号这一天,大家常玩微博的小伙伴都发现微博又陷入到了突然的瘫痪之中,这可是让大家急坏了。好多网友都说,明明就是想吃一下赵丽颖还有冯绍峰的瓜...

    一墨编程学习
  • 科学瞎想系列之八十七 永磁电机(8)

    上一期负载特性讲的是对电机输出提出的要求,这一期我们说说电机的运行条件或者叫运行的限制条件和控制策略,如果说上一期说的是让马儿跑多快,让牛儿挤出...

    标准答案
  • 时序逻辑电路基础

    建立时间Tsu(setup):触发器在时钟上升沿到来之前,其数据输入端的数据必须保持不变的最小时间。

    NingHeChuan
  • 因为产品意见不合打架?想多了!

    我发现爆料人非常会炒作,本来就是两个人抢微波炉引起的冲突吧,结果说成程序员与产品经理因为产品意见不合而引起的冲突!

    叫我龙总
  • 真是笑skr人,贴膜师傅让我删掉这条推送!

    相信许多人都在依赖天桥上的小贩、手机店老板贴膜吧!然而很多人在不断进化过程中学会了贴膜这一项“技术活”。贴膜真的是技术活么?手机真的要贴膜么?什么膜更加适合你?...

    半夜喝可乐
  • Netty源码阅读入门实战(九)-编码及writeAndFlush()

    JavaEdge
  • 交互面试总结

    面了tx的交互,现在正在等待结果中,虽是面试但是却是感觉参加了一场分享会,收获颇丰,现在整理一下面试时候被问到的问题和一些感触很深的点。

    霖酱
  • 到底要不要去培训机构学习?

    你花了很短的时间就能去上手一门行业,虽然你花了一些钱,但是你节省了大量的时间和精力,找到了高薪的工作,然后很快就可以把这个款项还上了。

    叫我龙总
  • 我要开始写作了

    今天是2017年6月30日,这一年已经过半了,而在这半年中我有对生活,对工作有无数强烈的想法充斥于自己的脑海中,有一些想法灵感我会随手去记录,我时常发一些朋友圈...

    叫我龙总
  • WePhone创始人自杀后的感想--事缓则圆

    主人公据传是快40岁的人,本人又是高智商分子,却在社会经验方面一败涂地。这里简单罗列一下他事情。

    叫我龙总

扫码关注云+社区

领取腾讯云代金券