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

07-02 11阅读

在计算机视觉和图像处理领域,边缘检测(Edge Detection) 是一项基础且关键的技术。它可以帮助我们从图像中提取出物体的轮廓信息,为后续的目标识别、图像分割等任务提供重要依据。

本文将详细介绍如何使用 Python 和 OpenCV 库 来实现经典的 Canny 边缘检测算法(Canny Edge Detection),并展示完整的代码示例。文章内容包括:

Canny 边缘检测原理概述 环境准备与依赖安装 图像读取与灰度化 高斯滤波降噪 计算梯度幅值与方向 非极大值抑制(Non-Maximum Suppression) 双阈值检测与边缘连接 完整代码示例与结果分析

Canny 边缘检测原理概述

Canny 边缘检测是一种多阶段的边缘检测算法,由 John F. Canny 在 1986 年提出。其主要步骤如下:

高斯滤波器去噪:平滑图像以减少噪声干扰。计算图像梯度:使用 Sobel 算子计算每个像素点的梯度大小和方向。非极大值抑制(NMS):保留局部最大值,抑制非边缘点。双阈值检测与边缘连接:设定高低两个阈值,判断哪些是强边缘点、弱边缘点,并通过连接保留真正有意义的边缘。

这些步骤确保了 Canny 边缘检测具有良好的检测性能和定位精度。


环境准备与依赖安装

要运行本文中的代码,需要安装以下库:

opencv-pythonnumpy

可以通过以下命令安装:

pip install opencv-python numpy

图像读取与灰度化

首先,我们需要读取一张图像,并将其转换为灰度图。因为 Canny 检测通常作用于单通道图像。

import cv2import numpy as np# 读取图像image = cv2.imread('example.jpg')# 转换为灰度图gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)cv2.imshow('Grayscale Image', gray)cv2.waitKey(0)cv2.destroyAllWindows()

高斯滤波降噪

为了减少噪声对边缘检测的影响,先对图像进行高斯模糊处理。

# 高斯模糊blurred = cv2.GaussianBlur(gray, (5, 5), 1.4)cv2.imshow('Blurred Image', blurred)cv2.waitKey(0)cv2.destroyAllWindows()

计算梯度幅值与方向

使用 Sobel 算子分别计算 x 和 y 方向的梯度:

# Sobel 算子sobel_x = cv2.Sobel(blurred, cv2.CV_64F, 1, 0, ksize=3)sobel_y = cv2.Sobel(blurred, cv2.CV_64F, 0, 1, ksize=3)# 计算梯度幅值和方向gradient_magnitude = np.sqrt(sobel_x**2 + sobel_y**2)gradient_direction = np.arctan2(sobel_y, sobel_x)

非极大值抑制(NMS)

这一步是为了细化边缘,只保留局部最大值的梯度点。

def non_max_suppression(img, direction):    M, N = img.shape    result = np.zeros((M, N), dtype=np.int32)    angle = direction * 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                # 根据角度确定邻域像素位置                if (0 <= angle[i, j] < 22.5) or (157.5 <= angle[i, j] <= 180):                    q = img[i, j + 1]                    r = img[i, j - 1]                elif 22.5 <= angle[i, j] < 67.5:                    q = img[i + 1, j - 1]                    r = img[i - 1, j + 1]                elif 67.5 <= angle[i, j] < 112.5:                    q = img[i + 1, j]                    r = img[i - 1, j]                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):                    result[i, j] = img[i, j]                else:                    result[i, j] = 0            except IndexError as e:                pass    return resultnms_image = non_max_suppression(gradient_magnitude, gradient_direction)

双阈值检测与边缘连接

根据设定的高低阈值,筛选出强边缘和弱边缘,并通过连接保留连续的边缘。

def threshold(img, low_threshold_ratio=0.05, high_threshold_ratio=0.15):    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 >= low_threshold) & (img < high_threshold))    res[weak_i, weak_j] = weak    return res, weak, strongthresholded, weak, strong = threshold(nms_image)

完整代码整合与结果展示

下面是将上述所有步骤整合后的完整代码:

import cv2import numpy as npdef canny_edge_detection(image_path):    # 读取图像    image = cv2.imread(image_path)    gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)    # 高斯模糊    blurred = cv2.GaussianBlur(gray, (5, 5), 1.4)    # Sobel 梯度    sobel_x = cv2.Sobel(blurred, cv2.CV_64F, 1, 0, ksize=3)    sobel_y = cv2.Sobel(blurred, cv2.CV_64F, 0, 1, ksize=3)    gradient_magnitude = np.sqrt(sobel_x**2 + sobel_y**2)    gradient_direction = np.arctan2(sobel_y, sobel_x)    # 非极大值抑制    def non_max_suppression(img, direction):        M, N = img.shape        result = np.zeros((M, N), dtype=np.int32)        angle = direction * 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                    if (0 <= angle[i, j] < 22.5) or (157.5 <= angle[i, j] <= 180):                        q = img[i, j + 1]                        r = img[i, j - 1]                    elif 22.5 <= angle[i, j] < 67.5:                        q = img[i + 1, j - 1]                        r = img[i - 1, j + 1]                    elif 67.5 <= angle[i, j] < 112.5:                        q = img[i + 1, j]                        r = img[i - 1, j]                    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):                        result[i, j] = img[i, j]                    else:                        result[i, j] = 0                except IndexError as e:                    pass        return result    nms_image = non_max_suppression(gradient_magnitude, gradient_direction)    # 双阈值检测    def threshold(img, low_threshold_ratio=0.05, high_threshold_ratio=0.15):        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 >= low_threshold) & (img < high_threshold))        res[weak_i, weak_j] = weak        return res, weak, strong    thresholded, weak, strong = threshold(nms_image)    # 边缘连接(可选)    def 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 as e:                        pass        return img    final_image = hysteresis(thresholded.copy(), weak, strong)    # 显示结果    cv2.imshow('Original Image', image)    cv2.imshow('Canny Edge Detection', final_image)    cv2.waitKey(0)    cv2.destroyAllWindows()# 执行函数canny_edge_detection('example.jpg')

总结

本文详细介绍了 Canny 边缘检测的实现原理,并通过 Python 和 OpenCV 实现了一个完整的 Canny 边缘检测程序。整个流程包括图像预处理、梯度计算、非极大值抑制、双阈值检测与边缘连接等步骤。读者可以根据自己的需求修改参数或优化算法逻辑。

如果你希望进一步扩展该系统,可以尝试:

将边缘检测与 Hough 变换结合用于直线检测;使用 GPU 加速提升处理速度;对视频流实时进行边缘检测。

如需获取文中使用的测试图片 example.jpg 或有任何技术问题,欢迎留言交流!

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

目录[+]

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

微信号复制成功

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