文章目录

  • 前言
  • 一、基础知识
  • 二、灰度质心法原理
  • 三、UMAX
  • 四、IC_Angle如何做加速运算
  • 总结

前言

本博客结合哔哩大学视频ORBSLAM2【ORBSLAM2源码讲解专题一】ORB特征点提取与均匀化策略和高翔的《视觉SLAM十四讲》总结。
代码参照github的ORB_SLAM2_detailed_comments

ORBSLAM2代码很经典,而且代码量大,会分成多个博客研究,下为slam专题的其他链接:
代码要求环境做了总结,解决了配置中的一些常见问题:slam的环境配置大全–保姆教学
代码如何运行记录:ORB_SLAM2代码的简介安装运行
有篇博客和本博客内容一样,他写的更详细:orb_slam代码解析(2)Tracking线程


一、基础知识

特征点法:我们需要从图像中选取比较有代表性的点,这些点在相机发生少量视角变化时会保持不变,在此基础上讨论相机位姿估计问题,在经典的SLAM模型中我们称他们为路标,在视觉SLAM中路标则是图像特征(feature)。

特征点: 特征点是图像里一些特别的地方,我们可以把图像中的角点、边缘和区块都当成图像中的代表性的地方。为此,计算机视觉领域研究出了很多特征点的提取方法,如著名的SIFT、SURF、ORB,这些人工设计的特征点有一下特点:

  • 可重复性:形同的“区域”可以在不同的图像中找到。
  • 可区别性:不同的“区域”有不同的表达。
  • 高效率:同一图像中,特征点的数量远小于像素的数量。
  • 本地性:特征仅与一小片图像区域有关。

关键点和描述子: 特征点由关键点(Key-point)和描述子(Descriptor)两部分组成,比如当要谈论SIFT特征时,是指“提取SIFT关键点,并计算SIFT描述子”两件事情。关键点是指该特征点在图像里的位置,有些特征点还具有朝向、大小等信息。描述子通常是一个向量,按照认为的方式描述该点周围像素的信息。描述子是按照“外观相似的特征应该有相似的描述子”的原则设计的。

ORB特征: 不同的图像特征特点不同。例如SIFT充分考虑了图像变换中的关照、尺度等变化,很精确但计算量很大,普通的CPU无法实时计算SIFT特征,进行定位建图。ORB适当的降低了精度和健壮性以提升计算速度,在同一图像中提取1000个特征点,ORB要15.3ms,SURF要217.3ms,SIFT约花费5228.7ms。ORB特征也是由关键点和描述子组成,他的关键点称为“Oriented FAST”,Oriented FAST的详细描述为本篇博客主要内容。他的描述子称为BRIEF(Binary Robust Independent Elementary Feature)。因此,提取ORB特征分为如下两个步骤:

  • FAST角点提取:找出图像中的“角点”。相较于原版的FAST,ORB中计算了特征点的主方向,为后续的BRIEF描述子增加了旋转不变的特性。他的思想是,如果一个像素与邻域的像素差别较大,那么他很可能是角点。

  • BRIEF描述子:对前一步提取出特征点的周围图像区域进行描述。

Oriented FAST的详细描述为本篇博客主要内容

二、灰度质心法原理

P为几何中心,Q为灰度质心
在一个圆内计算灰度质心(思考:为什么是圆?不是正方形?)
ORB通过灰度质心法来找Oriented FAST,为什么要画一个圆行区域的像素, 因为圆具有旋转不变性。每个特征点都会找灰度置信,每次要旋转到PQ为x轴上,方形无法旋转。但圆形区域就需要一些算法来实现。


具体计算方法为公式,我也看不懂。

目前ORB提取的代码都在ORBextractor.cc里,里面有很对函数,我们先看ORBextractor的构造函数,其代码注释如下:

