各位同学好,今天和大家分享一下图像边缘检测的几个算子,主要内容有:
(1)sobel算子:cv2.Sobel();(2)scharr算子:cv2.Scharr();(3)laplacian算子:cv2.Laplacian();(4)canny算子:cv2.Canny()
在开始前,我们先导入需要的库文件和图片,再定义一个图像显示函数,方便绘图。
import numpy as np
import cv2
# 获取图片所在文件夹
filepath = 'C:\\Users\\admin\\.spyder-py3\\test\\opencv\\img'
# 获取文件夹中的某一张图片
img = cv2.imread(filepath+'\\mh1.jpg',cv2.IMREAD_GRAYSCALE)
# 定义绘图函数
def cv_show(name,img):
# 传入自定义图像名,即图像变量
cv2.imshow(name,img)
# 图片不会自动消失
cv2.waitKey(0)
# 手动关闭窗口
cv2.destroyWindow()
1. Sobel算子
假设在一幅图像中,一块区域内如果全为黑或全为白,都是同一种颜色此时灰度的变化率为0;当一块区域内既有白又有黑,那么在黑白交界处的灰度变化就不为0,这些梯度不为0的区域也就是我们需要找的边缘。
Sobel算子的特点:根据像素点上下、左右邻点灰度加权差,在边缘处达到极值这一现象检测边缘。对噪声具有平滑作用,提供较为精确的边缘方向信息,但边缘定位精度不够高。当对精度要求不是很高时,是一种较为常用的边缘检测方法。
sobel算子计算方法:使用两个3*3的卷积核分别和原始图像运算,卷积核中的每一个值和图像做内积,得到的值代替卷积核滑窗所框住的图像的中间值。分别得到横向 G(x) 和纵向 G(y) 的梯度值,如果梯度值大于某一个阈值,则认为该点为边缘点。
sobel算子函数:
dst = cv2.Sobel(src, ddepth, dx, dy, ksize)
src:输入图像
ddepth:图像深度,通常指定-1,表示输入深度和输出深度相同
dx和dy:代表水平和竖直方向。当dx=1,dy=0,表示对x方向求梯度,y方向不求。
ksize:是sobel算子的大小,指定核的大小,默认为3,卷积核为上图所示。
图像深度是指存储每个像素值所用的位数,如:CV_16S(16位有符号数),CV_16U(16位无符号数),CV_32F(32位浮点数),CV_64F(64位浮点数)
我们先对水平方向计算梯度
# 输入图像,计算结果能带负数;dx=1,dy=0,表明计算水平的;卷积核3*3
sobelx = cv2.Sobel(img,cv2.CV_64F,1,0,ksize=3)
# 绘图
cv_show('sobelx',sobelx)
由于opencv自带的sobel算子在进行卷积运算时没有求绝对值,每个像素值是在0-255之间,计算时所有结果小于零的值都会变成0。截断操作,即计算结果大于255则输出为255,小于0则输出为0,其余不变。因此我们需要对小于0的像素值取绝对值,将这些值在图像上显示出来。
取绝对值函数为:
cv2.convertScaleAbs(src)
输入图像src,计算绝对值,然后将结果转换为uint8类型
# 取绝对值
sobelx = cv2.convertScaleAbs(sobelx)
cv_show('abs',sobelx)
第一张是原图,第二张是sobel函数计算结果,第三张是取绝对值之后
我们完成了x方向的梯度计算,接下来对y方向计算梯度,方法和上面一样,不再赘述。
# 对x轴求完之后,再对y轴求。只计算竖直方向梯度,不计算水平方向dx=0
sobely = cv2.Sobel(img,cv2.CV_64F,0,1,ksize=3)
sobely = cv2.convertScaleAbs(sobely)
cv_show('abs',sobely)
由于Sobel算子是在x和y两个方向上分别计算梯度的,最后还需要计算整个图像的梯度。
公式为: 或
图像叠加或图像混合加权函数为:
cv2.addWeighted(src1, alpha, src2, beta, gamma)
其中alpha是第一幅图片的权重;beta是第二个的权重,;gamma是加到最后结果上的偏置,一般为0
# 偏置项一般为0,比例分配自定义
sobelxy = cv2.addWeighted(sobelx, 0.5, sobely, 0.5, 0)
cv_show('sobelxy',sobelxy)
第一张是对x轴求梯度,第二张是对y轴求梯度,第三张是叠加后的
2. Scharr算子
scharr与sobel算子思想一样,只是卷积核的系数不同,scharr算子提取边界也更加灵敏,能提取到更细小的边界,但越是灵敏就越是可能误判。下图左边是scharr算子计算水平梯度时的卷积核,右边是计算垂直梯度时的卷积核
scharr算子函数:
cv2.Scharr(src, ddepth, dx, dy, ksize)
src表示输入的图片。ddepth表示图片的深度,通常使用-1,这里使用cv2.CV_64F允许结果是负值。具体深度值同Sobel算法。dx表示计算x轴方向梯度,dy表示计算y轴方向梯度。ksize为卷积核大小默认为3
#(1)图像梯度Scharr算子
# 对x方向求梯度,保留计算后的负号
scharrx = cv2.Scharr(img,cv2.CV_64F,1,0)
# 对图像像素求绝对值
scharrx = cv2.convertScaleAbs(scharrx)
# 对y方向求梯度
scharry = cv2.Scharr(img,cv2.CV_64F,0,1)
scharry = cv2.convertScaleAbs(scharry)
# 将两幅图像按比例叠加
scharrxy = cv2.addWeighted(scharrx,0.5,scharry,0.5,0)
cv_show('scharr',scharrxy)
下图第一张是原图,第二张是scharr算法处理后的
3. Laplacian算子
Laplacian算子是一个二阶的算子,它的运算规则是在水平方向运算两次,垂直方向运算两次,两个结果相叠加替换中心点(锚点)的像素值,对噪音比较敏感,直接使用时效果不太好。其卷积核为:
而sobel算子和scharr算子一般先算一个水平梯度,再算一个垂直方向梯度,然后把两个结果按照0.5的权重进行图像融合以得到完整的边界。
Laplacian算子函数:
dst = cv2.Laplacian(src, ddepth, ksize)
src为输入图像,ddepth为图像深度,同sobel函数,ksize为卷积核大小默认为1
#(2)图像梯度laplacian算子
# 求二阶导,对于噪音点比较敏感,直接用效果不太好
# 通过中间点和周围的比较来求,不需要x和y轴
laplacian = cv2.Laplacian(img, cv2.CV_64F,ksize=3)
laplacian = cv2.convertScaleAbs(laplacian)
cv_show('laplacian',laplacian)
4. canny算子
canny算子边缘检测步骤:
(1)高斯滤波器,平滑图像,消除噪声
(2)计算图像中每个像素点的梯度强度和方向。把小的梯度抑制,保留大的,体现最明显的边缘
(3)应用非极大值抑制,消除边缘检测带来的杂散响应
(4)应用双阈值检测来确定真实和潜在的边缘。对所有可能的边界再过滤,只保留最真实的边界
(5)通过抑制孤立的弱边缘最终完成边缘检测
4.1 高斯滤波器
使用高斯滤波,对图像去噪。卷积核在图像上滑动,将核的锚点放在该特定位置的像素上,同时,核内的其他值与该像素邻域的各像素重合;卷积核内的权重值服从高斯分布,离中心点越近的点权值越大。将卷积核内的值进行归一化处理。将卷积核内各值与相应像素值做内积,将乘积相加后求平均,将所得结果放到与锚点对应的像素上。如下图,H代表归一化后的高斯核,A代表高斯核框住的图像的像素值数据,e代表使用e值替换原图像上被高斯核框住的最中间的值。
4.2 梯度和方向
使用sobel算子计算梯度和方向。Sx和Sy分别代表x方向和y方向上的卷积核,先计算x方向的梯度Gx和y方向的梯度Gy,再计算合成梯度G。然后计算梯度方向。
4.3 非极大值抑制
(1)方法1:线性插值法。
梯度方向和边界方向是垂直关系。比较当前点的梯度值和它梯度方向上的2个点的梯度值。
已知c点梯度方向,如下图蓝色线所示,判断c点是否是极大值点,和dtmp1dtmp2的梯度值相比较。在图像上g1、g2、g3、g4都是可以计算的,那就通过线段比例去计算dtmp1和dtmp2。M(dtmp1)=w*M(g2)+(1-w)*M(g1),权重参数w等于dtmp1到g2的距离除以g1到g2的距离。
得到dtmp1和dtmp2的梯度值之后与c的梯度值相比较。如果c是极大值点,把像素c点保留,否则把c点抑制。
(2)方法2:
将线性插值简化成8个方向,由于梯度方向和边界方向是垂直关系,因此很容易就可以找出边界方向。在简化方向时,看梯度方向上的点距离这八个方向哪一个最近,就归为那个方向。如上图中的dtmp1就归为朝上方向。
对比当前点和梯度方向的两个点的梯度大小,如果A的梯度比B和C都要大,那么A就保存下来,由于梯度方向和边界垂直,这样就能找出A的边界。
4.4 双阈值检测
如果计算出的A点的梯度值大于maxval,那就将A点处理为边界。如果梯度值小于minval的,即不是边界,就将该点舍弃。如果梯度值在maxval和minval之间,如c点,连有边界那也将c视为边界,如B点没有连接边界,对B点舍弃。
4.5 代码实现
cv2.Canny(img, minval, maxval, apertureSize, L2gradient)
img为输入图像;minval为最小阈值;maxval为最大阈值。
可选参数:
apertureSize为sobel算子卷积核大小;
L2gradient是一个布尔值,如果为真,则使用更精确的L2范数进行计算(两个方向的导数的平方和再开放),否则使用L1范数(直接将两个方向导数的绝对值相加)。
# 获取图像
img = cv2.imread(filepath+'\\mh1.jpg',cv2.IMREAD_GRAYSCALE)
# 使用canny算子,指定最大和最小阈值
# minval阈值指定的小,对边缘的要求没那么高,能检测出尽可能多的边界
# maxval阈值指定的大,对边界的要求很高
# 对比不同与之参数
v1 = cv2.Canny(img,50,100) #阈值小
v2 = cv2.Canny(img,150,200) #阈值大,边界越少
# 绘图
res = np.hstack((v1,v2))
cv_show('res',res)
第一张为原图,第二张图为阈值较小的结果,第三张图为阈值较大的结果。