使用 Python 实现图像边缘检测:Canny 算法详解与实践

07-02 11阅读

图像处理是计算机视觉中的基础任务之一,广泛应用于自动驾驶、医学影像分析、安防监控等多个领域。在众多图像处理技术中,边缘检测(Edge Detection) 是提取图像特征的重要手段。其中,Canny 边缘检测算法 以其良好的性能和准确性成为最常用的边缘检测方法之一。

本文将详细介绍 Canny 边缘检测的原理,并通过 Python 和 OpenCV 实现一个完整的边缘检测程序,帮助读者掌握其具体应用方式。


Canny 边缘检测简介

Canny 边缘检测是一种多阶段的边缘检测算法,由 John F. Canny 在 1986 年提出。该算法的目标是在噪声抑制和边缘定位之间取得良好平衡。其主要步骤包括:

高斯滤波去噪计算图像梯度(Sobel 算子)非极大值抑制(Non-Maximum Suppression)双阈值检测(Double Thresholding)边缘连接(Edge Tracking by Hysteresis)

实现环境准备

为了实现 Canny 边缘检测,我们需要以下工具:

Python 3.xOpenCV 库NumPy 库

你可以使用 pip 安装所需的库:

pip install opencv-python numpy

Python 实现 Canny 边缘检测

我们将使用 OpenCV 提供的 cv2.Canny() 函数来实现边缘检测。此外,我们也会展示如何手动实现部分步骤以加深理解。

3.1 使用 OpenCV 快速实现 Canny 检测

import cv2import numpy as npfrom matplotlib import pyplot as plt# 读取图像并转换为灰度图image = cv2.imread('test.jpg', cv2.IMREAD_COLOR)gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)# 高斯模糊降噪blurred = cv2.GaussianBlur(gray, (5, 5), 0)# Canny 边缘检测edges = cv2.Canny(blurred, threshold1=50, threshold2=150)# 显示结果plt.figure(figsize=(10, 5))plt.subplot(1, 2, 1)plt.title("Original Image")plt.imshow(cv2.cvtColor(image, cv2.COLOR_BGR2RGB))plt.axis('off')plt.subplot(1, 2, 2)plt.title("Canny Edges")plt.imshow(edges, cmap='gray')plt.axis('off')plt.show()

注意:请将 'test.jpg' 替换为你本地的一张测试图片路径。

这段代码展示了从图像读取到边缘检测的完整流程。我们可以看到 OpenCV 的封装非常方便,但为了深入理解,下面我们将逐步实现 Canny 的核心步骤。


手动实现 Canny 边缘检测的关键步骤

虽然 OpenCV 提供了高效的封装函数,但我们也可以尝试手动实现其中的部分过程。

4.1 高斯滤波去噪

图像在采集过程中可能会受到噪声干扰,因此需要先进行平滑处理。这里我们使用 5x5 的高斯核对图像进行卷积操作。

def gaussian_kernel(size, sigma=1):    size = int(size) // 2    x, y = np.mgrid[-size:size+1, -size:size+1]    kernel = np.exp(-(x**2 + y**2) / (2 * sigma**2))    return kernel / kernel.sum()# 示例:生成一个 5x5 的高斯核g_kernel = gaussian_kernel(5, sigma=1.4)print(g_kernel)

4.2 Sobel 算子计算图像梯度

Sobel 算子用于检测图像在 x 和 y 方向上的梯度,从而得到边缘强度和方向。

def sobel_filters(img):    Kx = np.array([[-1, 0, 1], [-2, 0, 2], [-1, 0, 1]], np.float32)    Ky = np.array([[1, 2, 1], [0, 0, 0], [-1, -2, -1]], np.float32)    Ix = cv2.filter2D(img, -1, Kx)    Iy = cv2.filter2D(img, -1, Ky)    G = np.hypot(Ix, Iy)    G = G / G.max() * 255    theta = np.arctan2(Iy, Ix)    return G, theta

4.3 非极大值抑制(NMS)

非极大值抑制的目的是保留局部最大值的边缘点,去除其他非边缘点。

