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