当前位置:主页 > 软件编程 > Python代码 >

基于OpenCV4.2实现单目标跟踪

时间:2023-01-12 12:39:18 | 栏目:Python代码 | 点击:

在本教程中,我们将学习使用OpenCV跟踪对象。OpenCV 3.0开始引入跟踪API。我们将学习如何和何时使用OpenCV 4.2中可用的8种不同的跟踪器- BOOSTING, MIL, KCF, TLD, MEDIANFLOW, GOTURN, MOSSE和CSRT。我们还将学习现代跟踪算法背后的一般理论。

1.什么是目标跟踪

简单地说,在视频的连续帧中定位一个对象称为跟踪。

这个定义听起来很简单,但在计算机视觉和机器学习中,跟踪是一个非常广泛的术语,它包含了概念相似但技术不同的想法。例如,以下所有不同但相关的思想通常都是在对象跟踪中研究的

1.Dense Optical flow(稠密光流):这些算法有助于估计视频帧中每个像素的运动矢量。

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

3.Kalman Filtering(卡尔曼滤波):一种非常流行的信号处理算法,用于基于先验运动信息预测运动目标的位置。该算法的早期应用之一是导弹制导!

4.Meanshift and Camshift:这些是定位密度函数最大值的算法。它们也被用于跟踪。

5.Single object trackers(单一对象追踪器):在这类跟踪器中,第一帧使用一个矩形来标记我们想要跟踪的对象的位置。然后使用跟踪算法在随后的帧中跟踪目标。在大多数现实生活中的应用程序中,这些跟踪器是与对象检测器结合使用的。

6.Multiple object track finding algorithms(多目标追踪算法):在我们有快速目标检测器的情况下,在每帧中检测多个目标,然后运行轨迹查找算法来识别一帧中的哪个矩形对应于下一帧中的矩形是有意义的。

2.跟踪与检测

如果你曾经玩过OpenCV人脸检测,你知道它是实时工作的,你可以很容易地在每一帧中检测人脸。那么,为什么一开始就需要跟踪呢?让我们来探讨一下你可能想要在视频中跟踪对象而不仅仅是重复检测的不同原因。

1.跟踪比检测快:通常跟踪算法要比检测算法快。原因很简单。当您在跟踪前一帧中检测到的对象时,您会对该对象的外观有很多了解。你也知道在前一个坐标系中的位置以及它运动的方向和速度。所以在下一帧中,你可以利用所有这些信息来预测下一帧中物体的位置,并对物体的预期位置做一个小搜索来精确地定位物体。一个好的跟踪算法会使用它所拥有的关于目标的所有信息,而检测算法总是从头开始。因此,在设计一个高效的系统时,通常在每n帧上进行目标检测,在n-1帧之间使用跟踪算法。为什么我们不直接在第一帧检测目标,然后跟踪它呢?跟踪确实可以从它所拥有的额外信息中获益,但如果一个物体在障碍物后面停留了很长一段时间,或者它移动得太快,以至于跟踪算法无法跟上,你也会失去对它的跟踪。跟踪算法也经常会累积误差,跟踪对象的包围框会慢慢地偏离跟踪对象。我们会经常使用检测算法解决跟踪算法的这些问题。检测算法基于大数据训练,因此,他们对对象的一般类别有更多的了解。另一方面,跟踪算法更了解它们所跟踪的类的具体实例。

2.当检测失败时,跟踪可以提供帮助:如果你在视频中运行人脸检测器,而这个人的脸被物体遮挡,人脸检测器很可能会失败。一个好的跟踪算法将解决某种程度的遮挡。

3.跟踪保护身份ID:对象检测的输出是一个包含对象的矩形数组。但是,该对象没有附加身份。例如,在下面的视频中,一个检测红点的探测器将输出与它在一帧中检测到的所有点相对应的矩形。在下一帧中,它将输出另一个矩形数组。在第一帧中,一个特定的点可能由数组中位置10的矩形表示,而在第二帧中,它可能位于位置17。当在帧上使用检测时,我们不知道哪个矩形对应哪个对象。另一方面,追踪提供了一种将这些点连接起来的方法!

3.使用OpenCV 4实现对象跟踪

OpenCV 4附带了一个跟踪API,它包含了许多单对象跟踪算法的实现。在OpenCV 4.2中有8种不同的跟踪器可用- BOOSTING, MIL, KCF, TLD, MEDIANFLOW, GOTURN, MOSSE,和CSRT。

注意: OpenCV 3.2实现了这6个跟踪器- BOOSTING, MIL, TLD, MEDIANFLOW, MOSSE和GOTURN。OpenCV 3.1实现了这5个跟踪器- BOOSTING, MIL, KCF, TLD, MEDIANFLOW。OpenCV 3.0实现了以下4个跟踪器- BOOSTING, MIL, TLD, MEDIANFLOW。

