4、图像预处理基础:灰度化、直方图均衡化、高斯滤波、中值滤波的原理与OpenCV实现

图像预处理,说白了就是给原始图像“洗把脸”。

在工业缺陷检测里,相机拍回来的图往往带着各种“脏东西”——光照不均、传感器噪声、背景纹理干扰。你直接拿这些图去做检测,算法很容易被带偏。我刚开始做项目时,就吃过这个亏。当时一个手机玻璃盖板划痕检测项目,死活跑不出效果,折腾了两周才发现是光源频闪导致的灰度波动没处理。

从那以后,我养成了一个习惯:预处理做扎实了,后面的算法才能站得住脚。

核心观点:预处理不是“可有可无的步骤”,它是整个检测流程的基石。你花在预处理上的每一分钟,都会在后续算法调试中加倍回报给你。

图像预处理核心流程 原始图像 步骤1:灰度化 步骤2:直方图均衡化 步骤3a:高斯滤波 步骤3b:中值滤波 预处理完成图像 根据噪声类型选择 高斯噪声→高斯滤波 椒盐噪声→中值滤波

4.1 灰度化:扔掉颜色,保留结构

工业相机拍出来的图,大部分是彩色图(BGR格式)。但缺陷检测真的需要颜色信息吗?

说实话,90%的工业场景不需要。划痕、凹坑、脏污、裂纹——这些缺陷在灰度图上就已经足够明显。彩色图反而会引入额外的计算量和干扰。

灰度化的本质,就是把每个像素的R、G、B三个通道,按一定权重合并成一个值。OpenCV里最常用的方法是:

gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

这行代码背后,OpenCV用的是这个公式:

Gray = 0.299 * R + 0.587 * G + 0.114 * B

为什么权重不一样?因为人眼对绿色最敏感,对蓝色最不敏感。这个权重是心理学实验测出来的,咱们直接用就行。

我的经验:如果你处理的图像是单色光源(比如红光照明),可以考虑直接用单通道提取。比如用红光照明,直接取R通道的效果往往比标准灰度化更好。我在一个金属表面划痕项目中试过,对比度提升了约15%。

4.2 直方图均衡化:让暗的地方亮起来

你想想看,如果一张图整体偏暗,或者整体偏亮,缺陷区域和背景的对比度就会很低。这时候就需要直方图均衡化出手了。

直方图均衡化的原理,说白了就是把像素值的分布“拉平”。原本集中在某个小范围的灰度值,被映射到整个0-255区间。这样暗的地方变亮,亮的地方变暗,对比度就上来了。

OpenCV实现分两种:

方法 适用场景 代码
全局均衡化 光照整体不均匀 cv2.equalizeHist(gray)
CLAHE(自适应) 局部光照差异大 cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8,8))

这里我要特别说一下CLAHE。全局均衡化有个毛病——它会放大噪声。尤其是图像中原本比较均匀的区域,均衡化后会出现“块状感”。

避坑指南:我曾经在一个PCB焊点检测项目里,直接用全局均衡化,结果把背景纹理的噪声放大了,导致误检率飙升到30%。后来换成CLAHE,把clipLimit设到2.0,tileGridSize用8x8,误检率直接降到3%以下。记住:工业场景优先用CLAHE。

import cv2

# 全局均衡化
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
eq_global = cv2.equalizeHist(gray)

# CLAHE自适应均衡化
clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8,8))
eq_clahe = clahe.apply(gray)

4.3 高斯滤波:温柔的模糊

高斯滤波是工业视觉里最常用的平滑滤波器。它的核心思想很简单:用邻域像素的加权平均来替代中心像素。权重由高斯函数决定,离中心越近的像素权重越大。

为什么叫“高斯”?因为权重分布服从高斯分布(正态分布)。

OpenCV里一行代码搞定:

blurred = cv2.GaussianBlur(gray, (5,5), sigmaX=1.5)

参数说明:

  • 核大小 (5,5):必须是奇数。核越大,模糊越强,但细节丢失也越多。
  • sigmaX:X方向的标准差。一般设1.0-2.0之间。如果设为0,OpenCV会根据核大小自动计算。

关键点:高斯滤波擅长处理高斯噪声——也就是那种在图像上随机分布的、幅度符合高斯分布的噪声。这种噪声在工业场景中很常见,比如传感器热噪声、低光照下的电子噪声。

我个人的习惯是:先试5x5核,sigmaX=1.5。如果噪声还明显,再加大核大小。但要注意,核太大容易把缺陷边缘也模糊掉。在一个轴承表面划痕项目里,我用7x7的核,结果把细划痕直接抹平了……后来换回5x5才搞定。

4.4 中值滤波:专治“椒盐噪声”

中值滤波的思路和高斯滤波完全不同。它不是算平均,而是取邻域内所有像素值的中位数来替代中心像素。

这招对“椒盐噪声”特别有效。什么是椒盐噪声?就是图像上随机出现的黑白点——像撒了盐和胡椒一样。这种噪声在工业相机传输过程中偶尔会出现,尤其是线缆接触不良的时候。

median = cv2.medianBlur(gray, ksize=5)

参数ksize也是奇数。3、5、7是常用值。

为什么中值滤波能干掉椒盐噪声?你想想看,一个黑点(像素值0)周围都是正常像素(比如值128),取中位数后,这个黑点就被“拉回”到正常值了。而高斯滤波遇到这种情况,会把黑点的影响“扩散”到周围,反而让一片区域都变暗。

我的经验:中值滤波的核大小不要超过7。我见过有人用9x9甚至11x11,结果图像变得像油画一样,细节全没了。工业场景里,3x3或5x5基本够用。

4.5 如何选择?一张表说清楚

噪声类型 推荐滤波器 典型参数 注意事项
高斯噪声(传感器热噪声) 高斯滤波 核5x5, sigmaX=1.5 核太大会模糊边缘
椒盐噪声(传输干扰) 中值滤波 核3x3或5x5 核不要超过7x7
均匀噪声(光照波动) 高斯滤波 + CLAHE 先滤波后均衡化 顺序不能反
混合噪声 先中值后高斯 中值3x3 + 高斯5x5 先去除离群点再平滑

4.6 完整预处理流水线

在实际项目中,我通常会把这几个步骤串起来。下面是一个典型的预处理流水线:

import cv2
import numpy as np

def preprocess_pipeline(img_path):
    # 1. 读取图像
    img = cv2.imread(img_path)
    if img is None:
        raise ValueError("图像读取失败,检查路径")

    # 2. 灰度化
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

    # 3. CLAHE自适应均衡化
    clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8,8))
    enhanced = clahe.apply(gray)

    # 4. 判断噪声类型并滤波
    # 这里简单起见,先用高斯滤波
    filtered = cv2.GaussianBlur(enhanced, (5,5), sigmaX=1.5)

    # 如果发现椒盐噪声,可以再加一步中值滤波
    # filtered = cv2.medianBlur(filtered, 3)

    return filtered

重要提醒:预处理不是越复杂越好。我见过有人把预处理搞了七八步,结果图像质量反而下降了。记住一个原则:能用一步解决的,绝不用两步。每多一步,都会引入额外的信息损失。

嗯,到这里,图像预处理的基础内容就差不多了。灰度化帮你扔掉冗余的颜色信息,直方图均衡化让对比度拉满,高斯滤波和中值滤波各司其职。把这些基础打牢了,后面做缺陷分割、特征提取的时候,你会省很多力气。