Bootstrap

OpenCV-Python快速入门(七):边缘检测

前言

  • 本文是个人快速入门OpenCV-Python的电子笔记,由于水平有限,难免出现错漏,敬请批评改正。
  • 更多精彩内容,可点击进入
    OpenCV-Python快速入门
    专栏或我的个人主页查看

前提条件

实验环境

  • Python 3.x (面向对象的高级语言)
  • OpenCV 4.0(python第三方库)pip3 install opencv-python

边缘检测

图像梯度

  • 图像梯度计算的是图像变化的速度。对于图像的边缘部分,其灰度值变化较大,梯度值也较大;相反,对于图像中比较平滑的部分,其灰度值变化较小,相应的梯度值也较小。一般情况下,图像梯度计算的是图像的边缘信息。
  • 严格来讲,图像梯度计算需要求导数,但是图像梯度一般通过计算像素值的差来得到梯度的近似值(近似导数值)。
  • 在图像处理中,我们经常使用用 Sobel 算子、Scharr算子、 Laplacian 算子来计算图像的边缘信息。
  • 由于本文撰写的目的是快速入门,上述知识点,在入门阶段,了解并且会应用即可。

Sobel 算子

  • Sobel 算子是一种离散的微分算子,该算子结合了高斯平滑和微分求导运算。该算子利用局部差分寻找边缘,计算所得的是一个梯度的近似值。
  • Sobel 算子,一般定义为 [ − 1 0 1 − 2 0 2 − 1 0 1 ] \left[ \begin{matrix} -1 & 0 & 1\\ -2 & 0 & 2 \\ -1 & 0 & 1 \end{matrix} \right] 121000121 [ − 1 − 2 − 1 0 0 0 − 1 2 1 ] \left[ \begin{matrix} -1 & -2 & -1\\ 0 & 0 & 0 \\ -1 & 2 & 1 \end{matrix} \right] 101202101
  • 计算水平方向偏导数的近似值,将 Sobel 算子与原始图像 img 进行卷积计算 Sobel 算子与原始图像 img 进行卷积计算。 G x = [ − 1 0 1 − 2 0 2 − 1 0 1 ] ∗ i m g G_x=\left[ \begin{matrix} -1 & 0 & 1\\ -2 & 0 & 2 \\ -1 & 0 & 1 \end{matrix} \right]*img Gx= 121000121 img其中, G x G_x Gx表示水平方向偏导数。
  • 计算垂直方向偏导数的近似值,将 Sobel 算子与原始图像 img 进行卷积计算 Sobel 算子与原始图像 img 进行卷积计算。 G y = [ − 1 − 2 − 1 0 0 0 − 1 2 1 ] ∗ i m g G_y=\left[ \begin{matrix} -1 & -2 & -1\\ 0 & 0 & 0 \\ -1 & 2 & 1 \end{matrix} \right]*img Gy= 101202101 img其中, G y G_y Gy表示垂直方向偏导数。
  • 注:需要了解卷积运算的,可查阅OpenCV-Python快速入门(六):图像平滑
import cv2
import numpy as np

img1=cv2.imread("1.jpg",0)
img1_resize=cv2.resize(img1,(400,400))
'''
dst = cv2.Sobel( src, ddepth, dx, dy[,ksize[, scale[, delta[, borderType]]]] )
参数说明:
    dst 代表目标图像。
    src 代表原始图像。
    ddepth 代表输出图像的深度。
    dx 代表 x 方向上的求导阶数。
    dy 代表 y 方向上的求导阶数。
    ksize 代表 Sobel 核的大小。该值为-1 时,则会使用 Scharr 算子进行运算。
    scale 代表计算导数值时所采用的缩放因子,默认情况下该值是 1,是没有缩放的。
    delta 代表加在目标图像 dst 上的值,该值是可选的,默认为 0。
    borderType 代表边界样式。
'''
# 计算水平方向边缘(梯度):dx=1,dy=0
Sobelx = cv2.Sobel(img1_resize,cv2.CV_64F,1,0)
# 计算垂直方向边缘(梯度):dx=0,dy=1
Sobely = cv2.Sobel(img1_resize,cv2.CV_64F,0,1)
# 计算两个方向边缘(梯度):dx=1,dy=1
Sobelxy = cv2.Sobel(img1_resize,cv2.CV_64F,1,1)
# 计算x方向和y方向的边缘叠加
Sobelx_add_Sobely=cv2.addWeighted(Sobelx,0.5,Sobely,0.5,0)
# 取计算结果绝对值
SobelAbs=cv2.convertScaleAbs(Sobelxy)
cv2.imshow("origin",img1_resize)
cv2.imshow("Sobelx",Sobelx)
cv2.imshow("Sobely",Sobely)
cv2.imshow("Sobelxy",Sobelxy)
cv2.imshow("Sobelx_add_Sobely",Sobelx_add_Sobely)
cv2.imshow("SobelAbs",SobelAbs)
cv2.waitKey()
cv2.destroyAllWindows()

