前言
Canny 边缘检测是数字图像处理里最经典的边缘提取算法,广泛用于视觉检测、目标轮廓识别。Python/Matlab 写 Canny 算法很简单,一次性读取整张图片做运算;但是FPGA 做实时 Canny 完全是另一套思路:FPGA 是流式逐像素处理,不能缓存完整一帧图像,要靠流水线、行缓存、定点运算实现硬件加速。
本文分为两部分:先用大白话讲懂 Canny 算法 4 个步骤,再从 FPGA 硬件实现角度做专业拆解,适合图像项目开发、面试复习。
博客标签:#FPGA #Canny 边缘检测 #Verilog #图像算法 #硬件加速
一、Canny 算法原理(大白话版)
Canny 的目标:找到物体轮廓,输出单像素细边缘,尽量过滤噪声伪边缘。一共 4 个固定步骤。
步骤 1:高斯滤波(降噪磨皮)
大白话:图像传感器拍出来的图片自带噪点,一个个随机亮点暗点。这些噪点亮度突变,会被误当成边缘。高斯滤波就是给图片磨皮,把突兀噪点抹平。 拿一个 3×3/5×5 的小窗口,在整张图滑动。窗口内像素做加权平均,离窗口中心越近的像素,权重越高。把突变的杂点平滑掉。
注意:模糊力度不能太大,不然物体真实边缘也会被磨没。
专业描述:采用二维高斯核做图像卷积,抑制高斯白噪声。高斯核满足正态分布,中心像素权重最高,向四周衰减。
\(G(x,y)=\frac{1}{2\pi\sigma^2}\exp\left(-\frac{x^2+y^2}{2\sigma^2}\right)\) σ 为高斯核标准差,σ 越大,模糊效果越强。工程上常用 3×3 高斯核。
步骤 2:Sobel 梯度计算(求边缘强度 + 边缘方向)
大白话 :边缘是什么?就是相邻像素亮度突然变化的位置。比如左边亮、右边突然变暗,这个分界线就是边缘。 我们用两个小算子,分别算图片水平方向(左右)亮度变化 Gx ,竖直方向(上下)亮度变化 Gy。
- 梯度幅值:代表亮度变化有多剧烈,数值越大越像边缘
- 梯度角度:代表这个边缘倾斜的方向(水平、垂直、45°、135°)
专业描述:使用 Sobel 算子对图像做卷积,分别得到 X、Y 方向梯度分量\(G_x、G_y\)。 梯度幅值:
\(G_{mag}=\sqrt{G_x^2+G_y^2}\) 梯度方向:
\(\theta=\arctan\left(\frac{G_y}{G_x}\right)\) FPGA 中为了避免消耗大量 DSP 做开方、反正切运算,一般用近似公式估算幅值,角度量化为 4 个离散方向(0°、45°、90°、135°),降低硬件开销。
步骤 3:非极大值抑制(NMS,把粗边缘削成 1 像素细线)
大白话 :第二步算出来的边缘是粗线条,有好几个像素宽度。NMS 就是 "瘦身"。 沿着当前像素的边缘方向,看它前后相邻两个像素。只有当前像素的边缘强度,大于同方向前后两个邻居,才保留这个点;否则直接置 0,删掉。 举例子:边缘是竖直走向,就对比当前像素上下两个点;只有中间这个点亮度变化最强,才保留,旁边的点全部剔除。经过这一步,粗边缘就变成单像素宽度的细线。
专业描述:沿梯度方向对梯度幅值做局部最大值筛选。仅保留局部梯度最大值点,其余置零,将边缘压缩为单像素宽度,消除边缘宽度带来的检测误差。
步骤 4:双阈值检测 + 边缘连通性筛选
大白话:瘦身完成后,图里还有一些噪声产生的假边缘。设置两个阈值:高阈值、低阈值。
- 像素梯度 > 高阈值:强边缘,确定是真实物体边缘,直接保留
- 像素梯度 < 低阈值:直接丢弃,判定为噪声
- 梯度介于高低阈值之间:弱边缘。不直接丢弃,检查这个弱边缘像素有没有和强边缘相连。和强边缘连通就保留;孤立无连接,判定噪声删掉。
核心逻辑:真实物体的轮廓是连续线条;噪声产生的弱边缘大多是孤立点。
专业描述:双阈值分割把梯度图像分为强边缘、弱边缘、非边缘三类。基于 8 邻域连通性判断弱边缘是否属于目标轮廓:弱边缘像素只要 8 邻域内存在强边缘,则保留;否则剔除。解决单阈值难以兼顾漏检与误检的问题。
二、FPGA 硬件实现专业解析
软件(Python/OpenCV)实现思路:读取整帧图像,矩阵运算,循环遍历像素。 FPGA 是流式流水线架构,像素是一个接一个串行输入,不能等整帧缓存。Canny 的 4 个步骤天然适合流水线,每一级流水线并行处理一组像素。
1. 核心难点:Line Buffer 行缓存
做 3×3 卷积窗口(高斯、Sobel 都需要 3×3 邻域像素)。当像素逐行逐个输入时,FPGA 必须缓存前面 2 行图像数据,加上当前行,一共 3 行,才能拼凑出 3×3 窗口。
软件:直接索引图像二维数组;FPGA:用 BRAM / 移位寄存器搭建 Line Buffer 行缓存,滑动窗口实时输出邻域像素。图像分辨率越高,需要的存储资源越大。
2. 定点量化处理
计算机软件用浮点数计算高斯系数、梯度幅值。FPGA 硬件没有浮点单元,全部转为定点整数运算:
- 高斯核小数系数放大为整数,卷积之后再做右移缩放;
- 梯度幅值避开平方根运算,用 \(|G_x|+|G_y|\) 近似替代,节省大量 DSP 乘法器;
- 梯度角度量化为 4 个方向,不用复杂反正切函数。
代价:引入微小精度损失,但是大幅降低 DSP、LUT 资源消耗,是图像 FPGA 工程标准做法。
3. 流水线分段设计
把 Canny4 个算法步骤拆成 4 级流水线: 输入图像 → 高斯滤波流水线 → Sobel梯度流水线 → NMS非极大抑制流水线 → 双阈值+边缘连通流水线 → 输出边缘图 每一拍时钟,每一级同时处理一个像素,吞吐率:1 时钟周期输出 1 像素,实现视频流实时处理。
4. 双阈值连通检测的硬件难点
弱边缘连通判断,需要检测当前像素 8 邻域有没有强边缘标记。 软件很容易遍历邻域;FPGA 流式处理,只能拿到当前窗口的像素,需要额外寄存器缓存邻域标记信息,实现连通判断。
三、FPGA 版 Canny 优缺点
✅优点
- 硬件流水线并行,延迟极低,支持高清视频实时边缘检测;
- 纯硬件逻辑,不需要 CPU 参与,适合嵌入式视觉、高速工业检测场景。
❌缺点
- 消耗 BRAM、DSP 资源,高分辨率图像会占用大量片上存储;
- 为了节省资源做定点近似,相比 OpenCV 浮点 Canny,精度有少量损失;
- 调试难度高,需要逐像素抓取波形查看中间图像数据。
四、面试高频问答
Q:Canny 相比单独 Sobel 边缘检测,优势在哪? A:Sobel 只计算梯度,输出边缘粗,噪声干扰大。Canny 增加高斯降噪、非极大值抑制、双阈值连通筛选,输出单像素细边缘,抗噪声能力更强。
Q:FPGA 做 Canny 为什么要用行缓存 Line Buffer? A:算法需要 3×3 邻域像素做卷积运算,像素串行流入 FPGA,无法一次性读取整帧图像。Line Buffer 缓存前几行像素,动态滑动生成 3×3 窗口。
Q:双阈值为什么不用单阈值? A:单阈值两难:阈值设高会丢失真实弱边缘;阈值设低会保留大量噪声伪边缘。双阈值借助连通性,可以把和强边缘相连的真实弱边缘保留,过滤孤立噪声。
五、总结
Canny 算法分为:高斯降噪、Sobel 梯度求解、非极大值抑制、双阈值连通筛选四步。 软件实现是基于整张图像矩阵遍历;FPGA 的核心思想是流水线 + 行缓存 + 定点量化,把算法改造为逐像素流式硬件逻辑,换取实时高速图像边缘提取能力。
