视觉里程计这一部分的第一个主要内容是ORB特征点的提取与匹配。这里主要关注两个内容:

(1)特征点的匹配方法及代码实现

(2)ORB特征点的BRIEF描述子如何实现旋转不变性及在示例代码中的体现

1.ORB特征点匹配方法

高博在十四讲P158提到:特征匹配解决了SLAM中的数据关联问题,最简单的特征匹配方法就是暴力匹配(Brute Matcher),此外快速近似最近邻(FLANN)算法更加适合于匹配点数量极多的情况。然而在第七章的示例代码orb_cv.cpp中所使用的并不是这两种方法之一。代码如下:

//方法一:DescriptorMatcher 查询和训练
vector<DMatch> matches;
t1 = chrono::steady_clock::now();
matcher->match(descriptors_1, descriptors_2, matches);
t2 = chrono::steady_clock::now();
time_used = chrono::duration_cast<chrono::duration<double>>(t2 - t1);// 方法一优化:
// 计算最小距离和最大距离
auto min_max = minmax_element(matches.begin(), matches.end(),[](const DMatch &m1, const DMatch &m2) { return m1.distance < m2.distance; });
double min_dist = min_max.first->distance;
double max_dist = min_max.second->distance;//当描述子之间的距离大于两倍的最小距离时,即认为匹配有误.但有时候最小距离会非常小,设置一个经验值30作为下限.
std::vector<DMatch> good_matches;
for (int i = 0; i < descriptors_1.rows; i++) {if (matches[i].distance <= max(2 * min_dist, 30.0)) {good_matches.push_back(matches[i]);}
}

此方法将两张图像提取到的特征点分别称为“查询描述子集合”和“训练描述子集合”,匹配方法是在训练描述子集合中寻找与查询描述子集合中每个描述子最佳匹配的一个描述子。在此基础上手动添加一步优化算法,限定hamming距离的上限,排除误差较大的匹配结果。

此外,OpenCV还封装了多种不同的特征匹配方法,包括暴力匹配、FLANN匹配、RANSAC优化特征点匹配。这里分别尝试一下书中提到的两种方法。

暴力匹配方法代码如下:

//方法二:暴力匹配法 BFMatcher
//开启交叉检测
BFMatcher matcher_BF(NORM_HAMMING,1);
vector<DMatch> matches_BF;
vector<DMatch> good_matches_BF;
matcher_BF.match(descriptors_1, descriptors_2, matches_BF);
//方法二优化:
auto min_max_BF = minmax_element(matches_BF.begin(), matches_BF.end(),[](const DMatch &m1_BF, const DMatch &m2_BF) { return m1_BF.distance < m2_BF.distance; });
double min_dist_BF = min_max_BF.first->distance;
double max_dist_BF = min_max_BF.second->distance;for(int i=0;i<matches_BF.size();i++)
{if(matches_BF[i].distance <= max(2 * min_dist_BF, 30.0)){good_matches_BF.push_back(matches_BF[i]);}
}

暴力匹配使用方法和第一种方法类似,这里在初始化匹配器的时候选择开启了“交叉检测”,当标志位为“0”时数据出结果和运行时间和方法一几乎相同,没有明显区别。由于暴力匹配法使用的评价标注仍为hamming距离,因此优化部分使用相同方法。

FLANN匹配算法代码:

//方法三:FLANN
FlannBasedMatcher matcher_FLANN;
vector<DMatch> matches_FLANN;
vector<DMatch> good_matches_FLANN;
//注意数据类型转换
descriptors_1.convertTo(descriptors_1,CV_32F);
descriptors_2.convertTo(descriptors_2,CV_32F);
matcher_FLANN.match(descriptors_1, descriptors_2, matches_FLANN);
//方案三优化:
auto min_max_FLANN = minmax_element(matches_FLANN.begin(), matches_FLANN.end(),[](const DMatch &m1_FLANN, const DMatch &m2_FLANN) { return m1_FLANN.distance < m2_FLANN.distance; });
double min_dist_FLANN = min_max_FLANN.first->distance;
double max_dist_FLANN = min_max_FLANN.second->distance;
for(int i=0;i<matches_FLANN.size();i++)
{if(matches_FLANN[i].distance <= 0.35*max_dist_FLANN){good_matches_FLANN.push_back(matches_FLANN[i]);}
}

