3、图像预处理:图像去噪、直方图均衡化、几何变换(仿射/透视)、图像金字塔

图像预处理,说白了就是给原始图像“洗个澡、化个妆”。

我做了这么多年感知,发现一个规律:模型的好坏,一半靠预处理。你想想看,如果输入的数据本身就脏兮兮的,再牛的神经网络也白搭。今天咱们就聊聊这四个最常用的预处理手段。

3.1 图像去噪:把“雪花点”去掉

摄像头采集的图像,多少都有噪声。尤其是夜间或低光照场景,那噪点简直像下雪一样。

3.1.1 常见的噪声类型

  • 高斯噪声:传感器热噪声,呈正态分布。说白了就是每个像素上叠加了一个随机小扰动。
  • 椒盐噪声:图像上随机出现白点或黑点,像撒了盐和胡椒。我遇到过摄像头排线接触不良时,整张图全是这种点。
  • 泊松噪声:光子计数噪声,低光照下更明显。

3.1.2 去噪方法对比

方法 原理 适用场景 我个人的习惯
均值滤波 邻域像素取平均 高斯噪声 基本不用,太模糊边缘
中值滤波 邻域像素取中值 椒盐噪声 椒盐噪声首选,效果好
高斯滤波 加权平均,权重符合高斯分布 高斯噪声 轻度去噪时常用
双边滤波 考虑空间距离+像素值差异 保边去噪 我项目中经常用,边缘保留好

核心经验:去噪不是越干净越好。过度去噪会丢失细节,比如车道线边缘会变模糊。我建议先轻后重,能看清目标就行。

# 中值滤波示例(OpenCV)
import cv2
img = cv2.imread('road.jpg')
# 核大小3x3,奇数
denoised = cv2.medianBlur(img, 3)

小技巧:核大小选3或5就够了。我曾经试过用7x7,结果车道线直接糊成一团,检测器全废了。

3.2 直方图均衡化:让暗处亮起来

你有没有遇到过这种情况?大晴天开车进隧道,图像瞬间黑成一团。直方图均衡化就是干这个的——把像素分布拉开,让暗部细节显现出来。

3.2.1 原理一句话

把原始图像的灰度直方图,从集中分布变成均匀分布。说白了就是“劫富济贫”——把亮的地方的像素值匀一点给暗的地方。

3.2.2 两种常用方法

  • 全局直方图均衡化:整张图一起做。简单粗暴,但容易过曝。
  • 自适应直方图均衡化(CLAHE):分块做,每块单独均衡,再拼接。我强烈推荐这个。

注意:全局均衡化在自动驾驶场景下容易出问题。比如天空本来就亮,均衡后更亮,反而把路面细节压没了。我曾经踩过这个坑,后来全换成CLAHE了。

# CLAHE 示例
import cv2
img = cv2.imread('tunnel.jpg', 0)  # 灰度图
clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8,8))
equalized = clahe.apply(img)

参数调优:clipLimit控制对比度限制,我一般设2.0-3.0。tileGridSize是分块大小,8x8是默认值,效果不错。

3.3 几何变换:让图像“摆正”

摄像头装在车上,角度固定,但路面有坡度、弯道。几何变换就是用来校正这些畸变的。

3.3.1 仿射变换

仿射变换保持“平行线依然平行”。说白了就是平移、旋转、缩放、错切这些操作。

我记得有一次做车道线检测,摄像头装歪了,图像整体偏了5度。用仿射变换旋转回来,检测准确率直接提升20%。

# 仿射变换示例
import cv2
import numpy as np
img = cv2.imread('lane.jpg')
rows, cols = img.shape[:2]
# 旋转矩阵:绕中心旋转30度
M = cv2.getRotationMatrix2D((cols/2, rows/2), 30, 1)
rotated = cv2.warpAffine(img, M, (cols, rows))

3.3.2 透视变换

透视变换更厉害,它可以把梯形变成矩形。比如你从车头看前方的车道,本来是近宽远窄的梯形,透视变换可以把它变成俯视图的矩形。

应用场景:鸟瞰图(BEV)生成、车道线检测、泊车辅助。我做的泊车项目中,就是用透视变换把四个鱼眼摄像头图像合成俯视图。

# 透视变换示例
import cv2
import numpy as np
img = cv2.imread('road.jpg')
# 源点:原始图像中的四个点(梯形)
src_pts = np.float32([[100, 200], [500, 200], [0, 400], [600, 400]])
# 目标点:变换后的四个点(矩形)
dst_pts = np.float32([[0, 0], [600, 0], [0, 400], [600, 400]])
M = cv2.getPerspectiveTransform(src_pts, dst_pts)
warped = cv2.warpPerspective(img, M, (600, 400))

避坑指南:我曾经选错源点坐标,结果变换出来的图像扭曲得像哈哈镜。记住:源点必须对应实际场景中的矩形区域,比如车道线的四个角点。

3.4 图像金字塔:多尺度看世界

为什么要用图像金字塔?因为目标有大有小。远处的车只有几个像素,近处的车占满屏幕。单一尺度处理,要么漏掉小目标,要么大目标计算量太大。

3.4.1 两种金字塔

  • 高斯金字塔:先高斯模糊,再下采样。每层缩小一半。
  • 拉普拉斯金字塔:保留高频细节,用于图像重建。

3.4.2 实际应用

我常用的场景是目标检测中的多尺度融合。比如YOLO系列,就是在不同尺度的特征图上做检测。小尺度特征图检测大目标,大尺度特征图检测小目标。

# 高斯金字塔示例
import cv2
img = cv2.imread('car.jpg')
# 构建3层金字塔
pyramid = [img]
for i in range(3):
    img = cv2.pyrDown(img)  # 下采样
    pyramid.append(img)
# pyramid[0]原图,pyramid[1]缩小一半,依次类推

经验之谈:金字塔层数不是越多越好。我一般用3-4层,再往下图像太小,信息丢失严重。你想想看,32x32的图还能看出什么?

3.5 预处理流程实战建议

在实际项目中,这些预处理不是孤立使用的。我一般按这个顺序来:

  1. 先几何变换:把图像摆正,比如透视变换生成BEV图
  2. 再去噪:用双边滤波或中值滤波
  3. 再增强:用CLAHE做直方图均衡化
  4. 最后建金字塔:根据任务需求构建多尺度

总结一句话:预处理做得好,模型训练事半功倍。我见过太多人把原始数据直接扔进网络,结果调参调到崩溃。嗯,希望你别走这个弯路。

好了,图像预处理就聊到这儿。下一章咱们进入特征提取,聊聊SIFT、HOG这些经典方法。到时候我会分享一些我在特征匹配中踩过的坑,挺有意思的。