fast-canny 0.1.0

Industrial-grade Zero-Allocation SIMD Canny Edge Detector
Documentation
//! NMS + 双阈值化融合算子(切片接口版本)

/// 融合算子:非极大值抑制(NMS)+ 双阈值化。
///
/// 使用切片接口替代裸指针,消除调用方的 unsafe 需求。
///
/// # 参数
/// - `mag`:梯度幅值切片(只读),长度为 `width × height`
/// - `dir`:梯度方向切片(只读),值域 `{0, 1, 2, 3}`
/// - `edge_out`:边缘输出切片(可写),与 `dir` 必须是不同的缓冲区
/// - `width`:图像宽度
/// - `x_start`:起始列(含),需满足 `x_start >= 1`
/// - `x_end`:结束列(不含),需满足 `x_end <= width - 1`
/// - `y`:当前行号,需满足 `y >= 1` 且 `y <= height - 2`
/// - `low_thresh` / `high_thresh`:双阈值
#[inline]
pub fn nms_and_threshold_slice(
    mag: &[f32],
    dir: &[u8],
    edge_out: &mut [u8],
    width: usize,
    x_start: usize,
    x_end: usize,
    y: usize,
    low_thresh: f32,
    high_thresh: f32,
) {
    debug_assert!(x_start >= 1, "x_start must be >= 1");
    debug_assert!(x_end <= width - 1, "x_end must be <= width - 1");
    debug_assert!(y >= 1, "y must be >= 1");
    debug_assert_eq!(mag.len(), dir.len());
    debug_assert_eq!(mag.len(), edge_out.len());

    #[cfg(debug_assertions)]
    log::trace!(
        "[nms] y={}, x=[{}, {}), low={:.2}, high={:.2}",
        y,
        x_start,
        x_end,
        low_thresh,
        high_thresh
    );

    let w = width as isize;

    // 预计算 4 个量化方向的偏移量
    let offsets: [[isize; 2]; 4] = [
        [-1, 1],         // 0°   水平
        [-w + 1, w - 1], // 45°  右上/左下
        [-w, w],         // 90°  垂直
        [-w - 1, w + 1], // 135° 左上/右下
    ];

    let base_idx = y * width;

    for x in x_start..x_end {
        let idx = base_idx + x;

        // Early Exit:低于低阈值直接抑制
        let m = mag[idx];
        if m < low_thresh {
            edge_out[idx] = 0;
            continue;
        }

        let d = dir[idx] as usize;

        #[cfg(debug_assertions)]
        if d > 3 {
            log::error!(
                "[nms] CORRUPTED dir={} at idx={} (x={}, y={}), skipping",
                d,
                idx,
                x,
                y
            );
            edge_out[idx] = 0;
            continue;
        }

        let off1 = offsets[d][0];
        let off2 = offsets[d][1];

        // SAFETY:
        // - idx = y*width + x,其中 y>=1, x>=1, x<width-1, y<height-1
        // - 偏移量最大为 ±(width+1),不会越出 [0, width*height) 范围
        // - 此处使用 get_unchecked 避免重复边界检查,安全性由上方 debug_assert 保证
        let n1_idx = (idx as isize + off1) as usize;
        let n2_idx = (idx as isize + off2) as usize;

        // 使用安全索引(release 下编译器会消除冗余检查)
        let m1 = mag[n1_idx];
        let m2 = mag[n2_idx];

        // 非对称比较:确保等值相邻像素只保留一个
        if m >= m1 && m > m2 {
            edge_out[idx] = if m >= high_thresh { 255 } else { 127 };
        } else {
            edge_out[idx] = 0;
        }
    }

    #[cfg(debug_assertions)]
    log::trace!("[nms] DONE y={}, x=[{}, {})", y, x_start, x_end);
}

/// 带统计的 NMS(仅 debug 构建使用)。
#[cfg(debug_assertions)]
pub fn nms_and_threshold_with_stats(
    mag: &[f32],
    dir: &[u8],
    edge_out: &mut [u8],
    width: usize,
    x_start: usize,
    x_end: usize,
    y: usize,
    low_thresh: f32,
    high_thresh: f32,
) -> (usize, usize, usize) {
    use std::time::Instant;
    let t_start = Instant::now();

    nms_and_threshold_slice(
        mag,
        dir,
        edge_out,
        width,
        x_start,
        x_end,
        y,
        low_thresh,
        high_thresh,
    );

    let base_idx = y * width;
    let mut strong = 0usize;
    let mut weak = 0usize;
    let mut supp = 0usize;

    for x in x_start..x_end {
        match edge_out[base_idx + x] {
            255 => strong += 1,
            127 => weak += 1,
            _ => supp += 1,
        }
    }

    let total = x_end - x_start;
    log::debug!(
        "[nms_stats] y={} strong={} ({:.1}%) weak={} ({:.1}%) supp={} ({:.1}%) {:.3}ms",
        y,
        strong,
        strong as f64 / total as f64 * 100.0,
        weak,
        weak as f64 / total as f64 * 100.0,
        supp,
        supp as f64 / total as f64 * 100.0,
        t_start.elapsed().as_secs_f64() * 1000.0
    );

    (strong, weak, supp)
}