【经典算法】Candy

背景介绍

Canny边缘检是在在1986年提出来的,到今天已经30多年过去了,但Canny算法仍然是图像边缘检测算法中最经典、先进的算法之一。

相比Sobel、Prewitt等算子,Canny算法更为优异。Sobel、Prewitt等算子有如下缺点:

  • 没有充分利用边缘的梯度方向。
  • 最后得到的二值图,只是简单地利用单阈值进行处理。

而Canny算法基于这两点做了改进,提出了:

  • 基于边缘梯度方向的非极大值抑制。
  • 双阈值的滞后阈值处理。

实现步骤

 

1.滤波平滑

滤波平滑可以平滑噪声,噪声在对检测边缘时候影像很大。常见平滑滤波有高斯滤波,1/159 是对图片的像素值进行归一化,A为图片。

2.计算图像梯度

  • 利用sobel算子算出中心点附近的分别沿 x轴和 y轴的差值

  • 计算梯度幅值

  • 计算梯度方向

\theta = arctan(s_y/s_x)

 

3.根据角度对幅值进行非极大值抑制

 

根据上述计算出的角度,分别对应八个领域,四个梯度方向。

在每一点上,领域中心 x 与沿着其对应的梯度方向的两个像素相比,若中心像素为最大值,则保留,否则中心置0,这样可以抑制非极大值,保留局部梯度最大的点,以得到细化的边缘。

 

 

def DecideAndConnectEdge(g_l,g_t,threshold = None):
    if threshold == None:
        lower_boundary = g_l.mean()*0.5
        threshold = [lower_boundary,lower_boundary*3]
    result = np.zeros(g_l.shape)

    for i in range(g_l.shape[0]):
        for j in range(g_l.shape[1]):
            isLocalExtreme = True
            eight_neiborhood = g_l[max(0,i-1):min(i+2,g_l.shape[0]),max(0,j-1):min(j+2,g_l.shape[1])]

            if eight_neiborhood.shape == (3,3):
                if g_t[i,j] <= -1:
                    x = 1/g_t[i,j]
                    first = eight_neiborhood[0,1] + (eight_neiborhood[0,1] - eight_neiborhood[0,0])*x
                    x = -x
                    second = eight_neiborhood[2,1] + (eight_neiborhood[2,2] - eight_neiborhood[2,1])*x
                    if not (g_l[i,j] > first and g_l[i,j] > second):
                        isLocalExtreme = False
                elif g_t[i,j] >= 1:
                    x = 1 / g_t[i, j]
                    first = eight_neiborhood[0, 1] + (eight_neiborhood[0, 2] - eight_neiborhood[0, 1]) * x
                    x = -x
                    second = eight_neiborhood[2, 1] + (eight_neiborhood[2, 1] - eight_neiborhood[2, 0]) * x
                    if not (g_l[i, j] > first and g_l[i, j] > second):
                        isLocalExtreme = False
                elif g_t[i,j] >= 0 and g_t[i,j] < 1:
                    y = g_t[i, j]
                    first = eight_neiborhood[1, 2] + (eight_neiborhood[0, 2] - eight_neiborhood[1, 2]) * y
                    y = -y
                    second = eight_neiborhood[1, 0] + (eight_neiborhood[1, 0] - eight_neiborhood[2, 0]) * y
                    if not (g_l[i, j] > first and g_l[i, j] > second):
                        isLocalExtreme = False
                elif g_t[i,j] < 0 and g_t[i,j] > -1:
                    y = g_t[i, j]
                    first = eight_neiborhood[1, 2] + (eight_neiborhood[1, 2] - eight_neiborhood[2, 2]) * y
                    y = -y
                    second = eight_neiborhood[1, 0] + (eight_neiborhood[0, 0] - eight_neiborhood[1, 0]) * y
                    if not (g_l[i, j] > first and g_l[i, j] > second):
                        isLocalExtreme = False
            if isLocalExtreme:
                result[i,j] = g_l[i,j]       #非极大值抑制

    result[result>=threshold[1]] = 255
    result[result<=threshold[0]] = 0


    result = judgeConnect(result,threshold)
    result[result!=255] = 0
    return result