FLANN算法在使用时,要注意描述子的数据类型,必要是调整。优化部分,由于算法不再使用Hamming距离,而是采用聚类算法分析。因此在优化时使用了不同的方法,将匹配结果的最大距离*0.35设置为距离上限以排除误匹配结果。

三种方法运行结果如下表:

匹配方法 初始匹配对数 匹配时间 最大距离 最小距离 优化后匹配对数
查询训练 500 0.6ms 95 7 81
暴力匹配 229 1.4ms 95 7 72
FLANN 500 3.8ms 479.6 80.2 19

从结果来看,不开启交叉检测的暴力匹配和方法一几乎完全相同,开启后匹配点较少但准确度更高,优化后结果相近,代价是运行时间更长。但FLANN就比较惨烈了,属于是又慢、又不准、还少。。。不知道是不是特征点数量不够多不能体现优越性还是我代码哪里写错了,还是他就是这么菜。至于运行的结果图都长得差不多(一样的混乱)就不一一列举了。

2.BREIF描述子的旋转不变性

首先在书中P157提到:ORB在FAST特征点提取阶段计算了关键点的方向,可以利用方向信息计算旋转之后的特征使得描述子具有较好的旋转不变性。文中的内容到此为止没有过多讨论,但是我并没有理解是如何实现的,思考许久之后我得出的结论是“用于计算描述子的对应点应随着关键点的方向旋转以保持一致性”,可能听上去还是不太好理解,尤其是看到书中手写ORB特征的示例代码更是一脸懵B......,这里我们根据代码一步步分析:

第一步是计算质心及特征点方向:

//计算质心
//注意:这里是以关键点坐标为块的原点坐标
float m01 = 0, m10 = 0;
for (int dx = -half_patch_size; dx < half_patch_size; ++dx) {for (int dy = -half_patch_size; dy < half_patch_size; ++dy) {uchar pixel = img.at<uchar>(kp.pt.y + dy, kp.pt.x + dx);m10 += dx * pixel;m01 += dy * pixel;}}// angle should be arc tan(m01/m10);
float m_sqrt = sqrt(m01 * m01 + m10 * m10) + 1e-18; // avoid divide by zero
float sin_theta = m01 / m_sqrt;
float cos_theta = m10 / m_sqrt;

这里要注意的是,质心的是指以所选的关键点为中心构建的正方形区域的质心,为了得到质心与特征点坐标之间的角度θ,而且得到夹角可以理解为以关键点为原点,以x轴正方向为起点,逆时针旋转的角度,这里可以根据m10和m01的正负判断。S为关键点,M为质心,如下图:

下一步就是要考虑如何计算BRIEF描述子了,我们希望描述子可以具有旋转不变性。BRIEF描述子的本质是:在关键点附近随机得选择一些点对,比较他们的像素值,定义当以第一个点像素点较大则记为“1”,反之为“0”。因此只需要一串二进制数据就可以存储大量信息。这里我们先看一种错误情况,首先用abcd标注图像方向,这里选择p、q两点计算一位BRIEF描述子信息,注意这里的p、q一定是相对S选择的,比如p的坐标大概是(0,n),q的坐标大概是(-m,0),而二者在实际图像下的坐标就需要加上关键点S的坐标(x,y),设此时θ=45°刚好指向b方向。

考虑旋转之后的情况,我们顺时针旋转90°,仍然用相对坐标选择点对p、q,注意即使图像旋转,质心与关键点坐标的夹角方向仍是指向b方向的,同时注意θ的角度。结果如图,这里我们看到这让选择的p、q显然不是旋转前的对应点,结果也毫无关系。也就是说,此时不具备旋转不变性

