基于OpenCV的图像梯度与边缘检测!
共 7538字,需浏览 16分钟
·
2021-10-13 19:02
点击上方“小白学视觉”,选择加"星标"或“置顶”
重磅干货,第一时间送达
严格的说,梯度计算需要求导数。但是图像梯度的计算,是通过计算像素值的差得到梯度的近似值。图像梯度表示的是图像变化的速度,反映了图像的边缘信息。
为了检测边缘,我们需要检测图像中的不连续性,可以使用图像梯度来检测不连续性。但是,图像梯度也会受到噪声的影响,因此建议先对图像进行平滑处理。
1. 图像梯度与几种算子
Sobel算子
Scharr算子
Roberts算子
Laplacian算子
2. Canny边缘检测
高斯滤波
计算梯度强度和方向
非极大值抑制(NMS)
用双阈值算法检测和连接边缘
3. 基于OpenCV的实现
Sobel算子函数
Scharr算子
Laplacian算子
“滤波器”也可以称为“卷积核”,“掩膜”,“算子”等。
1.1 Sobel算子
梯度有方向,对于一个图像,可以通过Sobel算子分别计算水平方向和垂直方向的偏导数的近似值。
计算水平方向偏导数的近似值
设原图像大小为,水平方向偏导数为:
计算垂直方向偏导数的近似值
设原图像大小为,垂直方向偏导数为:
计算像素点P5的梯度,需要利用邻域内的像素点,公式为:
1.2 Scharr算子
x和y方向的Scharr算子分别为:
Sobel算子与Scharr算子比较:Sobel算子的缺点是,当结构较小是,精确度不高,Scharr算子具有更高的精度。
1.3 Roberts算子
当图像边缘接近于正45°或负45°时,该算法处理效果更理想。其缺点是对边缘的定位不太准确,提取的边缘线条较粗。正45°和负45°方向的Roberts算子分别为:
计算P5的近似导数值,如下:
以低的错误率检测边缘,也即意味着需要尽可能准确的捕获图像中尽可能多的边缘。
检测到的边缘应精确定位在真实边缘的中心。
图像中给定的边缘应只被标记一次,并且在可能的情况下,图像的噪声不应产生假的边缘。
完成一个Canny边缘检测算法可以分为以下四步:
1.利用高斯滤波去噪。噪声会影响边缘检测的准确性,因此要先将噪声过滤掉。
2.计算梯度幅值和方向。
3.非极大值抑制。
4.应用双阈值确定真实的和可能的边缘。
2.1 高斯滤波
假设为src原图像,dst为高斯滤波后的图像,M为5×5的高斯卷积核(M不固定):(*表示卷积运算)
2.2 计算梯度强度和方向
其中为梯度大小, 表示梯度方向,为反正切函数。通过上式我们可以得到一个具有梯度大小和方向的矩阵。如下图:
角度的确定:
八个区域如下图:
2.3 非极大值抑制(NMS)
在每一点上,邻域中心与沿着其对应的梯度方向的两个像素相比,若中心像素()为最大值,则保留,否则中心置0,这样可以抑制非极大值,保留局部梯度最大的点,以得到细化的边缘。
如果该点是方向上的局部最大值,则保留该点
如果不是,则将其置为0
如下图,A,B,C三点中,梯度方向上A点的局部梯度值最大,所以保留A点,其余两点被抑制。
2.4 用双阈值算法检测和连接边缘
如果当前边缘点的梯度值大于或等于maxVal,,则将当前边缘标记为强边缘。
如果当前边缘点的梯度值介于maxVal与minVal之间,则将当前边缘标记为虚边缘。
如果当前边缘点的梯度值小于minVal,,则抑制当前边缘。
对得到的虚边缘,再做以下处理:
与强边缘相连,该边缘为边缘
与强边缘无连接,该边缘为弱边缘,将其抑制
实际中maxVal:minVal=2:1的比例效果比较好,其中maxVal可以指定,也可以设计算法来自适应的指定,比如定义梯度直方图的前30%的分界线为maxVal。
3.1 Sobel算子函数
OpenCV使用Sobel 算子的方法是cv2.Sobel()
dst = cv2.Sobel(src,ddepth,dx,dy,ksize,scale,delta,borderType)
参数:
src 原图像
ddepth 输出图像的深度,具体关系:
输入图像深度(src.depth()) | 输出图像深度(ddepth) |
---|---|
cv2.CV_8U | -1/cv2.CV_16S/cv2.CV_32F/cv2.CV_64F |
cv2.CV_16U/cv2.CV_16S | -1/cv2.CV_32F/cv2.CV_64F |
cv2.CV_32F | -1/cv2.CV_32F/cv2.CV_64F |
cv2.CV_64F | -1/cv2.CV_64F |
dx:x方向上的求导阶数
dy:y方向上的求导阶数
ksize:Sobel核的大小。该值为-1时,会使用Scharr算子进行运算
scale:计算导数时采用的缩放因子,默认为1,是没有缩放的
delta:加在目标图像dst上的值,默认为0
borderType:边界样式,默认值为cv2.BORDER_DEFAULT。
关于参数ddepth:
代码示例:
# -*- coding: utf-8 -*-
import cv2
#读取图像
img = cv2.imread('D:/yt/picture/Sobel/laplacian.bmp',0)
#计算x方向边缘信息
sobelx = cv2.Sobel(img,cv2.CV_64F,1,0,ksize=5)
#计算y方向边缘信息
sobely = cv2.Sobel(img,cv2.CV_64F,0,1,ksize=5)
#求绝对值
sobelx = cv2.convertScaleAbs(sobelx)
sobely = cv2.convertScaleAbs(sobely)
#x方向和y方向的边缘叠加
sobelxy = cv2.addWeighted(sobelx,0.5,sobely,0.5,0)
#显示图像
cv2.imshow("origin image",img)
cv2.imshow("x",sobelx)
cv2.imshow("y",sobely)
cv2.imshow("xy",sobelxy)
cv2.waitKey(0)
cv2.destroyAllWindows()
结果:
计算x方向的边缘
计算y方向的边缘
计算x方向和y方向的边缘叠加
同时令dx=1和dy=1时
3.2 Scharr算子
OpenCV使用Scharr算子的函数是cv2.Scharr()
dst = cv2.Scharr(src,ddepth,dx,dy,scale,delta,borderType)
参数:
src 原图像
ddepth 输出图像的深度,该值与函数cv2.Sobel()中的参数ddepth的含义相同。
dx x方向上的求导阶数
dy y方向上的求导阶数
scale 计算导数时采用的缩放因子,默认为1,是没有缩放的
delta 加在目标图像dst上的值,默认为0
borderType 边界样式,默认值为cv2.BORDER_DEFAULT。
在cv2.Sobel()中,ksize=-1时,则会使用Scharr算子。所以下面两个语句等价:
dst = cv2.Scharr(src,ddepth,dx,dy)
dst = cv2.Sobel(src,ddepth,dx,dy,-1)
注意:
参数ddepth的值应该设置为“cv2.CV_64F”,并对函数cv2.Scharr()的计算结果取绝对值。
dx和dy不能同时为1,否则语句是错误的。
计算x方向和y方向的边缘叠加时,应先令dx=1,dy=0,得到一个结果;再令dx=0,dy=1,得到一个结果。将两个结果相加,而不是同时令dx=1和dy=1。
代码示例:
import cv2
#读取图像
img = cv2.imread('D:/yt/picture/Sobel/lena.bmp',0)
#计算水平方向边缘信息
scharrx = cv2.Scharr(img,cv2.CV_64F,1,0)
#计算垂直方向边缘信息
scharry = cv2.Scharr(img,cv2.CV_64F,0,1)
#求绝对值
scharrx = cv2.convertScaleAbs(scharrx)
scharry = cv2.convertScaleAbs(scharry)
#水平方向和垂直方向的边缘叠加
scharrxy = cv2.addWeighted(scharrx,0.5,scharry,0.5,0)
#显示图像
cv2.imshow("origin image",img)
cv2.imshow("x",scharrx)
cv2.imshow("y",scharry)
cv2.imshow("xy",scharrxy)
cv2.waitKey(0)
cv2.destroyAllWindows()
结果:
计算x方向的边缘
计算y方向的边缘
计算x方向和y方向的边缘叠加
3.3 Laplacian算子
OpenCV使用Laplacian算子的函数是cv2.Laplacian()
dst = cv2.Laplacian(src,ddepth,ksize,scale,delta,borderType)
参数:
src 原图像
ddepth 输出图像的深度,该值与函数cv2.Sobel()中的参数ddepth的含义相同。
ksize 计算二阶导数的核尺寸大小,必须为正的奇数。
scale 计算导数时采用的缩放因子,默认为1,是没有缩放的
delta 加在目标图像dst上的值,默认为0
borderType 边界样式,默认值为cv2.BORDER_DEFAULT。
该函数分别对x和y方向进行二次求导:
import cv2
#读取图像
img = cv2.imread('D:/yt/picture/Sobel/laplacian.bmp',0)
#计算边缘信息
laplace = cv2.Laplacian(img,cv2.CV_64F)
#求绝对值
laplace = cv2.convertScaleAbs(laplace)
#显示图像
cv2.imshow("origin image",img)
cv2.imshow("laplace",laplace)
cv2.waitKey(0)
cv2.destroyAllWindows()
结果
4. Canny函数
OpenCV使用函数cv2.Cannyl()实现Canny边缘检测
edges = cv2.Canny(image,threshold1,threshold2,apertureSize,L2gradient)
参数:
image 输入图像,必须为8位图像
threshold1 第一个阈值
threshold2 第二个阈值
apertureSize Sobel算子的大小
L2gradient 计算图像梯度幅度的表示。默认值为False,使用L1范数计算;如果为True,则使用更精确的L2范数计算。
代码示例:
# -*- coding: utf-8 -*-
import cv2
#读取图像,为8位灰度图像
img = cv2.imread('D:/yt/picture/Sobel/lena.bmp',cv2.IMREAD_GRAYSCALE)
#canny边缘检测
#去噪
img = cv2.GaussianBlur(img,(3,3),0)
#threshold1为128,threshold2为200时的边缘检测结果
canny1 = cv2.Canny(img, 128, 200)
#threshold1为32,threshold2为128时的边缘检测结果
canny2 = cv2.Canny(img, 32, 128)
#显示图像
cv2.imshow("origin image",img)
cv2.imshow("canny1",canny1)
cv2.imshow("canny2",canny2)
cv2.waitKey(0)
cv2.destroyAllWindows()
结果:
threshold1为128,threshold2为200时的边缘检测结果
threshold1为32,threshold2为128时的边缘检测结果
交流群
欢迎加入公众号读者群一起和同行交流,目前有SLAM、三维视觉、传感器、自动驾驶、计算摄影、检测、分割、识别、医学影像、GAN、算法竞赛等微信群(以后会逐渐细分),请扫描下面微信号加群,备注:”昵称+学校/公司+研究方向“,例如:”张三 + 上海交大 + 视觉SLAM“。请按照格式备注,否则不予通过。添加成功后会根据研究方向邀请进入相关微信群。请勿在群内发送广告,否则会请出群,谢谢理解~