Bootstrap

若楠带你初识OpenCV(4) -- 图像边缘检测

OpenCV

OpenCV(Open Source Computer Vision Library)是一个开源的计算机视觉和机器学习库,它主要用于实时图像处理和计算机视觉任务。

本篇所用图片数据:

链接:图片数据
https://pan.baidu.com/s/1-C6sxtw-S3vL1I5SwaStlQ?pwd=dzvx
提取码:dzvx

图像边缘检测

边缘检测:是图形图像处理、计算机视觉和机器视觉中的一个基本工具,通常用于特征提取和特征检测,旨在检测一张数字图像中有明显变化的边缘或者不连续的区域。

边缘检测的方法有:sobel算子、Scharr算子、Laplacian算子以及Canny边缘检测。

sobel 算子

Sobel 算子是一种离散的微分算子,该算子结合了高斯平滑和微分求导运算。该算子利用局部差分寻找边缘,计算所得的是一个梯度的近似值。

Sobel算子包含2组3×3的矩阵,分别为横向和纵向模板,将之与图像作平面卷积,即可分别得出横向及纵向的亮度差分近似值。

在这里插入图片描述

目标步骤:得到x方向的边缘,在得到y方向上的边缘,将二者整合,即可得到一个完整的边缘。

1. x方向上的边缘

yuan = cv2.imread('yuan.png')
cv2.imshow('yuan',yuan)
cv2.waitKey(0)

# x方向上的边缘
# -1:该位置参数表示图像深度,-1表示与传入图片相同;dx,dy表示卷积方向
yuan_x = cv2.Sobel(yuan,-1,dx=1,dy=0)
cv2.imshow('yuan_x',yuan_x)
cv2.waitKey(0)

在这里插入图片描述

我们发现图片右边的边缘没有显示出来,那是因为在卷积计算时,右边计算的值为负值,但是范围为【0~255】,所以没有得到负值,那我们要如何将复制保存下来呢?

2. 保存负值

