B07 - 图像处理 - 图像梯度与边缘检测

通过图像梯度检测边缘的算子集合,一般是先转为二值图再进行边缘寻找

图像上的边缘类型?

  • 台阶模型,对应于阶跃信号,边缘模型是灰度级在相邻像素点发生垂直的台阶突变;
  • 斜坡模型,对应于斜变信号,边缘模型是灰度级变化的斜坡
  • 屋顶模型,是线的模型,用于细特征的建模
  • 模拟生成以上边缘类型
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    h ,w = 100, 600
    onesW = np.ones((h, 1), dtype=np.int)
    x = range(w)
    # 台阶模型 (step edge)
    imgStep = np.zeros((h, w), np.uint8)
    imgStep[:, 300:600] = 255
    print(imgStep.max(), imgStep.min())
    # 斜坡模型 (ramp edge)
    imgRamp = np.zeros((h,w), np.uint8)
    for i in range(200, 400):
    imgRamp[:, i] = 255 * (i-199) / 200
    imgRamp[:, 400:] = 255
    # 屋顶模型 (roof edge)
    imgRoof = np.zeros((h,w), np.uint8)
    for i in range(250, 300):
    imgRoof[:, i] = 255 * (i-249) / 50
    for i in range(300, 350):
    imgRoof[:, i] = 255 - 255 * (i-299) / 50
    imgRoof[:, 350:] = 0

图像边缘检测原理?

  • 用于识别对象的边界(边缘)或图像中的区域,边缘特征是像素强度的突然变化,要检测边缘,需要在相邻像素中寻找这样的变化,也就是梯度
  • 红色区曲线表示原始边缘,绿色曲线表示一阶微分结果,蓝色区域表示二级微分结果。可知一阶微分算子可以检测图像边缘。对于剧烈变化的图像边缘,一阶微分效果比较理想。但对于缓慢变化的图像边缘,通过对二阶微分并寻找过零点可以很精确的定位边缘中心

Sobel 梯度算子边缘检测算法?

  • (1) 定义 Sobel 卷积因子: 该算子包含两组 3x3 的矩阵,分别为横向及纵向,将之与图像作平面卷积,即可分别得出横向及纵向的亮度差分近似

Kx=[101202101],Ky=[121000121]K_x=\begin{bmatrix}-1&0&1\\ -2&0&2\\ -1&0&1\end{bmatrix},K_y=\begin{bmatrix}-1&-2&-1\\ 0&0&0\\ 1&2&1\end{bmatrix}

  • (2) 计算某一点的灰度值:分别使用计算 y 方向的卷积和、x 方向的卷积核,然后使用公式求灰度

G=Gx+Gy|G|=\left|G_{x}\right|+\left|G_{y}\right|

Scharr 梯度算子边缘检测算法?

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

Gx=[30310010303],Gy=[310300103103]G_x=\begin{bmatrix}-3&0&3\\ -10&0&10\\ -3&0&3\end{bmatrix},G_y=\begin{bmatrix}-3&10&-3\\ 0&0&10\\ 3&10&3\end{bmatrix}

