在上一篇blog中,我们分析了CMT的整体算法流程及前面几步的实现分析,接下来我们继续分析后面的几步。
这几步就是通过跟踪和特征匹配来获取这一帧的特征点,将两者融合在一起。
上一篇文章分析了光流,这里再分析一下特征匹配。源代码如下:
//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);
}
}
}
首先就如何计算的问题,这个其实原理非常简单,就是一开始我们已经存储了初始的特征点,而且是正规化的特征点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最后几步。
本文为原创文章,转载请注明出处:https://blog.csdn.net/songrotek
版权声明:本文为博主原创文章,未经博主允许不得转载。
原文地址:http://blog.csdn.net/songrotek/article/details/47830463