Canny边缘检测算法原理及实现(Python + OpenCV)

  • Canny算法思想
    Canny算子是John F.Canny 大佬在1986年在其发表的论文 《Canny J. A computational approach to edge detection [J]. IEEE Transactions on Pattern Analysis and Machine Intelligence, 1986 (6): 679-698.》提出来的
    Canny边缘检测算法原理及实现(Python + OpenCV)_第1张图片
    可以说,Canny 边缘检测算法是被业界公认的性能最为优良的边缘检测算法之一。Canny算法不是像Roberts、Prewitt、Sobel等这样简单梯度算子或锐化模板,它是在梯度算子基础上,引入了一种能获得抗噪性能好、定位精度高的单像素边缘的计算策略。
    Canny把边缘检测问题转化为检测单位函数极大值问题。在高斯噪声假设中,一个典型的边缘代表一个阶跃的强度变化。根据这个模型,一个好的边缘检测算子应满足以下3个指标:

(1) 低失误概率;(2) 高位置精度;(3) 对每个边缘有唯一的响应。
换句话说边缘检测问题就是要做到: 抑制噪声,精确定位边缘

Canny 边缘检测算法的主要计算步骤包括四个方面:
(1) 利用高斯滤波器对原始图像进行平滑滤波,以提高算法的抗噪性。
(2) 用一阶有限差分近似代替偏导数,计算图像梯度强度和方向。计算梯度可以利用先前的Roberts、 Prewitt、Sobel等算子(文中用的是Prewitt 算子)。其中,方向信息是为了下一步计算需要。
(3) 利用第(2)步梯度方向划分(划分为四个方向 0\ -45\ 90 \45 )进行梯度强度的非极大抑制,获取单像素边缘点。
(4) 双(或滞后)阈值进行边缘的二值化。

目前,Canny提出的以上边缘计算策略已经成为图像边缘检测的精髓,且已融入到各种经典检测算子和一些新的边缘检测方法之中。

有不少学者发表论文,认为Roberts、Prewitt、Sobel等梯度算子不能获取单像素边缘,并进行必要的计算结果对比展示。实际上,以上简单的梯度算子与图像进行卷积滤波,仅仅是对图像的一个锐化过程,得到的仅是原始图像的梯度图,而不是最终的边缘结果,当然不会是单像素边缘。

然而,我们发现Matlab图像处理工具中的各种经典算子,最后得到的处理结果就都是单像素边缘、而且效果与Canny算法的差异不大。这是因为Matlab工具中的各种经典边缘算子,计算过程中引入了非极大抑制(即边缘细化)、双阈值边缘二值化等Canny算法策略。因此,可以获得与Canny算法效果相当的边缘结果。自己编程计算时,一般与算子(模板)进行卷积计算后,进行简单阈值分割,看起来处理效果总是不理想。

  • Canny算法实现
    按照上述四步操作,进行如下处理
import cv2
import numpy as np

m1 = np.array([[-1,0,1],[-1,0,1],[-1,0,1]])
m2 = np.array([[-1,-1,-1],[0,0,0],[1,1,1]])
from matplotlib import pyplot as plt
# 第一步:完成高斯平滑滤波
img = cv2.imread("rice.jpg",0)
img = cv2.GaussianBlur(img,(3,3),2)

# 第二步:完成一阶有限差分计算,计算每一点的梯度幅值与方向
img1 = np.zeros(img.shape,dtype="uint8") # 与原图大小相同
theta = np.zeros(img.shape,dtype="float")  # 方向矩阵原图像大小
img = cv2.copyMakeBorder(img,1,1,1,1,borderType=cv2.BORDER_REPLICATE)
rows,cols = img.shape
for i in range(1,rows-1):
    for j in range(1,cols-1):
        # Gy
        Gy = (np.dot(np.array([1, 1, 1]), (m1 * img[i - 1:i + 2, j - 1:j + 2]))).dot(np.array([[1], [1], [1]]))
        # Gx
        Gx = (np.dot(np.array([1, 1, 1]), (m2 * img[i - 1:i + 2, j - 1:j + 2]))).dot(np.array([[1], [1], [1]]))
        if Gx[0] == 0:
            theta[i-1,j-1] = 90
            continue
        else:
            temp = (np.arctan(Gy[0] / Gx[0]) ) * 180 / np.pi
        if Gx[0]*Gy[0] > 0:
            if Gx[0] > 0:
                theta[i-1,j-1] = np.abs(temp)
            else:
                theta[i-1,j-1] = (np.abs(temp) - 180)
        if Gx[0] * Gy[0] < 0:
            if Gx[0] > 0:
                theta[i-1,j-1] = (-1) * np.abs(temp)
            else:
                theta[i-1,j-1] = 180 - np.abs(temp)
        img1[i-1,j-1] = (np.sqrt(Gx**2 + Gy**2))
