4. 图像预处理:高斯滤波、中值滤波、直方图均衡化、图像金字塔

图像预处理,说白了就是给算法「喂」干净、好消化的数据。我做了这么多年嵌入式视觉,见过太多算法在实验室跑得飞起,一上硬件就崩——十有八九是预处理没做好。

运动对焦追踪对实时性要求极高,每一帧的处理时间都是硬约束。所以这一章,我们只聊四个最常用、也最能在嵌入式平台上落地的预处理操作。

4.1 高斯滤波:平滑噪声,保留边缘

高斯滤波是线性滤波的代表。它的核函数服从二维高斯分布,中心权重最大,越往边缘权重越小。

为什么用高斯?因为自然图像中的噪声大多服从高斯分布,用高斯核去滤高斯噪声,数学上最匹配。我早期做安防摄像头时,发现 sensor 在低照度下噪点特别多,试过均值滤波,结果边缘糊成一片。换成高斯滤波后,画面干净了,边缘细节也保住了。

核心公式: 高斯核权重由 σ 控制,σ 越大,平滑效果越强,但边缘保留能力下降。

实际项目中,我习惯用 3×3 或 5×5 的核。再大,计算量翻倍,效果提升有限。

// C 语言实现 3×3 高斯滤波(定点数优化版)
// 核系数:1/16 * [1 2 1; 2 4 2; 1 2 1]
void gaussian_blur_3x3(uint8_t *src, uint8_t *dst, int w, int h) {
    for (int y = 1; y < h-1; y++) {
        for (int x = 1; x < w-1; x++) {
            int sum = 0;
            sum += src[(y-1)*w + (x-1)] * 1;
            sum += src[(y-1)*w + x]     * 2;
            sum += src[(y-1)*w + (x+1)] * 1;
            sum += src[y*w + (x-1)]     * 2;
            sum += src[y*w + x]         * 4;
            sum += src[y*w + (x+1)]     * 2;
            sum += src[(y+1)*w + (x-1)] * 1;
            sum += src[(y+1)*w + x]     * 2;
            sum += src[(y+1)*w + (x+1)] * 1;
            dst[y*w + x] = sum >> 4;  // 除以16
        }
    }
}
我的习惯: 在 ARM Cortex-A 系列上,我会用 NEON 指令集一次处理 8 个像素,速度能快 4-5 倍。

4.2 中值滤波:干掉椒盐噪声

中值滤波是非线性滤波。它取邻域内所有像素的中位数作为输出值。

这东西对付椒盐噪声特别管用。椒盐噪声是随机的黑白点,均值或高斯滤波会把它扩散开,反而让一片区域都脏了。中值滤波直接把它替换成周围正常像素的值,干净利落。

我曾经在一个工业检测项目里,产线上有颗螺丝偶尔反光,在图像上形成极亮的斑点。用中值滤波 3×3 窗口,一帧就搞定了。换成高斯滤波,反而把反光区域模糊成一片,导致后续边缘检测误判。

注意: 中值滤波的计算量比高斯滤波大,因为每次都要排序。在嵌入式平台上,建议窗口不超过 5×5,否则帧率会掉得厉害。
滤波类型 适用噪声 边缘保留 计算量 嵌入式推荐
高斯滤波 高斯噪声 中等 低 3×3 或 5×5
中值滤波 椒盐噪声 好 中 3×3,不超过 5×5
均值滤波 均匀噪声 差 最低 尽量不用

4.3 直方图均衡化:让暗部细节现形

直方图均衡化,说白了就是把图像的灰度分布「拉平」。原本集中在暗区的像素,会被映射到更宽的灰度范围,暗部细节就出来了。

运动对焦追踪里,这个操作特别有用。比如无人机在黄昏时追踪地面目标,画面整体偏暗,对比度低。不做均衡化,特征点提取根本找不到足够的角点。

但要注意——全局直方图均衡化会放大噪声。我建议只在感兴趣区域(ROI)上做局部均衡化,或者用 CLAHE(限制对比度自适应直方图均衡化)。

