4. 图像处理基础(下):图像滤波、边缘检测、二值化与形态学操作、直方图均衡化

好,咱们接着往下聊。上一节我们把图像处理的基础概念和像素操作过了一遍,这一节要上点硬货了。说白了,这些技术就是人脸识别系统的「预处理流水线」——图像进模型之前,得先经过这几道工序。

我个人习惯把这一节的内容分成四大块:滤波去噪边缘检测二值化与形态学直方图均衡化。每一块在实际项目中都踩过坑,咱们一个一个说。

4.1 图像滤波:让图像更干净

滤波是干嘛的?说白了就是去噪。摄像头采集的图像,受光照、传感器影响,总会有噪声。噪声不除掉,后续的边缘检测、人脸定位都会受影响。

4.1.1 均值滤波

均值滤波是最简单的。它用一个窗口(比如3x3),把窗口内所有像素的平均值赋给中心点。嗯,这里要注意:它会把边缘也模糊掉。

我在项目中遇到过一个问题:用均值滤波去噪后,人脸轮廓变模糊了,导致后续的 landmark 检测精度下降。所以均值滤波适合噪声均匀的场景,比如高斯噪声,但对椒盐噪声效果一般。

import cv2
import numpy as np

img = cv2.imread('face.jpg')
# 均值滤波,核大小为5x5
blurred = cv2.blur(img, (5, 5))

4.1.2 高斯滤波

高斯滤波比均值滤波更「聪明」。它根据高斯分布给窗口内的像素分配权重——离中心越近,权重越大。这样能更好地保留边缘信息。

我建议在人脸识别预处理中优先用高斯滤波。它去噪的同时,对边缘的破坏比均值滤波小得多。你想想看,人脸的眼睛、鼻子这些关键特征,边缘信息多宝贵啊。

# 高斯滤波,核大小5x5,标准差1.5
gaussian = cv2.GaussianBlur(img, (5, 5), 1.5)
我的经验:高斯滤波的核大小和标准差要配合着调。核越大,模糊越强;标准差越大,权重分布越平缓。一般核大小取奇数,3、5、7比较常用。

4.1.3 中值滤波

中值滤波就更有意思了。它取窗口内所有像素的中位数作为输出值。为什么说它特别?它对椒盐噪声(就是图像上那些黑白点)有奇效。

我曾经在一个安防项目中,摄像头因为传输线路干扰,图像上全是椒盐噪声。均值滤波和高斯滤波都试了,效果不理想。换成中值滤波后,噪声几乎全消了。这就是实战中积累的经验。

# 中值滤波,核大小为5
median = cv2.medianBlur(img, 5)

4.2 边缘检测:Canny 算法

边缘检测是人脸识别中非常关键的一步。人脸的五官轮廓、脸部边界,本质上都是边缘信息。Canny 算法是业界最常用的边缘检测方法,没有之一。

Canny 的流程其实不复杂:先高斯滤波去噪,然后计算梯度幅值和方向,接着做非极大值抑制(把不是边缘的像素去掉),最后用双阈值检测连接边缘。

这里有个坑:双阈值的设置。阈值设高了,边缘断断续续;设低了,噪声也被当成边缘。我一般用 cv2.Canny(img, 50, 150) 作为起点,然后根据实际效果微调。

# Canny 边缘检测
edges = cv2.Canny(gaussian, 50, 150)
注意:Canny 的输入图像最好是灰度图。如果是彩色图,先转灰度再处理。另外,低阈值和高阈值的比例一般在1:2到1:3之间。

4.3 图像二值化与形态学操作

4.3.1 图像二值化

二值化就是把图像变成只有黑和白。说白了,就是设定一个阈值,像素值大于阈值的变成255(白),小于的变成0(黑)。

最简单的就是全局阈值:

# 全局阈值二值化
_, binary = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)

但实际项目中,光照不均匀是常态。全局阈值在光照暗的地方会把细节全丢掉。这时候就要用自适应阈值:

# 自适应阈值,根据局部区域计算阈值
adaptive = cv2.adaptiveThreshold(gray, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C,
                                 cv2.THRESH_BINARY, 11, 2)

我建议在人脸相关的二值化任务中,优先尝试自适应阈值。它比全局阈值鲁棒得多。

4.3.2 形态学操作:腐蚀与膨胀

二值化之后,图像上经常会有一些小白点(噪声)或者断裂的线条。形态学操作就是用来处理这些问题的。

腐蚀:让白色区域「瘦」一圈。可以去掉小的白点噪声。

膨胀:让白色区域「胖」一圈。可以连接断裂的线条。

我经常把它们组合使用:先腐蚀去噪,再膨胀恢复。这叫「开运算」。反过来先膨胀再腐蚀叫「闭运算」,用来填充小孔。

kernel = np.ones((3,3), np.uint8)

# 腐蚀
eroded = cv2.erode(binary, kernel, iterations=1)

# 膨胀
dilated = cv2.dilate(binary, kernel, iterations=1)

# 开运算:先腐蚀后膨胀
opening = cv2.morphologyEx(binary, cv2.MORPH_OPEN, kernel)

# 闭运算:先膨胀后腐蚀
closing = cv2.morphologyEx(binary, cv2.MORPH_CLOSE, kernel)
实战技巧:在人脸识别中,我常用开运算去掉眉毛、头发等细小的噪声区域,再用闭运算填充眼睛、嘴巴等区域的断裂。核大小一般取3x3或5x5,迭代次数不要超过2次,否则会丢失细节。

4.4 直方图均衡化

直方图均衡化是提升图像对比度的利器。你想想看,在光线不足或者逆光环境下,人脸图像往往偏暗,细节看不清。直方图均衡化可以把像素值的分布「拉伸」开,让暗的地方变亮,亮的地方更均匀。

它的原理不复杂:统计每个灰度级的像素数量,然后重新映射,让像素分布更均匀。OpenCV 里一行代码就能搞定:

# 直方图均衡化(输入必须是灰度图)
equalized = cv2.equalizeHist(gray)

但这里有个问题:全局直方图均衡化会放大噪声。尤其是在人脸图像中,皮肤区域的噪声会被放大,看起来不自然。

我推荐用 CLAHE(限制对比度自适应直方图均衡化)。它把图像分成小块,每个小块单独做均衡化,并且限制对比度的放大程度。效果比全局均衡化好得多。

# CLAHE 直方图均衡化
clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8,8))
clahe_img = clahe.apply(gray)
我的习惯:在人脸识别预处理中,我一般先用 CLAHE 提升对比度,再做高斯滤波去噪。顺序不能反——先均衡化再滤波,可以避免滤波把增强后的细节又模糊掉。

4.5 综合实战:人脸预处理流水线

最后,我把这些技术串起来,给一个实际的人脸预处理流程。这个流程我在多个项目中验证过,效果稳定。

def preprocess_face(img):
    # 1. 转灰度
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    
    # 2. CLAHE 增强对比度
    clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8,8))
    enhanced = clahe.apply(gray)
    
    # 3. 高斯滤波去噪
    blurred = cv2.GaussianBlur(enhanced, (3, 3), 1.0)
    
    # 4. 可选:边缘检测(用于特征提取)
    edges = cv2.Canny(blurred, 50, 150)
    
    return blurred, edges

嗯,这一节的内容就到这里。图像处理是计算机视觉的基石,这些操作虽然基础,但用好了能大幅提升人脸识别系统的鲁棒性。下一节我们会进入更核心的内容——特征提取与人脸检测算法。