Canny边缘检测算法

Canny算法是一种优秀的边缘检测方法,包括图像灰度化、高斯滤波、梯度计算、非极大值抑制和双阈值检测等步骤。它旨在找到图像中的最优边缘,确保好的检测、定位和最小响应。高斯滤波用于降噪,非极大值抑制用于细化边缘,双阈值检测则用于去除虚假边缘。
摘要由CSDN通过智能技术生成
Canny是目前最优秀的边缘检测算法之一,其目标为找到一个最优的边缘,其最优边缘的定义为:
1、好的检测:算法能够尽可能的标出图像中的实际边缘
2、好的定位:标识出的边缘要与实际图像中的边缘尽可能接近
3、最小响应:图像中的边缘只能标记一次

Canny边缘检测算法步骤:

1.图像灰度化:

Canny算法通常处理的图像为灰度图,因此如果摄像机获取的是彩色图像,那首先就得进行灰度化。对一幅彩色图进行灰度化,就是根据图像各个通道的采样值进行加权平均。以RGB格式的彩图为例,通常灰度化采用的方法主要有:
方法1:Gray=(R+G+B)/3;
方法2:Gray=0.299R+0.587G+0.114B;
注意1:至于其他格式的彩色图像,可以根据相应的转换关系转为RGB然后再进行灰度化;
注意2:在编程时要注意图像格式中RGB的顺序通常为BGR。

2.高斯滤波

为了尽可能减少噪声对边缘检测结果的影响,所以必须滤除噪声以防止由噪声引起的错误检测。为了平滑图像,使用高斯滤波器与图像进行卷积,该步骤将平滑图像,以减少边缘检测器上明显的噪声影响。
其实在各个算法库如Matlab、OpenCV等,在实现的时候,就是采用一个矩阵模板进行加权运算,拿图像的八连通区域来说,中间点的像素值就等于八连通区的像素值的均值,这样达到平滑的效果,该模板我们常成为高斯核。
根据上述分析可知,高斯核是整个求解的关键。很显然,它是通过二维高斯函数计算得来的。这里给出离散高斯核矩阵的计算公式,

离散的高斯卷积核H: (2k+1)×(2k+1)维,其元素计算方法为:

 

其中Sigma为方差,k确定核矩阵的维数。高斯卷积核大小的选择将影响Canny检测器的性能。尺寸越大,检测器对噪声的敏感度越低,但是边缘检测的定位误差也将略有增加。一般5x5是一个比较不错的平衡

 

3.计算图像中每个像素点的梯度强度和方向

图像的边缘可以指向不同方向,因此经典Canny算法用了四个梯度算子来分别计算水平,垂直和对角线方向的梯度。但是通常都不用四个梯度算子来分别计算四个方向。常用的边缘差分算子(如Rober,Prewitt,Sobel)计算水平和垂直方向的差分Gx和Gy。这样就可以如下计算梯度模和方向:

梯度角度θ范围从弧度-π到π,然后把它近似到四个方向,分别代表水平,垂直和两个对角线方向(0°,45°,90°,135°)。可以以±iπ/8(i=1,3,5,7)分割,落在每个区域的梯度角给一个特定值,代表四个方向之一。

4.非极大值(Non-Maximum Suppression)抑制

非极大值抑制是一种边缘稀疏技术,非极大值抑制的作用在于“瘦”边。对图像进行梯度计算后,仅仅基于梯度值提取的边缘仍然很模糊。对于标准3,对边缘有且应当只有一个准确的响应。而非极大值抑制则可以帮助将局部最大值之外的所有梯度值抑制为0,对梯度图像中每个像素进行非极大值抑制的算法是:

  • 将当前像素的梯度强度与沿正负梯度方向上的两个像素进行比较。
  • 如果当前像素的梯度强度与另外两个像素相比最大,则该像素点保留为边缘点,否则该像素点将被抑制

 如图:假设P是中心点,W,SW,NE,E是旁边的四个像素点,P1,P2是虚拟的像素点(亚像素),P2-P1的红线是梯度,那么P1,P2的像素值怎么算?

这里用到线性差值的算法去近似算一个值,

    def getSubPixel(self, weight, g1, g2, g3, g4):
        dp1 = weight * g1 + (1 - weight) * g2
        dp2 = weight * g3 + (1 - weight) * g4
        return dp1, dp2

weight就是权重,或者比例,假设权重是0.6,那么P2=SW*0.6+W*0.4,P1=E*0.4+NE*0.6

然后比较P2,P,P1三个点最大值,如果P是最大,保留当前值,否则,置为0

5.双阈值检测

完成非极大值抑制后,会得到一个二值图像,非边缘的点灰度值均为0,可能为边缘的局部灰度极大值点可设置其灰度为128(或其他)。
这样一个检测结果还是包含了很多由噪声及其他原因造成的假边缘。因此还需要进一步的处理。
双阈值检测:
如果边缘像素的梯度值高于高阈值,则将其标记为强边缘像素;
如果边缘像素的梯度值小于高阈值并且大于低阈值,则将其标记为弱边缘像素;
如果边缘像素的梯度值小于低阈值,则会被抑制。
大于高阈值为强边缘,小于低阈值不是边缘。介于中间是弱边缘。
阈值的选择取决于给定输入图像的内容。

6.抑制孤立的弱边缘

