第二周作业
介绍
黄捷洲 CSDN-AI13期学员
(本周共计6个必做作业)
1. 画图解释图像卷积滤波的基本原理,并进一步简述常见的图像平滑滤波算法。
图像卷积滤波的基本原理:
一个二维的滤波器矩阵(也就是卷积核)和一个要处理的二维图像;对于图像的每一个像素点,计算它的邻域像素和滤波器矩阵的对应元素的乘积,然后加起来,作为该像素位置的值,这样就完成了滤波过程。
不难发现,用3×3的核对一副6×6的图像进行卷积,得到的是4×4的图,图片缩小了!那怎么办呢?我们可以把原图扩充一圈,再卷积,这个操作叫padding。
事实上,原图为n×n,卷积核为f×f,最终结果图大小为(n-f+1) × (n-f+1)
cv2.copyMakeBorder()用来给图片添加边框,它有下面几个参数:
src:要处理的原图
top, bottom, left, right:上下左右要扩展的像素数
borderType:边框类型,这个就是需要关注的填充方式,详情请参考:BorderTypes
常用的填充类型有:固定值填充,取镜像对称的像素填充等。
常见的图像平滑滤波算法
均值滤波、盒型滤波
高斯滤波
中值滤波
双边滤波
1、均值滤波、盒型滤波
均值滤波是一种典型的线性滤波算法,主要是利用像素点邻域的像素值来计算像素点的值。其具体方法是首先给出一个滤波模板kernel,该模板将覆盖像素点周围的其他邻域像素点,去掉像素本身,将其邻域像素点相加然后取平均值即为该像素点的新的像素值,这就是均值滤波的本质。
函数:
cv2.blur(InputArray src, OutputArray dst, Size ksize, Point anchor=Point(-1,-1), int borderType=BORDER_DEFAULT )
参数解释:
InputArray src: 输入图像,可以是Mat类型,图像深度是CV_8U、CV_16U、CV_16S、CV_32F以及CV_64F其中的某一个。
OutputArray dst: 输出图像,深度和类型与输入图像一致
Size ksize: 滤波模板kernel的尺寸,一般使用Size(w, h)来指定,如Size(3,3)
Point anchor=Point(-1, -1): 字面意思是锚点,也就是处理的像素位于kernel的什么位置,默认值为(-1, -1)即位于kernel中心点,如果没有特殊需要则不需要更改
int borderType=BORDER_DEFAULT: 用于推断图像外部像素的某种边界模式,有默认值BORDER_DEFAULT
优点:使用简单,计算方便
特点:核中区域贡献率相同
2、高斯滤波
高斯滤波是对整幅图像进行加权平均的过程,每一个像素点的值都由其本身和邻域内的其他像素值经过加权平均后得到。
函数:
cv2.GaussianBlur(InputArray src, OutputArray dst, Size ksize, double sigmaX, double sigmaY=0, int borderType=BORDER_DEFAULT )
参数解释:
InputArray src: 输入图像,可以是Mat类型,图像深度为CV_8U、CV_16U、CV_16S、CV_32F、CV_64F。
OutputArray dst: 输出图像,与输入图像有相同的类型和尺寸。
Size ksize: 高斯内核大小,这个尺寸与前面两个滤波kernel尺寸不同,ksize.width和ksize.height可以不相同但是这两个值必须为正奇数,如果这两个值为0,他们的值将由sigma计算。
double sigmaX: 高斯核函数在X方向上的标准偏差
double sigmaY: 高斯核函数在Y方向上的标准偏差,如果sigmaY是0,则函数会自动将sigmaY的值设置为与sigmaX相同的值,如果sigmaX和sigmaY都是0,这两个值将由ksize.width和ksize.height计算而来。具体可以参考getGaussianKernel()函数查看具体细节。建议将size、sigmaX和sigmaY都指定出来。
int borderType=BORDER_DEFAULT: 推断图像外部像素的某种便捷模式,有默认值BORDER_DEFAULT,如果没有特殊需要不用更改,具体可以参考borderInterpolate()函数。
优点:对于服从正太分布的噪声非常有效
特点:核中区域贡献率与距离中心成正比,权重与高斯分布相关
3、中值滤波
用像素点邻域灰度值的中值来代替该像素点的灰度值,让周围的像素值接近真实的值从而消除孤立的噪声点。该方法在取出脉冲噪声、椒盐噪声的同时能保留图像的边缘细节。
函数:
cv2.medianBlur(InputArray src, OutputArray dst, int ksize)
参数解释:
InputArray src: 输入图像,图像为1、3、4通道的图像,当模板尺寸为3或5时,图像深度只能为CV_8U、CV_16U、CV_32F中的一个,如而对于较大孔径尺寸的图片,图像深度只能是CV_8U。
OutputArray dst: 输出图像,尺寸和类型与输入图像一致,可以使用Mat::Clone以原图像为模板来初始化输出图像dst
int ksize: 滤波模板的尺寸大小,必须是大于1的奇数,如3、5、7……
优点:对于椒盐噪声有效
特点:中心点的像素值被核中的中位数的像素代替
4、双边滤波
结合图像的空间邻近度和像素值相似度的一种折衷处理,同时考虑空间与信息和灰度相似性,达到保边去噪的目的,具有简单、非迭代、局部处理的特点。
函数:
cv2.bilateralFilter(InputArray src, OutputArray dst, int d, double sigmaColor, double sigmaSpace, int borderType=BORDER_DEFAULT)
参数解释:
InputArray src: 输入图像,可以是Mat类型,图像必须是8位或浮点型单通道、三通道的图像。
OutputArray dst: 输出图像,和原图像有相同的尺寸和类型。
int d: 表示在过滤过程中每个像素邻域的直径范围。如果这个值是非正数,则函数会从第五个参数sigmaSpace计算该值。
double sigmaColor: 颜色空间过滤器的sigma值,这个参数的值月大,表明该像素邻域内有月宽广的颜色会被混合到一起,产生较大的半相等颜色区域。
double sigmaSpace: 坐标空间中滤波器的sigma值,如果该值较大,则意味着颜色相近的较远的像素将相互影响,从而使更大的区域中足够相似的颜色获取相同的颜色。当d>0时,d指定了邻域大小且与sigmaSpace五官,否则d正比于sigmaSpace.
int borderType=BORDER_DEFAULT: 用于推断图像外部像素的某种边界模式,有默认值BORDER_DEFAULT.
作用:可以很好的保存图像边缘细节而滤除掉低频分量的噪音
特点:双边滤波器的效率不是太高,花费的时间相较于其他滤波器而言也比较长
以上算法的程序实现如下:
# -*- coding:utf-8 -*-
#本程序用于实现各种滤波算法
import cv2 #导入opencv模块
import numpy as np
print("Hellow word!") #打印“Hello word!”,验证模块导入成功
img = cv2.imread("imag2.jpg") #导入图片,图片放在程序所在目录
cv2.namedWindow("imagshow", 2) #创建一个窗口
cv2.imshow('imagshow', img) #显示原始图片
# 均值滤波
img_mean = cv2.blur(img, (3,3)) #参数1输入图像,参数2核大小
cv2.namedWindow("mean", 2) #创建一个窗口
cv2.imshow('mean', img_mean) #显示原始图片
# 高斯滤波
img_Guassian = cv2.GaussianBlur(img,(3,3),0)
cv2.namedWindow("Guassian", 2) #创建一个窗口
cv2.imshow('Guassian', img_Guassian) #显示原始图片
# 中值滤波
img_median = cv2.medianBlur(img, 5)
cv2.namedWindow("median", 2) #创建一个窗口
cv2.imshow('median', img_median) #显示原始图片
# 双边滤波
img_bilater = cv2.bilateralFilter(img,9,75,75)
cv2.namedWindow("bilater", 2) #创建一个窗口
cv2.imshow('bilater', img_bilater) #显示原始图片
cv2.waitKey()
从左到右分别为:原图、中值滤波、双边滤波、均值滤波、高斯滤波。
2. 简述边缘检测的基本原理,以及Sobel、LoG和Canny算子的原理差异。
图象的边缘是指图象局部区域亮度变化显著的部分,该区域的灰度剖面一般可以看作是一个阶跃,既从一个灰度值在很小的缓冲区域内急剧变化到另一个灰度相差较大的灰度值。图象的边缘部分集中了图象的大部分信息,图象边缘的确定与提取对于整个图象场景的识别与理解是非常重要的,同时也是图象分割所依赖的重要特征,边缘检测主要是图象的灰度变化的度量、检测和定位。
边缘检测的基本原理: 边缘检测的本质是微分,如上图所示,当相邻两个像素点的灰度值差异越大时,也就是其斜率越陡,也就是微分值越大,进而通过这个来判断边缘,实际中常用差分,x方向和y方向。
Sobel算子
原理:算子使用两个3X3的矩阵(图1)算子使用两个3X3的矩阵(图1)去和原始图片作卷积,分别得到横向G(x)和纵向G(y)的梯度值,如果梯度值大于某一个阈值,则认为该点为边缘点。
LoG算子
原理:首先对图像做高斯滤波,然后再求其拉普拉斯(Laplacian)二阶导数。即图像与 Laplacian of the Gaussian function 进行滤波运算。最后,通过检测滤波结果的零交叉(Zero crossings)可以获得图像或物体的边缘。因而,也被业界简称为Laplacian-of-Gaussian (LoG)算子。
Canny算子
算法步骤:
第一步:对原始图像进行灰度化、对图像进行高斯滤波
Canny算法通常处理的图像为灰度图,因此如果摄像机获取的是彩色图像,那首先就得进行灰度化。对一幅彩色图进行灰度化,就是根据图像各个通道的采样值进行加权平均。图像高斯滤波的实现可以用两个一维高斯核分别两次加权实现,也可以通过一个二维高斯核一次卷积实现。
第二步:用一阶偏导的有限差分来计算梯度的幅值方向
关于图像灰度值得梯度可使用一阶有限差分来进行近似,这样就可以得图像在x和y方向上偏导数的两个矩阵。
第三步:对梯度幅值进行非极大值抑制
图像梯度幅值矩阵中的元素值越大,说明图像中该点的梯度值越大,但这不不能说明该点就是边缘(这仅仅是属于图像增强的过程)。
第四步:用双阈值算法检测和连接边缘
Canny算法中减少假边缘数量的方法是采用双阈值法。选择两个阈值(关于阈值的选取方法在扩展中进行讨论),根据高阈值得到一个边缘图像,这样一个图像含有很少的假边缘,但是由于阈值较高,产生的图像边缘可能不闭合,为解决这样一个问题采用了另外一个低阈值。在高阈值图像中把边缘链接成轮廓,当到达轮廓的端点时,该算法会在断点的8邻域点中寻找满足低阈值的点,再根据此点收集新的边缘,直到整个图像边缘闭合。
三种边缘检测的程序实现:
# -*- coding:utf-8 -*-
#本程序用于边缘检测
import cv2 #导入opencv模块
import numpy as np
print("Hellow word!") #打印“Hello word!”,验证模块导入成功
img = cv2.imread("lena.jpg") #导入图片,图片放在程序所在目录
cv2.namedWindow("imagshow", 2) #创建一个窗口
cv2.imshow('imagshow', img) #显示原始图片
#高斯模糊
blurred = cv2.GaussianBlur(img, (3, 3), 0)
#转换为灰度图
out_img_GRAY=cv2.cvtColor(blurred,cv2.COLOR_BGR2GRAY)#将图片转换为灰度图
cv2.namedWindow("img_GRAY", 2) #创建一个窗口
cv2.imshow('img_GRAY', out_img_GRAY) #显示原始图片
#使用Canny算子进行边缘检测
edge_output = cv2.Canny(out_img_GRAY, 10, 300)
cv2.namedWindow("Canny", 2) #创建一个窗口
cv2.imshow('Canny', edge_output) #显示原始图片
#使用sobel算子进行边缘检测
sobel = cv2.Sobel(out_img_GRAY,-1,1,0,ksize=3)
cv2.namedWindow("sobel", 2) #创建一个窗口
cv2.imshow('sobel', sobel) #显示原始图片
#使用laplacian算子进行边缘检测
laplacian = cv2.Laplacian(out_img_GRAY,-1)
cv2.namedWindow("laplacian", 2) #创建一个窗口
cv2.imshow('laplacian', laplacian) #显示原始图片
cv2.waitKey()
实现效果如图:
从左到右分别为:灰度图、Canny算子、soble算子、laplacian算子
3. 简述图像直方图的基本概念,及使用大津算法进行图像分割的基本原理。
图像直方图的基本概念:
统计各个灰度级别在图像中的出现次数或概率,并用直方图显示出来,一般直方图显示图像数据时会以左暗又亮的分布曲线形式呈现出来。
且具有图像平移、旋转、缩放不变性等众多优点,直方图在进行图像计算处理时代价较小,所以经常用于图像处理。
Lena图像的灰度直方图和RGB直方图显示:
python程序实现,使用matplotlib模块绘制:
# -*- coding:utf-8 -*-
#本程序用于显示图片的直方图
import cv2 #导入opencv模块
import numpy as np
import matplotlib.pyplot as plt
print("Hellow word!") #打印“Hello word!”,验证模块导入成功
img = cv2.imread("lena.jpg") #导入图片,图片放在程序所在目录
cv2.namedWindow("imagshow", 2) #创建一个窗口
cv2.imshow('imagshow', img) #显示原始图片
gray=cv2.cvtColor(img,cv2.COLOR_BGR2GRAY) #转换为灰度图
plt.hist(gray.ravel(), 256, [0, 256]) #计算灰度直方图
plt.show()
# 蓝 绿 红
color = ('b', 'g', 'r')#颜色分量
for i, col in enumerate(color):
histr = cv2.calcHist([img], [i], None, [256], [0, 256]) #计算颜色分量直方图
plt.plot(histr, color=col) #绘制直方图
plt.xlim([0, 256])
plt.show() #显示直方图
cv2.waitKey()
大津算法的基本原理:
遍历灰度取值,确定最佳阈值,使背景和目标之间的类间方差最大 (因为二者差异最大);
记t为前景与背景的分割阈值,前景点数占图像比例为w0,平均灰度为u0;背景点数占图像比例为w1,平均灰度为u1。
则图像的总平均灰度为:u=w0u0+w1u1。
前景和背景图象的方差:g=w0*(u0-u)(u0-u)+w1(u1-u)(u1-u)=w0w1*(u0-u1)*(u0-u1),此公式为方差公式。
可参照概率论课本上面的g的公式也就是下面程序中的sb的表达式。当方差g最大时,可以认为此时前景和背景差异最大,此时的灰度t是最佳阈值sb = w0w1(u1-u0)*(u0-u1)
python算法实现及效果:
# -*- coding:utf-8 -*-
#本程序用于大津算法的实现
import cv2 #导入opencv模块
import numpy as np
print("Hellow word!") #打印“Hello word!”,验证模块导入成功
img = cv2.imread("test.jpg") #导入图片,图片放在程序所在目录
cv2.namedWindow("imagshow", 2) #创建一个窗口
cv2.imshow('imagshow', img) #显示原始图片
gray=cv2.cvtColor(img,cv2.COLOR_BGR2GRAY) #转换为灰度图
retval,dst = cv2.threshold(gray, 0, 255, cv2.THRESH_OTSU) #使用大津算法进行图像二值化
cv2.namedWindow("dst", 2) #创建一个窗口
cv2.imshow("dst", dst)
cv2.waitKey()
二值化效果图及图像直方图:
4. 简述Harris算子对角点的定义,进行角点检测的基本原理,并说明引入角点响应函数的意义。
Harris算子的定义:
Harris算子是Harris和Stephens在1998年提出的一种基于信号的点特征提取算子。其前身是Moravec算子。
其基本思想是:在图像中设计一个局部检测窗口,当该窗口沿各个方向做微小移动时,考察窗口的平均能量变化,当该能量变化超过设定的阈值时,就将窗口的中心像素点提取为角点。
在角点处,任意方向移动,窗口灰度值均剧烈变化
引入角点响应函数的意义:
对于图像I(x,y)I(x,y),当在点(x,y)(x,y)处平移(Δx,Δy)(Δx,Δy)后的自相似性,可以通过自相关函数给出:
c(x,y;Δx,Δy)=∑(u,v)∈W(x,y)w(u,v)(I(u,v)–I(u+Δx,v+Δy))2
根据泰勒展开,对图像I(x,y)I(x,y)在平移(Δx,Δy)(Δx,Δy)后进行一阶近似:
I(u+Δx,v+Δy)=I(u,v)+Ix(u,v)Δx+Iy(u,v)Δy+O(Δx2,Δy2)≈I(u,v)+Ix(u,v)Δx+Iy(u,v)Δy
其中,Ix,IyIx,Iy是图像I(x,y)I(x,y)的偏导数,这样的话,自相关函数则可以简化为:
c(x,y;Δx,Δy)≈∑w(Ix(u,v)Δx+Iy(u,v)Δy)2=[Δx,Δy]M(x,y)[ΔxΔy]
也就是说图像I(x,y)I(x,y)在点(x,y)(x,y)处平移(Δx,Δy)(Δx,Δy)后的自相关函数可以近似为二项函数:
c(x,y;Δx,Δy)≈AΔx2+2CΔxΔy+BΔy2
其中
A=∑wI2x,B=∑wI2y,C=∑wIxIy
二次项函数本质上就是一个椭圆函数。椭圆的扁率和尺寸是由M(x,y)M(x,y)的特征值λ1、λ2λ1、λ2决定的,椭贺的方向是由M(x,y)M(x,y)的特征矢量决定的,如下图所示,椭圆方程为:
[Δx,Δy]M(x,y)[ΔxΔy]=1
椭圆函数特征值与图像中的角点、直线(边缘)和平面之间的关系如下图所示。共可分为三种情况:
图像中的直线。一个特征值大,另一个特征值小,λ1≫λ2λ1≫λ2或λ2≫λ1λ2≫λ1。自相关函数值在某一方向上大,在其他方向上小。
图像中的平面。两个特征值都小,且近似相等;自相关函数数值在各个方向上都小。
图像中的角点。两个特征值都大,且近似相等,自相关函数在所有方向都增大。
根据二次项函数特征值的计算公式,我们可以求M(x,y)M(x,y)矩阵的特征值。但是Harris给出的角点差别方法并不需要计算具体的特征值,而是计算一个角点响应值RR来判断角点。RR的计算公式为:
R=detM−α(traceM)2
R为正值是,检测到的是角点,R为负时检测到的是边,R很小时检测到的是平坦区域。
所以,引入角点响应函数的意义是由于λ1、λ2的大小是相对而言的,无法量化进行判断,所以引入角点响应函数,方便判断该点是否是角点。
以下是 Opencv+python 的代码实现
import cv2
import numpy as np
img = cv2.imread("left03.jpg")
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY) #图像灰度化
gray = np.float32(gray)
#图像转换为float32
dst = cv2.cornerHarris(gray,2,3,0.04)#角点检测
#result is dilated for marking the corners, not important
dst = cv2.dilate(dst,None)#图像膨胀提升后续图像角点标注的清晰准确度
# Threshold for an optimal value, it may vary depending on the image.
#print(dst)
#img[dst>0.00000001*dst.max()]=[0,0,255] #可以试试这个参数,角点被标记的多余了一些
img[dst>0.01*dst.max()]=[0,0,255]#角点位置用红色标记
#这里的打分值以大于0.01×dst中最大值为边界
cv2.namedWindow("Harris", 2) #创建一个窗口
cv2.imshow('Harris',img)
cv2.waitKey()
主要函数cv2.cornerHarris(img,blockSize,ksize,k) 可以用来进行角点检测。参数如下:
img - 数据类型为 float32 的输入图像。
blockSize - 角点检测中要考虑的领域大小。
ksize - Sobel 求导中使用的窗口大小
k - Harris 角点检测方程中的自由参数,取值参数为 [0,04,0.06].
效果图:
5. 简述Hough变换的基本原理(包括参数空间变换及参数空间划分网格统计)。
Hough变换思想(参数空间变换):
在原始图像坐标系下的一个点对应了参数坐标系中的一条直线,同样参数坐标系的一条直线对应了原始坐标系下的一个点,然后,原始坐标系下呈现直线的所有点,它们的斜率和截距是相同的,所以它们在参数坐标系下对应于同一个点。这样在将原始坐标系下的各个点投影到参数坐标系下之后,看参数坐标系下有没有聚集点,这样的聚集点就对应了原始坐标系下的直线。
在实际应用中,y=kx+b形式的直线方程没有办法表示x=c形式的直线(这时候,直线的斜率为无穷大)。所以实际应用中,是采用参数方程p=xcos(theta)+y*sin(theta)。这样,图像平面上的一个点就对应到参数p---theta平面上的一条曲线上,其它的还是一样。
Hough变换思想(参数空间划分网格统计):
为了检测出直角坐标X-Y中由点所构成的直线,可以将极坐标a-p量化成许多小格。根据直角坐标中每个点的坐标(x,y),在a = 0-180°内以小格的步长计算各个p值,所得值落在某个小格内,便使该小格的累加记数器加1。当直角坐标中全部的点都变换后,对小格进行检验,计数值最大的小格,其(a,p)值对应于直角坐标中所求直线。
Hough变换的程序实现:
import cv2
import numpy as np
#hough变换的程序实现
img = cv2.imread("building.jpg")#读取图片
img2 = img.copy()
gray =cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)#将图片转换为灰度图
edges = cv2.Canny(gray,50,150,apertureSize = 3)#canny算法提取轮廓
#基于概率的hough变换......................................................
lines_Probabilitys = cv2.HoughLinesP(edges,1,np.pi/180,30,minLineLength=100,maxLineGap=10)#概率hough变换
lines_Probability = lines_Probabilitys[:,0,:]#提取为二维
for x1,y1,x2,y2 in lines_Probability[:]:
cv2.line(img,(x1,y1),(x2,y2),(255,0,0),1)
cv2.namedWindow("HoughLines_Probabilitys", 2) #创建一个窗口
cv2.imshow('HoughLines_Probabilitys', img) #显示原始图片
#标准的hough变换......................................................
lines_standards = cv2.HoughLines(edges,1,np.pi/180,200) #标准hough变换查找直线
#绘制hough变换后找到的所有直线,返回数据是一个二位数组
for lines_standard in lines_standards:
for rho,theta in lines_standard:
a = np.cos(theta)
b = np.sin(theta)
x0 = a*rho
y0 = b*rho
x1 = int(x0 + 1000*(-b))
y1 = int(y0 + 1000*(a))
x2 = int(x0 - 1000*(-b))
y2 = int(y0 - 1000*(a))
cv2.line(img2,(x1,y1),(x2,y2),(0,0,255),2)
print(lines_standards)#打印出找到的直线的极坐标系坐标、
cv2.namedWindow("HoughLines_standards", 2) #创建一个窗口
cv2.imshow('HoughLines_standards', img2) #显示原始图片
cv2.waitKey()
函数参数解释:
HoughLinesP(image, rho, theta, threshold, lines=None, minLineLength=None, maxLineGap=None)
image: 必须是二值图像,推荐使用canny边缘检测的结果图像;
rho: 线段以像素为单位的距离精度,double类型的,推荐用1.0
theta: 线段以弧度为单位的角度精度,推荐用numpy.pi/180
threshod: 累加平面的阈值参数,int类型,超过设定阈值才被检测出线段,值越大,基本上意味着检出的线段越长,检出的线段个数越少。根据情况推荐先用100试试
lines:这个参数的意义未知,发现不同的lines对结果没影响,但是不要忽略了它的存在
minLineLength:线段以像素为单位的最小长度,根据应用场景设置
maxLineGap:同一方向上两条线段判定为一条线段的最大允许间隔(断裂),超过了设定值,则把两条线段当成一条线段,值越大,允许线段上的断裂越大,越有可能检出潜在的直线段
cvHoughLines2( CvArr* image, void* line_storage, int method,double rho, double theta, int threshold,double param1=0, double param2=0 );
image输入 8-比特、单通道 (二值) 图像,其内容可能被函数所改变
line_storage检测到的线段存储.
method
CV_HOUGH_STANDARD - 传统或标准 Hough 变换. 每一个线段由两个浮点数 (ρ, θ)表示,其中 ρ 是点与原点 (0,0) 之间的距离, θ 线段与 x-轴之间的夹角。因此,矩阵类型必须是 CV_32FC2 type.
CV_HOUGH_PROBABILISTIC - 概率 Hough 变换(如果图像包含一些长的线性分割,则效率更高). 它返回线段分割而不是整个线段。每个分割用起点和终点来表示,所以矩阵(或创建的序列)类型是 CV_32SC4.
CV_HOUGH_MULTI_SCALE - 传统 Hough 变换的多尺度变种。线段的编码方式与CV_HOUGH_STANDARD 的一致。
rho与象素相关单位的距离精度
theta弧度测量的角度精度
threshold阈值参数。如果相应的累计值大于 threshold, 则函数返回的这个线段.
param1第一个方法相关的参数:
对传统 Hough 变换,不使用(0).
对概率 Hough 变换,它是最小线段长度.
对多尺度 Hough 变换,它是距离精度 rho 的分母 (大致的距离精度是 rho 而精确的应该是 rho / param1 ).
param2第二个方法相关参数:
对传统 Hough 变换,不使用 (0).
对概率 Hough 变换,这个参数表示在同一条直线上进行碎线段连接的最大间隔值(gap), 即当同一条直线上的两条碎线段之间的间隔小于 param2 时,将其合二为一。
对多尺度 Hough 变换,它是角度精度 theta 的分母 (大致的角度精度是 theta 而精确的角度应该是 theta / param2).
检测效果:
6. 简述SIFT原理(重点是尺度空间和方向直方图原理)及ORB算子原理(重点是FAST和BRIEF)。
SIFT算法分为如下四步:
尺度空间极值检测:搜索所有尺度上的图像位置。通过高斯微分函数来识别潜在的对于尺度和旋转不变的兴趣点。
关键点定位:在每个候选的位置上,通过一个拟合精细的模型来确定位置和尺度。关键点的选择依据于它们的稳定程度。
方向确定:基于图像局部的梯度方向,分配给每个关键点位置一个或多个方向。所有后面的对图像数据的操作都相对于关键点的方向、尺度和位置进行变换,从而提供对于这些变换的不变性。
关键点描述:在每个关键点周围的邻域内,在选定的尺度上测量图像局部的梯度。这些梯度被变换成一种表示,这种表示允许比较大的局部形状的变形和光照变化。
尺度空间理论的基本思想是:在图像信息处理模型中引入一个被视为尺度的参数,通过连续变化尺度参数获得多尺度下的尺度空间表示序列,对这些序列进行尺度空间主轮廓的提取,并以该主轮廓作为一种特征向量,实现边缘、角点检测和不同分辨率上的特征提取等。
方向直方图原理:为了使描述符具有旋转不变性,需要利用图像的局部特征为给每一个关键点分配一个基准方向。使用图像梯度的方法求取局部结构的稳定方向。对于在DOG金字塔中检测出的关键点点,采集其所在高斯金字塔图像3σ邻域窗口内像素的梯度和方向分布特征。
ORB算子
ORB采用FAST(features from accelerated segment test)算法来检测特征点。FAST核心思想就是找出那些卓尔不群的点,即拿一个点跟它周围的点比较,如果它和其中大部分的点都不一样就可以认为它是一个特征点。
FAST具体计算过程:
从图片中选取一个像素点P,下面我们将判断它是否是一个特征点。我们首先把它的密度(即灰度值)设为Ip。
设定一个合适的阙值t :当2个点的灰度值之差的绝对值大于t时,我们认为这2个点不相同。
考虑该像素点周围的16个像素。(见上图)
现在如果这16个点中有连续的n个点都和点不同,那么它就是一个角点。 这里n设定为12。
我们现在提出一个高效的测试,来快速排除一大部分非特征点的点。该测试仅仅检查在位置1、9、5和13四个位置的像素(首先检查1和9,看它们是否和点相同。如果是,再检查5和13)。如果是一个角点,那么上述四个像素点中至少有3个应该和点相同。如果都不满足,那么不可能是一个角点。
BRIEF:
得到特征点后我们需要以某种方式F描述这些特征点的属性。这些属性的输出我们称之为该特征点的描述子(Feature DescritorS).ORB采用BRIEF算法来计算一个特征点的描述子。BRIEF算法的核心思想是在关键点P的周围以一定模式选取N个点对,把这N个点对的比较结果组合起来作为描述子。
具体来讲分为以下几步。
1.以关键点P为圆心,以d为半径做圆O。
2.在圆O内某一模式选取N个点对。这里为方便说明,N=4,实际应用中N可以取512.
假设当前选取的4个点对如上图所示分别标记为:
3.定义操作T
4.分别对已选取的点对进行T操作,将得到的结果进行组合。
假如:
则最终的描述子为:1011
算法程序实现
import numpy as np
import cv2
#from matplotlib import pyplot as plt
#print cv2.__version__
img1 = cv2.imread('box.png',0) # queryImage
img2 = cv2.imread('box_in_scene.png',0) # trainImage
def drawMatches(img1, kp1, img2, kp2, matches):
# Create a new output image that concatenates the two images together
# (a.k.a) a montage
rows1 = img1.shape[0]
cols1 = img1.shape[1]
rows2 = img2.shape[0]
cols2 = img2.shape[1]
out = np.zeros((max([rows1,rows2]),cols1+cols2,3), dtype='uint8')
# Place the first image to the left
out[:rows1,:cols1] = np.dstack([img1, img1, img1])
# Place the next image to the right of it
out[:rows2,cols1:] = np.dstack([img2, img2, img2])
# For each pair of points we have between both images
# draw circles, then connect a line between them
for mat in matches:
# Get the matching keypoints for each of the images
img1_idx = mat.queryIdx
img2_idx = mat.trainIdx
# x - columns
# y - rows
(x1,y1) = kp1[img1_idx].pt
(x2,y2) = kp2[img2_idx].pt
# Draw a small circle at both co-ordinates
# radius 4
# colour blue
# thickness = 1
cv2.circle(out, (int(x1),int(y1)), 4, (255, 0, 0), 1)
cv2.circle(out, (int(x2)+cols1,int(y2)), 4, (255, 0, 0), 1)
# Draw a line in between the two points
# thickness = 1
# colour blue
cv2.line(out, (int(x1),int(y1)), (int(x2)+cols1,int(y2)), (255, 0, 0), 1)
# Show the image
cv2.imshow('Matched Features', out)
cv2.waitKey(0)
cv2.destroyWindow('Matched Features')
# Also return the image if you'd like a copy
return out
# Initiate SIFT detector
orb = cv2.ORB_create()
# find the keypoints and descriptors with SIFT
kp1, des1 = orb.detectAndCompute(img1,None)
kp2, des2 = orb.detectAndCompute(img2,None)
# create BFMatcher object
bf = cv2.BFMatcher(cv2.NORM_HAMMING, crossCheck=True)
# Match descriptors.
matches = bf.match(des1,des2)
# Sort them in the order of their distance.
matches = sorted(matches, key = lambda x:x.distance)
# Draw first 10 matches.
img3 = drawMatches(img1,kp1,img2,kp2,matches[:10])
cv2.imshow('dst',img3)
if cv2.waitKey(0) & 0xff == 27:
cv2.destroyAllWindows()