默认参数uint8改为float64,可保存负数
修改过后:x方向上的边缘,包括负数信息(右边),已经保存了,但是显示不出来,因为范围是(0~255
# x方向上的边缘,包括负数信息(右边),已经保存了,但是显示不出来,因为范围是(0~255)
yuan_x_64 = cv2.Sobel(yuan,cv2.CV_64F,dx=1,dy=0) # 默认uint8改为float64,可保存负数
cv2.imshow('yuan_x_64',yuan_x_64)
cv2.waitKey(0)

在这里插入图片描述

由于负值无法显示,所以图片依然只有一半,那我们该怎么将另一半显示出来呢?简单理解,负值显示不出来,那就将它转化为正值呗~,将其绝对值。

3. 取绝对值

cv2.convertScaleAbs()方法
# 若想将负数值部分显示出来,可将它们取绝对值
yuan_x_full = cv2.convertScaleAbs(yuan_x_64) # 转化为绝对值,负数转化为正数
cv2.imshow('yuan_x_full',yuan_x_full)
cv2.waitKey(0)

在这里插入图片描述

这样我们就可以看见一个完整x方向的边缘咯。

当然,这只是单一x方向上的边缘,我们还需要得到y方向的边缘。

4. y方向上的边缘

重复获得x方向边缘的步骤:

# y方向上边缘
yuan_y = cv2.Sobel(yuan,-1,dx=0,dy=1)
cv2.imshow('yuan_y',yuan_y)
cv2.waitKey(0)

yuan_y_64 = cv2.Sobel(yuan,cv2.CV_64F,dx=0,dy=1)
yuan_y_full = cv2.convertScaleAbs(yuan_y_64)
cv2.imshow('yuan_y_full',yuan_y_full)
cv2.waitKey(0)

在这里插入图片描述

5. 加权运算

图像加权运算,将x,y两个方向上的边缘组合在一起:

yuan_xy_full = cv2.addWeighted(yuan_x_full,1,yuan_y_full,1,0)
cv2.imshow('yuan_xy_full',yuan_xy_full)
cv2.waitKey(0)

在这里插入图片描述

这样我们就完整得到了圆的边缘,那么为什么我们不直接对x和y方向同时卷积呐?

不要双方向同时卷积

  • 结果混淆:如果尝试同时对图像进行水平和垂直两个方向的卷积,并将结果直接相加或合并,可能会导致边缘检测的结果变得混淆不清。因为每个像素点在不同方向上的边缘强度可能不同,直接合并可能无法准确反映真实的边缘信息。
  • 计算复杂度增加:虽然现代计算机的计算能力强大,但无意义的双方向同时卷积会增加不必要的计算量,降低处理速度。
  • 边缘定位精度:Sobel算子本身在边缘定位上可能存在一定的模糊性,双方向同时卷积可能会进一步加剧这一问题,使得边缘检测结果不够精确。

尝试双方向一起卷积:

# 同时使用x,y方向边缘(不建议使用)
yuan_xy = cv2.Sobel(yuan,-1,dx=1,dy=1)
cv2.imshow('yuan_xy',yuan_xy)
cv2.waitKey(0)

在这里插入图片描述

读取猪猪侠查看效果

"""---------sobel算子---------"""
a = cv2.imread('GGbond.jpg',cv2.IMREAD_GRAYSCALE)
gg = cv2.resize(a,dsize=None,fx=0.5,fy=0.5)
gg_x_64 = cv2.Sobel(gg,cv2.CV_64F,dx=1,dy=0)
gg_x_full = cv2.convertScaleAbs(gg_x_64)
gg_y_64 = cv2.Sobel(gg,cv2.CV_64F,dx=0,dy=1)
gg_y_full = cv2.convertScaleAbs(gg_y_64)
gg_xy_Sobel_full = cv2.addWeighted(gg_x_full,1,gg_y_full,1,0)
cv2.imshow('gg_xy_Sobel_full',gg_xy_Sobel_full)
cv2.waitKey(0)

在这里插入图片描述

Scharr 算子

Scharr 算子是 Soble 算子在 ksize=3 时的优化,与 Soble 的速度相同,且精度更高。Scharr 算子与 Sobel 算子的不同点是在平滑部分,其中心元素占的权重更重,相当于使用较小标准差的高斯函数,也就是更瘦高的模板。

所以Scharr 算子在操作方面与Sobel算子相同:

"""--------Scharr算子-------"""
a = cv2.imread('GGbond.jpg',cv2.IMREAD_GRAYSCALE)
gg = cv2.resize(a,dsize=None,fx=0.5,fy=0.5)
# x方向
gg_x_64 = cv2.Scharr(gg,cv2.CV_64F,dx=1,dy=0)
gg_x_full = cv2.convertScaleAbs(gg_x_64)
# y方向
gg_y_64 = cv2.Scharr(gg,cv2.CV_64F,dx=0,dy=1)
gg_y_full = cv2.convertScaleAbs(gg_y_64)
# 加权
gg_xy_Scharr_full = cv2.addWeighted(gg_x_full,1,gg_y_full,1,0)
cv2.imshow('gg_xy_Scharr_full',gg_xy_Scharr_full)
cv2.waitKey(0)

在这里插入图片描述

Laplacian 算子

Laplacian 算子不再以x和y的方向计算,而是以圆方向计算变化率。因此不需要Gx+Gy。故而,不需要分两个方向分别取边缘。

"""-----Laplacian算子-----"""
a = cv2.imread('GGbond.jpg',cv2.IMREAD_GRAYSCALE)
gg = cv2.resize(a,dsize=None,fx=0.5,fy=0.5)

# 算子计算
gg_lap = cv2.Laplacian(gg,cv2.CV_64F)
# 绝对值
gg_lap_full = cv2.convertScaleAbs(gg_lap)

cv2.imshow('gg_lap_full',gg_lap_full)
cv2.waitKey(0)

在这里插入图片描述

canny边缘检测

优点

Canny边缘检测的优点:

  • 低错误率。因为一般的边缘检测算子可能存在检测到伪边缘的情况,因此Canny算法检测到的边缘尽可能地是真实的边缘。
  • 较好地定位边缘点。由检测器标记的边缘点与真实边缘点中心尽可能地接近。
  • 单一的边缘响应。图像中的边缘只标记出一次。

理论步骤四部分

canny边缘检测分为四个部分:1、图像降噪,2、梯度计算,3、非极大值抑制,4、双阈值边界跟踪

1. 图像降噪

图像去噪是进行边缘检测的第一步,通过去噪可以去除图像中的一些噪点,从而使边缘检测时免受噪点干扰。

降噪方法:高斯滤波

2. 梯度计算

要进行边缘检测,就需要得到图像梯度信息,根据图像的梯度幅值和梯度方向来确定边缘,一般均采用sobel算子对图像进行梯度幅值与梯度方向计算。

3. 非极大值抑制

一阶微分在灰度值斜坡过渡时不为零且存在较粗的边缘,较粗的边缘会增大边缘检测的误差,因此需要细化边缘,一种较为常用的方法是非极大值抑制。

非极大值抑制:即在梯度图像中寻找梯度方向上的最大值作为边缘,不是梯度方向上的最大值则抑制为0。因为梯度方向是灰度变化最大的方向。比较梯度图像中每一点的灰度值与梯度方向上至少两个梯度图像像素点灰度值的大小,根据上述大小关系来确定是否保留该点的灰度值。

在这里插入图片描述

4. 双阈值边界跟踪

双阈值处理就是根据实际情况需要设置一个灰度高阈值和一个灰度低阈值对NMS后的图像进行过滤,使得得到的边缘尽可能是真实的边缘。

在这里插入图片描述

示例应用

虽然canny边缘检测的理论看起来比较复杂,但是在应用时却很简单,因为上述步骤的操作已经都写在了cv2.Canny()方法中,只需要调用该函数即可应用:

"""-----canny边缘检测-----"""
a = cv2.imread('GGbond.jpg',cv2.IMREAD_GRAYSCALE)
gg = cv2.resize(a,(400,400))
gg_canny = cv2.Canny(gg,100,150)#设置阈值 低,高
cv2.imshow('gg_canny',gg_canny)
cv2.waitKey(0)

在这里插入图片描述

我们可以看到,canny边缘检测的方法对图像处理效果很好,边缘清晰,线条简单。

总结

本篇介绍了图像的边缘检测方法:

  1. sobel 算子:分为x和y两个方向计算
  2. Scharr 算子:是sobel 算子在 ksize=3 时的优化,也是分为x和y两个方向计算
  3. Laplacian 算子:不再以x和y的方向计算,而是以圆方向计算变化率
  4. canny边缘检测:高效、准确,寻找最优边缘
;