【opencv】(5) 图像边缘检测:sobel、scharr、laplacian、canny

各位同学好,今天和大家分享一下图像边缘检测的几个算子,主要内容有:

(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两个方向上分别计算梯度的,最后还需要计算整个图像的梯度。

公式为: G = |Gx|+|Gy|  或  G = \sqrt{G_{x}^{\,2}+G_{y}^{\,2}}

图像叠加或图像混合加权函数为:

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。然后计算梯度方向\Theta


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)

第一张为原图,第二张图为阈值较小的结果,第三张图为阈值较大的结果。

  • 6
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

立Sir

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值