边缘检测
边缘检测是图像处理和计算机视觉中的基本问题,边缘检测的目的是标识数字图像中亮度变化明显的点。图像属性中的显著变化通常反映了属性的重要事件和变化。边缘检测是特征提取中的一个研究领域。
图像边缘检测大幅度地减少了数据量,并且剔除了可以认为不相关的信息,保留了图像重要的结构属性。有许多方法用于边缘检测,它们的绝大部分可以划分为两类:基于查找一类和基于零穿越的一类。基于查找的方法通过寻找图像一阶导数中的最大值和最小值来检测边界,通常是将边界定位在梯度最大的方向。基于零穿越的方法通过寻找图像二阶导数零穿越来寻找边界,通常是Laplacian过零点或者非线性差分表示的过零点。
如果将边缘认为是一定数量点亮度发生变化的地方,那么边缘检测大体上就是计算这个亮度变化的导数。
(一)检测方法
边缘检测的方法大致可分为两类:基于搜索和基于零交叉
基于搜索的边缘检测方法首先计算边缘强度,通常用一阶导数表示,例如梯度模,然后,用计算估计边缘的局部方向,通常采用梯度的方向,并利用此方向找到局部梯度模的最大值。
基于零交叉的方法找到由图像得到的二阶导数的零交叉点来定位边缘,通常用拉普拉斯算子或非线性微分方程的零交叉点。
滤波作为边缘检测的预处理通常是必要的,通常采用高斯滤波。
(二)Sobel边缘检测算子
Sobel边缘检测算法比较简单,实际应用中效率比canny边缘检测效率要高,但是边缘不如Canny检测的准确,但是很多实际应用的场合,sobel边缘却是首选,Sobel算子是高斯平滑与微分操作的结合体,所以其抗噪声能力很强,用途较多。尤其是效率要求较高,而对细纹理不太关系的时候。算子的模板为:
Sobel算子是一种带有方向的过滤器,openCV中Sobel算子的函数为cv2.Sobel ()。
Sobel_x_or_y = cv2.Sobel(src, ddepth, dx, dy, dst, ksize, scale, delta, borderType)
dst及dst之后的参数都是可选参数。
第一个参数是传入的图像,第二个参数是图像的深度,dx和dy指的是求导的阶数,0表示这个方向上没有求导,所填的数一般为0、1、2。ksize是Sobel算子的大小,即卷积核的大小,必须为奇数1、3、5、7。如果ksize=-1,就演变成为3x3的Scharr算子,scale是缩放导数的比例常数,默认情况为没有伸缩系数。borderType是判断图像边界的模式,这个参数默认值为cv2.BORDER_DEFAULT。
# Sobel边缘检测算子
img = cv2.imread('luotuo.jpg', 0)
x = cv2.Sobel(img, cv2.CV_16S, 1, 0)
y = cv2.Sobel(img, cv2.CV_16S, 0, 1)
# cv2.convertScaleAbs(src[, dst[, alpha[, beta]]])
# 可选参数alpha是伸缩系数,beta是加到结果上的一个值,结果返回uint类型的图像
Scale_absX = cv2.convertScaleAbs(x) # convert 转换 scale 缩放
Scale_absY = cv2.convertScaleAbs(y)
result = cv2.addWeighted(Scale_absX, 0.5, Scale_absY, 0.5, 0)
cv2.imshow('img', img)
cv2.imshow('Scale_absX', Scale_absX)
cv2.imshow('Scale_absY', Scale_absY)
cv2.imshow('result', result)
cv2.waitKey(0)
cv2.destroyAllWindows()
Sobel函数求完导数后会有负值,还有会大于255的值。而原图像是uint8,即8位无符号数,所以Sobel建立的图像位数不够,会有截断。因此要使用16位有符号的数据类型,即cv2.CV_16S。处理完图像后,再使用cv2.convertScaleAbs()函数将其转回原来的uint8格式,否则图像无法显示。
Sobel算子是在两个方向计算的,最后还需要用cv2.addWeighted( )函数将其组合起来。
result = cv2.addWeighted(src1, alpha, src2, beta, gamma[, dst[, dtype]])
其中alpha是第一幅图片中元素的权重,beta是第二个图像的权重,gamma是加到最后结果上的一个值。
(三)Scharr算子
由(二)中可知,当Sobel()函数的参数ksize=-1时,就演变成了3x3的Scharr算子。算子的模板为:
# Scharr算子
img = cv2.imread('luotuo.jpg', 0)
x = cv2.Sobel(img, cv2.CV_16S, 1, 0, ksize=-1)
y = cv2.Sobel(img, cv2.CV_16S, 0, 1, ksize=-1)
# ksize=-1 Scharr算子
# cv2.convertScaleAbs(src[, dst[, alpha[, beta]]])
# 可选参数alpha是伸缩系数,beta是加到结果上的一个值,结果返回uint类型的图像
Scharr_absX = cv2.convertScaleAbs(x) # convert 转换 scale 缩放
Scharr_absY = cv2.convertScaleAbs(y)
result = cv2.addWeighted(Scharr_absX, 0.5, Scharr_absY, 0.5, 0)
cv2.imshow('img', img)
cv2.imshow('Scharr_absX', Scharr_absX)
cv2.imshow('Scharr_absY', Scharr_absY)
cv2.imshow('result', result)
cv2.waitKey(0)
cv2.destroyAllWindows()
从Scharr算子与Sobel算子最终的结果比较可以看出,Scharr对图像梯度的变化更加敏感。
(四)拉普拉斯(Laplacian)算子
Laplacian函数实现的方法是先用Sobel算子计算二阶x和y导数,再求和。
laplacian = cv2.Laplacian(src, ddepth[, dst[, ksize[, scale[, delta[, borderType]]]]])
前两个参数是必选参数,其后是可选参数。第一个参数是需要处理的图像,第二个参数是图像的深度,-1表示采用的是原图像相同的深度,目标图像的深度必须大于等于原图像的深度;ksize参数是算子的大小,即卷积核的大小,必须为1,3,5,7。默认为1。
scale是缩放导数的比例chan常数,默认情况下没有伸缩系数;borderType是判断图像边界的模式。这个参数默认值为cv2.BORDER_DEFAULT。
# 拉普拉斯算子
img = cv2.imread('luotuo.jpg', 0)
laplacian = cv2.Laplacian(img, cv2.CV_16S, ksize=3)
dst = cv2.convertScaleAbs(laplacian)
cv2.imshow('laplacian', dst)
cv2.waitKey(0)
cv2.destroyAllWindows()
当ksize=1时,图像为
ksize=5时
ksize=7时
由此可见,当参数ksize越大即卷积核越大时,算子对图像梯度的变化越敏感。ksize=3时的图像还不是很好,在经过高斯模糊处理一下,去掉了很多噪声。
blur = cv2.GaussianBlur(img, (3, 3), 0)
laplacian = cv2.Laplacian(blur, cv2.CV_16S, ksize=3)
(五)Canny算子
图像边缘检测必须满足两个条件,一能有效地抑制噪声;二必须尽量精确确定边缘的位置。
根据对信噪比与定位乘积进行测度,得到最优化逼近算子。这就是Canny边缘检测算子。
算法的基本步骤为:
1.用高斯滤波器平滑图像;
2.用一阶偏导的有限差分来计算梯度的幅值和方向;
3.对梯度幅值进行非极大抑制;
4.用双阈值算法检测和连接边缘。
更详细的介绍可以参考:canny算子1 canny算子2
canny = cv2.Canny(image, threshold1, threshold2[, edges[, apertureSize[, L2gradient ]]])
第一个参数是需要处理的原图像单通道的灰度图,第二个参数是阈值1,第二个参数是阈值2,较大的阈值2用于检测图像中明显的边缘,但一般情况下检测的效果不会那么完美,边缘检测出来是断断续续的。所以这时候应用较小的第一个阈值来将这些间断的边缘连接起来。可选参数中aperaperturesize参数就是卷积核的大,而L2gradient参数就是一个布尔值,如果为true,则就使用更精确的L2范数进行计算(即两个方向的倒数的平方和再开方),否则使用L1范数(直接将两个方向导数的绝对值相加)。
# canny算子
img = cv2.imread('luotuo.jpg', 0)
blur = cv2.GaussianBlur(img, (3, 3), 0) # 用高斯滤波处理原图像降噪
canny = cv2.Canny(blur, 50, 150) # 50是最小阈值,150是最大阈值
cv2.imshow('canny', canny)
cv2.waitKey(0)
cv2.destroyAllWindows()
可以看到对图像的处理效果还是比较不错的,在阈值的选取上我找到了一个可以在运行时调整阈值大小的程序。
lowThreshold = 0
max_lowThreshold = 100
ratio = 3
kernel_size = 3img = cv2.imread('luotuo.jpg')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)cv2.namedWindow('canny demo')cv2.createTrackbar('Min threshold', 'canny demo', lowThreshold, max_lowThreshold, CannyThreshold)CannyThreshold(0) # initialization
if cv2.waitKey(0) == 27:cv2.destroyAllWindows()