def non_max_suppression(img, D):    M, N = img.shape    Z = np.zeros((M, N), dtype=np.int32)    angle = D * 180. / np.pi    angle[angle < 0] += 180    for i in range(1, M-1):        for j in range(1, N-1):            try:                q = 255                r = 255                # angle 0                if (0 <= angle[i,j] < 22.5) or (157.5 <= angle[i,j] <= 180):                    q = img[i, j+1]                    r = img[i, j-1]                # angle 45                elif 22.5 <= angle[i,j] < 67.5:                    q = img[i+1, j-1]                    r = img[i-1, j+1]                # angle 90                elif 67.5 <= angle[i,j] < 112.5:                    q = img[i+1, j]                    r = img[i-1, j]                # angle 135                elif 112.5 <= angle[i,j] < 157.5:                    q = img[i-1, j-1]                    r = img[i+1, j+1]                if (img[i,j] >= q) and (img[i,j] >= r):                    Z[i,j] = img[i,j]                else:                    Z[i,j] = 0            except IndexError:                pass    return Z

4.4 双阈值检测与边缘连接

最后一步是设定两个阈值,分别用于判断强边缘点和弱边缘点,并通过连接弱边缘点形成连续的边缘。

def threshold(img, low_threshold_ratio=0.05, high_threshold_ratio=0.09):    high_threshold = img.max() * high_threshold_ratio    low_threshold = high_threshold * low_threshold_ratio    M, N = img.shape    res = np.zeros((M, N), dtype=np.int32)    weak = np.int32(25)    strong = np.int32(255)    strong_i, strong_j = np.where(img >= high_threshold)    zeros_i, zeros_j = np.where(img < low_threshold)    res[strong_i, strong_j] = strong    res[zeros_i, zeros_j] = 0    weak_i, weak_j = np.where((img <= high_threshold) & (img >= low_threshold))    res[weak_i, weak_j] = weak    return res, weak, strongdef hysteresis(img, weak, strong=255):    M, N = img.shape    for i in range(1, M-1):        for j in range(1, N-1):            if img[i, j] == weak:                try:                    if ((img[i+1, j-1] == strong) or (img[i+1, j] == strong) or (img[i+1, j+1] == strong)                        or (img[i, j-1] == strong) or (img[i, j+1] == strong)                        or (img[i-1, j-1] == strong) or (img[i-1, j] == strong) or (img[i-1, j+1] == strong)):                        img[i, j] = strong                    else:                        img[i, j] = 0                except IndexError:                    pass    return img

整合所有步骤并运行

现在我们将上述步骤整合成一个完整的边缘检测流程:

def canny_edge_detector(image_path):    # 读取图像    image = cv2.imread(image_path, cv2.IMREAD_GRAYSCALE)    blurred = cv2.GaussianBlur(image, (5, 5), 1.4)    # 计算梯度    gradient, direction = sobel_filters(blurred)    # 非极大值抑制    nms = non_max_suppression(gradient, direction)    # 双阈值检测    thresholded, weak, strong = threshold(nms)    # 边缘连接    final_image = hysteresis(thresholded, weak, strong)    return final_image# 运行自定义 Canny 边缘检测result = canny_edge_detector('test.jpg')# 显示结果plt.imshow(result, cmap='gray')plt.title("Custom Canny Edge Detection")plt.axis('off')plt.show()

总结

本文详细介绍了 Canny 边缘检测的原理及其五个关键步骤,并通过 Python 和 OpenCV 实现了一个完整的边缘检测系统。我们不仅使用了 OpenCV 的高效接口,还手动实现了 Canny 的各个核心模块,以便更深入地理解其工作原理。

通过本教程,你应该能够:

掌握图像处理的基本流程;理解 Canny 边缘检测的数学原理;能够用 Python 编写图像边缘检测程序;对比 OpenCV 内置函数与手动实现的差异。

如果你希望进一步拓展这个项目,可以尝试加入更多图像预处理(如直方图均衡化)、后处理(如霍夫变换检测直线)等内容。


参考文献:

Canny, J. (1986). A Computational Approach to Edge Detection. IEEE Transactions on Pattern Analysis and Machine Intelligence.OpenCV Documentation: https://docs.opencv.org/Gonzalez, R. C., & Woods, R. E. (2002). Digital Image Processing.

如果你喜欢这类技术文章,欢迎继续关注后续内容,我们将会带来更多图像处理、深度学习等领域的实战教程。

免责声明:本文来自网站作者,不代表CIUIC的观点和立场,本站所发布的一切资源仅限用于学习和研究目的;不得将上述内容用于商业或者非法用途,否则,一切后果请用户自负。本站信息来自网络,版权争议与本站无关。您必须在下载后的24个小时之内,从您的电脑中彻底删除上述内容。如果您喜欢该程序,请支持正版软件,购买注册,得到更好的正版服务。客服邮箱:ciuic@ciuic.com

目录[+]

您是本站第1357名访客 今日有14篇新文章

微信号复制成功

打开微信,点击右上角"+"号,添加朋友,粘贴微信号,搜索即可!