for i in range(1,rows - 2):
    for j in range(1, cols - 2):
        if (    ( (theta[i,j] >= -22.5) and (theta[i,j]< 22.5) ) or
                ( (theta[i,j] <= -157.5) and (theta[i,j] >= -180) ) or
                ( (theta[i,j] >= 157.5) and (theta[i,j] < 180) ) ):
            theta[i,j] = 0.0
        elif (    ( (theta[i,j] >= 22.5) and (theta[i,j]< 67.5) ) or
                ( (theta[i,j] <= -112.5) and (theta[i,j] >= -157.5) ) ):
            theta[i,j] = 45.0
        elif (    ( (theta[i,j] >= 67.5) and (theta[i,j]< 112.5) ) or
                ( (theta[i,j] <= -67.5) and (theta[i,j] >= -112.5) ) ):
            theta[i,j] = 90.0
        elif (    ( (theta[i,j] >= 112.5) and (theta[i,j]< 157.5) ) or
                ( (theta[i,j] <= -22.5) and (theta[i,j] >= -67.5) ) ):
            theta[i,j] = -45.0

# 第三步:进行 非极大值抑制计算
img2 = np.zeros(img1.shape) # 非极大值抑制图像矩阵

for i in range(1,img2.shape[0]-1):
    for j in range(1,img2.shape[1]-1):
        if (theta[i,j] == 0.0) and (img1[i,j] == np.max([img1[i,j],img1[i+1,j],img1[i-1,j]]) ):
                img2[i,j] = img1[i,j]

        if (theta[i,j] == -45.0) and img1[i,j] == np.max([img1[i,j],img1[i-1,j-1],img1[i+1,j+1]]):
                img2[i,j] = img1[i,j]

        if (theta[i,j] == 90.0) and  img1[i,j] == np.max([img1[i,j],img1[i,j+1],img1[i,j-1]]):
                img2[i,j] = img1[i,j]

        if (theta[i,j] == 45.0) and img1[i,j] == np.max([img1[i,j],img1[i-1,j+1],img1[i+1,j-1]]):
                img2[i,j] = img1[i,j]

# 第四步:双阈值检测和边缘连接
img3 = np.zeros(img2.shape) #定义双阈值图像
# TL = 0.4*np.max(img2)
# TH = 0.5*np.max(img2)
TL = 50
TH = 100
#关键在这两个阈值的选择
for i in range(1,img3.shape[0]-1): 
    for j in range(1,img3.shape[1]-1):
        if img2[i,j] < TL:
            img3[i,j] = 0
        elif img2[i,j] > TH:
            img3[i,j] = 255
        elif (( img2[i+1,j] < TH) or (img2[i-1,j] < TH )or( img2[i,j+1] < TH )or
                (img2[i,j-1] < TH) or (img2[i-1, j-1] < TH )or ( img2[i-1, j+1] < TH) or
                   ( img2[i+1, j+1] < TH ) or ( img2[i+1, j-1] < TH) ):
            img3[i,j] = 255


cv2.imshow("1",img)  		  # 原始图像
cv2.imshow("2",img1)       # 梯度幅值图
cv2.imshow("3",img2)       #非极大值抑制灰度图
cv2.imshow("4",img3)       # 最终效果图
cv2.imshow("theta",theta) #角度值灰度图
cv2.waitKey(0)

- 实验效果图
rice.jpg 效果图Canny边缘检测算法原理及实现(Python + OpenCV)_第2张图片
lena.tiff 效果图
Canny边缘检测算法原理及实现(Python + OpenCV)_第3张图片
参考博文:http://blog.sciencenet.cn/blog-425437-776470.html

你可能感兴趣的:(随笔)