在本教程中,我们将了解OpenCV 3中引入的OpenCV目标跟踪API。我们将学习如何以及何时使用OpenCV 3中提供的8种不同的跟踪器BOOSTING,MIL,KCF,TLD,MEDIANFLOW,GOTURN,MOSSE和CSRT。我们还将学习目前跟踪算法的通用原理。OpenCV版本至少OpenCV3.4.1以上。同时需要调用opencv_contrib库,OpenCV大量的算法存放在opencv_contrib目录下面的未稳定功能模块里,release版本没有。

正如下面这个完美的实时面部跟踪器所示!动画演示了我们想要的理想目标跟踪器:速度,准确性和鲁棒性。

https://s4.51cto.com/images/blog/202204/18114723_625cdf4b1178016513.gif?x-oss-process=image/watermark,size_14,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_30,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=

1 背景

1.1 什么是目标跟踪

简而言之,在视频的连续帧中定位对象称为跟踪。该定义听起来很直接,但在计算机视觉和机器学习中,跟踪是一个非常广泛的术语,概念上相似但实现的技术却大大不同。例如,通常在目标跟踪有以下方法:

1)密集光流:这些算法有助于估计视频帧中每个像素的运动情况。

2)稀疏光流:这些算法,如Kanade-Lucas-Tomashi(KLT)特征跟踪器,跟踪图像中几个特征点的位置。

3)卡尔曼滤波:一种非常流行的信号处理算法,用于根据先前的运动信息预测运动物体的位置。该算法的早期应用之一是daodan制导!还提到这里,阿波罗11号登月舱的降落到月球车载计算机有一个卡尔曼滤波器。。

4)均值偏移(Meanshift)和Camshift(Meanshift的改进,连续自适应的MeanShift算法):这些是用于定位密度函数的最大值的算法。它们也用于跟踪。

5)单目标跟踪算法:在此类跟踪器中,第一帧使用矩形表示我们要跟踪的对象的位置。然后使用跟踪算法在后续帧中跟踪对象。在大多数实际应用中,这些跟踪器与目标检测算法结合使用。

6)多目标跟踪算法:在我们有快速对象检测器的情况下,检测每个帧中的多个对象然后运行跟踪查找算法来识别一个帧中的哪个矩形对应于下一帧中的矩形是很有效的。

1.2 跟踪与检测

如果你曾经用过OpenCV人脸检测,你知道它可以实时工作,你可以轻松地在每一帧中检测到脸部。那么,为什么你需要首先进行跟踪?让我们探讨一下您可能想要进行视频跟踪的原因,而不仅仅是重复检测。

1)跟踪比检测更快:通常跟踪算法比检测算法更快。原因很简单。当您跟踪在前一帧中检测到的对象时,您对该对象的外观了解很多。您还可以知道前一帧中的位置以及其运动的方向和速度。因此,在下一帧中,您可以使用所有这些信息来预测下一帧中对象的位置,并围绕对象的预期位置进行小搜索,以准确定位对象。一个好的跟踪算法将使用它对该对象的所有信息,而检测算法总是从头开始。因此,在设计高效的系统时,通常在每第n帧上运行物体检测,而在之间的n-1帧中采用跟踪算法。

为什么我们不直接检测第一帧中的对象并随后跟踪?确实,跟踪可以从它拥有的额外信息中获益,如果它们移动速度太快以至于跟踪算法无法赶上时,您也可能失去对象的跟踪。跟踪算法累积错误也很常见,跟踪对象的边界框会慢慢偏离其正在跟踪的对象。为了通过跟踪算法解决这些问题,每隔一段时间运行一次检测算法。

2)当检测失败时,跟踪可以提供帮助:如果您在视频上运行人脸检测器并且人脸被对象遮挡,则人脸检测器很可能会失败。另一方面,良好的跟踪算法将处理某种程度的遮挡。

3)跟踪保留标识:对象检测的输出是包含对象的矩形数组。但是,该对象没有附加标识。例如在第一张图检测到多个对象会画检测框,但是第二张图又检测多个对象。但是不知道哪个矩形对应于哪个对象。

2 OpenCV的目标跟踪函数

2.1 函数调用

OpenCV的扩展库OpenCV_contrib有一个目标跟踪API,其中包含许多单个对象跟踪算法的实现。OpenCV中有8种不同的跟踪器,分别是BOOSTING,MIL,KCF,TLD,MEDIANFLOW,GOTURN,MOSSE和CSRT。本文只讲其中7种,GOTURN涉及到深度学习以后再讲。其他算法可以直接用OpenCV contrib库函数调用。

但是如果使用OpenCV_contrib函数库需要重新编译源代码。

C++需要编译源代码,具体见:

windows

linux

Python稍微很简单,先卸载安装的Opencv,然后直接pip/pip3安装contrib库:

pip uninstall opencv-python

pip install opencv-contrib-python

在我们提供算法的简要描述之前,让我们看一下参数设置和用法。在下面的注释代码中,我们首先通过选择跟踪器类型来设置跟踪器,BOOSTING,MIL,KCF,TLD,MEDIANFLOW,GOTURN,MOSSE或CSRT。然后我们打开一个视频并逐帧读图。我们定义一个包含第一帧对象的边界框,并用第一帧和边界框初始化跟踪器。最后,我们从视频中读取帧更新跟踪器以获得当前帧的新边界框,并显示结果。

代码下载地址:

代码如下:

C++:

// Opencv_Tracker.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//

#include "pch.h"
#include <opencv2/opencv.hpp>
#include <opencv2/tracking.hpp>
#include <opencv2/core/ocl.hpp>

using namespace cv;
using namespace std;

int main()
{
    //跟踪算法类型
    string trackerTypes[7] = { "BOOSTING", "MIL", "KCF", "TLD","MEDIANFLOW", "MOSSE", "CSRT" };

    // Create a tracker 创建跟踪器
    string trackerType = trackerTypes[5];

    Ptr<Tracker> tracker;

    if (trackerType == "BOOSTING")
        tracker = TrackerBoosting::create();
    if (trackerType == "MIL")
        tracker = TrackerMIL::create();
    if (trackerType == "KCF")
        tracker = TrackerKCF::create();
    if (trackerType == "TLD")
        tracker = TrackerTLD::create();
    if (trackerType == "MEDIANFLOW")
        tracker = TrackerMedianFlow::create();
    if (trackerType == "MOSSE")
        tracker = TrackerMOSSE::create();
    if (trackerType == "CSRT")
        tracker = TrackerCSRT::create();

    // Read video 读视频
    VideoCapture video("video/chaplin.mp4");

    // Exit if video is not opened 如果没有视频文件
    if (!video.isOpened())
    {
        cout << "Could not read video file" << endl;
        return 1;
    }

    // Read first frame 读图
    Mat frame;
    bool ok = video.read(frame);

    // Define initial boundibg box 初始检测框
    Rect2d bbox(287, 23, 86, 320);

    // Uncomment the line below to select a different bounding box 手动在图像上画矩形框
    //bbox = selectROI(frame, false);

    // Display bounding box 展示画的2边缘框
    rectangle(frame, bbox, Scalar(255, 0, 0), 2, 1);
    imshow("Tracking", frame);

    //跟踪器初始化
    tracker->init(frame, bbox);

    while (video.read(frame))
    {
        // Start timer 开始计时
        double timer = (double)getTickCount();

        // Update the tracking result 跟新跟踪器算法
        bool ok = tracker->update(frame, bbox);

        // Calculate Frames per second (FPS) 计算FPS
        float fps = getTickFrequency() / ((double)getTickCount() - timer);

        if (ok)
        {
            // Tracking success : Draw the tracked object 如果跟踪到目标画框
            rectangle(frame, bbox, Scalar(255, 0, 0), 2, 1);
        }
        else
        {
            // Tracking failure detected. 没有就输出跟踪失败
            putText(frame, "Tracking failure detected", Point(100, 80), FONT_HERSHEY_SIMPLEX, 0.75, Scalar(0, 0, 255), 2);
        }

        // Display tracker type on frame 展示检测算法类型
        putText(frame, trackerType + " Tracker", Point(100, 20), FONT_HERSHEY_SIMPLEX, 0.75, Scalar(50, 170, 50), 2);

        // Display FPS on frame 表示FPS
        putText(frame, "FPS : " + to_string(int(fps)), Point(100, 50), FONT_HERSHEY_SIMPLEX, 0.75, Scalar(50, 170, 50), 2);

        // Display frame.
        imshow("Tracking", frame);

        // Exit if ESC pressed.
        int k = waitKey(1);
        if (k == 27)
        {
            break;
        }
    }
    return 0;
}

Python:

import cv2
import sys