现在尝试利用方向信息计算旋转之后的特征使得描述子具有较好的旋转不变性 ,我们将最初选择好的p、q点坐标相对S点逆时针旋转θ角,再来看一下结果,首先是旋转前的情况,已知p、q的情况下,将二者逆时针旋转θ角,将得到的p’和q’作为新的点对,计算二者对应的像素并得到描述子。注意此时p’相对S的方向指向a,q’指向d。

再看一下顺时针旋转90°的情况,同样是旋转θ角度。由于是逆时针旋转θ,也就是顺时针旋转2π-θ角, 得到此时的p'、q'两点,此时可以发现此时p’相对S的方向指向a,q’指向d。也就是说此时p'、q'相对S的位置是不变的,也就具有了旋转不变性。

这时只要解决了如何进行二位平面上的旋转就可以了。如下图

到这里我们就明白了,BRIEF描述子是如何通过质心角度θ来实现描述子的旋转不变形的,在了解其中原理之后,就可以看懂代码了,至于定义的巨大数组ORB_pattern应该是用来定义256对用来计算描述子的点对相对关键点的坐标,因此数据都是整数,正负都有数值也不大,就是关键点周围的随机点。

DescType desc(8, 0);
for (int i = 0; i < 8; i++) {
uint32_t d = 0;
for (int k = 0; k < 32; k++) {int idx_pq = i * 32 + k;//读取每行前两个为p后两个为qcv::Point2f p(ORB_pattern[idx_pq * 4], ORB_pattern[idx_pq * 4 + 1]);cv::Point2f q(ORB_pattern[idx_pq * 4 + 2], ORB_pattern[idx_pq * 4 + 3]);//旋转公式cv::Point2f pp = cv::Point2f(cos_theta * p.x - sin_theta * p.y, sin_theta * p.x + cos_theta * p.y)+ kp.pt;//在旋转后的结果上加上关键点坐标cv::Point2f qq = cv::Point2f(cos_theta * q.x - sin_theta * q.y, sin_theta * q.x + cos_theta * q.y)+ kp.pt;if (img.at<uchar>(pp.y, pp.x) < img.at<uchar>(qq.y, qq.x)) {//位操作:32位数据每一位都是一对点的描述结果d |= 1 << k;}}desc[i] = d;

整个过程其实非常简单,但是最初我却完全没有看懂他在做什么,也不清楚这样做有什么意义,至少现在明白一些了。

视觉SALM十四讲学习笔记——第七讲 视觉里程计(1)相关推荐

  1. 视觉SLAM十四讲学习笔记-第七讲-视觉里程计-三角测量和实践

     专栏汇总 视觉SLAM十四讲学习笔记-第一讲_goldqiu的博客-CSDN博客 视觉SLAM十四讲学习笔记-第二讲-初识SLAM_goldqiu的博客-CSDN博客 视觉SLAM十四讲学习笔记-第 ...

  2. 视觉SLAM十四讲学习笔记-第七讲-视觉里程计-对极几何和对极约束、本质矩阵、基础矩阵

    专栏系列文章如下:  专栏汇总 视觉SLAM十四讲学习笔记-第一讲_goldqiu的博客-CSDN博客 视觉SLAM十四讲学习笔记-第二讲-初识SLAM_goldqiu的博客-CSDN博客 视觉SLA ...

  3. 视觉SLAM十四讲学习笔记-第七讲-视觉里程计-PnP和实践

      专栏汇总 视觉SLAM十四讲学习笔记-第一讲_goldqiu的博客-CSDN博客 视觉SLAM十四讲学习笔记-第二讲-初识SLAM_goldqiu的博客-CSDN博客 视觉SLAM十四讲学习笔记- ...

  4. 视觉SLAM十四讲学习笔记-第七讲-视觉里程计-特征点法和特征提取和匹配实践

    专栏系列文章如下: 视觉SLAM十四讲学习笔记-第一讲_goldqiu的博客-CSDN博客 视觉SLAM十四讲学习笔记-第二讲-初识SLAM_goldqiu的博客-CSDN博客 视觉SLAM十四讲学习 ...

  5. 视觉SLAM十四讲学习笔记——第七讲 视觉里程计(2)

    视觉SLAM的前端也就是视觉里程计实际上要解决的问题是根据匹配的特征点估计相机运动.根据不同的已知条件,选择不同的方法. 1.针对单目相机2D-2D:对极几何 对于单目相机,前后两幅图像之间存在着对极 ...

  6. 视觉SLAM十四讲学习笔记-第六讲学习笔记总结(1)---非线性优化原理

    第六讲学习笔记如下: 视觉SLAM十四讲学习笔记-第六讲-非线性优化的状态估计问题_goldqiu的博客-CSDN博客 ​​​​​​视觉SLAM十四讲学习笔记-第六讲-非线性优化的非线性最小二乘问题_ ...

  7. 视觉SLAM十四讲学习笔记-第六讲-非线性优化的实践-高斯牛顿法和曲线拟合

    专栏系列文章如下: 视觉SLAM十四讲学习笔记-第一讲_goldqiu的博客-CSDN博客 视觉SLAM十四讲学习笔记-第二讲-初识SLAM_goldqiu的博客-CSDN博客 视觉SLAM十四讲学习 ...

  8. 视觉SLAM十四讲学习笔记-第六讲-非线性优化的非线性最小二乘问题

    专栏系列文章如下: 视觉SLAM十四讲学习笔记-第一讲_goldqiu的博客-CSDN博客 视觉SLAM十四讲学习笔记-第二讲-初识SLAM_goldqiu的博客-CSDN博客 视觉SLAM十四讲学习 ...

  9. 视觉SLAM十四讲学习笔记-第六讲-非线性优化的状态估计问题

    专栏系列文章如下: 视觉SLAM十四讲学习笔记-第一讲_goldqiu的博客-CSDN博客 视觉SLAM十四讲学习笔记-第二讲-初识SLAM_goldqiu的博客-CSDN博客 视觉SLAM十四讲学习 ...

最新文章

  1. python2 与 python3的区别总结
  2. 方法练习3_打印指定次数的HelloWorld
  3. 一文带你了解V4L2
  4. 失业几个月找不到工作是一种怎样的体验?
  5. 教你6步定制你的Ubuntu桌面
  6. NetTiers学习笔记09---RADGrid + EntityDataSource设置默认排序, 及表格自动排序,分页的方法...
  7. java-redis字符类数据操作示例(一)
  8. 如何在SQLite中检查表是否存在?
  9. Linux操作系统中pkg-config用法示例
  10. 如何使用BootStrapDialog实现数据的添加与删除?
  11. 解决webSocket不兼容IE浏览器问题
  12. 计算机算法专业英语,计算机算法相关术语的英语词汇
  13. shell 脚本中常用的列表
  14. word排版一般步骤
  15. sublime license key
  16. 大A股票主力对敲倒量,接盘返点有哪些特征
  17. Siri语音测试用例设计点
  18. xilinx debug
  19. 局域网查看工具LanSee使用教程【图文】
  20. pptp-client连接设置

热门文章

  1. Linux下C语言程序的编译过程
  2. pc端,使用jsignature,实现签名功能
  3. 登陆注册按钮的样式设计
  4. 关于App的一些迷思以及一些动画效果开源库的推荐
  5. eclipse开发linux内核/驱动/应用测试实例
  6. 写字机器人——pca9685控制芯片
  7. 如何建设一个移动端网站
  8. Android介绍及视频教程
  9. 中国大陆的网站的特点
  10. QLExpress规则引擎介绍