Canny边缘检测算法原理与实现

目录

Canny边缘检测算法原理与实现

算法原理

Python实现代码

关键步骤详解

算法特点

实现代码

运行输出


Canny边缘检测算法原理与实现

Canny边缘检测是计算机视觉中最经典的边缘检测算法之一,由John F. Canny在1986年提出。下面我将详细介绍其原理,并提供Python实现代码。

算法原理

Canny边缘检测包含以下5个步骤:

  1. 高斯滤波:使用高斯滤波器平滑图像,减少噪声影响

  2. 计算梯度:使用Sobel算子计算图像的梯度幅值和方向

  3. 非极大值抑制:保留梯度幅值局部最大值,细化边缘

  4. 双阈值检测:使用高低阈值确定强边缘和弱边缘

  5. 边缘连接:通过滞后阈值处理连接边缘

Python实现代码

关键步骤详解

  1. 高斯滤波

    • 使用高斯核平滑图像,减少噪声影响

    • 高斯核大小影响边缘检测的敏感度

  2. 梯度计算

    • 使用Sobel算子计算x和y方向的梯度

    • 梯度幅值:G = sqrt(Gx² + Gy²)

    • 梯度方向:θ = arctan(Gy/Gx)

  3. 非极大值抑制

    • 检查每个像素在梯度方向上的邻域

    • 只保留梯度幅值最大的像素,细化边缘

  4. 双阈值检测

    • 高阈值:确定强边缘

    • 低阈值:确定弱边缘

    • 典型高低阈值比为1:2或1:3

  5. 边缘连接

    • 弱边缘只有在连接到强边缘时才保留

    • 确保边缘的连续性

算法特点

  1. 优点

    • 低错误率:尽可能少地检测非边缘点

    • 高定位性:检测的边缘点尽可能接近真实边缘

    • 最小响应:边缘点尽可能少,每个边缘只检测一次

  2. 参数选择

    • 高斯核大小:影响平滑程度,通常5x5

    • 高低阈值:需要根据图像内容调整

    • Sobel核大小:通常3x3

这个实现展示了Canny边缘检测的核心原理,并与OpenCV的实现进行了比较。实际应用中,通常直接使用OpenCV的cv2.Canny()函数,因为它经过了高度优化。

实现代码

python 复制代码
import numpy as np
import cv2
import matplotlib.pyplot as plt

def canny_edge_detector(image, low_threshold=50, high_threshold=150, kernel_size=5):
    """
    手动实现Canny边缘检测算法
    
    参数:
        image: 输入灰度图像
        low_threshold: 低阈值
        high_threshold: 高阈值
        kernel_size: 高斯核大小
        
    返回:
        edges: 边缘检测结果
    """
    # 步骤1: 高斯滤波
    blurred = cv2.GaussianBlur(image, (kernel_size, kernel_size), 0)
    
    # 步骤2: 使用Sobel算子计算梯度
    grad_x = cv2.Sobel(blurred, cv2.CV_64F, 1, 0, ksize=3)
    grad_y = cv2.Sobel(blurred, cv2.CV_64F, 0, 1, ksize=3)
    
    # 计算梯度幅值和方向
    grad_magnitude = np.sqrt(grad_x**2 + grad_y**2)
    grad_direction = np.arctan2(grad_y, grad_x) * 180 / np.pi
    
    # 将角度规范化到0-180度
    grad_direction = np.mod(grad_direction, 180)
    
    # 步骤3: 非极大值抑制
    suppressed = non_maximum_suppression(grad_magnitude, grad_direction)
    
    # 步骤4和5: 双阈值检测和边缘连接
    edges = hysteresis_thresholding(suppressed, low_threshold, high_threshold)
    
    return edges