ORBextractor::ORBextractor(int _nfeatures,       //指定要提取的特征点数目float _scaleFactor,    //指定图像金字塔的缩放系数int _nlevels,     //指定图像金字塔的层数int _iniThFAST,     //指定初始的FAST特征点提取参数,可以提取出最明显的角点int _minThFAST):       //如果初始阈值没有检测到角点,降低到这个阈值提取出弱一点的角点nfeatures(_nfeatures), scaleFactor(_scaleFactor), nlevels(_nlevels),iniThFAST(_iniThFAST), minThFAST(_minThFAST)//设置这些参数
{//存储每层图像缩放系数的vector调整为符合图层数目的大小mvScaleFactor.resize(nlevels);  //存储这个sigma^2,其实就是每层图像相对初始图像缩放因子的平方mvLevelSigma2.resize(nlevels);//对于初始图像,这两个参数都是1mvScaleFactor[0]=1.0f;mvLevelSigma2[0]=1.0f;//然后逐层计算图像金字塔中图像相当于初始图像的缩放系数 for(int i=1; i<nlevels; i++)  {//其实就是这样累乘计算得出来的mvScaleFactor[i]=mvScaleFactor[i-1]*scaleFactor;//原来这里的sigma^2就是每层图像相对于初始图像缩放因子的平方mvLevelSigma2[i]=mvScaleFactor[i]*mvScaleFactor[i];}//接下来的两个向量保存上面的参数的倒数mvInvScaleFactor.resize(nlevels);mvInvLevelSigma2.resize(nlevels);for(int i=0; i<nlevels; i++){mvInvScaleFactor[i]=1.0f/mvScaleFactor[i];mvInvLevelSigma2[i]=1.0f/mvLevelSigma2[i];}//调整图像金字塔vector以使得其符合设定的图像层数mvImagePyramid.resize(nlevels);//每层需要提取出来的特征点个数,这个向量也要根据图像金字塔设定的层数进行调整mnFeaturesPerLevel.resize(nlevels);//图片降采样缩放系数的倒数float factor = 1.0f / scaleFactor;//第0层图像应该分配的特征点数量float nDesiredFeaturesPerScale = nfeatures*(1 - factor)/(1 - (float)pow((double)factor, (double)nlevels));//用于在特征点个数分配的,特征点的累计计数清空int sumFeatures = 0;//开始逐层计算要分配的特征点个数,顶层图像除外(看循环后面)for( int level = 0; level < nlevels-1; level++ ){//分配 cvRound : 返回个参数最接近的整数值mnFeaturesPerLevel[level] = cvRound(nDesiredFeaturesPerScale);//累计sumFeatures += mnFeaturesPerLevel[level];//乘系数nDesiredFeaturesPerScale *= factor;}//由于前面的特征点个数取整操作,可能会导致剩余一些特征点个数没有被分配,所以这里就将这个余出来的特征点分配到最高的图层中mnFeaturesPerLevel[nlevels-1] = std::max(nfeatures - sumFeatures, 0);//成员变量pattern的长度,也就是点的个数,这里的512表示512个点(上面的数组中是存储的坐标所以是256*2*2)const int npoints = 512;//获取用于计算BRIEF描述子的随机采样点点集头指针//注意到pattern0数据类型为Points*,bit_pattern_31_是int[]型,所以这里需要进行强制类型转换const Point* pattern0 = (const Point*)bit_pattern_31_; //使用std::back_inserter的目的是可以快覆盖掉这个容器pattern之前的数据//其实这里的操作就是,将在全局变量区域的、int格式的随机采样点以cv::point格式复制到当前类对象中的成员变量中std::copy(pattern0, pattern0 + npoints, std::back_inserter(pattern));//This is for orientation//下面的内容是和特征点的旋转计算有关的// pre-compute the end of a row in a circular patch//预先计算圆形patch中行的结束位置//+1中的1表示那个圆的中间行umax.resize(HALF_PATCH_SIZE + 1);//cvFloor返回不大于参数的最大整数值,cvCeil返回不小于参数的最小整数值,cvRound则是四舍五入int v,        //循环辅助变量v0,     //辅助变量vmax = cvFloor(HALF_PATCH_SIZE * sqrt(2.f) / 2 + 1);    //计算圆的最大行号,+1应该是把中间行也给考虑进去了//NOTICE 注意这里的最大行号指的是计算的时候的最大行号,此行的和圆的角点在45°圆心角的一边上,之所以这样选择//是因为圆周上的对称特性//这里的二分之根2就是对应那个45°圆心角int vmin = cvCeil(HALF_PATCH_SIZE * sqrt(2.f) / 2);//半径的平方const double hp2 = HALF_PATCH_SIZE*HALF_PATCH_SIZE;//利用圆的方程计算每行像素的u坐标边界(max)for (v = 0; v <= vmax; ++v)umax[v] = cvRound(sqrt(hp2 - v * v));       //结果都是大于0的结果,表示x坐标在这一行的边界// Make sure we are symmetric//这里其实是使用了对称的方式计算上四分之一的圆周上的umax,目的也是为了保持严格的对称(如果按照常规的想法做,由于cvRound就会很容易出现不对称的情况,//同时这些随机采样的特征点集也不能够满足旋转之后的采样不变性了)for (v = HALF_PATCH_SIZE, v0 = 0; v >= vmin; --v){while (umax[v0] == umax[v0 + 1])++v0;umax[v] = v0;++v0;}
}

三、UMAX

上面代码的后半部分在算umax, umax在后面算方向很重要。umax是四分之一圆的每一行的u轴坐标边界。如下如的右上四分之一圆,横轴为u,竖轴为v,点在圆上滑动,其中横坐标到原点的距离就是umax,对应着图上三个点的AC、AH、AD距离是三个点的umax,E点的umax是0.

四、IC_Angle如何做加速运算

要计算特征点的角度,我们要在一个圆域中算出m10和m01,计算步骤是先算出中间红线的m10,然后在平行于x轴算出m10和m01,一次计算相当于图像中的同个颜色的两个line。先算出中间红线的m10,然后在平行于x轴算出m10和m01,一次计算 相当于图像中的同个颜色的两个line。

其代码注释如下:

static float IC_Angle(const Mat& image, Point2f pt,  const vector<int> & u_max)
{//图像的矩,前者是按照图像块的y坐标加权,后者是按照图像块的x坐标加权int m_01 = 0, m_10 = 0;//获得这个特征点所在的图像块的中心点坐标灰度值的指针centerconst uchar* center = &image.at<uchar> (cvRound(pt.y), cvRound(pt.x));// Treat the center line differently, v=0//这条v=0中心线的计算需要特殊对待//后面是以中心行为对称轴,成对遍历行数,所以PATCH_SIZE必须是奇数for (int u = -HALF_PATCH_SIZE; u <= HALF_PATCH_SIZE; ++u)//注意这里的center下标u可以是负的!中心水平线上的像素按x坐标(也就是u坐标)加权m_10 += u * center[u];// Go line by line in the circular patch  //这里的step1表示这个图像一行包含的字节总数。参考[https://blog.csdn.net/qianqing13579/article/details/45318279]int step = (int)image.step1();//注意这里是以v=0中心线为对称轴,然后对称地每成对的两行之间进行遍历,这样处理加快了计算速度for (int v = 1; v <= HALF_PATCH_SIZE; ++v){// Proceed over the two lines//本来m_01应该是一列一列地计算的,但是由于对称以及坐标x,y正负的原因,可以一次计算两行int v_sum = 0;// 获取某行像素横坐标的最大范围,注意这里的图像块是圆形的!int d = u_max[v];//在坐标范围内挨个像素遍历,实际是一次遍历2个// 假设每次处理的两个点坐标,中心线下方为(x,y),中心线上方为(x,-y) // 对于某次待处理的两个点:m_10 = Σ x*I(x,y) =  x*I(x,y) + x*I(x,-y) = x*(I(x,y) + I(x,-y))// 对于某次待处理的两个点:m_01 = Σ y*I(x,y) =  y*I(x,y) - y*I(x,-y) = y*(I(x,y) - I(x,-y))for (int u = -d; u <= d; ++u){//得到需要进行加运算和减运算的像素灰度值//val_plus:在中心线下方x=u时的的像素灰度值//val_minus:在中心线上方x=u时的像素灰度值int val_plus = center[u + v*step], val_minus = center[u - v*step];//在v(y轴)上,2行所有像素灰度值之差v_sum += (val_plus - val_minus);//u轴(也就是x轴)方向上用u坐标加权和(u坐标也有正负符号),相当于同时计算两行m_10 += u * (val_plus + val_minus);}//将这一行上的和按照y坐标加权m_01 += v * v_sum;}//为了加快速度还使用了fastAtan2()函数,输出为[0,360)角度,精度为0.3°return fastAtan2((float)m_01, (float)m_10);
}

总结

Oriented Fast神奇高效的代码实现方式——ORBSLAM2源码讲解(二)相关推荐

  1. 通用权限管理系统组件 中集成多个子系统的单点登录(网站入口方式)附源码

    通用权限管理系统组件 (GPM - General Permissions Manager) 中集成多个子系统的单点登录(网站入口方式)附源码 上文中实现了直接连接数据库的方式,通过配置文件,自定义的 ...

  2. OpenCV SIFT源码讲解——代码逻辑宏观窥探

    OpenCV SIFT源码讲解--代码逻辑宏观窥探 一.暴露在外的接口:SIFT 二.隐藏在SIFT背后的本质:SIFT_Impl 三.使用sift算法全流程 一.暴露在外的接口:SIFT 一般来说, ...

  3. C++fast power快速指数的实现(附完整源码)

    C++fast power快速指数的实现算法 C++fast power快速指数的实现算法完整源码(定义,实现,main函数测试) C++fast power快速指数的实现算法完整源码(定义,实现,m ...

  4. FreeMarker_模板引擎_代码自动生成器_源码下载

    首先我们先来认识一下Freemarker 1.what is the FreeMarker? 你可以到freemarker的官网上去,那里有很详细的介绍:http://freemarker.org/ ...

  5. HTML5七夕情人节表白网页(抖音-流动爱心表白)HTML+CSS+JavaScript 求婚示爱代码 520情人节告白代码 程序员表白源码 3D旋转相册 js烟花代码 爱心表白网页

    HTML5七夕情人节表白网页❤抖音-流动爱心表白❤ HTML+CSS+JavaScript 求婚示爱代码 520情人节告白代码 程序员表白源码 3D旋转相册 js烟花代码 爱心表白网页 这是程序员表白 ...

  6. HTML5七夕情人节表白网页(流星动画3D相册) HTML+CSS+JS 求婚 html生日快乐祝福代码网页 520情人节告白代码 程序员表白源码 3D旋转相册 js烟花代码 css爱心表白

    HTML5七夕情人节表白网页❤流星动画3D相册❤ HTML+CSS+JS 求婚 html生日快乐祝福代码网页 520情人节告白代码 程序员表白源码 抖音3D旋转相册 js烟花代码 css爱心表白 这是 ...

  7. HTML5七夕情人节表白网页(星空萤火虫) HTML+CSS+JS 求婚 html生日快乐祝福代码网页 520情人节告白代码 程序员表白源码 抖音3D旋转相册 js烟花代码 css爱心表白

    HTML5七夕情人节表白网页(星空萤火虫) HTML+CSS+JS 求婚 html生日快乐祝福代码网页 520情人节告白代码 程序员表白源码 抖音3D旋转相册 js烟花代码 css爱心表白 这是程序员 ...

  8. HTML5七夕情人节表白网页(雪花爱心表白) HTML+CSS+JS 求婚 html生日快乐祝福代码网页 520情人节告白代码 程序员表白源码 抖音3D旋转相册 js烟花代码 css爱心表白

    HTML5七夕情人节表白网页❤雪花爱心❤ HTML+CSS+JS 求婚 html生日快乐祝福代码网页 520情人节告白代码 程序员表白源码 抖音3D旋转相册 js烟花代码 css爱心表白 这是程序员表 ...

  9. HTML5七夕情人节表白网页(抖音超火3D炫酷魔方) HTML+CSS+JavaScript 求婚示爱代码 520情人节告白代码 程序员表白源码 3D旋转相册 js烟花代码 css爱心表白

    HTML5七夕情人节表白网页❤抖音超火❤3D炫酷魔方❤ HTML+CSS+JavaScript 求婚示爱代码 520情人节告白代码 程序员表白源码 3D旋转相册 js烟花代码 css爱心表白 这是程序 ...

最新文章

  1. Java单元测试的意义_单元测试重要意义及方法介绍
  2. 【转】Unity3D将来时:IL2CPP(上)
  3. 流量分析的瑞士军刀:Zeek
  4. android源码上面开发App
  5. vce 题库导入_Visual CertExam(VCE)试题制作教程.pdf
  6. BOS Studio(金蝶BOS)的简单介绍
  7. 关于连接数据库出现Connection failed: Access denied for user ‘root‘@‘localhost‘ (using password: YES)解决方案(最有用)
  8. 腐烂国度计算机配置要求,腐烂国度一周年求生版配置要求 略有小幅度提升
  9. 如何利用vga接口的显示器做笔记本的副屏
  10. 2022年暑假ACM热身练习3
  11. @CacheEvict-缓存
  12. D3DAPI大全,全部函数
  13. 动态ani_你是哪张Ani专辑?
  14. leetcode-167-两数之和 II
  15. 2014 SuperMap GIS自主创新与应用研讨会资料集
  16. mathcad使用说明
  17. YTU 3090 团体操排序
  18. Linux个人防火墙的设计与实现
  19. 网络协议:TCP三次握手与四次挥手
  20. Js转换long类型时间

热门文章

  1. eai java实现,月光软件站 - 编程文档 - Java - 建立EAI方式与SAI方式之间的通信
  2. Java 021 IO流(字符流、String类编解码、数据流、内存操作流、打印流、输入输出流、随机访问流、序列化、Properties)
  3. 智能手机的演变历程及发展趋势
  4. Unity3D 无限滚动列表
  5. 摄像头视角鼠标滚轮拉伸fieldOfView
  6. homeassistant
  7. poi方式读取word目录大纲
  8. Android 穿过点画平滑曲线
  9. 在这个云时代,如何选择性价比更高的云服务器
  10. Django网站开发 01.Web网站与前端HTML标签