在这里插入图片描述

Scharr 算子

  • 在离散的空间上,有很多方法可以用来计算近似导数,在使用 3×3 的 Sobel 算子时,可能计算结果并不太精准。OpenCV 提供了 Scharr 算子,该算子具有和 Sobel 算子同样的速度,且精度更高。
  • Scharr 算子,一般定义为 [ − 3 0 3 − 10 0 10 − 3 0 3 ] \left[ \begin{matrix} -3 & 0 & 3\\ -10 & 0 & 10 \\ -3 & 0 & 3 \end{matrix} \right] 31030003103 [ − 3 − 10 − 3 0 0 0 − 3 10 3 ] \left[ \begin{matrix} -3 & -10 & -3\\ 0 & 0 & 0 \\ -3 & 10 & 3 \end{matrix} \right] 30310010303
  • Scharr 算子的计算方式与Sobel 算子的一样,只是算子不一样。
import cv2
import numpy as np

img1=cv2.imread("1.jpg",0)
img1_resize=cv2.resize(img1,(400,400))
'''
dst = cv2.Scharr( src, ddepth, dx, dy[, scale[, delta[, borderType]]] )
参数说明:
    dst 代表输出图像。
    src 代表原始图像。
    ddepth 代表输出图像深度。该值与函数 cv2.Sobel()中的参数 ddepth 的含义相同
    dx 代表 x 方向上的导数阶数。
    dy 代表 y 方向上的导数阶数。
    scale 代表计算导数值时的缩放因子,该项是可选项,默认值是 1,表示没有缩放。
    delta 代表加到目标图像上的亮度值,该项是可选项,默认值为 0。
    borderType 代表边界样式。
'''
# 计算水平方向边缘(梯度):dx=1,dy=0
Scharrx = cv2.Scharr(img1_resize,cv2.CV_64F,1,0)
# 取计算结果绝对值
ScharrxAbs=cv2.convertScaleAbs(Scharrx)
# 计算垂直方向边缘(梯度):dx=0,dy=1
Scharry = cv2.Scharr(img1_resize,cv2.CV_64F,0,1)
# 取计算结果绝对值
ScharryAbs=cv2.convertScaleAbs(Scharry)
# 计算x方向和y方向的边缘叠加
Scharrx_add_Scharry=cv2.addWeighted(Scharrx,0.5,Scharry,0.5,0)

cv2.imshow("origin",img1_resize)
cv2.imshow("Scharrx",Scharrx)
cv2.imshow("ScharrxAbs",ScharrxAbs)
cv2.imshow("Scharry",Scharry)
cv2.imshow("SobelyAbs",ScharryAbs)
cv2.imshow("Scharrx_add_Scharry",Scharrx_add_Scharry)
cv2.waitKey()
cv2.destroyAllWindows()

在这里插入图片描述

Laplacian 算子

  • Laplacian(拉普拉斯)算子是一种二阶导数算子,其具有旋转不变性,可以满足不同方向的图像边缘锐化(边缘检测)的要求。通常情况下,其算子的系数之和需要为零。
  • 一个 3×3 大小的 Laplacian 算子为 [ 0 1 0 1 − 4 1 0 1 0 ] \left[ \begin{matrix} 0 & 1 & 0\\ 1 & -4 & 1 \\ 0 & 1 & 0 \end{matrix} \right] 010141010
  • Laplacian 算子的计算方式与Sobel 算子、Scharr 算子的一样,只是算子不一样。