Canny 边缘检测算法?

  • Canny 边缘检测算法是目前最优秀和最流行的边缘检测算法之一。算法不容易受噪声影响,能够识别图像中的弱边缘和强边缘,并能结合强弱边缘的位置关系给出图像整体的边缘信息
  • 步骤:(1) 用高斯滤波器平滑图像(2) 用 Sobel 等梯度算子计算梯度幅值和方向: 图像灰度值的梯度可以使用最简单的一阶有限差分来进行近似,保留这四个方向的梯度:0°/45°/90°/135;(3) 对梯度幅值进行非极大值抑制: 沿着该点梯度方向,比较前后两个点的幅值大小,若该点大于前后两点,则保留,若该点小于前后两点,则置为 0;(4) 用双阈值算法检测和连接边缘: 指定一个低阈值 A,一个高阈值 B,一般取 B 为图像整体灰度级分布的 70%,且 B 为 1.5 到 2 倍大小的 A;灰度值介于 A 和 B 之间的,考察改像素点临近的 8 像素是否有灰度值为 255 的,若没有 255 的,表示这是一个孤立的局部极大值点,予以排除,置为 0;若有 255 的,表示这是一个跟其他边缘有 “接壤” 的可造之材,置为 255,之后重复执行该步骤,直到考察完之后一个像素点
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    img = cv2.imread("../images/imgLena.tif", flags=0)  # flags=0 读取为灰度图像
    # 高斯核低通滤波器,sigmaY 缺省时 sigmaY=sigmaX
    kSize = (5, 5)
    imgGauss1 = cv2.GaussianBlur(img, kSize, sigmaX=1.0) # sigma=1.0
    imgGauss2 = cv2.GaussianBlur(img, kSize, sigmaX=10.0) # sigma=2.0
    # 高斯差分算子 (Difference of Gaussian)
    imgDoG = imgGauss2 - imgGauss1 # sigma=1.0, 10.0
    # Canny 边缘检测, kSize 为高斯核大小,t1,t2为阈值大小
    t1, t2 = 50, 150
    imgCanny = cv2.Canny(imgGauss1, t1, t2)
    show_images([img,imgDoG,imgCanny])

Laplacian 边缘检测算法?

  • Laplacian 算子具有各方向同性的特点,能够对任意方向的边缘进行提取,具有无方向性的优点,因此提取边缘不需要分别检测 X 方向的边缘和 Y 方向的边缘,只需要一次边缘检测即可
  • Laplacian 算子是一种二阶导数算子,对噪声比较敏感,因此常需要配合高斯滤波一起使用
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    imgGray = cv2.imread("../images/Fig0905a.tif", flags=0)
    # scipy.signal 实现卷积运算 (注意:不能用 cv2.filter2D 处理)
    from scipy import signal
    kernelLaplace = np.array([[1, 1, 1], [1, -8, 1], [1, 1, 1]]) # Laplacian kernel
    imgLaplace = signal.convolve2d(imgGray, kernelLaplace, boundary='symm', mode='same') # same 卷积
    kernel1 = np.array([[-1, -1, -1], [2, 2, 2], [-1, -1, -1]]) # 0 degree, horizontal
    kernel2 = np.array([[2, -1, -1], [-1, 2, -1], [-1, -1, 2]]) # +45 degree
    kernel3 = np.array([[-1, 2, -1], [-1, 2, -1], [-1, 2, -1]]) # 90 degree, vertical
    kernel4 = np.array([[-1, -1, 2], [-1, 2, -1], [2, -1, -1]]) # -45 degree
    imgLine1 = signal.convolve2d(imgGray, kernel1, boundary='symm', mode='same') # horizontal kernel
    imgLine2 = signal.convolve2d(imgGray, kernel2, boundary='symm', mode='same')
    imgLine3 = signal.convolve2d(imgGray, kernel3, boundary='symm', mode='same') # vertical kernel
    imgLine4 = signal.convolve2d(imgGray, kernel4, boundary='symm', mode='same')

