fast-canny
工业级零分配 SIMD Canny 边缘检测器
基于 Rust 实现的高性能 Canny 边缘检测库,专为实时图像处理场景设计。 通过 AVX2+FMA / NEON 硬件加速、零堆分配 BFS 和 L1 Cache 友好的 Tiling 流水线,在保持算法精度的同时实现极致吞吐量。
目录
项目介绍
Canny 边缘检测是计算机视觉领域最经典的算法之一,广泛应用于:
- 工业质检(缺陷轮廓提取)
- 自动驾驶(车道线、障碍物检测)
- 医学影像(病灶边界分割)
- 实时视频流处理
fast-canny 针对以下痛点进行了系统性优化:
| 痛点 | 解决方案 |
|---|---|
| 每帧大量堆分配 | Bump Arena + Zero-Copy Workspace |
| 单线程瓶颈 | Rayon 多核 Tiling 流水线 |
| 标量 Sobel 计算慢 | AVX2+FMA / NEON SIMD 内核 |
| 硬件耦合难移植 | SobelKernel trait 运行时分发 |
功能特性
算法流水线(五阶段)
输入 f32 灰度图
│
▼
① 高斯平滑 — 基于 libblur,可配置 sigma,sigma≤0 时跳过
│
▼
② Sobel 梯度 — 融合幅值+方向,AVX2 一次处理 8 像素
│
▼
③ NMS — 非极大值抑制,细化为单像素宽边缘
│
▼
④ 双阈值化 — 强边缘(255) / 弱边缘(127) / 非边缘(0)
│
▼
⑤ Hysteresis — 零分配 BFS,弱边缘连通强边缘则提拔,否则丢弃
│
▼
输出 u8 二值边缘图(仅含 0 / 255)
核心特性
- 零帧间分配:
CannyWorkspace预分配所有缓冲区,帧间仅执行O(W×H)清零 - SIMD 加速:x86_64 优先启用 AVX2+FMA,aarch64 使用 NEON,自动回退标量
- 多核 Tiling:
TILE_SIZE=64的 L1 Cache 友好分块,Rayon 并行处理行 Tile - 独立方向缓冲:
dir_map与edge_map分离,消除原地覆盖 UB - 边界安全内聚:
track_edges入口主动清零四周边界,不依赖调用方约定 - C-FFI 支持:提供
canny_workspace_new/canny_process_ex等 C 接口
技术架构
模块结构
src/
├── lib.rs # 对外 API:canny()、CannyConfig、CannyError、C-FFI
├── workspace.rs # CannyWorkspace:预分配缓冲区 + Bump Arena
├── gaussian.rs # 高斯平滑(委托 libblur)
├── kernel/
│ ├── mod.rs # SobelKernel trait + 运行时分发 detect()
│ ├── avx2.rs # AVX2+FMA Sobel 融合算子
│ └── aarch64.rs # NEON Sobel 融合算子(aarch64)
├── pipeline.rs # Tiling 多核流水线 execute_tiled_pipeline()
├── nms.rs # NMS + 双阈值化融合算子
├── hysteresis.rs # 零分配 BFS Hysteresis
└── pipeline_ptr.rs # SendPtr / SendConstPtr 跨线程指针包装
内存布局(CannyWorkspace)
buffer_b [f32 × W×H] ← 高斯平滑输出(Sobel 输入)
buffer_a [f32 × W×H] ← 梯度幅值 (mag)
dir_map [u8 × W×H] ← 梯度方向 (0/1/2/3)
edge_map [u8 × W×H] ← NMS 输出 / Hysteresis 最终结果
arena [Bump] ← BFS 栈(帧间 O(1) reset)
kernel [Box<dyn SobelKernel>] ← 运行时选定的硬件实现
依赖说明
| 依赖 | 版本 | 用途 |
|---|---|---|
rayon |
1.10 | 多核并行 Tiling |
bumpalo |
3.16 | 零分配 Bump Arena(需 collections feature) |
bytemuck |
1.15 | 安全类型转换 |
log |
0.4 | 结构化日志(trace/debug/info/warn/error) |
libblur |
0.23.3 | 高性能高斯模糊(支持 f32 单通道) |
开发/测试依赖:
| 依赖 | 用途 |
|---|---|
image |
示例程序图像 I/O |
env_logger |
测试日志输出 |
安装与配置
环境要求
- Rust 2024 Edition(
edition = "2024") - Rust 工具链 ≥ 1.80(稳定版)
- x86_64:支持 AVX2+FMA 的 CPU(Haswell 及以后)
- aarch64:ARMv8-A 及以后(NEON 强制基线)
作为库依赖引入
在你的 Cargo.toml 中添加:
[]
= { = "path/to/fast-canny" }
启用 AVX2 编译优化
在项目根目录创建 .cargo/config.toml:
[]
= ["-C", "target-feature=+avx2,+fma"]
[]
= ["-C", "target-feature=+avx2,+fma"]
注意:不设置此项时,库会在运行时通过
is_x86_feature_detected!自动检测 并回退到标量实现,无需手动配置也能正确运行。
构建
# Debug 构建
# Release 构建(启用 LTO + 全优化)
# 构建动态库(cdylib,供 C/Android 调用)
快速上手
f32 输入(主 API)
use ;
u8 输入(canny_u8 API)
适用于直接来自摄像头或图像解码的 u8 灰度图,内部自动完成 u8→f32 转换并写入预分配缓冲区,无额外堆分配:
use ;
使用 Builder 配置
use CannyConfig;
// 标准配置
let cfg = builder
.sigma
.thresholds
.build?;
// 跳过高斯平滑(适合已预处理的输入)
let cfg_no_blur = builder
.sigma
.thresholds
.build?;
// 使用默认值(sigma=1.0, low=50.0, high=150.0)
let cfg_default = default;
C-FFI 调用示例
void* ;
void ;
int ;
int
API 文档
CannyWorkspace
// 创建(尺寸必须 >= 3x3,否则返回 Err(InvalidDimensions))
字段说明:
| 字段 | 类型 | 说明 |
|---|---|---|
width / height |
usize |
图像尺寸 |
capacity |
usize |
width * height |
buffer_a |
Vec<f32> |
梯度幅值缓冲区 |
buffer_b |
Vec<f32> |
高斯平滑输出缓冲区 |
dir_map |
Vec<u8> |
梯度方向图(值域 0/1/2/3) |
edge_map |
Vec<u8> |
最终边缘图(值域 0/255) |
arena |
Bump |
BFS 零分配 Arena |
CannyConfig / CannyConfigBuilder
builder
.sigma
.thresholds
.build
canny() / canny_u8()
// f32 输入
返回的 &[u8] 生命周期绑定到 ws,下次调用 canny()/canny_u8() 或 ws.reset() 前有效。
CannyError
| 变体 | 触发条件 |
|---|---|
InvalidDimensions { width, height } |
width < 3 或 height < 3 |
InputLengthMismatch { expected, actual } |
src.len() != width * height |
InvalidThresholds { low, high } |
low_thresh > high_thresh |
NullPointer |
C-FFI 传入空指针 |
SobelKernel trait(内核扩展接口)
可通过实现此 trait 接入自定义硬件后端。
gaussian::apply
委托 libblur::gaussian_blur_f32 实现,使用 EdgeMode::Clamp 边界处理。
示例程序
detect_image — 对真实图片执行边缘检测
# 基本用法(输出自动命名为 input_edge.jpg)
# 完整参数
参数说明:
| 参数 | 默认值 | 说明 |
|---|---|---|
<输入路径> |
必填 | 支持 png/jpg/bmp/tiff/webp |
[输出路径] |
自动生成 | 未指定时为 <stem>_edge.<ext> |
[sigma] |
1.0 |
高斯平滑强度,0.0 跳过 |
[low_thresh] |
50.0 |
NMS 低阈值 |
[high_thresh] |
150.0 |
NMS 高阈值 |
输出示例:
=== fast-canny edge detection ===
input: input.jpg
output: input_edge.jpg
sigma: 1
low_thresh: 50
high_thresh: 150
✓ image loaded: 1920x1080 (2073600 pixels)
✓ workspace created (2.3 ms)
✓ canny done (4.71 ms) — edge pixels: 38420 (1.85%)
✓ result saved: input_edge.jpg
total elapsed: 8.12 ms
visual_demo — 多维度视觉验证
生成 10 类合成测试图像并执行边缘检测,同时进行阈值和 sigma 敏感性扫描:
输出目录:target/visual_demo/,每个用例生成三张图:
| 文件后缀 | 内容 |
|---|---|
*_src.png |
原始输入图(归一化灰度) |
*_edge.png |
二值边缘图(0/255) |
*_overlay.png |
红色边缘叠加在原图上 |
验证用例列表:
| 编号 | 场景 | 预期结果 |
|---|---|---|
| 01 | 均匀图像 | 零边缘 |
| 02 | 垂直阶跃 | 单列边缘 |
| 03 | 水平阶跃 | 单行边缘 |
| 04 | 棋盘格(32px) | 网格边缘 |
| 05 | 圆形 | 闭合曲线 |
| 06 | 噪声图(高阈值) | 稀疏边缘 |
| 07 | 噪声图(低阈值) | 密集边缘 |
| 08 | 线性渐变 | 无边缘(低梯度) |
| 09 | 同心矩形框 | 多条平行边缘 |
| 10 | 最小尺寸 3×3 | 不 panic |
额外扫描:
- 阈值敏感性:棋盘格图像,5 档阈值(very_low → very_high),验证边缘数单调递减
- Sigma 敏感性:圆形图像,5 档 sigma(0.0 → 4.0),验证平滑越强边缘越少
- 多帧复用:同一 workspace 交替处理阶跃图和均匀图,验证
reset()正确性
测试与验证
运行测试
# 运行所有单元测试 + 集成测试
# 显示 log 输出(含 trace/debug 级别)
# 仅运行集成测试
# 运行指定模块
测试模块说明
canny_u8_tests(6 个用例)
| 用例 | 验证内容 |
|---|---|
test_canny_u8_uniform_no_edges |
均匀 u8 图像输出零边缘 |
test_canny_u8_output_binary |
输出仅含 0/255 |
test_canny_u8_step_image_has_edges |
阶跃图像产生边缘 |
test_canny_u8_matches_f32_path |
u8 路径与 f32 路径结果一致(sigma=0) |
test_canny_u8_length_mismatch |
输入长度不匹配返回正确错误 |
test_canny_u8_invalid_thresholds |
非法阈值返回正确错误 |
nms_boundary_tests(2 个用例)
| 用例 | 验证内容 |
|---|---|
test_boundary_pixels_always_zero |
四周边界像素 hysteresis 后必须为 0 |
test_step_edges_not_on_boundary |
阶跃边缘仅出现在内部区域 |
hysteresis_tests(3 个用例)
| 用例 | 验证内容 |
|---|---|
test_no_strong_seeds_yields_no_edges |
无强边缘种子时输出全为 0 |
test_no_weak_edges_in_output |
输出中不存在值为 127 的弱边缘 |
test_equal_thresholds_no_weak_edges |
low==high 时输出严格二值 |
determinism_tests(2 个用例)
| 用例 | 验证内容 |
|---|---|
test_deterministic_output |
同一输入多次调用结果完全一致 |
test_interleaved_inputs_deterministic |
不同输入交替调用结果与单独调用一致 |
threshold_behavior_tests(2 个用例)
| 用例 | 验证内容 |
|---|---|
test_higher_threshold_fewer_or_equal_edges |
阈值越高边缘数单调不增 |
test_zero_low_thresh_keeps_all_gradient_pixels |
low=0 保留所有梯度像素 |
special_input_tests(5 个用例)
| 用例 | 验证内容 |
|---|---|
test_all_zero_input |
全零输入不 panic,输出全 0 |
test_all_max_input |
全 255 输入不 panic,输出全 0 |
test_horizontal_line_has_edges |
单像素宽水平线产生边缘 |
test_checkerboard_edge_count_vs_cell_size |
格子越大边缘数越少 |
test_larger_sigma_fewer_or_equal_edges |
sigma 越大边缘数单调不增 |
Debug 构建额外检查
cargo test(debug profile)时自动启用以下运行时断言:
- NMS 方向值越界检测(
dir > 3时记录 error 并跳过) - NMS x 范围合法性检查
track_edges边界清零自验证- 边缘密度过高警告(> 20% 时提示调高阈值)
性能基准
在 Intel Core i7-12700K(AVX2+FMA 启用)上的参考数据:
| 分辨率 | 帧率(Release) | 内存分配/帧 |
|---|---|---|
| 640×480 | ~2000 fps | 0(Arena 复用) |
| 1920×1080 | ~400 fps | 0(Arena 复用) |
| 3840×2160 | ~90 fps | 0(Arena 复用) |
以上数据为估算值,实际性能取决于 CPU 型号、内存带宽和阈值配置。