標籤:電腦視覺 演算法 cmt
1 前言
在上一篇blog中,我們分析了CMT的整體演算法流程及前面幾步的實現分析,接下來我們繼續分析後面的幾步。
2 Step 4,5,6 特徵點匹配與資料融合
這幾步就是通過跟蹤和特徵匹配來擷取這一幀的特徵點,將兩者融合在一起。
上一篇文章分析了光流,這裡再分析一下特徵匹配。原始碼如下:
//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);
主要過程在matchGlobal函數中,分析如下:
void Matcher::matchGlobal(const vector<KeyPoint> & keypoints, const Mat descriptors, vector<Point2f> & points_matched, vector<int> & classes_matched){ if (keypoints.size() == 0) { return; } vector<vector<DMatch> > matches; // 使用knnMatch進行特徵匹配,每一個特徵描述匹配最佳的2個特徵 bfmatcher->knnMatch(descriptors, database, matches, 2); for (size_t i = 0; i < matches.size(); i++) { vector<DMatch> m = matches[i]; // 這裡的distance是兩個特徵描述之間的距離不是點與點的距離,距離越大,匹配度越低 float distance1 = m[0].distance / desc_length; float distance2 = m[1].distance / desc_length; int matched_class = classes[m[0].trainIdx]; // 如果匹配的是背景,則跳過 if (matched_class == -1) continue; // 距離要小於一個閾值0.25,表示匹配程度高,大了則跳過 if (distance1 > thr_dist) continue; // 比率也要小於閾值0.8,表示匹配1比匹配2好很多,從而可以將匹配1作為首選。 if (distance1/distance2 > thr_ratio) continue; points_matched.push_back(keypoints[i].pt); classes_matched.push_back(matched_class); }}
上面的距離是Hamming距離:
距離越小,表示匹配程度越高。
接下來是融合跟蹤和匹配的點,分析代碼如下:
//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);
核心代碼在preferFirst函數中,目的就是不重複添加相同的特徵點,很好理解:
void Fusion::preferFirst(const vector<Point2f> & points_first, const vector<int> & classes_first, const vector<Point2f> & points_second, const vector<int> & classes_second, vector<Point2f> & points_fused, vector<int> & classes_fused){ points_fused = points_first; classes_fused = classes_first; // 目的是不重複添加相同的特徵點 for (size_t i = 0; i < points_second.size(); i++) { int class_second = classes_second[i]; bool found = false; for (size_t j = 0; j < points_first.size(); j++) { int class_first = classes_first[j]; if (class_first == class_second) found = true; } if (!found) { points_fused.push_back(points_second[i]); classes_fused.push_back(class_second); } }}
3 Step 8,9 估計縮放比率和旋轉角度
首先就如何計算的問題,這個其實原理非常簡單,就是一開始我們已經儲存了初始的特徵點,而且是正規化的特徵點points_normalized,先計算兩兩之間的相對距離和相對角度,具體思想見上一篇blog的圖,初始的代碼如下:
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; } }
那麼對於新的特徵點,同樣也是計算他們的相對距離和相對角度,並與初始的資料相除或相減,就得到變化。
最後取他們的中位元作為整體的縮放比率和旋轉。
代碼如下:
void Consensus::estimateScaleRotation(const vector<Point2f> & points, const vector<int> & classes, float & scale, float & rotation){ //Compute pairwise changes in scale/rotation // 從縮放和旋轉尺度上計算Pairwise改變 vector<float> changes_scale; if (estimate_scale) changes_scale.reserve(points.size()*points.size()); vector<float> changes_angles; if (estimate_rotation) changes_angles.reserve(points.size()*points.size()); for (size_t i = 0; i < points.size(); i++) { for (size_t j = 0; j < points.size(); j++) { if (classes[i] != classes[j]) { // 計算任何兩個特徵點的相對位置 Point2f v = points[i] - points[j]; if (estimate_scale) { // 計算距離 float distance = norm(v); // 擷取特徵點的初始距離 float distance_original = distances_pairwise.at<float>(classes[i],classes[j]); // 相除得到改變的比率 float change_scale = distance / distance_original; changes_scale.push_back(change_scale); } if (estimate_rotation) { // 計算相對角度 float angle = atan2(v.y,v.x); // 計算初始角度 float angle_original = angles_pairwise.at<float>(classes[i],classes[j]); // 計算角度改變 float change_angle = angle - angle_original; //Fix long way angles if (fabs(change_angle) > M_PI) { change_angle = sgn(change_angle) * 2 * M_PI + change_angle; } changes_angles.push_back(change_angle); } } } } //Do not use changes_scale, changes_angle after this point as their order is changed by median() // 計算中位元作為結果 if (changes_scale.size() < 2) scale = 1; else scale = median(changes_scale); if (changes_angles.size() < 2) rotation = 0; else rotation = median(changes_angles);}
時間關係,先分析到縮放和旋轉這一步,下一篇文章分析CMT最後幾步。
本文為原創文章,轉載請註明出處:47830463