標籤:電腦視覺 演算法 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通過光StreamCompute這一幀的對應位置points_tracked,然後反過來使用points_tracked計算對應的上一幀的位置points_back,然後對比points_prev和points_back之間的距離,按道理應該是接近0才對,但是因為光StreamCompute有誤差,因此,有的可能比較大,因此作者設定了一個閾值thr_fb 30,如果大於該閾值,表示得到的資料有誤,刪掉該點。
這麼做的目的是為了使跟蹤得到的結果更可靠。
由於時間關係,本文先分析到這。下一篇文章分析接下來的步驟。
本文為原創文章,轉載請註明出處:47775131