計算機視覺CV 之 CMT跟蹤算法分析二

1 前言

在上一篇文章中,我對CMT算法做了初步的介紹,並且初步分析了一下CppMT的代碼,在本篇文章中,我將結合作者的論文更全面細緻的分析CMT算法。

這裏先說明一下,作者關於CMT算法寫了兩篇文章:
Consensus-based Matching and Tracking of Keypoints for Object Tracking (wacv2014 best paper reward)
Clustering of Static-Adaptive Correspondences for Deformable Object Tracking (cvpr 2015)
其中wacv的文章從更工程的角度來分析CMT的算法,寫出來其詳細的流程,比較推薦閱讀。

2 CMT 算法流程

這裏寫圖片描述
這裏我直接截取了文章中的原圖。

我把它翻譯一下:

算法 CMT
輸入: 視頻幀,初始的物體框
輸出:每一幀視頻的物體框
要求:後繼的物體框能夠保持框住初始框框住的物體
步驟:
Step 1:檢測初始視頻幀的所有特徵點和特徵描述,不僅僅是框內的點而是整個圖像的點,所以在代碼中可以看到,初始的database數據庫的特徵描述包含了前景(框內)和背景(框外)的特徵
Step 2:將初始框內的特徵描述賦給K1
Step 3:從第二幀開始
Step 4:檢測視頻幀的特徵點P
Step 5:將特徵點P與O匹配,獲取匹配的特徵點M
Step 6:利用上一幀的特徵點使用光流法跟蹤得到這一幀的特徵點的位置T
Step 7:融合特徵點M和特徵點T得到這一幀的總的特徵點K’
Step 8:根據K’估計特徵點相對初始幀特徵的縮放比例
Step 9:根據K’估計特徵點相對初始幀特徵的旋轉比例
Step 10:根據Step7,8,9得到的數據計算每一個特徵點的Vote
Step 11:採用聚類的方法選取最大的類也就是最一致的VoteC
Step 12:將VoteC轉換回特徵點得到最後這一幀的有效特徵點
Step 13:判斷VoteC的長度是否大於最小閾值,如果是則計算最後新的旋轉矩形框的參數,如果不是也就是框太小了則輸出0

下面結合代碼及論文分析每一步

Step 1,2 初始化

在CMT.cpp的代碼中可以比較清晰的理解,就是使用openCV的Fast或者BRISK特徵檢測及特徵描述。然後關鍵是存儲有效的數據在數據庫用於之後的匹配,主要在以下兩個代碼

    //Initialize matcher 初始化匹配器
    matcher.initialize(points_normalized, descs_fg, classes_fg, descs_bg, center);

    //Initialize consensus 初始化一致器
    consensus.initialize(points_normalized);

進去看一下細節:

void Matcher::initialize(const vector<Point2f> & pts_fg_norm, const Mat desc_fg, const vector<int> & classes_fg,
        const Mat desc_bg, const Point2f center)
{

    //Copy normalized points 存儲 正規化的點
    this->pts_fg_norm = pts_fg_norm;

    //Remember number of background points 存儲背景的特徵點的數量
    this->num_bg_points = desc_bg.rows;

    //Form database by stacking background and foreground features
    // 合成前景和背景的特徵到一個Mat文件中
    if (desc_bg.rows > 0 && desc_fg.rows > 0)
        vconcat(desc_bg, desc_fg, database);
    else if (desc_bg.rows > 0)
        database = desc_bg;
    else
        database = desc_fg;

    //Extract descriptor length from features 根據特徵抽取描述其長度
    desc_length = database.cols*8;

    // classes的作用就是爲了對應找到的特徵,從而可以知道上一幀的特徵位置計算一致性
    //Create background classes (-1) 創建背景索引
    vector<int> classes_bg = vector<int>(desc_bg.rows,-1);

    //Concatenate fg and bg classes 連接前景和背景的索引
    classes = classes_bg;
    classes.insert(classes.end(), classes_fg.begin(), classes_fg.end());

    //Create descriptor matcher 創建描述匹配器BruteForce-Hamming
    bfmatcher = DescriptorMatcher::create("BruteForce-Hamming");

}
void Consensus::initialize(const vector<Point2f> & points_normalized)
{

    //Copy normalized points 複製正規化的點
    this->points_normalized = points_normalized;

    size_t num_points = points_normalized.size();

    //Create matrices of pairwise distances/angles 創建矩陣用於計算任何兩個點之間的距離和角度
    distances_pairwise = Mat(num_points, num_points, CV_32FC1);
    angles_pairwise = Mat(num_points, num_points, CV_32FC1);

    for (size_t i = 0; i < num_points; i++)
    {
        for (size_t j = 0; j < num_points; j++)
        {
            Point2f v = points_normalized[i] - points_normalized[j];

            float distance = norm(v);
            float angle = atan2(v.y,v.x);
            // 這裏計算特徵點兩兩相對距離和相對角度用於計算一致性
            distances_pairwise.at<float>(i,j) = distance;
            angles_pairwise.at<float>(i,j) = angle;
        }

    }

}

那麼特徵的角度距離是按下圖意思計算的,我想對應一下代碼還是很好理解的:
這裏寫圖片描述

接下來分析Step 3,4,5,6.

Step 3,4,5,6 分析

關鍵是跟蹤和匹配
在processFrame函數中可以看到。
跟蹤使用:

    //Track keypoints
    vector<Point2f> points_tracked;
    vector<unsigned char> status;
    // 利用光流法計算關鍵點的當前位置。
    tracker.track(im_prev, im_gray, points_active, points_tracked, status);

深入查看代碼發現作者使用了雙向的跟蹤:

void Tracker::track(const Mat im_prev, const Mat im_gray, const vector<Point2f> & points_prev,
        vector<Point2f> & points_tracked, vector<unsigned char> & status)
{

    if (points_prev.size() > 0)
    {
        vector<float> err; //Needs to be float

        //Calculate forward optical flow for prev_location 計算前向位置的光流(即特徵點的移動)
        calcOpticalFlowPyrLK(im_prev, im_gray, points_prev, points_tracked, status, err);

        vector<Point2f> points_back;
        vector<unsigned char> status_back;
        vector<float> err_back; //Needs to be float

        //Calculate backward optical flow for prev_location 計算後向光流
        calcOpticalFlowPyrLK(im_gray, im_prev, points_tracked, points_back, status_back, err_back);

        //Traverse vector backward so we can remove points on the fly 刪除掉飛掉的點 
        for (int i = points_prev.size()-1; i >= 0; i--)
        {
            float l2norm = norm(points_back[i] - points_prev[i]);

            bool fb_err_is_large = l2norm > thr_fb;

            if (fb_err_is_large || !status[i] || !status_back[i])
            {
                points_tracked.erase(points_tracked.begin() + i);

                //Make sure the status flag is set to 0
                status[i] = 0;
            }

        }

    }

}

基本的思路就是先使用上一幀的特徵點points_prev通過光流計算這一幀的對應位置points_tracked,然後反過來使用points_tracked計算對應的上一幀的位置points_back,然後對比points_prev和points_back之間的距離,按道理應該是接近0纔對,但是因爲光流計算有誤差,因此,有的可能比較大,因此作者設置了一個閾值thr_fb 30,如果大於該閾值,表示得到的數據有誤,刪掉該點。
這麼做的目的是爲了使跟蹤得到的結果更可靠。

由於時間關係,本文先分析到這。下一篇文章分析接下來的步驟。

本文爲原創文章,轉載請註明出處:https://blog.csdn.net/songrotek

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章