1 簡介
這個算法的全稱是Clustering of Static-Adaptive Correspondences for Deformable Object Tracking,文章發表在CVPR2015上,官方的網址爲:
http://www.gnebehay.com/cmt/
這個作者就是OpenTLD的C++版本的作者,包括ROS版的OpenTLD也是基於他的代碼改的。
對於CMT這個跟蹤算法,我在iPhone5s上做了實際測試,跟蹤效果超乎想象,在我比較了TLD,CT,Color Tracking,Struck等等頂級算法的效果之後得到了這樣的結論。這個算法是可以實用的跟蹤算法,雖然它也有缺點,之後我會說。實時性和跟蹤效果都一流。
本文主要目的是分析這個跟蹤算法。
2 基本原理
對於物體的視覺跟蹤,基本的思路就是能夠不斷監測到物體的特徵,從而不斷的得到物體的位置,實現跟蹤。常見的有三種方法:
第1是基於整體的模型來跟蹤,比如說TLD,通過不斷的更新模型(也就是學習的過程)來實現對物體特徵的良好表示。
第2是基於物體的局部來跟蹤,就是將物體分解爲多個部分,對每一部分進行單獨的跟蹤,採用光流等方法
第3是基於物體的特徵點來跟蹤,就是實時的監測物體的特徵點,與一開始的特徵點進行匹配的方法來實現物體的跟蹤。
從上面的方法看現在的跟蹤算法確實不能簡單的用跟蹤兩個字來描述,裏面的算法其實使用了物體檢測,識別,機器學習等各種各樣的方法。只要能框住視頻中的一個物體,然後不斷跟着,那麼這個算法就是跟蹤算法,跟蹤算法的好壞也完全取決於能不能很好的框住。實際上,現在很多跟蹤算法就是檢測的算法。
那麼CMT算法採用的是第三種思路,就是利用特徵點。爲什麼?第一個理由恐怕是快!如果以神經網絡來獲取特徵,那麼那麼多的參數,算到什麼時候?不知道。而採用特徵點的話,我們知道OpenCV中集成了很多檢測特徵點的算法,比如SIFT,FAST,BRISK等等,有的比如FAST速度很快的。而且這麼做連滑動窗口檢測都可以省掉啦。
那麼問題在於如何判斷下一幀的圖像中哪些特徵點是與當前的框中的特徵點相匹配的問題了?只要能夠很好地找到下一幀中物體的特徵點,跟蹤也就完成了。
因此,爲了解決這個問題,作者做了一個看起來很簡單的創新:就是計算特徵點的相對位置,以框的中心來進行計算,對於不形變的物體而言,不管物體怎麼移動旋轉,其上面的特徵點相對中心的距離是在縮放比例下是確定的,因此可以由此來排除不是的特徵點。
作者獲取下一幀的特徵點做了兩部分工作:1個是計算前一幀的框中的特徵點的光流,從而得到當前幀的特徵點位置,另一個方法是直接計算當前幀的特徵點,並與上一幀的特徵點進行匹配,得到相匹配的特徵點,然後把兩個得到的特徵點都融合在一起。就得到了下一幀的初步的特徵點。然後在對特徵點進行篩選,採用的就是上一段說的方法。
基本原理就是上面說的,下面我們從代碼級別來分析一下這個算法。
3 CMT.cpp代碼分析
在使用CMT算法時,我們要先初始化,也就是確定第一幀以及框框,然後進行處理下一幀。
void initialize(const Mat im_gray, const cv::Rect rect); void processFrame(const Mat im_gray);
先看初始化的代碼:
void CMT::initialize(const Mat im_gray, const cv::Rect rect)
{
//Remember initial size 存儲跟蹤區域的初始大小
size_initial = rect.size();
//Remember initial image 存儲初始灰度圖像
im_prev = im_gray;
//Compute center of rect 計算跟蹤區域的中心位置
Point2f center = Point2f(rect.x + rect.width/2.0, rect.y + rect.height/2.0);
//Initialize detector and descriptor 初始化檢測器FAST和描述器BRISK
detector = FeatureDetector::create(str_detector);
descriptor = DescriptorExtractor::create(str_descriptor);
//Get initial keypoints in whole image and compute their descriptors
vector<KeyPoint> keypoints;
detector->detect(im_gray, keypoints); // 檢測初始圖像的所有關鍵點
//Divide keypoints into foreground and background keypoints according to selection 分離出前景和背景的關鍵點,前景即跟蹤框內
vector<KeyPoint> keypoints_fg;
vector<KeyPoint> keypoints_bg;
for (size_t i = 0; i < keypoints.size(); i++)
{
KeyPoint k = keypoints[i];
Point2f pt = k.pt;
if (pt.x > rect.x && pt.y > rect.y && pt.x < rect.br().x && pt.y < rect.br().y)
{
keypoints_fg.push_back(k);
}
else
{
keypoints_bg.push_back(k);
}
}
//Create foreground classes 創建前景索引序號(即每個序號對應一個特徵點)
vector<int> classes_fg;
classes_fg.reserve(keypoints_fg.size());
for (size_t i = 0; i < keypoints_fg.size(); i++)
{
classes_fg.push_back(i);
}
//Compute foreground/background features 計算前景和背景的特徵描述
Mat descs_fg;
Mat descs_bg;
descriptor->compute(im_gray, keypoints_fg, descs_fg);
descriptor->compute(im_gray, keypoints_bg, descs_bg);
//Only now is the right time to convert keypoints to points, as compute() might remove some keypoints 將關鍵點轉換爲點存儲
vector<Point2f> points_fg;
vector<Point2f> points_bg;
for (size_t i = 0; i < keypoints_fg.size(); i++)
{
points_fg.push_back(keypoints_fg[i].pt);
}
for (size_t i = 0; i < keypoints_bg.size(); i++)
{
points_bg.push_back(keypoints_bg[i].pt);
}
//Create normalized points 創建正規化的點,即計算前景的關鍵點到前景框中的相對位置作爲正規化的點的座標
vector<Point2f> points_normalized;
for (size_t i = 0; i < points_fg.size(); i++)
{
points_normalized.push_back(points_fg[i] - center);
}
//Initialize matcher 初始化匹配器
matcher.initialize(points_normalized, descs_fg, classes_fg, descs_bg, center);
//Initialize consensus 初始化一致器
consensus.initialize(points_normalized);
//Create initial set of active keypoints 創建初始的活動點,即前景關鍵點的座標
for (size_t i = 0; i < keypoints_fg.size(); i++)
{
points_active.push_back(keypoints_fg[i].pt);
classes_active = classes_fg;
}
}
再看處理的函數:
void CMT::processFrame(Mat im_gray) {
//Track keypoints
vector<Point2f> points_tracked;
vector<unsigned char> status;
// 利用光流法計算關鍵點的當前位置。
tracker.track(im_prev, im_gray, points_active, points_tracked, status);
//keep only successful classes 只保留正確的類
vector<int> classes_tracked;
for (size_t i = 0; i < classes_active.size(); i++)
{
if (status[i])
{
classes_tracked.push_back(classes_active[i]);
}
}
//Detect keypoints, compute descriptors 計算當前圖像的關鍵點
vector<KeyPoint> keypoints;
detector->detect(im_gray, keypoints);
// 計算當前圖像特徵點的描述
Mat descriptors;
descriptor->compute(im_gray, keypoints, descriptors);
//Match keypoints globally 在全局和之前的數據庫匹配特徵點,計算出匹配的特徵點
vector<Point2f> points_matched_global;
vector<int> classes_matched_global;
matcher.matchGlobal(keypoints, descriptors, points_matched_global, classes_matched_global);
//Fuse tracked and globally matched points
//融合跟蹤和匹配的點 將兩種點都放在一起,並且不重複
vector<Point2f> points_fused;
vector<int> classes_fused;
fusion.preferFirst(points_tracked, classes_tracked, points_matched_global, classes_matched_global,
points_fused, classes_fused);
// 估計旋轉和縮放利用最終的融合點
//Estimate scale and rotation from the fused points
float scale;
float rotation;
consensus.estimateScaleRotation(points_fused, classes_fused, scale, rotation);
//Find inliers and the center of their votes
//計算一致性,獲取相關的在框內的點inlier和中心
Point2f center;
vector<Point2f> points_inlier;
vector<int> classes_inlier;
consensus.findConsensus(points_fused, classes_fused, scale, rotation,
center, points_inlier, classes_inlier);
//Match keypoints locally 局部匹配
vector<Point2f> points_matched_local;
vector<int> classes_matched_local;
matcher.matchLocal(keypoints, descriptors, center, scale, rotation, points_matched_local, classes_matched_local);
//Clear active points
points_active.clear();
classes_active.clear();
//Fuse locally matched points and inliers
// 融合局部匹配的點和inliers
fusion.preferFirst(points_matched_local, classes_matched_local, points_inlier, classes_inlier, points_active, classes_active);
//TODO: Use theta to suppress result
// 計算出新的跟蹤窗口
bb_rot = RotatedRect(center, size_initial * scale, rotation/CV_PI * 180);
//Remember current image 更新上一幀圖像
im_prev = im_gray;
}
從上面的代碼可以比較清晰的知道整個處理流程,具體的細節比如匹配的方式,檢測一致性的算法,得在具體看其他文件的代碼,本文暫時不展開。
4 CMT算法評價
缺點:
1、沒有更新模型的過程,導致物體角度變化大時找不到特徵點
2、特徵點太多的時候會導致速度變慢
3、特徵點少的時候就跟蹤不上
4、移動物體在很多情況下特徵點會發生變化,很容易導致跟蹤不上。
優點:
1、代碼簡單,C++和Python都有,使用OpenCV實現
2、速度還是比較快的,至少我iPhone上測試還不錯
3、跟蹤效果特別靜態物體簡直近乎完美。夠了。