使用 Python 实现图像边缘检测算法:Canny 边缘检测详解
在计算机视觉和图像处理领域,边缘检测(Edge Detection) 是一项基础且关键的技术。它可以帮助我们从图像中提取出物体的轮廓信息,为后续的目标识别、图像分割等任务提供重要依据。
本文将详细介绍如何使用 Python 和 OpenCV 库 来实现经典的 Canny 边缘检测算法(Canny Edge Detection),并展示完整的代码示例。文章内容包括:
Canny 边缘检测原理概述 环境准备与依赖安装 图像读取与灰度化 高斯滤波降噪 计算梯度幅值与方向 非极大值抑制(Non-Maximum Suppression) 双阈值检测与边缘连接 完整代码示例与结果分析Canny 边缘检测原理概述
Canny 边缘检测是一种多阶段的边缘检测算法,由 John F. Canny 在 1986 年提出。其主要步骤如下:
高斯滤波器去噪:平滑图像以减少噪声干扰。计算图像梯度:使用 Sobel 算子计算每个像素点的梯度大小和方向。非极大值抑制(NMS):保留局部最大值,抑制非边缘点。双阈值检测与边缘连接:设定高低两个阈值,判断哪些是强边缘点、弱边缘点,并通过连接保留真正有意义的边缘。这些步骤确保了 Canny 边缘检测具有良好的检测性能和定位精度。
环境准备与依赖安装
要运行本文中的代码,需要安装以下库:
opencv-python
numpy
可以通过以下命令安装:
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
或有任何技术问题,欢迎留言交流!