到目前为止,被划分为强边缘的像素点已经被确定为边缘,因为它们是从图像中的真实边缘中提取出来的。
然而,对于弱边缘像素,将会有一些争论,因为这些像素可以从真实边缘提取也可以是因噪声或颜色变化引起的。
为了获得准确的结果,应该抑制由后者引起的弱边缘:
通常,由真实边缘引起的弱边缘像素将连接到强边缘像素,而噪声响应未连接。
为了跟踪边缘连接,通过查看弱边缘像素及其8个邻域像素,只要其中一个为强边缘像素,
则该弱边缘点就可以保留为真实的边缘。

上正菜!!!

import cv2
import matplotlib.pyplot as plt
import numpy as np
import random


# 1.对图像进行灰度化
# 2.对图像进行高斯滤波:
#   根据待滤波的像素点及其邻域点的灰度值按照一定的参数规则进行加权平均。这样可以有效滤去理想图像中叠加的高频噪声。
# 3.检测图像中的水平、垂直和对角边缘(如Prewitt,Sobel算子等),计算梯度。
# 4.对梯度幅值进行非极大值抑制:通俗意义上是指寻找像素点局部最大值,将非极大值点所对应的灰度值置为0,这样可以剔除掉一大部分非边缘的点。
# 5.用双阈值算法检测和连接边缘
class Canny:
    # Prewitt算子
    kernelx = np.array(
        [
            [1, 1, 1],
            [0, 0, 0],
            [-1, -1, -1]
        ]
    )

    kernely = np.array(
        [
            [-1, 0, 1],
            [-1, 0, 1],
            [-1, 0, 1]
        ]
    )

    # 灰度化图像
    def grayscaleImage(self, filePath):
        imageGrayscaleArray = cv2.imread(filePath)
        gray_img = cv2.cvtColor(imageGrayscaleArray, cv2.COLOR_RGB2GRAY)
        return gray_img

    # 高斯滤波
    def gaussFilterImage(self, filePath):
        gaussFilterImage = cv2.GaussianBlur(self.grayscaleImage(self, filePath), (5, 5), 0, 0)
        return gaussFilterImage

    # 边缘检测&非极大值抑制
    def nonmaxLimit(self, filePath):
        image = self.gaussFilterImage(self, filePath)

        # Sobel
        x = cv2.Sobel(image, cv2.CV_16S, 1, 0)
        y = cv2.Sobel(image, cv2.CV_16S, 0, 1)

        # Prewitt
        # x = cv2.filter2D(image, -1, self.kernelx)
        # y = cv2.filter2D(image, -1, self.kernely)

        # 转换数据 并 合成
        absX = cv2.convertScaleAbs(x)  # 格式转换函数
        absY = cv2.convertScaleAbs(y)

        # 图像混合后的数组
        result = cv2.addWeighted(absX, 0.5, absY, 0.5, 0)

        width, height = result.shape

        tempImage = np.zeros_like(image)
        g1 = 0
        g2 = 0
        g3 = 0
        g4 = 0
        for i in range(1, width - 1):
            for j in range(1, height - 1):
                # 梯度>1
                if (result[i][j] > 1):
                    g1 = image[i - 1][j - 1]
                    g2 = image[i][j - 1]
                    g3 = image[i][j + 1]
                    g4 = image[i + 1][j + 1]
                # 梯度<1
                if (result[i][j] < 1):
                    g1 = image[i - 1][j - 1]
                    g2 = image[i][j - 1]
                    g3 = image[i][j + 1]
                    g4 = image[i + 1][j + 1]
                # 梯度=1
                if (result[i][j] == 1):
                    g1 = g2 = image[i - 1][j - 1]
                    g3 = g4 = image[i + 1][j + 1]
                # 梯度=-1
                if (result[i][j] == 1):
                    g1 = g2 = image[i + 1][j - 1]
                    g3 = g4 = image[i - 1][j + 1]

                dp1, dp2 = self.getSubPixel(self, result[i][j], g1, g2, g3, g4)
                if image[i, j] == max(image[i, j], dp1, dp2):
                    tempImage[i][j] = image[i][j]

        # 基于梯度统计信息计算法实现自适应阈值
        MAX = image.max()
        MIN = image.min()
        MED = np.median(image)
        average = (MAX + MIN + MED) / 3
        sigma = 0.33
        # 低阈值
        lowThreshold = max(0, (1 - sigma) * average)
        # 高阈值
        highThreshold = min(255, (1 + sigma) * average)

        imageEdge = np.zeros_like(tempImage)

        for i in range(1, width - 1):
            for j in range(1, height - 1):
                if tempImage[i][j] >= highThreshold:
                    imageEdge[i][j] = 255
                elif tempImage[i][j] > lowThreshold:
                    # python切片 https://blog.csdn.net/Cai_Xu_Kun/article/details/114978189
                    around = tempImage[i - 1: i + 2, j - 1: j + 2]
                    if around.max() >= highThreshold:
                        imageEdge[i, j] = 255

        fig, axes = plt.subplots(1, 3)
        axes[0].imshow(image)
        axes[1].imshow(result)
        axes[2].imshow(imageEdge)
        plt.show()

    # 计算亚像素
    def getSubPixel(self, weight, g1, g2, g3, g4):
        dp1 = weight * g1 + (1 - weight) * g2
        dp2 = weight * g3 + (1 - weight) * g4
        return dp1, dp2


Canny.nonmaxLimit(Canny, "image/aaa.jpg")

  • 3
    点赞
  • 34
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值