• 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[
      101202101" role="presentation" style="position: relative;">101202101
      \right]
      121000121
      [ − 1 − 2 − 1 0 0 0 − 1 2 1 ] \left[
      121000121" role="presentation" style="position: relative;">121000121
      \right]
      101202101
    • 计算水平方向偏导数的近似值,将 Sobel 算子与原始图像 img 进行卷积计算 Sobel 算子与原始图像 img 进行卷积计算。 G x = [ − 1 0 1 − 2 0 2 − 1 0 1 ] ∗ i m g G_x=\left[
      101202101" role="presentation" style="position: relative;">101202101
      \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[
      121000121" role="presentation" style="position: relative;">121000121
      \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()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    在这里插入图片描述

    Scharr 算子

    • 在离散的空间上,有很多方法可以用来计算近似导数,在使用 3×3 的 Sobel 算子时,可能计算结果并不太精准。OpenCV 提供了 Scharr 算子,该算子具有和 Sobel 算子同样的速度,且精度更高。
    • Scharr 算子,一般定义为 [ − 3 0 3 − 10 0 10 − 3 0 3 ] \left[
      30310010303" role="presentation" style="position: relative;">30310010303
      \right]
      31030003103
      [ − 3 − 10 − 3 0 0 0 − 3 10 3 ] \left[
      31030003103" role="presentation" style="position: relative;">31030003103
      \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()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    在这里插入图片描述

    Laplacian 算子

    • Laplacian(拉普拉斯)算子是一种二阶导数算子,其具有旋转不变性,可以满足不同方向的图像边缘锐化(边缘检测)的要求。通常情况下,其算子的系数之和需要为零。
    • 一个 3×3 大小的 Laplacian 算子为 [ 0 1 0 1 − 4 1 0 1 0 ] \left[
      010141010" role="presentation" style="position: relative;">010141010
      \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()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    在这里插入图片描述

    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=
      {|dldx|+|dldy|L2gradient=False(dldx)2+(dldy)2L2gradient=True" role="presentation" style="position: relative;">{|dldx|+|dldy|L2gradient=False(dldx)2+(dldy)2L2gradient=True
      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()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    在这里插入图片描述

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

    参考文献

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

  • 相关阅读:
    【C】文件操作fopen与fclose
    D. Secret Santa(构造)
    四种常见软件架构简介
    2022-06-25 C++老八股之 new 的内存用 free 回收有危险
    二、EFCore 数据库表的创建和迁移
    React 类组件转换为函数式
    《web课程设计》 基于HTML+CSS+JavaScript实现中国水墨风的小学学校网站模板(6个网页)
    EasyClick易点云测低代码拖拽式在线UI编辑器
    查找算法-二分查找法(Binary Search)
    micro-ros arduino esp32 ros2 笔记
  • 原文地址:https://blog.csdn.net/FriendshipTang/article/details/126045199