if __name__ == '__main__' :

    # Set up tracker.
    # Instead of MIL, you can also use

    tracker_types = ['BOOSTING', 'MIL','KCF', 'TLD', 'MEDIANFLOW', 'MOSSE', 'CSRT']
    tracker_type = tracker_types[4]

    if tracker_type == 'BOOSTING':
        tracker = cv2.TrackerBoosting_create()
    if tracker_type == 'MIL':
        tracker = cv2.TrackerMIL_create()
    if tracker_type == 'KCF':
        tracker = cv2.TrackerKCF_create()
    if tracker_type == 'TLD':
        tracker = cv2.TrackerTLD_create()
    if tracker_type == 'MEDIANFLOW':
        tracker = cv2.TrackerMedianFlow_create()
    if tracker_type == "CSRT":
        tracker = cv2.TrackerCSRT_create()
    if tracker_type == "MOSSE":
    tracker = cv2.TrackerMOSSE_create()
    # Read video
    video = cv2.VideoCapture("video/chaplin.mp4")

    # Exit if video not opened.
    if not video.isOpened():
        print("Could not open video")
        sys.exit()

    # Read first frame.
    ok, frame = video.read()
    if not ok:
        print('Cannot read video file')
        sys.exit()

    # Define an initial bounding box
    bbox = (287, 23, 86, 320)

    # Uncomment the line below to select a different bounding box
    bbox = cv2.selectROI(frame, False)

    # Initialize tracker with first frame and bounding box
    ok = tracker.init(frame, bbox)

    while True:
        # Read a new frame
        ok, frame = video.read()
        if not ok:
            break

        # Start timer
        timer = cv2.getTickCount()

        # Update tracker
        ok, bbox = tracker.update(frame)

        # Calculate Frames per second (FPS)
        fps = cv2.getTickFrequency() / (cv2.getTickCount() - timer);

        # Draw bounding box
        if ok:
            # Tracking success
            p1 = (int(bbox[0]), int(bbox[1]))
            p2 = (int(bbox[0] + bbox[2]), int(bbox[1] + bbox[3]))
            cv2.rectangle(frame, p1, p2, (255,0,0), 2, 1)
        else :
            # Tracking failure
            cv2.putText(frame, "Tracking failure detected", (100,80), cv2.FONT_HERSHEY_SIMPLEX, 0.75,(0,0,255),2)

        # Display tracker type on frame
        cv2.putText(frame, tracker_type + " Tracker", (100,20), cv2.FONT_HERSHEY_SIMPLEX, 0.75, (50,170,50),2);

        # Display FPS on frame
        cv2.putText(frame, "FPS : " + str(int(fps)), (100,50), cv2.FONT_HERSHEY_SIMPLEX, 0.75, (50,170,50), 2);

        # Display result
        cv2.imshow("Tracking", frame)

        # Exit if ESC pressed
        k = cv2.waitKey(1) & 0xff
        if k == 27 : break

2.2 函数详解

在本节中,我们将深入研究不同的跟踪算法。目标不是要对每个跟踪器有深入的理论理解,而是从实际的角度理解它们。

在跟踪中,我们的目标是在当前帧中找到前一帧已经识别或者跟踪到的一个对象。由于我们已经跟踪了当前帧的对象,因此我们知道它是如何移动的。换句话说,我们知道运动模型的参数。运动模型只是一种有效的方式,表示你知道前一帧中该物体的位置和速度。如果您对该对象一无所知,则可以根据当前运动模型预测新位置,并且获得的结果将非常接近对象的新位置。

当我们有更多物体的信息,我们可以构建一个外观模型来表示对象的外观。该外观模型可用于在由运动模型预测的位置的小邻域中搜索,以更准确地预测对象的位置。运动模型预测对象的大致位置。外观模型可以精确调整此估计值,以便根据外观提供更准确的估计。如果目标对象非常简单并且外观变化不大,我们可以使用一个简单的模板作为外观模型并查找该模板。然而,现实生活并非那么简单。对象的外观可能会发生巨大变化。为了解决这个问题,在许多现代目标跟踪器中,外观模型是以在线方式训练的分类器。

分类器的工作是将图像的矩形区域分类为对象或背景。分类器将图像块作为输入,并返回0到1之间的分数,以表示图像块包含对象的概率。当绝对确定图像块是背景时得分为0,当绝对确定图像块是前景对象时得分为1。在机器学习中,我们使用“在线”一词来指代在运行时即时训练的算法。一个离线分类可能需要成千上万的例子训练分类,而是一个在线分类器使用很少的例子在运行时进行训练。通过将其分为正(对象)和负(背景)示例来训练分类器。如果你想建立一个用于检测猫的分类器,你可以使用包含猫的数千张图像和数千张不含猫的图像来训练它。通过这种方式,分类器学会区分什么是猫而不是什么。但在构建在线分类器的同时,我们没有数千个正面和负面类的例子。所以我们需要通过在线训练跟踪器,来实现目标跟踪。

  1. BOOSTING Tracker/集成学习跟踪器

该跟踪器基于AdaBoost的在线版本,即基于HAAR级联的检测器法。需要在运行时使用对象的正负示例训练此分类器。由用户(或另一个对象检测算法)提供的初始边界框被视为对象的正例,并且边界框外的许多图像块被视为背景。给定新帧,分类器在先前位置的邻域中的每个像素上运行,并且记录分类器的分数。对象的新位置是得分最大的位置。所以现在我们又有了一个分类器的正面例子。随着更多帧进入,分类器将使用此数据进行更新。

