opencv (opencv_contrib) 实现目标跟踪

opencv (opencv_contrib) 实现目标跟踪

前提

需要安装 opencvopencv_contrib

请参考文章:linux 下 opencv_contrib 源代码编译方法

为什么需要跟踪?

通常跟踪比检测快

单目标检测速度可能为 100+ ms,而单目标跟踪的速度为 10+ ms。

目标跟踪可以基于前一帧检测的位置,运动方向,速度预测下一帧的位置。并围绕预期位置进行小范围搜索以准确定位物体。

比无依赖直接进行检测会快很多。

一个高效的系统应该通常会在 n 帧的第一帧进行使用检测算法进行目标检测,此后 n - 1 帧使用跟踪算法。

为什么不基于第一次检测结果的基础上,后续全部使用跟踪算法?

因为跟踪在遇到遮挡,运动方向和速度高速变化以致于跟踪算法跟不上时,可能会导致跟踪失败。

而且跟踪在一定程度上会有误差参数,误差一直积累下去时,会导致跟踪偏离原始目标。

所以需要检测算法对目标进行修正,一定程度上避免跟踪误差的积累。

当检测失败时,跟踪可以提供帮助

检测在目标被遮挡时,会检测失败。

而一个好的跟踪算法,还能依靠前面帧的位置,方向,速度信息,预测当前的目标。

一定程度上解决一些遮挡问题、

目标跟踪

opencv (opencv_contrib 模块) 目前支持 8 种跟踪算法。

跟踪算法 opencv 支持的最低版本 说明
Boosting opencv 3.0.0 比较古老的目标跟踪算法,速度慢
MIL opencv 3.0.0 速度快,但是失败率较高
KCF opencv 3.1.0 比 Boosting 和 MIL 快,但是遮挡情况下效果差
TLD opencv 3.0.0 (TLD 目前有 3 种版本算法。TLD 1.0,TLD 2.0,TLD 3.0。opencv 的 TLD 跟踪效果较差,怀疑是基于 TLD 1.0 算法实现的)
MedianFlow opencv 3.0.0 跟踪效果较好,但对于快速移动/跳动的物体容易失败。
GOTURN opencv 3.2.0 基于深度学习的目标检测器。需要额外的 GOTURN 模型,可以自行训练目标跟踪模型。
MOSSE opencv 3.4.1 速度比其它算法快,但是准确率比 CSRT 和 KCF 低。
CSRT opencv 3.4.2 准确率比 KCF 稍高,但速度不如 KCF。

建议:

  • CSRT:如果可以接受较慢速度下,追求高准确度。
  • KCF:速度要求稍高,准确度要求不高。
  • MOSSE:只要求高速,不要求准确度。
  • GOTURN:可以自行训练模型,提高识别效果。

opencv 目标跟踪

参考代码如下:

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

#define TEST_MODE_VIDEO         0
#define TEST_MODE_CAMERA        1

#define ENABLE_TEST_MODE        TEST_MODE_VIDEO
#define ENABLE_SHOW_FPS         1

// #define TEST_VIDEO_FILE         "videos/run.mp4"

enum EnumTrackerType {
    E_TRACKER_TYPE_BOOSTING,
    E_TRACKER_TYPE_MIL,
    E_TRACKER_TYPE_KCF,
    E_TRACKER_TYPE_TLD,
    E_TRACKER_TYPE_MEDIANFLOW,
    E_TRACKER_TYPE_GOTURN,
    E_TRACKER_TYPE_MOSSE,
    E_TRACKER_TYPE_CSRT,
    E_TRACKER_TYPE_MAX
};

#define ENUM_TRACKER_TYPE(type)       E_TRACKER_TYPE_##type
#define TEXT_TRACKER_TYPE(type)       #type
#define VEC_TRACKER_TYPE(vec, type)   do {  \
                                          vec[ENUM_TRACKER_TYPE(type)] = TEXT_TRACKER_TYPE(type); \
                                      } while(0)

std::vector<std::string> trackerTypes(E_TRACKER_TYPE_MAX);

int initTrackerTypes(std::vector<std::string> & vec) {
    // vec.reserve(E_TRACKER_TYPE_MAX);
    VEC_TRACKER_TYPE(vec, BOOSTING);
    VEC_TRACKER_TYPE(vec, MIL);
    VEC_TRACKER_TYPE(vec, KCF);
    VEC_TRACKER_TYPE(vec, TLD);
    VEC_TRACKER_TYPE(vec, MEDIANFLOW);
    VEC_TRACKER_TYPE(vec, GOTURN);
    VEC_TRACKER_TYPE(vec, MOSSE);
    VEC_TRACKER_TYPE(vec, CSRT);
    return vec.size();
}

cv::Ptr<cv::Tracker> createTrackerByName(EnumTrackerType eTrackerType) {
    switch( eTrackerType ) {
        case ENUM_TRACKER_TYPE(BOOSTING):   return cv::TrackerBoosting::create();
        case ENUM_TRACKER_TYPE(MIL):        return cv::TrackerMIL::create();
        case ENUM_TRACKER_TYPE(KCF):        return cv::TrackerKCF::create();
        case ENUM_TRACKER_TYPE(TLD):        return cv::TrackerTLD::create();
        case ENUM_TRACKER_TYPE(MEDIANFLOW): return cv::TrackerMedianFlow::create();
        case ENUM_TRACKER_TYPE(GOTURN):     return cv::TrackerGOTURN::create();
        case ENUM_TRACKER_TYPE(MOSSE):      return cv::TrackerMOSSE::create();
        case ENUM_TRACKER_TYPE(CSRT):       return cv::TrackerCSRT::create();
        default:
            std::cout << "Incorrect tracker index : " << eTrackerType << std::endl;
            for (int i = 0, len = trackerTypes.size(); i < len; ++i) {
                printf(" [ %d ] %s\n", i, trackerTypes[i].c_str());
            }
            break;
    }
    cv::Ptr<cv::Tracker> tracker;
    return tracker;
}

