源码:https://github.com/PacktPublishing/OpenCV3-Computer-Vision-Application-Programming-Cookbook-Third-Edition
在OpenCV的C++API中,所有的类和函数都在命名空间cv内定义。访问他们的方法共有两种:
(1)在定义main函数前使用如下声明:
using namespace cv ;
(2)是根据命名空间规范给所有OpenCV的类和函数加上前缀cv::,添加前缀后,代码中OpenCV的类和函数将更容易识别。
highgui模块中国有一批能帮助我们轻松显示图像并对图像进行操作的函数。
//读取一个图像文件,并将其转换为灰度图像
image = cv::imread("puppy.bmp",CV::IMREAD_GRAYSCALE);
使用imread函数装在图像时,通过设置选项把它转化为灰度图像,有些计算机视觉算法必须使用灰度图像。在读入图像的同时进行色彩转换,可以提高运行速度并减少内存的使用。这样生成的图像由无符号字节(unsigned byte,c++中为unsigned char)构成,在OpenCV中用常量CV_8U表示。
//读取图像,并将其转换为三通道彩色图像
image = cv::imread("puppy.bmp",CV::IMREAD_COLOR);
在这样创建的图像中,每个像素有3个字节,OpenCV中用CV_8UC3表示。如果输入的图像文件是灰度图像,这三个通道的值就是相同的。如果在读入图像时采用文件本身的格式,只需把第二个参数设置为负数。
std::cout<<"this image has "<
可用channels方法检查图像的通道数。
注意:当用imread打开路径置顶不完整的图像时,imread会自动采用默认目录:
如果从控制台运行程序,默认路录显然就是当前控制台的目录;
如果直接在IDE中运行程序,默认目录就是项目文件所在的目录(主函数文件所在目录);
imshow显示由整数(CV_16U表示16为无符号整数,CV_32S表示32为有符号整数)构成的图像时,图像每个像素会被除以256,以便能够与在256级灰度中显示。同样在显示由浮点数构成的图像时,指的范围会被假设为0.0(显示黑色)~1.0(显示白色)。超出这个范围的值会被显示为白色(大于1.0的值)或者黑色(小于0.0的值)。
cv::flip(image,image,1);//就地处理,对image进行水平翻转
//正数表示水平,0表示垂直,负数表示水平和垂直
回调函数:响应特定事件的时候被程序调用。为了能被程序识别,回调函数需要具备特定的签名,并且必须注册。
cv::Mat iamge(240,320,CV_8UC3,cv::Scanlar(0,0,255));//创建一个红色图像,通道次序为BGR
CV ::Mat类是用来存放图像(以及其他矩阵数据)的数据结构。
一旦没有了指定cv::Mat对象的引用,分配的内存就会被自动释放。避免了C++动态内存分配中经常发生的内存泄漏问题。实现方法是通过cv::Mat实现计数引用和浅复制。深复制可以使用copyTo方法。或者产生一个图像副本的方法clone。
把一幅图像复制到另一幅图像中,且两者的数据类型不一定相同,那就要使用convertTo方法;
图像掩码:
OpenCV中国的有些操作(copyTo)可以用来定义掩码。函数或方法通常对图像中所有的像素进行操作,通过定义掩码可以限制这些函数或方法的作用范围。掩码是一个8位图像,如果掩码中某个位置的值不为0,在这个位置上的操作就会起作用;如果为0,则不起作用。
操作像素:
椒盐噪声(salt-and-pepper noise):它随机选择一些像素,把他们的颜色替换成白色或者黑色。如果通信出错,部分像素的值在传输时丢失,就会产生这种噪声。
为了降低分析的复杂性,有时需要减少图像多种颜色的数量。一种实现方法就是把RGB空间细分到大小相等的方块中。例如,如果把每种颜色数量减少到1/8,那么颜色总数就变为了32*32*32.将旧图像中的每个颜色值划分到一个方块,该方块的中间值就是新的颜色值;新图像使用新的颜色值,颜色数就减少了。
减色算法实现:假设N是减色因子,将图像中每个像素的值除以N(这里假设使用整数除法,不保留余数)。然后将结果乘以N,得到N的倍数,并且刚好不超过院士像素值。加上N/2,就得到相邻的N倍数之间的中间值。对所有8位通道值重复这个过程,就会得到(256/N)*(256/N)*(256/N)种可能的颜色值。
其他减色算法:
data[i] = (data[i] / div)*div +div/2;
减色计算也可以使用取模运算符,可以直接得到div的倍数:
data[i] = data[i] - data[i] % div +div /2;
还可以使用位运算。如果把减色因子限定为2的指数,即div = pow(2,n),那么把像素值的前n为掩码后就能得到最接近的div的倍数。可以用简单的位操作获得掩码:
//用来截取像素值的掩码
uchar mask = 0xFF << n; //如div = 16,则mask= 0xF0
*data & = mask; //掩码
*data++ += div >>1; //加上div/2
//这里的+也可改为“按位或”运算符
锐化滤波器:
0 |
-1 |
0 |
-1 |
5 |
-1 |
0 |
-1 |
0 |
在对像素领域进行计算式,通常用一个核心矩阵来表示。这个核心矩阵展现了如何将于计算相关的像素组合起来得到预期的效果。
除非另有说明,当前像素用核心矩阵中心单元格表示。核心矩阵中的每个单元格表示相关像素的乘法系数,像素应用核心矩阵得到的结果,即这些乘积的累加。核心矩阵的大小就是邻域的大小。
根据锐化滤波器的要求,水平和垂直方向的四个相邻像素与-1相乘,当前像素与5相乘。这也是信号处理中卷积概念的基础。
策略设计模式:
策略设计模式把算法封装进类,尽可能的将算法的复杂性隐藏在一个直观的编程接口后面,更有利于算法的部署。可以通过创建类的实例来部署算法,实例通常是在程序初始化的时候创建的。在运行构造函数时,类的实例会用默认值初始化算法的各种参数,使其立即进入可用状态。还可以用适当的方法来读写算法的参数值。
GrabCut算法:
背景/前景分割步骤:
GrabCut算法是一个逐步改进分割结果的迭代过程。
CIEL*a*b颜色模型:
利用RGB色彩空间计算颜色之间的差距并不是衡量两个颜色相似度的最好方式。实际上,RGB并不是感知均匀的色彩空间。
CIEL*a*b*就是一种具有感知均匀特性的颜色表示法。把图像转换到这种表示法后,就可以真正的使用图像像素与目标颜色之间的欧几里得距离,来度量颜色之间的视觉相似度。
使用OpenCV的函数cv::cvtColor可以轻松的转换图像的色彩空间。
//转换成Lab色彩空间
cv::cvtColor(image,converted,CV_BGR2Lab);
CIEL*u*v*是另一种感知均匀的色彩空间。若想从BGR转换成CIEL*u*v*,可使用代码CV_BGR2Luv.
L*a*b*和L*u*v*对亮度通道使用同样的转换公式,但对色度通道则使用不同的表示法。,另外,为了实现视觉感知上的均匀,这两种色彩空间都扭曲了RGB的颜色范围,所以这些转换过程都是非线性的(不完全可逆)。
HSV和HLS这两种色彩空间,把颜色分解成加值的色调和饱和度组件或亮度组件。用这种方式描述的颜色会更加自然。
把图像的通道分割到三个独立的图像中,看到HSV的组件:
std::vector channels;
cv:split(hsv , channels);
//channels[0]是色调
//channels[1]是饱和度
//channels[2]是亮度
引入色调/饱和度/亮度的色彩空间概念:因为人们喜欢凭直觉用色彩、彩度、亮度等直观属性来描述分辨各种颜色,色调(hue)表示主色,饱和度(saturation)表示颜色的鲜艳程度,柔和的颜色饱和度较低,而彩虹的颜色饱和度就很高;亮度(brightness)是一个主观的属性,表示某种颜色的光亮程度。
注意:如果颜色的饱和度很低,计算出来的色调就很不靠谱。7
肤色检测领域的大量研究已经表明:来自不同人种的人群的皮肤颜色,可以在色调-饱和度色彩空间中很好的归类。
直方图是一个简单的表格,表示一幅图像(有时是一组图像)中具有某个值的像素的数量。因此,灰度图像的直方图有256个项目。也叫箱子(bin)。0号箱子提供值为0的像素的数量,1号箱子提供值为1 的像素的数量,以此类推。如果把直方图的所有箱子进行累加,得到的结果就是像素的总数。可以把直方图归一化,即所有箱子的累加和等于1。这时,每个箱子的数值表示对应的像素数量占总数的百分比。
利用查找表修改图像外观:
1、对像素强度进行简单的反转,即强度为0变成255、1变成254、255变成0,生成原始图像的反向图像。
2、定义一个修改原始图像直方图的查找表可以提高图像的对比度。假设图中根本没有大于200的像素值。可以通过延伸直方图来生成以个对比度更高的图像。使用以个百分比阈值,在强度值中找到最小值和最大值,然后重新映射。
3、在彩色图像上应用查找表实现减色函数。
直方图均衡化:
通过伸展直方图使它布满可用强度值的全部范围,增强图像对比度。但是很多时候,图像的视觉缺陷并不因为它的使用的强度值范围太窄,而是因为部分强度值的使用频率远高于其他强度值。因此,均衡对所有像素强度值的使用频率可以作为提高图像质量的一种手段。(查找表是针对整幅图像的多对一的转换过程,所以直方图是不能做到完全平稳的。)
反向投影直方图检测特定图像内容:
假设需要在某幅图像中检测出特定的内容,首先要做的就是选择一个包含所需样本的感兴趣的区域。接着提取该ROI的直方图,通过归一化直方图,可以得到一个函数,得到特定强度值的像素属于这个区域的概率。反向投影直方图的过程:从归一化后的直方图中读取概率值并把输入图像中的每个像素替换成与之对应的概率值。(实验中除了云彩,其他区域也被错误的检测到了,这个概率函数是从简单的灰度直方图提取的,很多其他像素的强度值域云彩像素的强度值是相同的,在对直方图进行反向投影时会用相同的概率值替换相同强度值的像素。还可以进一步利用色彩信息提高检测效果。)
均值偏移算法:
均值偏移算法是一个迭代过程,用于定位概率函数的局部最大值,方法是寻找预定义窗口内部数据点的重心或加权平均值。然后,把窗口移动到重心的位置,并重复该过程,知道窗口中心收敛到一个稳定的点。OpenCV实现该算法定义了两个停止条件:迭代次数达到最大值;窗口中心的偏移值小于某个限值,可认为该位置收敛到一个稳定点。
比较直方图搜索相似图像:
计算机视觉的重要课题:基于内容的图像检索;而直方图是标识内容的一种有效方式;
原理:逐个箱子的比较每个直方图中的数值,并保存最小的值。然后把这些最小值累加,作为相似度测量值。(两个没有相同颜色的直方图得到的交叉值为0 ,而两个完全相同的直方图得到的值就等于像素总数。)
其他算法:
积分图像:
快速计算矩形区域内的像素累加和;
原理:取矩形图像左上方的全部像素计算累加和,并利用这个累加和替换图像中的每一个像素,用这种方式得到的图像称为积分图像。计算积分图像时,只需要对图像扫描一次。实际上,当前像素的积分值等于上方像素的积分值加上当前行的累计值。因此积分图像就是一个包含像素累加和的新图像。为了防止溢出,积分图像的值通常采用int类型(CV_32S)或float类型(CV_32F)。
积分图像的像素A包含左上角区域,计算完积分图像后,只需要访问四个像素就可以得到任何矩形区域的像素累加和。计算有A、B、C、D四个像素表示区域的像素累加和,先读取D的积分值,然后再减去B的像素值和C的左手边区域的像素值。但这样就把A左上角的像素累加和减了两次,因此需要重新加上A的积分值。所以计算A、B、C、D区域内的像素累加的公式为A-B-C+D。
自适应阈值化:
即采用局部阈值,根据美国像素的邻域计算阈值。将每个像素的值域邻域的平均值进行比较。如果某像素的值域它的局部平均值差别很大,就会被当做异常值在阈值化过程中剔除。(自适应阈值化需要计算每个像素周围的局部平均值,这需要多次计算图像窗口的累计值,可以通过积分图像提高计算效率。)
bug
OpenCV的颜色空间转换函数:
参数dstCn原来一直沿用CV_BGR2GRAY, CV_RGB2GRAY, CV_GRAY2BGR, CV_GRAY2RGB等格式,但最新几个版本的OpenCV已改为COLOR_BGR2GRAY类似形式,今天才看源代码imgproc.hpp才发现,但官方文档还没修改,如下图,如果程序中使用较新的版本OpenCV,采用CV_BGR2GRAY可能会一直出错。
新的OpenCV中imgproc.hpp的定义如下:
...
COLOR_BGR2HSV =40,
COLOR_RGB2HSV =41,
COLOR_BGR2Lab =44,
COLOR_RGB2Lab =45,
...
解决:使用OpenCV3.4版本,导入opencv2
数学形态学:用于分析和处理离散图像。它定义了一系列运算,用预先定义的形状元素探测图像,从而实现图像的转换。这个形状元素与像素邻域的相交方式决定了运算的结果。
用形态学滤波器腐蚀(cv::erode)和膨胀(cv::dilate)图像
结构元素:像素的组合。在对应的像素上定义了一个原点(也称锚点)
形态学滤波器的应用过程就包含了用这个结构元素探测图像中每个像素的操作过程。把某个像素设为结构元素的原点后,结构元素的图像重叠部分的像素集就是特定形态学运算的应用对象。
用形态学滤波器开启和闭合图像
开启和闭合滤波器的定义只与基本的腐蚀和膨胀运算有关;
定义:
闭合:是对图像先膨胀后腐蚀;开启:是对图像先腐蚀后膨胀;
//膨胀原图像
cv::dilate(image,result,cv::Mat());
//就地腐蚀膨胀后的图像
cv::erode(result,result,cv::Mat());
调换这两个函数的调用次序,就能得到开启滤波器;滤波器常用于目标检测;
闭合滤波器:可以把错误分裂成小碎片的物体连接起来;
开启滤波器:可以移除因图像噪声产生的斑点;
因此:最好按一定的顺序调用这些滤波器。如果优先考虑过滤噪声,可以先开启后闭合,但这样做的坏处是会消除部分物体碎片;
注意:对同一幅图像进行多次同样的开启运算是没有作用的(闭合运算也一样),因为第一次使用开启滤波器时已经填充了空隙,再使用同一个滤波器将不会使图像产生变化。即这些运算是幂等(idempotent)的。
形态学梯度运算可以提取图像的边缘(cv::morpholgyEx函数)得到图像中物体的轮廓。
//用3*3结构元素得到梯度图像
cv::Mat result;
cv::morphlogyEx(image,result,cv::MORPH_GRADIENT,cv::Mat());
形态学顶帽(hat-top)变换可以从图像中提取出局部的小型前景物体。(提取图像中的文字)
//使用7*7结构元素做顶帽变换
cv::Mat element7(7,7,CV_8U,cv::Scanlar(1));
cv::morphologyEx(image,result,cv::MORPH_BLACKHAT,element7);
形态学运算在灰度图像上的效果理解:
把图像看做是一个拓扑地貌,不同灰度级别代表不同的高度(海拔),明亮的区域代表高山,黑暗的区域代表深谷;边缘相当于黑暗和明亮像素之间的快速过渡,因此可以比作陡峭的悬崖。腐蚀这种地形的最终结果是:每个像素被替换成特定领域内的最小值,从而降低它的高度。结果是悬崖“缩小”,山谷“扩大”。膨胀的效果刚好相反,即悬崖“扩大”,山谷“缩小”。但不管哪种情况,平地(即强度值固定的区域)都会相对保持不变。
根据这个结论,可以得到一种检测图像边缘(悬崖)的简单方法Beucher梯度:通过计算膨胀后的图像与腐蚀后的图像之间的差距得到边缘。因为这两种转换后的图像的差别主要在边缘地带。显然,结构元素越大,检测到的边缘就越宽。
另外两种简单的方法得到类似效果:用膨胀后的图像减去原始图像,或者用原始图像减去腐蚀后的图像,那样得到的边缘会更窄。
分水岭算法实现图像分割
分水岭变换是一种流行的图像处理算法,用于快速将图像分割成多个同质区域。
思想:如果把图像看作一个扩屏地貌,那么同类区域就相当于陡峭边缘内相对平坦的盆地。平、分水岭算法通过逐步增高水位,把地貌分割成多个部分。(cv::watershed函数)
MSER算法提取特征区域
最大稳定外部区域(MSER)
该算法用相同的水淹类比,以便从图像中提取有意义的区域。创建这些区域时也使用逐步提高水位的方法,但是该算法关注的是水淹过程中的某段时间内,保持相对稳定的盆地。这个稳定的盆地就是MSER。检测方法:计算区域的当前面积以及该区域原先的面积(比当前水位低一个特定值的时候),并比较这两个面积,如果相对变化达到局部最小值,就认为这个区域时MSER。
滤波是信号和图像处理中的基本操作,它的目的是选择性的提取图像中某些方面的内容;滤波可以去除图像中的噪声,提取有用的视觉特征,对图像重新采样。
频域(frequency domain):图像中灰度级的变化频率;傅里叶变换或余弦变换显示图像的频率成分;
图像是二维的,因此,频率分为:垂直频率和水平频率;
空域(spatial domain):灰度分布来描述图像特征;
在频域分析框架下,滤波器时一种放大(也可以不变)图像中的某些频段,同时滤掉(或减弱)其他频段的算子。eg:低通滤波器的作用是消除图像中的高频部分;高通滤波器刚好相反,用来消除图像中的低频部分;
缩减像素采样(downsampling):降低图像精度的过程;
提升像素采样(upsampling):提升图像精度的过程;
Nyquist-Shannon定理:如果把图像缩小一半,那么其可见的频率带宽也将减少一半。为了避免混叠现象的发生,在缩减图像之前必须进行低筒滤波(低通滤波可以消除在缩减后的图像中无法表示的高频部分)
像素插值:进行插值的最基本的方法是使用最近邻策略。把待生成图像的像素网格放在原图像的上方,每个新像素被赋予原图像中最邻近像素的值。当像素升采样(即新网格比原始网格更密集时),会根据同一个院士像素,确定新网格中多个像素的值。
中值滤波器:因为中值滤波器是非线性的,所以不能用核心矩阵表示,也不能进行卷积运算。但它也是通过操作一个像素的邻域,来确定输出的像素值的。(中值滤波器把当前像素和它的邻域组成一个集合,然后计算出这个集合的中间值,以此作为当前像素的值)。
中值滤波器对消除椒盐噪声非常有用;因为,如果在某个像素邻域中有一个异常的黑色或白色像素,该像素将无法作为中间值(它是最大值或最小值),因此肯定会被邻域的值替换掉。
中值滤波器还有利于保留边缘的尖锐度,但它会洗去均质区域中的纹理(例如背景中的树木)
因为中值滤波器具有良好的视觉效果,因此照片编辑软件常用它创建特效。可以用彩色照片生成类似卡通的图像。
用定向滤波器检测边缘:
高通滤波器进行边缘检测:放大图像中的高频成分;
Sobel滤波器:它只对垂直或水平方向的图像频率起作用(具体方向取决于滤波器选用的内核)所以是一种定向滤波器。(图像浮雕化的特效就是定向滤波器生成的)Sobel算子称作边缘检测器,是一种典型的用于边缘检测的线性滤波器。如果把图像看作二维函数,那么Sobel算子就是图像在垂直和水平方向变化的速度(梯度),它是一个二维向量,向量的元素时横竖两个方向的函数的一阶导数;
Sobel算子在水平和垂直方向计算像素值的差分,得到图像梯度的近似值。它在像素周围的一定范围内进行运算,以减少噪声带来的影响。cv::Sobel函数使用Sobel内核来计算图像的卷积。
梯度是一个二维向量,所以它有范数和方向,梯度向量的范数表示变化的振幅,计算时通常被当做欧几里得范数(L2范数)。
梯度向量总是指向变化最剧烈的方向。对于一幅图像来说,这意味着梯度的方向与边缘垂直;
梯度算子:Prewitt算子;Roberts算子;Scharr算子;所有的这些定向滤波器都会计算图像函数的一阶导数。计算图像导数的滤波器被称为高通滤波器。
拉普拉斯算子(cv::Laplacian):一种基于图像导数运算的高通线性滤波器,它通过计算二阶导数来度量图像函数的曲率;
用拉普拉斯算子增强图像的对比度:通过图像中减去它的拉普拉斯图像,可以增强图像的对比度;
高斯差分:用两个不同带宽的高斯滤波器对一幅图像做滤波,然后将这两个结果相减,就能得到由较高的频率构成的图像。这些频率被一个滤波器保留,被另一个滤波器抛弃。
要进行基于内容的图像分析,就必须从构成图像的像素集中提取出有意义的特征:轮廓、直线、斑点等基本的图像图元;
用Canny算子检测图像轮廓
核心原理:用两个不同的阈值来判断哪个点属于轮廓,一个是低阈值,一个是高阈值;
选择低阈值:保证它能包含所有属于重要图像轮廓的边缘像素。(由于使用了比较宽松的阈值,所以很多并不需要的边缘也被检测出来了)
选择高阈值:作用就是界定重要轮廓的边缘,排除掉异常的边缘。(得到肯定属于本场景中的重要轮廓)
Canny算法将这两种边缘分布图结合,生成最优的轮廓分布图(在低阈值边缘分布图上只保留具有连续路径的边缘点,同时把那些边缘点连接到属于高阈值边缘分布图的边缘上。)这种基于两个阈值获得二值分布图的策略被称为滞后阈值化;
用霍夫变换(Hough transform)检测直线
在霍夫变换中,用这个方程式表示直线:
ρ = X cosθ + Y sin θ //ρ可以为负数
霍夫变换的目的是在二值图像中找出全部直线,并且这些直线必须穿过足够多的像素点。
处理方法:检查输入的二值分布图中每个独立的像素点,识别出穿过该像素带你的所有可能直线。如果同一条直线穿过很多像素点,就说明这条直线明显到足以被认定。为了统计某条直线被标识的次数,霍夫变换使用了一个二维累加器。
霍夫变换也能用来检测其他任何可以用一个参数方程来表示的物体,但一般来说参数越多,累加器的维数越多,霍夫变换就越复杂,可靠性也越低;
点集的直线拟合:
使每个点到直线的距离之和最小化(欧几里得距离的计算速度最快),最小化计算的基础是M估算法技术,它采用迭代方式解决加权最小二乘法问题,其中权重与点到直线的距离成反比。
提取连续区域
原理:提取轮廓的算法,它系统的扫描图像,直到找到连续区域。从区域的起点开始,沿着它的轮廓对边界像素做标记。处理完这个轮廓后,就从上个位置继续扫描,直到发现新的区域。
计算区域的形状描述子
连续区域通常代表着场景中的某个物体。为了识别该物体,或将它与其他图像元素作比较,需要对此区域进行测量,以提取出部分特征。
边界框:
在表示和定位图像中区域的方法中,边界框可能是最简洁的。
定义:能完整包含该形状的最小垂直矩形。比较边界框的高度和宽度,可以获得物体在垂直或水平方向的特征,最小覆盖圆通常在只需要区域尺寸和位置的近似值时使用;
如果要更紧凑的表示区域的形状,可采用多边形逼近。在创建时要制定精确度参数,表示形状与对应的简化多边形之间能接受的最大距离。
兴趣点(关键点或特征点):在图像中选取某些特征点并对图像进行局部分析(提取局部特征),而非观察整幅图像(提取全局特征)只要图像中有足够多可检测的兴趣点,并且这些兴趣点各不相同且特征稳定、能被精确定位。
检测图像中的角点
在图像中搜索有价值的特征点时,使用角点是一种不错的方法。角点是很容易在图像中定位的局部特征。并且大量存在于人造物体中,角点的价值在于它是两条边缘线的结合点,是一种二维特征,可以被精确的检测,Harris特征检测是检测角点的经典方法。
Harris特征检测:在假定的兴趣点周围放置了一个小窗口,并观察窗口内某个方向上强度值的平均变化;
首先获得平均强度值变化最大的方向,然后检查垂直方向上的平均强度变化值,看它是否也很大;如果是,就说明这是一个角点;判断一个点为角点的条件是它的协方差矩阵的最小特征值要大于指定的阈值。
快速检测特征:
FAST对角点的定义基于候选特征点周围的图像强度值。以某个点为中心做一个圆,根据圆上的像素值判断该点是否为关键点。如果存在这样一段圆弧,它的连续长度超过周长的3/4,并且它上面所有像素的强度值都与圆心的强度值明显不同,那么久认为这是一个关键点。
尺度不变特征的检测
SURF特征(加速稳健特征)不仅是尺度不变特征,而且是具有较高计算效率的特征。是SIFT算法的加速版。SURT和SIFT是受专利保护的,在用于商业应用程序是必须遵守许可协议。这也是它们被放在cv::xfeatures2d包中的原因之一。
多尺度FAST特征的检测
BRISK(二元稳健恒定可扩展关键点)检测法
ORB(定向FAST和旋转BRIEF)检测法 (原理基于每个被检测的兴趣点总是关联了一个方向,ORB算法建议使用关键点周围的圆形邻域的重心方向。因为根据定义,FAST关键点肯定有一个偏离中心点的重心,中心点与重心连线的角度重视非常明确) 这两种可以实现快速可靠的图像匹配,如果搭上相关的二值描述子,它们的性能可以进一步提高。
局部模板匹配:
通过特征点匹配,可以将一幅图像的点集和另一幅图像(或一批图像)的点集关联起来。如果两个点集对应着现实世界中的同一个场景元素,它们就应该是匹配的。
仅凭单个像素就判断两个关键点的相似度显然是不够的,因此要在匹配过程中考虑每个关键点周围的图像块。如果两幅图像块对应着同一场景元素,那么它们的像素值应该会比较相似。
最常见的图像块是边长为奇数的正方形,关键点的位置就是正方形的中心。可通过比较块内像素的强度值来衡量两个正方形图像块的相似度。(常见的方案是采用简单的差的平方和(Sum of Squared Differents,SSD)先使用FAST检测器检测每幅图像的关键点。
只要两幅图像的视角和光照都比较相似,仅用差值平方和来比较两个图像窗口也能得到较好的结果。
实际上,只要光照有变化,图像块中所有像素的强度值就会增强或降低,差值平方也会发生很大的变化。为了减少光照对匹配结果的影响,还可以采用衡量图像窗口相似度的其他公式:如归一化的差值平方和。
模板匹配:
图像分析中的常见任务是检测图像中是否存在特定的图案或物体。实现方法是把包含该物体的小图像作为模板,然后在指定图像上搜索与模板相似的部分。搜索的范围通常仅限于可能发现该物体的区域。在这个区域上滑动模板,并在每个像素位置计算相似度。
cv::matchTemplate //函数的输入对象是一个小图像模板和一个被搜索的图像。
模板尺寸M×N,图像尺寸为W×H,则结果矩阵的尺寸就是(W-M+1)×(H-N+1)
描述并匹配局部强度值模式:
在图像分析中,可以用邻域包含的视觉信息来标识每个特征点,以便区分各个特征点。特征描述子通常是一个N维向量,在光照变化和拍摄角度发生微小扭曲时,它描述特征点的方式不会发生变化。通常可以用简单的差值矩阵来比较描述子(欧几里得距离)。
使用SURF和SIFT的特征和描述子可以进行尺度无关的匹配。
1、交叉检查匹配项
有一种简单的方法可以验证得到的匹配项,即重新进行同一个匹配过程,但在第二次匹配时,将第二幅图像的每个关键点逐个与第一幅图像的关键点进行比较。只有在两个方向都匹配了同一对关键点(即两个关键点互为最佳匹配)时,才认为是一个有效的匹配项。
cv::BFMatcher matcher2(cv::NORM_L2, //度量差距
true); //交叉检查标志
2、比率检验法
当场景中有很多相似的物体,一个关键点可以与多个其他关键点匹配,错误匹配项非常多,需要将其排除。因此需要找到两个最佳的匹配项。循环遍历每个关键点匹配项,然后执行比率检验法。
3、匹配差值的阈值化
把描述子之间的差值太大的匹配项排除。(多个策略结合使用提升匹配效果)
计算图像对的基础矩阵:
我们知道,沿着三维点X和相机中心点之间的连线,可在图像上找到对应的点x。反过来,在三维空间中,与成像平面上的位置x对应的肠镜点可以位于线条上的任何位置。这说明如果要根据图像中的一个点找到另一幅图像中对应的点,就需要在第二个成像平面上沿着这条线的投影搜素。这条虚线称为点x的对极线。它规定了两个对应点必须满足的基本条件,即对于一个点,在另一个视图中与它匹配的点必须位于它的对极线上,并且对极线的准确方向取决于两个相机的相对位置。事实上,所有对极线组成的结构决定了双视图系统的几何形状。
用RANSAC(随机抽样一致性)算法匹配图像
计算两幅图像之间的单应矩阵
仍然考虑三维点和它在相机中的影像之间的投影关系,会发现有两种特殊情况,这种特殊的矩阵称为单应矩阵:
第一:同一场景中两个视图之间的差别只有(这是外部矩阵的第四列全部变为0,即没有平移量),在这种特殊情况下,投影关系就变为了3×3的矩阵。
第二:如果拍摄目标是一个平面,也会出现类似的情况,这时,可以假设仍能保持通用性,即平面上的点都位于Z=0的位置。场景点中值为0 的坐标会消除掉投影矩阵的第三列,从而又变成一个3×3的矩阵。
在这种特殊情况下,世界坐标系的点和它的影像之间是线性关系。由于该矩阵是可逆的,所以只要两个视图只是经过了旋转或者拍摄的是平面物体,那么就可以将一个视图中的像素点与另一个视图中的对应的像素点直接关联起来。单应矩阵的格式为:
其中H是一个3×3矩阵。这个关系式包含了一个尺度因子,用s表示。计算得到这个矩阵后,一个视图中的所有点都可以根据这个关系式转换到另一个视图。(注意,在使用单位矩阵关系式后,基础矩阵就没有意义了。)
检测图像中的平面目标
采取的方法是检测这个平面物体的特征点,然后试着在图像中匹配这些特征点。然后用鲁棒匹配方案来验证这些匹配项,但要基于单应矩阵。如果有效匹配项的数量很多,就说明该平面目标在当前图像中。
由于不知道图像中目标物体的大小,所以我们把目标图像转换成一系列不同的尺寸,构建成一个金字塔。除了这种方法,也可以采用尺度不变特征。
执行三个步骤:
1、在输入图像中检测兴趣点。
2、将图像与目标金字塔总的每幅图像进行鲁棒匹配,并把优质匹配项最多的那一层保留下来;如果这一层的匹配项小足够多,就可以认为已经找到目标。
3、使用得到的单应矩阵和cv::getPerspectiveTransform函数,把目标中的四个角点重新投影到输入图像中。
相机标定
相机标定的基本原理是,确定场景中一系列点的三维坐标并拍摄这个场景,然后观测这些点再图像上投影的位置。有了足够多的三维点和图像上对应的二维点,就可以根据投影方程推断出准确的相机参数。显然,为了得到精确的结果,就要观测尽可能多的点。【1】是对一个包含大量三维点的场景取像。但是在实际操作中,这种做法几乎是不可行的。【2】更实用的做法是从不同的视角为一些三维点拍摄多个照片。但是它除了需要计算相机本身的参数,还需要计算每个相机视图的位置。