LoG 边缘检测算子?

  • 平滑算子与 Laplace 算子的结合,因而兼具平滑和二阶微分的作用,其定位精度高,边缘连续性好,计算速度快
  • 具体地,可以先对图像做 Gauss 平滑,再做 Laplace 变换;也可以预先算出高斯拉普拉斯卷积核 LoG,直接与图像卷积来实现,提高计算速度
  • Marr-Hildreth 算法让 LoG 核与原图像卷积,然后寻找过零点来确定边缘的位置
    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
    def ZeroDetect(img):  # 判断零交叉点
    h, w = img.shape[0], img.shape[1]
    zeroCrossing = np.zeros_like(img, np.uint8)
    for x in range(0, w-1):
    for y in range(0, h-1):
    if img[y][x] < 0:
    if (img[y][x-1] > 0) or (img[y][x+1] > 0)\
    or (img[y-1][x] > 0) or (img[y+1][x] > 0):
    zeroCrossing[y][x] = 255
    return zeroCrossing
    from scipy import signal
    img = cv2.imread("Fig1016a.tif", flags=0) # flags=0 读取为灰度图像
    imgBlur = cv2.blur(img, (3,3)) # Blur 平滑后再做 Laplacian 变换
    # 近似的 Marr-Hildreth 卷积核 (5*5)
    kernel_MH5 = np.array([
    [0, 0, -1, 0, 0],
    [0, -1, -2, -1, 0],
    [-1, -2, 16, -2, -1],
    [0, -1, -2, -1, 0],
    [0, 0, -1, 0, 0]])
    imgMH5 = signal.convolve2d(imgBlur, kernel_MH5, boundary='symm', mode='same')
    zeroMH5 = ZeroDetect(imgMH5) # 判断零交叉点
    # 由 Gauss 标准差计算 Marr-Hildreth 卷积核
    sigma = 3 # Gauss 标准差,输入参数
    size = int(2 * round(3 * sigma)) + 1 # 根据标准差确定窗口大小,3*sigma 占比 99.7%
    print("sigma={:d}, size={}".format(sigma, size))
    x, y = np.meshgrid(np.arange(-size/2+1, size/2+1), np.arange(-size/2+1, size/2+1)) # 生成网格
    norm2 = np.power(x, 2) + np.power(y, 2)
    sigma2, sigma4 = np.power(sigma, 2), np.power(sigma, 4)
    kernelLoG = ((norm2 - (2.0 * sigma2)) / sigma4) * np.exp(- norm2 / (2.0 * sigma2)) # 计算 LoG 卷积核
    # Marr-Hildreth 卷积运算
    imgLoG = signal.convolve2d(imgBlur, kernelLoG, boundary='symm', mode='same')
    # 判断零交叉点
    zeroCrossing = ZeroDetect(imgLoG)
    show_images([imgMH5,imgLoG,zeroMH5,zeroCrossing])

DoG 边缘检测算子?

  • 高斯差分算子 DoG(Difference of Gaussian)是两个不同尺度的高斯滤波器之差,可以实现 LoG 算子的近似。在具体处理中,DoG 算子就是将图像在不同参数下的高斯滤波结果相减
  • DoG 算子不仅实现简单、计算速度快,而且对噪声、尺度、仿射变化和旋转等具有很强的鲁棒性,能够提供丰富的局部特征信息
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    img = cv2.imread("../images/Fig1016a.tif", flags=0)  # flags=0 读取为灰度图像
    # 高斯核低通滤波器,sigmaY 缺省时 sigmaY=sigmaX
    kSize = (5, 5)
    imgGaussBlur1 = cv2.GaussianBlur(img, (5,5), sigmaX=1.0) # sigma=1.0
    imgGaussBlur2 = cv2.GaussianBlur(img, (5,5), sigmaX=2.0) # sigma=2.0
    imgGaussBlur3 = cv2.GaussianBlur(img, (5,5), sigmaX=4.0) # sigma=4.0
    imgGaussBlur4 = cv2.GaussianBlur(img, (5,5), sigmaX=16.0) # sigma=16.0
    show_images([imgGaussBlur2,imgGaussBlur3,imgGaussBlur4])
    # 高斯差分算子 (Difference of Gaussian)
    imgDoG1 = imgGaussBlur2 - imgGaussBlur1 # sigma=1.0,2.0
    imgDoG2 = imgGaussBlur3 - imgGaussBlur2 # sigma=2.0,4.0
    imgDoG3 = imgGaussBlur4 - imgGaussBlur3 # sigma=4.0,16.0
    show_images([imgDoG1,imgDoG2,imgDoG3])