4. 用双阈值算法检测和连接边缘

由3得到了各个离散点,在下面通过设置双阀值,取 max>h(x,y)>min 的各个点进行连接。

 

def judgeConnect(m2,threshold):
    e = 0.01
    s = []
    cood = []
    for i in range(m2.shape[0]):
        cood.append([])
        for j in range(m2.shape[1]):
            cood[-1].append([i,j])
            if abs(m2[i,j] - 255) < e:
                s.append([i,j])
    cood = np.array(cood)

    while not len(s) == 0:
        index = s.pop()
        jud = m2[max(0, index[0] - 1):min(index[0] + 2, m2.shape[1]), max(0, index[1] - 1):min(index[1] + 2, m2.shape[0])]
        jud_i = cood[max(0, index[0] - 1):min(index[0] + 2, cood.shape[1]), max(0, index[1] - 1):min(index[1] + 2, cood.shape[0])]
        jud = (jud > threshold[0])&(jud < threshold[1])
        jud_i = jud_i[jud]
        for i in range(jud_i.shape[0]):
            s.append(list(jud_i[i]))
            m2[jud_i[i][0],jud_i[i][1]] = 255

    return m2

复现代码

#coding:utf-8
import numpy as np
import matplotlib.pyplot as plt
import os
import math
import cv2

def convolve(filter,mat,padding,strides):
    '''
    :param filter:卷积核,必须为二维(2 x 1也算二维) 否则返回None
    :param mat:图片
    :param padding:对齐
    :param strides:移动步长
    :return:返回卷积后的图片。(灰度图,彩图都适用)
    '''
    result = None
    filter_size = filter.shape
    mat_size = mat.shape
    if len(filter_size) == 2:
        if len(mat_size) == 3:
            channel = []
            for i in range(mat_size[-1]):
                pad_mat = np.pad(mat[:,:,i], ((padding[0], padding[1]), (padding[2], padding[3])), 'constant')
                temp = []
                for j in range(0,mat_size[0],strides[1]):
                    temp.append([])
                    for k in range(0,mat_size[1],strides[0]):
                        val = (filter*pad_mat[j*strides[1]:j*strides[1]+filter_size[0],
                                      k*strides[0]:k*strides[0]+filter_size[1]]).sum()
                        temp[-1].append(val)
                channel.append(np.array(temp))

            channel = tuple(channel)
            result = np.dstack(channel)
        elif len(mat_size) == 2:
            channel = []
            pad_mat = np.pad(mat, ((padding[0], padding[1]), (padding[2], padding[3])), 'constant')
            for j in range(0, mat_size[0], strides[1]):
                channel.append([])
                for k in range(0, mat_size[1], strides[0]):
                    val = (filter * pad_mat[j * strides[1]:j * strides[1] + filter_size[0],
                                    k * strides[0]:k * strides[0] + filter_size[1]]).sum()
                    channel[-1].append(val)

            result = np.array(channel)


    return result