优点:没有。这个算法已有十年之久,并且运行正常,但我找不到使用它的充分理由,特别是当基于类似原理的其他高级跟踪器(MIL,KCF)可用时。

缺点:跟踪效果很普通,而且无法确定是否跟踪失败。

  1. MIL Tracker/多实例学习跟踪器

该跟踪器在概念上类似于上述的BOOSTING跟踪器。最大的区别在于,不仅考虑对象的当前位置作为正例,而是在当前位置周围的小邻域产生若干潜在的正例。您可能认为这是一个坏主意,因为在大多数这些正样本的例子中,对象不是居中的。

这是多实例学习跟踪器(MIL)要解决的问题。在MIL中,您没有指定正负样本的图像块,而是正负例的图像集合。正图像集合中的图像块并非都是正例。意思就是正图像集中有若干图像块,但只需要其中一个图像块是正例。在我们的示例中,正图像集以对象当前位置为中心的图像块,以及在其周围的小邻域中的图像块。即使被跟踪对象的当前位置不准确,当来自当前位置真正的样本图像块被放入正图像集中,该正图像集能够至少包含一个正确的图像块。具体算法见:

优点:表现非常好。它不会像BOOSTING跟踪器那样结果漂移,并且在部分遮挡下可以完成合理的工作。但是相对来多KCF更好,不过MIL低版本的OpenCV也能使用,KCF版本要求更高。

缺点:无法检测是否跟踪失败,速度慢,且无法处理遮挡物体。

  1. KCF Tracker/核相关滤波跟踪器

KCF跟踪器基于前两个跟踪器的思想,该跟踪器利用MIL跟踪器中使用的多个正样本具有大的重叠区域的特性。这种重叠的数据导致了一些很好的数学属性,这个属性被跟踪器利用,实现更快,更准确地跟踪。

优点:准确性和速度都优于MIL,同时会报告跟踪失败。

缺点:完全遮挡下效果不好。

  1. TLD Tracker/单目标长时间跟踪器

顾名思义TLD表示跟踪,学习和检测,该跟踪器将长期跟踪任务分解为三个部分跟踪,学习和检测。跟踪器在帧与帧之间跟踪对象。并获取所有物体的外观并在必要时纠正跟踪器。学习估计跟踪器的错误并更新它以避免将来出现这些错误。这个跟踪器的输出往往会跳跃一下。例如,如果您正在跟踪行人并且场景中还有其他行人,则此跟踪器有时可以临时跟踪与您要跟踪的行人不同的行人。从积极的方面来说,这种追踪器算法可以在更大运动和遮挡范围跟踪物体。如果您有一个视频序列,其中对象隐藏在另一个对象后面,则此跟踪器可能是一个不错的选择。

优点:在多帧的遮挡下工作效果最佳,对于缩放的图像效果也不错。

缺点:很多误报使它几乎无法使用。

  1. MEDIANFLOW Tracker

在内部,该跟踪器在时间上向前和向后方向上跟踪对象,并且测量这两个轨迹之间的差异。 最小化该ForwardBackward错误使它们能够可靠地检测跟踪失败并在视频序列中选择可靠的轨迹。

优点:跟踪失败会进行报告,当运动是可预测的并且没有遮挡时效果很好。

缺点:在大规模运动下失败。

  1. MOSSE tracker/相关滤波跟踪

MOSSE使用自适应相关性进行目标跟踪,当使用单个帧初始化时产生稳定的相关滤波器。并最小化实际输出的卷积和期望输出卷积之间的方差来更新获得合适的滤波器。

优点:速度很快,容易实现

缺点:性能不那么好。速度不快。

  1. CSRT tracker/判别相关滤波器跟踪

基于判别相关滤波器(DCF-CSR)中,是现在应用最广的跟踪算法。

优点:精度很高,比KCF快一点

缺点:速度很慢

2.3 综合评价

追踪器选择方法:

如果追求高准确度,又能忍受慢一些的速度,那么就用CSRT;

如果对准确度的要求不苛刻,想追求速度,那么就选KCF;

纯粹想节省时间就用MOSSE。

下表总结了不同版本的OpenCV中可使用的追踪器和具体速度情况。FPS在CPU(I5)下对640X360的视频进行跟踪所获得的平均结果。

Tracker

FPS

OpenCV Version

FPS

BOOSTING

43.9

OpenCV 3.0+

43.9

MIL

11.0

OpenCV 3.0+

11.0

KCF

48.0

OpenCV 3.2+

48.0

TLD

23.0

OpenCV 3.0+

23.0

MEDIANFLOW

277.5

OpenCV 3.0+

277.5

MOSSE

144.2

OpenCV 3.1+

144.2

CSRT

16.3

OpenCV 3.4+3

16.3