import cv2
import numpy as np

img1=cv2.imread("1.jpg",0)
img1_resize=cv2.resize(img1,(400,400))
'''
dst = cv2.Laplacian( src, ddepth[, ksize[, scale[, delta[, borderType]]]] )
参数说明:
    dst 代表目标图像。
    src 代表原始图像。
    ddepth 代表目标图像的深度。
    ksize 代表用于计算二阶导数的核尺寸大小。该值必须是正的奇数。
    scale 代表计算 Laplacian 值的缩放比例因子,该参数是可选的。默认情况下,该值为 1,
    表示不进行缩放。
    delta 代表加到目标图像上的可选值,默认为 0。
    borderType 代表边界样式。
'''
# 计算两个方向的梯度值
Laplacianxy = cv2.Laplacian(img1_resize,cv2.CV_64F)
# 取计算结果绝对值
LaplacianAbs = cv2.convertScaleAbs(Laplacianxy)
cv2.imshow("origin",img1_resize)
cv2.imshow("Laplacianxy",Laplacianxy)
cv2.imshow("LaplacianAbs",LaplacianAbs)
cv2.waitKey()
cv2.destroyAllWindows()

在这里插入图片描述

Canny 边缘检测

  • Canny 边缘检测是一种使用多级边缘检测算法检测边缘的方法。
  • Canny 边缘检测分为如下几个步骤。
    1. 去噪。噪声会影响边缘检测的准确性,因此首先要将噪声过滤掉。(通常使用高斯滤波进行去噪,需要了解高斯滤波的,可查阅OpenCV-Python快速入门(六):图像平滑
    2. 计算梯度的幅度与方向。
    3. 非极大值抑制,即适当地让边缘“变瘦”。
    4. 确定边缘。使用双阈值算法确定最终的边缘信息。
  • 由于本文撰写的目的是快速入门,Canny 边缘检测步骤,在入门阶段,了解并且会应用即可。
  • 扩展知识: n o r m = { ∣ d l d x ∣ + ∣ d l d y ∣ , L 2 g r a d i e n t = F a l s e ( d l d x ) 2 + ( d l d y ) 2 , L 2 g r a d i e n t = T r u e norm= \begin{cases} |\frac{dl}{dx}|+|\frac{dl}{dy}|,\quad L2gradient = False\\ \sqrt{(\frac{dl}{dx})^2 + (\frac{dl}{dy})^2},\quad L2gradient = True \end{cases} norm={dxdl+dydlL2gradient=False(dxdl)2+(dydl)2 L2gradient=True其中,L2gradient = False时,表达式是L1范数,L2gradient = True时,表达式是L2范数。
import cv2
import numpy as np

img1=cv2.imread("1.jpg",0)
img1_resize=cv2.resize(img1,(400,400))
'''
edges = cv.Canny( image, threshold1, threshold2[, apertureSize[, L2gradient]])
参数说明:
    edges 为计算得到的边缘图像。
    image 为 8 位输入图像。
    threshold1 表示处理过程中的第一个阈值。
    threshold2 表示处理过程中的第二个阈值。
    apertureSize 表示 Sobel 算子的孔径大小。
    L2gradient 为计算图像梯度幅度(gradient magnitude)的标识。
        其默认值为 False。如果为 True,则使用更精确的 L2 范数进行计算
        (即两个方向的导数的平方和再开方),否则使用 L1 范数
        (直接将两个方向导数的绝对值相加)。
'''
edges1 = cv2.Canny(img1_resize,128,200)
edges2 = cv2.Canny(img1_resize,32,128)
cv2.imshow("origin",img1_resize)
cv2.imshow("edges1",edges1)
cv2.imshow("edges2",edges2)
cv2.waitKey()
cv2.destroyAllWindows()

在这里插入图片描述

从输出结果上看,当函数 cv2.Canny()的参数 threshold1 和 threshold2 的值较小时,能够捕获更多的边缘信息。

参考文献

[1] https://opencv.org/
[2] 李立宗. OpenCV轻松入门:面向Python. 北京: 电子工业出版社,2019

;