经验值: CLAHE 的 clip limit 我一般设在 2.0~3.0,窗口大小 8×8。再大,会出现块状伪影。
// 直方图均衡化核心步骤(伪代码)
// 1. 统计灰度直方图 hist[256]
// 2. 计算累积分布函数 cdf[i] = sum(hist[0..i])
// 3. 归一化映射:new_pixel = (cdf[pixel] - cdf_min) / (w*h - cdf_min) * 255
void hist_equalize(uint8_t *img, int w, int h) {
    int hist[256] = {0};
    for (int i = 0; i < w*h; i++) hist[img[i]]++;
    
    int cdf[256];
    int sum = 0;
    for (int i = 0; i < 256; i++) {
        sum += hist[i];
        cdf[i] = sum;
    }
    
    int cdf_min = cdf[0];
    for (int i = 0; i < w*h; i++) {
        img[i] = (uint8_t)((cdf[img[i]] - cdf_min) * 255.0f / (w*h - cdf_min));
    }
}

4.4 图像金字塔:多尺度追踪的基石

图像金字塔,就是同一张图生成多个分辨率版本。从原图往下采样,一层比一层小。

为什么需要它?运动对焦追踪里,目标可能在画面中快速移动。如果只在原图上做特征匹配,搜索范围太大,计算量爆炸。先在低分辨率层找到大致位置,再回到高分辨率层精确定位——这就是金字塔的经典用法。

我记得做手机摄像头追焦时,目标从画面边缘突然闯入,位移超过 100 像素。如果不用金字塔,光流法直接崩掉。用了 3 层金字塔,顶层位移只有 20 像素,光流轻松收敛。

我常用的金字塔参数: 层数 3~4,下采样因子 0.5。每层用高斯滤波做 anti-aliasing,避免混叠。
// 构建高斯金字塔(简化版)
// 每层先高斯滤波,再隔行隔列采样
void build_gaussian_pyramid(uint8_t *src, int w, int h, 
                            uint8_t *pyramid[], int levels) {
    pyramid[0] = src;
    for (int l = 1; l < levels; l++) {
        int prev_w = w >> (l-1);
        int prev_h = h >> (l-1);
        int cur_w = w >> l;
        int cur_h = h >> l;
        
        // 先高斯滤波(3×3)
        gaussian_blur_3x3(pyramid[l-1], pyramid[l], prev_w, prev_h);
        
        // 再下采样
        for (int y = 0; y < cur_h; y++) {
            for (int x = 0; x < cur_w; x++) {
                pyramid[l][y*cur_w + x] = 
                    pyramid[l][(y*2)*prev_w + (x*2)];
            }
        }
    }
}

4.5 本章知识体系

下面这张图,把四个预处理操作的关系和适用场景串起来了。你想想看,实际项目中往往是组合使用:先滤波去噪,再均衡化增强,最后用金字塔做多尺度分析。

图像预处理知识体系 图像预处理 高斯滤波 中值滤波 直方图均衡化 图像金字塔 平滑高斯噪声 去除椒盐噪声 增强对比度 多尺度分析 嵌入式部署要点 • 高斯滤波:3×3核,定点数优化,NEON加速 • 中值滤波:窗口不超过5×5,避免排序开销 • 直方图均衡化:优先用CLAHE,clip limit 2~3

4.6 避坑指南

  • 滤波顺序不能乱: 先滤波,再均衡化。如果先均衡化,噪声会被放大,滤波效果大打折扣。
  • 金字塔层数不是越多越好: 层数太多,顶层图像太小,特征信息丢失严重。我一般不超过 4 层。
  • 中值滤波的边界处理: 边界像素没法取完整窗口,我习惯直接复制原值,或者用镜像填充。
  • 直方图均衡化后的量化误差: 8 位图像均衡化后,灰度级可能从 256 压缩到更少,出现「伪轮廓」。这时候可以加一点微小的随机抖动(dithering)。
我曾经踩过的坑: 在一个无人机追踪项目里,我用了全局直方图均衡化,结果天空区域的噪声被放大,导致光流法把云朵误判为运动目标。后来换成 CLAHE,只在目标区域做局部增强,问题就解决了。