def linear_convolve(filter,mat,padding=None,strides=[1,1]):
    '''
    :param filter:线性卷积核
    :param mat:图片
    :param padding:对齐
    :param strides:移动步长
    :return:返回卷积后的图片。(灰度图,彩图都适用) 若不是线性卷积核,返回None
    '''
    result = None
    filter_size = filter.shape
    if len(filter_size) == 2 and 1 in filter_size:
        if padding == None or len(padding) < 2:
            if filter_size[1] == 1:
                padding = [filter_size[0]//2,filter_size[0]//2]
            elif filter_size[0] == 1:
                padding = [filter_size[1]//2,filter_size[1]//2]
        if filter_size[0] == 1:
            result = convolve(filter,mat,[0,0,padding[0],padding[1]],strides)
        elif filter_size[1] == 1:
            result = convolve(filter, mat, [padding[0],padding[1],0,0], strides)

    return result

def _2_dim_divided_convolve(filter,mat):
    '''

    :param filter: 线性卷积核,必须为二维(2 x 1也算二维) 否则返回None
    :param mat: 图片
    :return: 卷积后的图片,(灰度图,彩图都适用) 若不是线性卷积核,返回None
    '''
    result = None
    if 1 in filter.shape:
        result = linear_convolve(filter,mat)
        result = linear_convolve(filter.T,result)

    return result

def judgeConnect(m2,threshold):
    e = 0.01
    s = []
    cood = []
    for i in range(m2.shape[0]):
        cood.append([])
        for j in range(m2.shape[1]):
            cood[-1].append([i,j])
            if abs(m2[i,j] - 255) < e:
                s.append([i,j])
    cood = np.array(cood)

    while not len(s) == 0:
        index = s.pop()
        jud = m2[max(0, index[0] - 1):min(index[0] + 2, m2.shape[1]), max(0, index[1] - 1):min(index[1] + 2, m2.shape[0])]
        jud_i = cood[max(0, index[0] - 1):min(index[0] + 2, cood.shape[1]), max(0, index[1] - 1):min(index[1] + 2, cood.shape[0])]
        jud = (jud > threshold[0])&(jud < threshold[1])
        jud_i = jud_i[jud]
        for i in range(jud_i.shape[0]):
            s.append(list(jud_i[i]))
            m2[jud_i[i][0],jud_i[i][1]] = 255

    return m2


def DecideAndConnectEdge(g_l,g_t,threshold = None):
    if threshold == None:
        lower_boundary = g_l.mean()*0.5
        threshold = [lower_boundary,lower_boundary*3]
    result = np.zeros(g_l.shape)

    for i in range(g_l.shape[0]):
        for j in range(g_l.shape[1]):
            isLocalExtreme = True
            eight_neiborhood = g_l[max(0,i-1):min(i+2,g_l.shape[0]),max(0,j-1):min(j+2,g_l.shape[1])]

            if eight_neiborhood.shape == (3,3):
                if g_t[i,j] <= -1:
                    x = 1/g_t[i,j]
                    first = eight_neiborhood[0,1] + (eight_neiborhood[0,1] - eight_neiborhood[0,0])*x
                    x = -x
                    second = eight_neiborhood[2,1] + (eight_neiborhood[2,2] - eight_neiborhood[2,1])*x
                    if not (g_l[i,j] > first and g_l[i,j] > second):
                        isLocalExtreme = False
                elif g_t[i,j] >= 1:
                    x = 1 / g_t[i, j]
                    first = eight_neiborhood[0, 1] + (eight_neiborhood[0, 2] - eight_neiborhood[0, 1]) * x
                    x = -x
                    second = eight_neiborhood[2, 1] + (eight_neiborhood[2, 1] - eight_neiborhood[2, 0]) * x
                    if not (g_l[i, j] > first and g_l[i, j] > second):
                        isLocalExtreme = False
                elif g_t[i,j] >= 0 and g_t[i,j] < 1:
                    y = g_t[i, j]
                    first = eight_neiborhood[1, 2] + (eight_neiborhood[0, 2] - eight_neiborhood[1, 2]) * y
                    y = -y
                    second = eight_neiborhood[1, 0] + (eight_neiborhood[1, 0] - eight_neiborhood[2, 0]) * y
                    if not (g_l[i, j] > first and g_l[i, j] > second):
                        isLocalExtreme = False
                elif g_t[i,j] < 0 and g_t[i,j] > -1:
                    y = g_t[i, j]
                    first = eight_neiborhood[1, 2] + (eight_neiborhood[1, 2] - eight_neiborhood[2, 2]) * y
                    y = -y
                    second = eight_neiborhood[1, 0] + (eight_neiborhood[0, 0] - eight_neiborhood[1, 0]) * y
                    if not (g_l[i, j] > first and g_l[i, j] > second):
                        isLocalExtreme = False
            if isLocalExtreme:
                result[i,j] = g_l[i,j]       #非极大值抑制

    result[result>=threshold[1]] = 255
    result[result<=threshold[0]] = 0


    result = judgeConnect(result,threshold)
    result[result!=255] = 0
    return result

def OneDimensionStandardNormalDistribution(x,sigma):
    E = -0.5/(sigma*sigma)
    return 1/(math.sqrt(2*math.pi)*sigma)*math.exp(x*x*E)

if __name__ == '__main__':




    sobel_kernel_y = np.array([[-1, -2, -1], [0, 0, 0], [1, 2, 1]])
    sobel_kernel_x = np.array([[-1, 0, 1], [-2, 0, 2], [-1, 0, 1]])

    pic_path = './img/'
    pics = os.listdir(pic_path)

    for i in pics:
        if i[-4:] == '.png'or i[-4:] == '.jpg' or i[-5:] == '.jpeg':
            filename = pic_path + i
            img = plt.imread(filename)
            if i[-4:] == '.png':
                img = img*255
            img = img.mean(axis=-1)  #this is a way to get a gray image.

            sigma = 1.52
            dim = int(np.round(6*sigma+1))
            if dim % 2 == 0:
                dim += 1
            linear_Gaussian_filter = [np.abs(t - (dim//2)) for t in range(dim)]

            linear_Gaussian_filter = np.array([[OneDimensionStandardNormalDistribution(t,sigma) for t in linear_Gaussian_filter]])
            linear_Gaussian_filter = linear_Gaussian_filter/linear_Gaussian_filter.sum()



            img2 = _2_dim_divided_convolve(linear_Gaussian_filter,img)
            # img2 = convolve(Gaussian_filter_5, img, [2, 2, 2, 2], [1, 1])

            plt.imshow(img2.astype(np.uint8), cmap='gray')
            plt.axis('off')
            plt.show()

            # 计算图像边缘点的梯度
            img3 = convolve(sobel_kernel_x,img2,[1,1,1,1],[1,1])
            img4 = convolve(sobel_kernel_y,img2,[1,1,1,1],[1,1])

            gradiant_length = (img3**2+img4**2)**(1.0/2)

            img3 = img3.astype(np.float64)
            img4 = img4.astype(np.float64)
            img3[img3==0]=0.00000001
            gradiant_tangent = img4/img3

            plt.imshow(gradiant_length.astype(np.uint8), cmap='gray')
            plt.axis('off')
            plt.show()

            #lower_boundary = 50
            final_img = DecideAndConnectEdge(gradiant_length,gradiant_tangent)

            cv2.imshow('edge',final_img.astype(np.uint8))
            cv2.waitKey(0)

 

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
Canny边缘检测算法是一种经典的图像处理算法,被广泛应用于计算机视觉领域。该算法的流程可以分为以下几个步骤: 1. 高斯滤波器:首先,对输入图像应用高斯滤波器进行去噪处理。这一步的目的是减少图像中的噪声对边缘检测结果的影响。通过调整滤波器的参数,可以控制平滑程度。 2. 计算梯度:在经过高斯滤波器处理后的图像上计算梯度信息。梯度表示图像中每个像素点的亮度变化情况。通过计算梯度的幅值和方向,我们可以得到图像中的边缘信息。 3. 非最大抑制:接下来,在梯度图像上进行非最大抑制处理。这一步的目的是将梯度图像中非边缘像素置为0,只保留边缘的细线条。非最大抑制的原理是在每个像素点的梯度方向上,比较该像素点与相邻像素点的梯度幅值,只保留梯度幅值最大的像素点作为边缘点。 4. 阈值处理:经过非最大抑制后,我们得到了一个二值图像,其中边缘点被设置为白色,非边缘点为黑色。然而,这个结果可能还存在一些假阳性(即被错误地标记为边缘的非边缘点)。为了去除这些假阳性,Canny边缘检测算法采用了双阈值处理。较大的阈值用于检测图像中明显的边缘,而较小的阈值用于将这些断续的边缘连接起来。通过调整这两个阈值,我们可以控制边缘检测的灵敏度和准确性。 5. 边缘连接:最后一步是进行边缘的连接。在前面的步骤中,我们得到了一些断断续续的边缘线段。为了得到连续的边缘,Canny边缘检测算法会分析所有的边缘及其之间的连接关系,将它们连接起来形成完整的边缘线段。 综上所述,Canny边缘检测算法通过高斯滤波器去噪、计算梯度、非最大抑制、阈值处理和边缘连接这几个步骤来实现对图像中边缘的检测。通过调节算法中的参数,我们可以控制边缘检测的准确性和灵敏度。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

wL魔法师

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值