void getRandomColors(std::vector<cv::Scalar> &colors, int numColors) {
    cv::RNG rng(0);
    for(int i = 0; i < numColors; ++i) {
        colors.push_back(cv::Scalar(rng.uniform(0,255), rng.uniform(0, 255), rng.uniform(0, 255)));
    }
}

int main(int argc, char * argv[]) {
    std::string videoPath = TEST_VIDEO_FILE;
    int trackerTypeMax = initTrackerTypes(trackerTypes);
    int trackerTypeIndex = 0;

    if (argc >= 2) {
        int index = argv[1][0] - '0';
        if (index >= 0 && index < trackerTypeMax) {
            trackerTypeIndex = index;
        }
    }
    if (argc >= 3) {
        videoPath = argv[2];
    }

    std::string trackerType = trackerTypes[trackerTypeIndex];
    std::vector<cv::Rect> bboxes;
    cv::Mat frame;

    std::cout << "Default tracking algoritm is " << trackerType << std::endl;
    std::cout << "Available tracking algorithms are:" << std::endl;
    for (int i = 0; i < trackerTypeMax; ++i) {
        printf(" [ %d ] %s\n", i, trackerTypes[i].c_str());
    }

#if (ENABLE_TEST_MODE == TEST_MODE_VIDEO)
    cv::VideoCapture cap(videoPath);
#elif (ENABLE_TEST_MODE == TEST_MODE_CAMERA)
    cv::VideoCapture cap(0);
#endif

    if(!cap.isOpened()) {
        std::cout << "Error opening video file " << videoPath << std::endl;
        return -1;
    }

    cap.read(frame);

    bool showCrosshair = true, fromCenter = false;
    std::cout << "\n==========================================================\n";
    std::cout << "OpenCV says press c to cancel objects selection process" << std::endl;
    std::cout << "It doesn't work. Press Escape to exit selection process" << std::endl;
    std::cout << "\n==========================================================\n";
    cv::selectROIs("MultiTracker", frame, bboxes, showCrosshair, fromCenter);

    if (bboxes.size() < 1) {
        return 0;
    }

    std::vector<cv::Scalar> colors;
    getRandomColors(colors, bboxes.size());
    cv::Ptr<cv::MultiTracker> multiTracker = cv::MultiTracker::create();

    for(int i = 0; i < bboxes.size(); ++i) {
        multiTracker->add(createTrackerByName((EnumTrackerType)trackerTypeIndex), frame, cv::Rect2d(bboxes[i]));
    }

    std::cout << "\n==========================================================\n";
    std::cout << "Started tracking, press ESC to quit." << std::endl;

#if ENABLE_SHOW_FPS
    char strText[30];
    sprintf(strText, "[ %s ] FPS: ", trackerType.c_str());
    const int len = strlen(strText);
#endif

    while(cap.isOpened()) {
        cap.read(frame);
        if (frame.empty()) {
            break;
        }

    #if ENABLE_SHOW_FPS
        double timer = (double) cv::getTickCount();
    #endif
        multiTracker->update(frame);
    #if ENABLE_SHOW_FPS
        float fps = cv::getTickFrequency() / ((double) cv::getTickCount() - timer);
    #endif

        for(unsigned i = 0; i < multiTracker->getObjects().size(); ++i) {
            cv::rectangle(frame, multiTracker->getObjects()[i], colors[i], 2, 1);
        }

    #if ENABLE_SHOW_FPS
        sprintf(strText + len, "%0.2f", fps);
        cv::putText(frame, strText, cv::Point(100,50), cv::FONT_HERSHEY_SIMPLEX, 0.75, cv::Scalar(0, 0, 250), 2);
    #endif
        cv::imshow("MultiTracker", frame);

        if (cv::waitKey(1) == 27) {
            break;
        }
    }
    return 0;
}

使用方式:

# 编译
g++ multiTracker.cpp `pkg-config --libs --cflags opencv` -o multiTracker

# 运行
# 第一个参数:使用哪一种跟踪算法
#     0 - BOOSTING
#     1 - MIL
#     2 - KCF
#     3 - TLD
#     4 - MEDIANFLOW
#     5 - GOTURN (需要下载 goturn 模型)
#     6 - MOSSE
#     7 - CSRT
# 第二个参数:测试视频的路径
./multiTracker 0 ./video.mp4
  1. 运行后,需要框选目标。
  2. 框选目标后,按 SPACE 或 ENTER 键确定
  3. 如果需要框选多个目标,重复步骤 1, 2.
  4. 选定目标后,按 ESC 进行目标跟踪。

GOTURN 运行需要安装 goturn 模型。

下载模块可以参考:GOTURN 模型

需要把模型放于与 multiTracker 同一级目录。

跟踪算法测试结果

0. BOOSTING

opencv (opencv_contrib) 实现目标跟踪

1. MIL

opencv (opencv_contrib) 实现目标跟踪

2. KCF

opencv (opencv_contrib) 实现目标跟踪

3. TLD

opencv (opencv_contrib) 实现目标跟踪

4. MEDIANFLOW

opencv (opencv_contrib) 实现目标跟踪

5. GOTURN

opencv (opencv_contrib) 实现目标跟踪

6. MOSSE

opencv (opencv_contrib) 实现目标跟踪

7. CSRT

opencv (opencv_contrib) 实现目标跟踪