def non_maximum_suppression(grad_mag, grad_dir):
    """
    非极大值抑制
    """
    height, width = grad_mag.shape
    suppressed = np.zeros_like(grad_mag)
    
    for i in range(1, height-1):
        for j in range(1, width-1):
            angle = grad_dir[i, j]
            mag = grad_mag[i, j]
            
            # 将角度分类到最近的45度倍数
            if (0 <= angle < 22.5) or (157.5 <= angle <= 180):
                neighbor1 = grad_mag[i, j+1]
                neighbor2 = grad_mag[i, j-1]
            elif 22.5 <= angle < 67.5:
                neighbor1 = grad_mag[i+1, j-1]
                neighbor2 = grad_mag[i-1, j+1]
            elif 67.5 <= angle < 112.5:
                neighbor1 = grad_mag[i+1, j]
                neighbor2 = grad_mag[i-1, j]
            else:  # 112.5 <= angle < 157.5
                neighbor1 = grad_mag[i-1, j-1]
                neighbor2 = grad_mag[i+1, j+1]
            
            # 如果当前点是局部最大值,则保留
            if mag >= neighbor1 and mag >= neighbor2:
                suppressed[i, j] = mag
    
    return suppressed

def hysteresis_thresholding(image, low_threshold, high_threshold):
    """
    双阈值检测和边缘连接
    """
    height, width = image.shape
    result = np.zeros_like(image, dtype=np.uint8)
    
    # 标记强边缘(>=高阈值)和弱边缘(>=低阈值且<高阈值)
    strong_edges = (image >= high_threshold)
    weak_edges = (image >= low_threshold) & (image < high_threshold)
    
    # 强边缘直接保留
    result[strong_edges] = 255
    
    # 对于弱边缘,如果连接到强边缘则保留
    for i in range(1, height-1):
        for j in range(1, width-1):
            if weak_edges[i, j]:
                # 检查8邻域是否有强边缘
                if np.any(strong_edges[i-1:i+2, j-1:j+2]):
                    result[i, j] = 255
    
    return result

def compare_with_opencv(image_path='test_image.png'):
    """
    比较手动实现和OpenCV实现的Canny边缘检测
    """
    # 读取图像
    if image_path == 'test_image.png':
        # 如果没有图片,创建一个简单的测试图像
        img = np.zeros((300, 400), dtype=np.uint8)
        cv2.line(img, (50, 50), (350, 50), 255, 2)  # 水平线
        cv2.line(img, (50, 100), (350, 200), 255, 2)  # 斜线
        cv2.line(img, (200, 50), (200, 250), 255, 2)  # 垂直线
        cv2.line(img, (100, 200), (300, 200), 255, 2)  # 另一条水平线
    else:
        img = cv2.imread(image_path, cv2.IMREAD_GRAYSCALE)
    
    # 手动实现
    manual_edges = canny_edge_detector(img)
    
    # OpenCV实现
    opencv_edges = cv2.Canny(img, 50, 150)
    
    # 可视化比较
    plt.figure(figsize=(15, 5))
    
    plt.subplot(1, 3, 1)
    plt.imshow(img, cmap='gray')
    plt.title('原始图像')
    plt.axis('off')
    
    plt.subplot(1, 3, 2)
    plt.imshow(manual_edges, cmap='gray')
    plt.title('手动实现Canny')
    plt.axis('off')
    
    plt.subplot(1, 3, 3)
    plt.imshow(opencv_edges, cmap='gray')
    plt.title('OpenCV Canny')
    plt.axis('off')
    
    plt.tight_layout()
    plt.show()

# 测试代码
if __name__ == "__main__":
    compare_with_opencv()

运行输出

相关推荐
梨落秋霜2 小时前
Python入门篇【元组】
android·数据库·python
i小杨2 小时前
python 项目相关
开发语言·python
CoderCodingNo2 小时前
【GESP】C++五级真题(贪心思想考点) luogu-P11960 [GESP202503 五级] 平均分配
开发语言·c++·算法
weixin_462446232 小时前
使用 Tornado + systemd 搭建图片静态服务(imgserver)
开发语言·python·tornado
别多香了2 小时前
python基础之面向对象&异常捕获
开发语言·python
POLITE32 小时前
Leetcode 76.最小覆盖子串 JavaScript (Day 6)
javascript·算法·leetcode
Silence_Jy2 小时前
Kimi K2技术报告
人工智能·python·深度学习·transformer
AI Echoes2 小时前
自定义 LangChain 文档加载器使用技巧
数据库·人工智能·python·langchain·prompt·agent
未来之窗软件服务3 小时前
幽冥大陆(八十五)Python 水果识别ONNX转手机mobile —东方仙盟练气期
开发语言·python·模型训练·仙盟创梦ide·东方仙盟