在OpenCV 3.3中,跟踪API已经改变。代码检查版本,然后使用相应的API。

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

3.1使用OpenCV 4实现对象跟踪 C++代码

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

using namespace cv;
using namespace std;

// 转换为字符串
#define SSTR( x ) static_cast< std::ostringstream & >( ( std::ostringstream() << std::dec << x ) ).str()

int main(int argc, char **argv)
{
    // OpenCV 3.4.1中的跟踪器类型列表
    string trackerTypes[8] = {"BOOSTING", "MIL", "KCF", "TLD","MEDIANFLOW", "GOTURN", "MOSSE", "CSRT"};
    // vector <string> trackerTypes(types, std::end(types));

    // 创建一个跟踪器
    string trackerType = trackerTypes[2];

    Ptr<Tracker> tracker;

    #if (CV_MINOR_VERSION < 3)
    {
        tracker = Tracker::create(trackerType);
    }
    #else
    {
        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 == "GOTURN")
            tracker = TrackerGOTURN::create();
        if (trackerType == "MOSSE")
            tracker = TrackerMOSSE::create();
        if (trackerType == "CSRT")
            tracker = TrackerCSRT::create();
    }
    #endif
    // 读取视频
    VideoCapture video("videos/chaplin.mp4");
    
    // 如果视频没有打开,退出
    if(!video.isOpened())
    {
        cout << "Could not read video file" << endl; 
        return 1; 
    } 

    // 读第一帧
    Mat frame; 
    bool ok = video.read(frame); 

    // 定义初始边界框
    Rect2d bbox(287, 23, 86, 320); 

    // 取消注释下面的行以选择一个不同的边界框 
    // bbox = selectROI(frame, false); 
    // 显示边界框
    rectangle(frame, bbox, Scalar( 255, 0, 0 ), 2, 1 ); 

    imshow("Tracking", frame); 
    tracker->init(frame, bbox);
    
    while(video.read(frame))
    {     
        // 启动定时器
        double timer = (double)getTickCount();
        
        // 更新跟踪结果
        bool ok = tracker->update(frame, bbox);
        
        // 计算每秒帧数(FPS)
        float fps = getTickFrequency() / ((double)getTickCount() - timer);
        
        if (ok)
        {
            // 跟踪成功:绘制被跟踪对象
            rectangle(frame, bbox, Scalar( 255, 0, 0 ), 2, 1 );
        }
        else
        {
            // 跟踪失败
            putText(frame, "Tracking failure detected", Point(100,80), FONT_HERSHEY_SIMPLEX, 0.75, Scalar(0,0,255),2);
        }
        
        // 在帧上显示跟踪器类型
        putText(frame, trackerType + " Tracker", Point(100,20), FONT_HERSHEY_SIMPLEX, 0.75, Scalar(50,170,50),2);
        
        // 帧显示FPS
        putText(frame, "FPS : " + SSTR(int(fps)), Point(100,50), FONT_HERSHEY_SIMPLEX, 0.75, Scalar(50,170,50), 2);

        // 显示帧
        imshow("Tracking", frame);
        
        // 按ESC键退出。
        int k = waitKey(1);
        if(k == 27)
        {
            break;
        }

    }
}

3.2使用OpenCV 4实现对象跟踪 Python代码

import cv2
import sys

(major_ver, minor_ver, subminor_ver) = (cv2.__version__).split('.')?

