OpenCV目标跟踪

Opencv作为图像处理开源库包含了Object Tracking目标追踪的一些API,使用Opencv能够方便快捷的编写目标追踪程序。OpenCV实现多种目标跟踪算法,只需要选择对应的跟踪器即可实现目标跟踪。纯应用而已,暂且不谈理论相关的内容。Demo是使用KCF跟踪器对视频中的汽车进行目标跟踪。

OpenCV 的跟踪算法

Opencv4.0目前包含了8种目标追踪算法:

Boosting

基于在线的AdaBoost, 这个分类器需要对对象的正、负例进行训练。用户提供的初始化框来作为对象的正例,并将边界框外的图像块作为背景。没有优点。这个算法已经有10年的历史了,不再推荐使用。

CSRT

判别性相关滤波器。 优点:精确度比KCF稍高。 缺点:速度不如KCF块。

GOTURN

在跟踪器类的所有跟踪算法中,这是唯一基于卷积神经网络(CNN)的算法。也是唯一一个使用离线训练的模型,因此它比其他跟踪器更快。从Opencv文档可以看出该算法对视角变化、光照、变形都具有很好的鲁棒性,但是对于遮挡性能较差。

KCF

这个跟踪器基于前面两个跟踪器中提出的想法。该跟踪器在MIL跟踪器中使用的多个正样本具有较大的重叠区域。 优点:精度和速度都比MIL好,建议在大多数应用程序中使用该算法。 缺点:还是完全遮挡

MedianFlow

经过测试发现这个跟踪器在小范围运动情况下表现最好。即使跟踪失败了还能继续跟踪,这个跟踪器知道什么时候失败。 优点:跟踪失败报告,小范围运动下表现好。缺点:大范围运动,该算法失灵。

MIL

这个跟踪器与上面描述的boost跟踪器类似。最大的区别是,它不是只考虑对象的当前位置作为正类,还考虑当前位置邻域范围的潜在位置作为正类。 优点: 性能很好。它不boosting跟踪器那样,在部分遮挡下依然表现挺佳。但效果不如KCF好。 缺点:跟踪失败没有可靠的报告。完全遮挡的话性能差

MOSSE

如果对速度要求非常高,MOSSE可能是你的更好选择。 优点:速度比CSRT和KCF都要快。 缺点:精度没有CSRT和KCF高。

TLD

TLD stands for Tracking, learning and detection. 如果有一个视频序列,对象隐藏在另一个对象后面,这个跟踪器可能是一个不错的选择。 优点:在多帧的情况下,在遮挡的情况下工作最好。此外,该算法能很好的应对尺度变化。缺点:大量的假判定得其几乎无法使用。

如何选择

当需要更高的目标跟踪精度并可承受较慢的fps吞吐量时,请使用 CSRT

当需要更快的FPS吞吐量,但可以允许对象跟踪精度稍低时,请使用 KCF

当需要纯速度快时使用 MOSSE

KCF跟踪器示例

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

首先要创建一个跟踪器并实例化,本文仅使用KCF跟踪器作为示例:

cv::Ptr<cv::TrackerKCF> tracker; // KCF跟踪器
tracker = cv::TrackerKCF::create();

初始化视频流,并定义显示窗口:

// 读取视频数据
cv::VideoCapture video("/Users/xxx/Downloads/tracker.mp4");
// 读取摄像头数据
//cv::VideoCapture video(0);

// 创建窗口
static const std::string kWinName = "object tracking in OpenCV";
cv::namedWindow(kWinName,cv::WINDOW_AUTOSIZE);
if(!video.isOpened()){
  std::cout <<"Could not read video file"<< std::endl;
  return -1;
}

为了能够在视频中选取跟踪目标,需要定义一个初始化ROI区域:

// 画框选中跟踪目标
cv::Rect2i bbox = cv::selectROI(frame, false);
cv::rectangle(frame, bbox, cv::Scalar(255, 0, 0), 2, 1);
// 跟踪器初始化
tracker->init(frame, bbox);

从视频流循环帧分析:

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

  // Update the tracking result 跟新跟踪器算法
  if(tracker->update(frame, bbox)) {
    std::cout << "Update the tracking result" << std::endl;
  }
  // Calculate Frames per second (FPS) 计算FPS
  float fps = cv::getTickFrequency() / ((double) cv::getTickCount() - timer);

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

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

  // Display frame.
  cv::imshow("OpenCV Tracking", frame);
  // Exit if ESC pressed.
  int k = cv::waitKey(1);
  if (k == 27) {
    break;
  }
}

因为每一帧对于跟踪器都会重新计算跟踪目标的位置。使用update()方法将定位对象的新位置,并返回true和边界box对象,通过确保跟踪器是新的,如果选择了某个对象,我们需要更新该对象的位置。

完整代码如下:

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

int main() {
    cv::Ptr<cv::TrackerKCF> tracker; // KCF跟踪器
    cv::Mat frame; // 保存每帧图像

    tracker = cv::TrackerKCF::create();
    // 读取视频数据
    cv::VideoCapture video("/Users/zchanglin/Downloads/tracker.mp4");
    // 读取摄像头数据
    //cv::VideoCapture video(0);
    // 创建窗口
    static const std::string kWinName = "object tracking in OpenCV";
    cv::namedWindow(kWinName,cv::WINDOW_AUTOSIZE);
    if(!video.isOpened()){
        std::cout <<"Could not read video file"<< std::endl;
        return -1;
    }
    bool ok = video.read(frame);
    // 画框选中跟踪目标
    cv::Rect2i bbox = cv::selectROI(frame, false);
    cv::rectangle(frame, bbox, cv::Scalar(255, 0, 0), 2, 1);


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

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

        // Update the tracking result 跟新跟踪器算法
        if(tracker->update(frame, bbox)) {
            std::cout << "Update the tracking result" << std::endl;
        }
        // Calculate Frames per second (FPS) 计算FPS
        float fps = cv::getTickFrequency() / ((double) cv::getTickCount() - timer);

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

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

        // Display frame.
        cv::imshow("OpenCV Tracking", frame);
        // Exit if ESC pressed.
        int k = cv::waitKey(1);
        if (k == 27) {
            break;
        }
    }

    // 释放跟踪器
    tracker.release();
    return 0;
}

下面是CMakeLists.txt

cmake_minimum_required(VERSION 3.19)
project(TrackerDemo)

set(CMAKE_CXX_STANDARD 11)

find_package(OpenCV REQUIRED)

include_directories(OpenCV_INCLUDE)

add_executable(TrackerDemo main.cpp)

target_link_libraries(TrackerDemo PUBLIC ${OpenCV_LIBS})

Demo视频外链: https://img.zouchanglin.cn/1c67a7edc7c1b1a575d07105a1f53c92.mp4