局部处理连接边缘?

  • 在实际应用中,由于噪声、光照等原因引起的边缘断裂,使边缘检测的结果并不是完全的、完整的边缘,通常要通过边缘连接算法,将边缘像素组合为有意义的边缘或区域边界
  • 边缘连接方分为局部处理方法和全局处理方法。边缘连接的局部处理方法:在局部分析中,主要基于梯度向量的幅值和方向进行边缘像素的相似性判断。E 是梯度向量的幅度阈值,A 是角度阈值
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    img = cv2.imread("../images/imgLena.tif", flags=0)  # flags=0 读取为灰度图像
    # img16 = np.array(img, dtype='uint16')
    hImg, wImg = img.shape#[0], img.shape[1]
    # Sobel 梯度算子
    kSobelX = np.array([[-1, 0, 1], [-2, 0, 2], [-1, 0, 1]])
    kSobelY = np.array([[1, 2, 1], [0, 0, 0], [-1, -2, -1]])
    gx = cv2.filter2D(img, cv2.CV_64F, kSobelX) # SobelX 水平梯度
    gy = cv2.filter2D(img, cv2.CV_64F, kSobelY) # SobelY 垂直梯度
    # 计算梯度向量的幅值 mag 与角度 angle
    # magn = np.sqrt(np.power(gx,2) + np.power(gy,2)) # 梯度向量的幅值
    magn = cv2.normalize(abs(gx)+abs(gy), None, 0, 255, cv2.NORM_MINMAX) # 用绝对值近似梯度幅值
    gxFlat, gyFlat = gx.flatten(), gy.flatten() # 展平为一维,便于计算角度
    angleFlat = np.arctan2(gy, gx) * 180 / np.pi # 梯度向量的角度,将弧度转为角度: (-180, 180)
    angle = angleFlat.reshape(hImg, wImg)
    # 边缘像素相似性判断
    edge = np.zeros((hImg,wImg), np.uint8)
    for h in range(1, hImg-1): # 对边界点不判断
    for w in range(1, wImg-1):
    if (abs(magn[h,w]-magn[h-1,w-1])<=30) and (abs(angle[h,w]-angle[h-1,w-1])<=15)\
    or (abs(magn[h,w]-magn[h-1,w+1])<=30) and (abs(angle[h,w]-angle[h-1,w+1])<=15)\
    or (abs(magn[h,w]-magn[h+1,w-1])<=30) and (abs(angle[h,w]-angle[h+1,w-1])<=15)\
    or (abs(magn[h,w]-magn[h+1,w+1])<=30) and (abs(angle[h,w]-angle[h+1,w+1])<=15):
    edge[h,w] = magn[h,w]
    show_images([img,magn,edge])

什么是凸缺陷?

  • 凸缺陷: 对象上的任何凹陷都被成为凸缺陷
  • Opencv 使用 cv: : convexityDefects 检测凸缺陷

参考:

  1. 【youcans 的 OpenCV 例程 200 篇】148. 图像分割之线检测_youcans_的博客 - CSDN 博客
  2. 【youcans 的 OpenCV 例程 200 篇】149. 图像分割之边缘模型_youcans_的博客 - CSDN 博客
  3. 【youcans 的 OpenCV 例程 200 篇】150. 边缘检测梯度算子_梯度算子检测边缘的模板_youcans_的博客 - CSDN 博客
  4. 【OpenCV 例程 300 篇】250. 梯度算子的传递函数_youcans_的博客 - CSDN 博客
  5. 图像矩 - 知乎
  6. Shape Matching using Hu Moments (C++ / Python) | LearnOpenCV
  7. Image moment - Wikipedia
  8. 【从零学习 OpenCV 4】图像矩的计算与应用 - 知乎
  9. 【youcans 的 OpenCV 例程 200 篇】152. 边缘检测之 LoG 算子_opencv log 算子_youcans_的博客 - CSDN 博客
  10. 【youcans 的 OpenCV 例程 200 篇】153. 边缘检测之 DoG 算子_dog 算法_youcans_的博客 - CSDN 博客
  11. 【youcans 的 OpenCV 例程 200 篇】154. 边缘检测之 Canny 算子_youcans_的博客 - CSDN 博客
  12. 【youcans 的 OpenCV 例程 200 篇】155. 边缘连接的局部处理方法_局部处理的边缘连接_youcans_的博客 - CSDN 博客