本节是非微分边缘检测算子——Canny算子
边缘是图像中灰度有阶跃变化,或屋顶变化的像素的结合。
1、 Canny算子边缘检测基本原理
该算子功能比前面几种都要好,但是它实现起来较为麻烦,Canny算子是一个具有滤波,增强,检测的多阶段的优化算子,在进行处理前,Canny算子先利用高斯平滑滤波器来平滑图像以除去噪声,Canny分割算法采用一阶偏导的有限差分来计算梯度幅值和方向,在处理过程中,Canny算子还将经过一个非极大值抑制的过程,最后Canny算子还采用两个阈值来连接边缘。
2、 Canny算子算法步骤
第一步:用高斯滤波器平滑图像。
见链接:https://blog.csdn.net/m0_37957160/article/details/119377528
第二步:计算整个图像的每一个像素的梯度幅值和梯度方向。
数字图像处理中提供了好多种算子,计算图像的梯度和梯度方向。比如Sobel算子。
比如下边是使用Sobel算子计算的整个图像中每一个像素的梯度和方向:
现在边太粗了需要变细。
第三步:对梯度幅值进行非极大值抑制。
Canny算法论文里边是这样介绍的:在上述梯度矩阵(包含大小和方向 ),这个梯度方向有可能不是离散的,是连续的。那么论文里边假设我就关注如下的4个轴即4个方向 ,
假设只关注4个方向,
比如说某一个梯度的方向是30度,那么在0到45度之间他是离45度方向比较近,那么就规定他为45度。就是查看他在上述方向划分的模式下,离哪一个比较近就把他的方向归并到哪一个方向上。
因为图像的像素分布是离散的,如果把其中一个像素的8邻域按照上述模式盖上去,
上述的NMS是Canny论文里边的思路,有后人改进的思想:用亚像素去线性差值的方法去进行非极大值抑制。
第四步:用双阈值算法检测和连接边缘。(阈值A和阈值B)
(PS:如果只是单一阈值的话,你这个阈值调的比较高,那整个图像里边一些细微的边缘(即非极大值抑制之后他那些梯度比较小的边)很可能就直接被扔掉了,但是呢,如果把阈值设置的比较低,好多杂乱无章的边也会包含进来,所以引进了双阈值)
如果做完NMS的梯度值小于阈值A的话,那么该点直接扔掉,比如说赋值为0;如果这个值比我设置的B值还要大的话,那么就认为该点是边缘点;如果在A和B之间的点叫做中庸点,对待中庸点会分两种情况:第一种情况就是该中庸点与已经确定的边缘点所组成的边有关系(就是相连,该中庸点与确定的边界相连),那么就认为该中庸点也是一个边缘点(置为255),如果该中庸点在一条边上那么该边也被认为是一条边界边。反之那么认为该中庸点是非边缘点。
3 、 Canny算子python实现
import cv2
import numpy as np
import mathdef Canny(img, threshold1, threshold2):# 高斯滤波gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)new_gray = cv2.GaussianBlur(gray, (5, 5), 1)gaussian_result = np.uint8(np.copy(new_gray))cv2.imshow('gaussian',gaussian_result)# 算梯度幅值W1, H1 = new_gray.shape[:2]dx = np.zeros([W1 - 1, H1 - 1])dy = np.zeros([W1 - 1, H1 - 1])d = np.zeros([W1 - 1, H1 - 1])ddgree = np.zeros([W1 - 1, H1 - 1])for i in range(1, W1 - 1):for j in range(1, H1 - 1):dx[i, j] = new_gray[i - 1, j - 1] + 2 * new_gray[i, j - 1] + new_gray[i + 1, j - 1] - \new_gray[i - 1, j + 1] - 2 * new_gray[i, j + 1] - new_gray[i + 1, j + 1]dy[i, j] = new_gray[i - 1, j - 1] + 2 * new_gray[i - 1, j] + new_gray[i - 1, j + 1] - \new_gray[i + 1, j - 1] - 2 * new_gray[i + 1, j] - new_gray[i + 1, j + 1]d[i, j] = np.sqrt(np.square(dx[i, j]) + np.square(dy[i, j])) # 图像梯度幅值作为图像强度值ddgree[i, j] = math.degrees(math.atan2(dy[i, j], dx[i, j]))if ddgree[i, j] < 0:ddgree += 360d_r = np.uint8(np.copy(d))cv2.imshow('tidu', d_r)# 非极大值抑制W2, H2 = d.shapeNMS = np.copy(d)NMS[0, :] = NMS[W2 - 1, :] = NMS[:, 0] = NMS[:, H2 - 1] = 0for i in range(1, W2 - 1):for j in range(1, H2 - 1):if d[i, j] == 0:NMS[i, j] = 0else:g1 = Noneg2 = Noneif (ddgree[i, j] <= 22.5 and ddgree[i, j] >= 0) or (ddgree[i, j] >= 337.5):g1 = NMS[i, j - 1]g2 = NMS[i, j + 1]elif (ddgree[i, j] <= 67.5 and ddgree[i, j] > 22.5) or (ddgree[i, j] <= 337.5 and ddgree[i, j] > 292.5):g1 = NMS[i - 1, j + 1]g2 = NMS[i + 1, j - 1]elif ddgree[i, j] <= 112.5 and ddgree[i, j] > 67.5 or (ddgree[i, j] <= 292.5 and ddgree[i, j] > 247.5):g1 = NMS[i - 1, j]g2 = NMS[i + 1, j]elif (ddgree[i, j] <= 157.5 and ddgree[i, j] > 112.5) or (ddgree[i, j] <= 247.5 and ddgree[i, j] > 202.5):g1 = NMS[i - 1, j - 1]g2 = NMS[i + 1, j + 1]else:g1 = NMS[i, j - 1]g2 = NMS[i, j + 1]if NMS[i, j] < g1 or NMS[i, j] < g2:NMS[i, j] = 0nms_r = np.uint8(np.copy(NMS))cv2.imshow('nms', nms_r)# 双阈值算法检测、连接边缘W3, H3 = NMS.shapeDT = np.zeros([W3, H3], dtype=np.uint8)# 定义高低阈值TL = min(threshold1, threshold2)TH = max(threshold1, threshold2)for i in range(1, W3 - 1):for j in range(1, H3 - 1):if (NMS[i, j] < TL):DT[i, j] = 0elif (NMS[i, j] > TH):DT[i, j] = 255else:if NMS[i - 1, j] > TH or NMS[i - 1, j - 1] > TH or NMS[i - 1, j + 1] > TH or NMS[i, j - 1] > TH \or NMS[i, j + 1] > TH or NMS[i + 1, j] > TH or NMS[i + 1, j - 1] > TH or NMS[i + 1, j + 1] > TH: \DT[i, j] = 255return DTimg = cv2.imread('test.jpg')
cv2.imshow('src', img)
result = Canny(img, 60, 120)
cv2.imshow('dst', result)
cv2.waitKey(0)
结果如下:
4、扩展部分
(1)边缘检测算子比较:
(2)边缘提取和边缘跟踪:
Canny算子B站讲解:https://space.bilibili.com/580742386?spm_id_from=333.788.b_765f7570696e666f.1
参考链接:https://www.cnblogs.com/wj-1314/p/9800272.html
https://www.bilibili.com/video/BV1U4411277i?from=search&seid=16066791906732345516