ORB图像特征检测
#ORB算法推导
ORB采用FAST (features from accelerated segment test) 算法来检测特征点。FAST核心思想就是找出那些卓尔不群的点,即拿一个点跟它周围的点比较,如果它和其中大部分的点都不一样就可以认为它是一个特征点。 首先来做几个定义: U : 参考像素点周围的区域阈值 t : 与参考像素点作对比的阈值点的灰度值当参考点的灰度值之差的绝对值大于t时,我们认为这两个点不相同 Gp : 像素点的灰度值 u : 区域阈值内不同的像素点数量 Un : 区域阈值内的连续像素点数目
FAST计算过程:
- 遍历图像矩阵一个参考像素点P。
- 考虑该像素点周围的Un个像素。
- 现在如果这Un个点中有连续的u个点都和参考点不同,那么它就是一个角点。
- 现在我们考虑一下这个检测思路,当我们遍历图像矩阵的时候还需要再一次的去遍历图像参考像素点周边的点,所以这个思路需要进行优化,所以我们 只需要检测参考像素点的矩形区域阈值内的对角像素即可,当对角像素内的像素点存在u (此处u与上面的连续点有所不同) 个不同的像素的时候。可以认为这个点与参考像素点不同。
计算特征描述子 (Feature DescritorS):
计算机是一个二进制机器,它读取的数据流或者是图像,只是一个data流或者是一个像素矩阵,它本身并没有任何特性,即使我们找到了像素的特征点坐 标,我们给它标记出来,可以通过肉眼直观的看到像素特征,但是计算机并不知道这些像素有什么意义,这个时候我们就需要构造一个特征描述子去描述当前像 素点的特征。
定义 O:参考特征点的圆形区域 d : 区域半径 N : 半径内点的个数 P(A,B):提取的点对的向量其中A, B为像素点A与像素点B的向量 具体来讲分为以下几步。
- 以关键点P为圆心,以d为半径做圆O。
- 在圆O内某一模式选取N个点对。(在OpenCV的FAST算法描述中,默认取512作为点对的个数)
- 定义一个函数T (P (A, B)) 使得当函数过程满足条件
的时候取0,
时取1。其中
为像素点的灰度值
通过上面的函数计算,就可以拿到列如101000101
这样的特征描述子 但是实际上来讲,我们提取到的特征描述子仅仅是在同一坐标系中的描述子,当我们旋转这张图片以后呢,就会发现,上面的算法,会导致即是是同一个点,但是因为旋转了 图像而导致匹配失效。
在现实生活中,我们从不同的距离,不同的方向、角度,不同的光照条件下观察一个物体时,物体的大小,形状,明暗都会有所不同。但我们的大脑依然可以判断它是同一件物体。理想的特征描述子应该具备这些性质。即,在大小、方向、明暗不同的图像中,同一特征点应具有足够相似的描述子,称之为描述子的可复现性。
所有获取到的特征描述子应该必须要有:
对光照(亮度)不敏感,具备尺度一致性(大小 ),旋转一致性(角度)
在OpenCV的ORB实现中采用了图像金字塔来改善这方面的性能。ORB主要解决BRIEF描述子不具备旋转不变性的问题。当我们选取点对的时候,是以当前关键点为原点,以水平方向为X轴,以垂直方向为Y轴建立坐标系。当图片发生旋转时,坐标系不变,同样的取点模式取出来的点却不一样,计算得到的描述子也不一样,这样进行的匹配会出问题,因此需要建立一个新的坐标系去解决这个问题,来保证我们的一致性。
在OpenCV中通过构建高斯金字塔,然后在每一层金字塔图像上检测角点,来实现尺度不变性。那么,对于局部不变性,我们还差一个问题没有解决,就是FAST特征点不具有方向,ORB的论文中提出了一种利用灰度质心法来解决这个问题,灰度质心法假设角点的灰度与质心之间存在一个偏移,这个向量可以用于表示一个方向。对于任意一个特征点 来说,我们定义 的邻域像素的矩为:
图像的质心为:
至此,我们可以把特征点与质心的夹角定义为FAST特征点的方向:
给BRIEF加上旋转不变性,把这种方法称为“Steer BREIF”。对于任何一个特征点来说,它的BRIEF描述子是一个长度为 的二值码串,这个二值串是由特征点周围n个点对(2n个点)生成的,现在我们将这2n个点
组成一个矩阵S
下面是OpenCV中的ORB实现函数
ORB::ORB(int nfeatures=500, float scaleFactor=1.2f, int nlevels=8, int edgeThreshold=31, int firstLevel=0, int WTA_K=2, int scoreType=ORB::HARRIS_SCORE, int patchSize=31)
nfeatures
- 最多提取的特征点的数量
scaleFactor
- 金字塔图像之间的尺度参数,类似于SIFT中的
nlevels
– 高斯金字塔的层数;
edgeThreshold
– 边缘阈值,这个值主要是根据后面的patchSize来定的,靠近边缘edgeThreshold以内的像素是不检测特征点的。
firstLevel - 看过SIFT都知道,我们可以指定第一层的索引值,这里默认为0。
WET_K
- 用于产生BIREF描述子的 点对的个数,一般为2个,也可以设置为3个或4个,那么这时候描述子之间的距离计算就不能用汉明距离了,而是应该用一个变种。OpenCV中,如果设置WET_K = 2,则选用点对就只有2个点,匹配的时候距离参数选择NORM_HAMMING,如果WET_K设置为3或4,则BIREF描述子会选择3个或4个点,那么后面匹配的时候应该选择的距离参数为NORM_HAMMING2。
scoreType
- 用于对特征点进行排序的算法,你可以选择HARRIS_SCORE,也可以选择FAST_SCORE,但是它也只是比前者快一点点而已。
patchSize
– 用于计算BIREF描述子的特征点邻域大小。
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/features2d/features2d.hpp>
using namespace cv;
int main(int argc, char** argv)
{
Mat img_1 = imread("test.png");
Mat img_2=imread("test2.png") ;
// -- Step 1: 初始化ORB匹配点集
std::vector<KeyPoint> keypoints_1,keypoints_2;
ORB orb;
orb.detect(img_1, keypoints_1);
orb.detect(img_2, keypoints_2);
// -- Stpe 2: 计算特征描述子
Mat descriptors_1, descriptors_2;
orb.compute(img_1, keypoints_1, descriptors_1);
orb.compute(img_2, keypoints_2, descriptors_2);
//-- Step 3: 匹配相似的特征向量
BFMatcher matcher(NORM_HAMMING);
std::vector<DMatch> mathces;
matcher.match(descriptors_1, descriptors_2, mathces);
// -- 绘制匹配线
Mat img_mathes;
drawMatches(img_1, keypoints_1, img_2, keypoints_2, mathces, img_mathes);
// --显示图像
imshow("Mathces", img_mathes);
waitKey(0);
return 0;
}
参考资料:
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- ubuntu中swap(虚拟内存)设置方法
- 详解Linux监控重要进程的实现方法
- CentOS环境下安装Redis3.0及phpredis扩展测试示例
- 使用Apache commons-cli包进行命令行参数解析的示例代码
- 详解如何在Ubuntu 16.04上增加Swap分区
- Mac本地文件上传到CentOS云服务器方法
- linux中把.c的文件编译成.so文件
- Ubuntu16.04 中 locate文件查找命令
- Ubuntu 16.04与Apache虚拟主机配置的步骤详解
- Linux删除目录下的文件的10种方法小结
- 利用Linux防火墙隔离本地欺骗地址的方法详解
- 视图在SQL中的作用是什么,它是怎样工作的?
- Linux命令行上如何使用日历详解
- 在Linux下修改和重置root密码的方法(超简单)
- 在Centos上搭建Maven中央仓库的方法