if __name__ == '__main__' :

    # 建立追踪器
    # 除了MIL之外,您还可以使用

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

    if int(minor_ver) < 3:
        tracker = cv2.Tracker_create(tracker_type)
    else:
        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 == 'GOTURN':
            tracker = cv2.TrackerGOTURN_create()
        if tracker_type == 'MOSSE':
            tracker = cv2.TrackerMOSSE_create()
        if tracker_type == "CSRT":
            tracker = cv2.TrackerCSRT_create()

    # 读取视频
    video = cv2.VideoCapture("videos/chaplin.mp4")

    # 如果视频没有打开,退出。
    if not video.isOpened():
        print "Could not open video"
        sys.exit()

    # 读第一帧。
    ok, frame = video.read()
    if not ok:
        print('Cannot read video file')
        sys.exit()
    
    # 定义一个初始边界框
    bbox = (287, 23, 86, 320)

    # 取消注释下面的行以选择一个不同的边界框
    # bbox = cv2.selectROI(frame, False)

    # 用第一帧和包围框初始化跟踪器
    ok = tracker.init(frame, bbox)

    while True:
        # 读取一个新的帧
        ok, frame = video.read()
        if not ok:
            break
        
        # 启动计时器
        timer = cv2.getTickCount()

        # 更新跟踪器
        ok, bbox = tracker.update(frame)

        # 计算帧率(FPS)
        fps = cv2.getTickFrequency() / (cv2.getTickCount() - timer);

        # 绘制包围框
        if ok:
            # 跟踪成功
            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 :
            # 跟踪失败
            cv2.putText(frame, "Tracking failure detected", (100,80), cv2.FONT_HERSHEY_SIMPLEX, 0.75,(0,0,255),2)

        # 在帧上显示跟踪器类型名字
        cv2.putText(frame, tracker_type + " Tracker", (100,20), cv2.FONT_HERSHEY_SIMPLEX, 0.75, (50,170,50),2);
    
        # 在帧上显示帧率FPS
        cv2.putText(frame, "FPS : " + str(int(fps)), (100,50), cv2.FONT_HERSHEY_SIMPLEX, 0.75, (50,170,50), 2);

        # 显示结果
        cv2.imshow("Tracking", frame)

        # 按ESC键退出
        k = cv2.waitKey(1) & 0xff
        if k == 27 : break

4.跟踪算法解析

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

让我首先解释一些跟踪的一般原则。在跟踪中,我们的目标是在当前帧中找到一个对象,因为我们已经成功地在所有(或几乎所有)之前的帧中跟踪了这个对象。

因为我们一直跟踪对象直到当前帧,所以我们知道它是如何移动的。换句话说,我们知道运动模型的参数。运动模型只是一种花哨的说法,表示你知道物体在前几帧中的位置和速度(速度+运动方向)。如果你对物体一无所知,你可以根据当前的运动模型预测新的位置,你会非常接近物体的新位置。

但我们有比物体运动更多的信息。我们知道物体在之前的每一帧中的样子。换句话说,我们可以构建一个对对象的外观进行编码的外观模型。该外观模型可用于在运动模型预测的小邻域内搜索位置,从而更准确地预测物体的位置。

运动模型预测了物体的大致位置。外观模型对这个估计进行微调,以提供基于外观的更准确的估计。

如果对象非常简单,并且没有太多改变它的外观,我们可以使用一个简单的模板作为外观模型,并寻找该模板。然而,现实生活并没有那么简单。物体的外观会发生巨大的变化。为了解决这个问题,在许多现代跟踪器中,这个外观模型是一个以在线方式训练的分类器。别慌!让我用更简单的术语解释一下。

分类器的工作是将图像中的矩形区域分类为物体或背景。分类器接收图像patch作为输入,并返回0到1之间的分数,表示图像patch包含该对象的概率。当完全确定图像patch是背景时,分数为0;当完全确定patch是对象时,分数为1。

在机器学习中,我们用“在线”这个词来指在运行时进行动态训练的算法。离线分类器可能需要数千个示例来训练一个分类器,但在线分类器通常在运行时使用很少的示例进行训练。

通过向分类器输入正(对象)和负(背景)的例子来训练分类器。如果您想要构建一个用于检测猫的分类器,您可以使用数千张包含猫的图像和数千张不包含猫的图像来训练它。这样分类器学会区分什么是猫,什么不是。在构建一个在线分类器时,我们没有机会拥有数千个正面和负面类的例子。

让我们看看不同的跟踪算法是如何处理在线训练的这个问题的。

4.1 BOOSTING Tracker

该跟踪器基于AdaBoost的在线版本——基于HAAR级联的人脸检测器内部使用的算法。这个分类器需要在运行时用对象的正面和反面例子进行训练。将用户提供的初始包围盒(或其他目标检测算法提供的初始包围盒)作为目标的正例,将包围盒外的许多图像patch作为背景。

给定一个新的帧,分类器在前一个位置附近的每个像素上运行,并记录分类器的得分。对象的新位置是分数最高的位置。现在分类器又多了一个正样本。当更多的帧进来时,分类器就会用这些额外的数据更新。

优点:没有。 这个算法已经有10年的历史了,而且运行良好,但我找不到使用它的好理由,特别是当基于类似原则的其他高级跟踪器(MIL, KCF)可用时。
缺点:跟踪性能平庸。 它不能可靠地知道何时跟踪失败了。

4.2 MIL Tracker

这个跟踪器在思想上与上述的BOOSTING跟踪器相似。最大的区别是,它不是只考虑对象的当前位置作为一个正样本,而是在当前位置周围的一个小领域中寻找几个潜在的正样本。你可能会认为这不是一个好主意,因为在大多数这些“正样本”的例子中,物体不是居中的。

