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()

运行输出

相关推荐
nLsUCWFJR13 分钟前
(Matlab)基于贝叶斯优化卷积双向长短期记忆网络(CNN-BiLSTM)回归预测
opencv
Σίσυφος190014 分钟前
PCL法向量估计 之 RANSAC 平面估计法向量
算法·机器学习·平面
xhbaitxl20 分钟前
算法学习day39-动态规划
学习·算法·动态规划
I_LPL21 分钟前
day23 代码随想录算法训练营 回溯专题2
算法·hot100·回溯算法·求职面试
智者知已应修善业22 分钟前
【洛谷P9975奶牛被病毒传染最少数量推导,导出多样例】2025-2-26
c语言·c++·经验分享·笔记·算法·推荐算法
玩大数据的龙威26 分钟前
农经权二轮延包—各种地块示意图
python·arcgis
ZH154558913128 分钟前
Flutter for OpenHarmony Python学习助手实战:数据库操作与管理的实现
python·学习·flutter
belldeep37 分钟前
python:用 Flask 3 , mistune 2 和 mermaid.min.js 10.9 来实现 Markdown 中 mermaid 图表的渲染
javascript·python·flask
喵手38 分钟前
Python爬虫实战:电商价格监控系统 - 从定时任务到历史趋势分析的完整实战(附CSV导出 + SQLite持久化存储)!
爬虫·python·爬虫实战·零基础python爬虫教学·电商价格监控系统·从定时任务到历史趋势分析·采集结果sqlite存储
m0_7369191039 分钟前
C++中的委托构造函数
开发语言·c++·算法