这就是多实例学习 (MIL) 的用武之地。在 MIL 中,您不指定正面和负面示例,而是指定正面和负面“袋子”。正面“袋子”中的图像集合并不都是正例。取而代之的是,正面袋子中只有一张图像需要是正面的例子!

在我们的示例中,一个正面袋子包含以对象当前位置为中心的patch,以及它周围的一个小邻域中的patch。即使被跟踪对象的当前位置不准确,当来自当前位置附近的样本被放入正面袋子中时,这个正面袋子很有可能包含至少一个对象很好地居中的图像。

优点:性能很好。 它不像BOOSTING跟踪器那样漂移,并且在部分遮挡下做了合理的工作。如果你正在使用OpenCV 3.0,这可能是你可用的最好的跟踪器。但是,如果您使用的是更高的版本,请考虑KCF。

缺点: 无法可靠地报告跟踪失败。不能从完全遮挡中恢复。

4.3 KCF Tracker

KFC 代表Kernelized Correlation Filters(Kernelized相关性过滤器)。该跟踪器建立在前两个跟踪器中提出的想法之上。该跟踪器利用了 MIL 跟踪器中使用的多个正样本具有较大重叠区域的事实。这种重叠数据导致了一些很好的数学特性,该跟踪器利用这些特性使跟踪更快、更准确。

优点:准确性和速度都优于 MIL,它报告的跟踪失败比 BOOSTING 和 MIL 更好。 如果您使用的是 OpenCV 3.1 及更高版本,我建议将其用于大多数应用程序。
缺点: 不能从完全遮挡中恢复。

4.4 TLD Tracker

TLD 代表跟踪、学习和检测。顾名思义,这个跟踪器将长期跟踪任务分解为三个部分——(短期)跟踪、学习和检测。从作者的论文中,“跟踪器逐帧跟踪对象。检测器定位到目前为止已观察到的所有外观,并在必要时纠正跟踪器。

学习估计检测器的错误并对其进行更新以避免将来出现这些错误。”这个跟踪器的输出往往会有点跳跃。例如,如果您正在跟踪行人并且场景中有其他行人,则此跟踪器有时可以临时跟踪与您打算跟踪的行人不同的行人。从积极的方面来说,这条轨迹似乎可以在更大的范围、运动和遮挡范围内跟踪对象。如果您有一个对象隐藏在另一个对象后面的视频序列,则此跟踪器可能是一个不错的选择。

优点:在多个帧的遮挡下效果最佳。此外,跟踪最好的规模变化。

缺点:大量的误报使得它几乎无法使用。

4.5 MEDIANFLOW Tracker

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

在我的测试中,我发现该跟踪器在运动可预测且较小时效果最佳。与其他跟踪器即使在跟踪明显失败时仍继续运行不同,该跟踪器知道跟踪何时失败。

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

缺点:在大运动下失败。

4.6 GOTURN tracker

在跟踪器类的所有跟踪算法中,这是唯一一种基于卷积神经网络 (CNN) 的算法。从 OpenCV 文档中,我们知道它“对视点变化、光照变化和变形具有鲁棒性”。但它不能很好地处理遮挡。

注意:GOTURN 是基于 CNN 的跟踪器,使用 Caffe 模型进行跟踪。 Caffe 模型和 proto 文本文件必须存在于代码所在的目录中。这些文件也可以从 opencv_extra 存储库下载、连接并在使用前提取。

4.7 MOSSE tracker

最小输出平方误差和 (MOSSE) 使用自适应相关性进行对象跟踪,在使用单帧初始化时会产生稳定的相关性滤波器。 MOSSE 跟踪器对光照、比例、姿势和非刚性变形的变化具有鲁棒性。它还根据峰值旁瓣(peak-to-sidelobe)比检测遮挡,这使跟踪器能够在对象重新出现时暂停并从中断的地方恢复。 MOSSE 跟踪器还以更高的 fps(450 fps 甚至更高)运行。除此之外,它还非常容易执行,与其他复杂追踪器一样准确,而且速度更快。但是,在性能尺度上,它落后于基于深度学习的跟踪器。

4.8 CSRT tracker

在DCF-CSR (Discriminative Correlation Filter with Channel and Spatial Reliability, DCF-CSR)中,我们使用空间可靠性映射来调整滤波器的支持度,使其适应帧中被选择区域的跟踪部分。这确保了所选区域的放大和定位,并改进了对非矩形区域或对象的跟踪。它只使用2个标准特性(hog和Colornames)。它也运行在一个相对较低的fps (25 fps),但提供了较高的目标跟踪精度。

您可能感兴趣的文章:

相关文章