3. 图像预处理实战:图像滤波、边缘检测与形态学操作

说实话,很多刚入行的同学觉得图像预处理就是「调参游戏」。高斯核多大?Canny阈值设多少?随便调调就行。

但我得说,这个想法很危险。我在实际项目中吃过不少亏——有一次在高速场景下,就因为滤波参数没选好,把远处的行人轮廓给抹掉了,差点酿成大错。所以今天这一讲,咱们把预处理这块硬骨头啃透。

3.1 图像滤波:去噪是第一步

传感器采集的图像,说白了就是一堆离散的像素点。受光照、传感器噪声影响,这些点总会有「毛刺」。滤波就是把这些毛刺磨平。

3.1.1 高斯滤波

高斯滤波是我最常用的。它的核心思想很简单:每个像素的值,由它周围像素的加权平均决定。权重符合高斯分布——离中心越近,权重越大。

为什么用高斯?因为自然界的噪声大多服从高斯分布。用高斯滤波去高斯噪声,这叫「对症下药」。

import cv2
import numpy as np

img = cv2.imread('road.jpg')
# 高斯滤波,核大小5x5,标准差1.5
gaussian = cv2.GaussianBlur(img, (5, 5), 1.5)
我的经验:核大小选奇数,3x3或5x5最常用。标准差sigma一般取1.0~2.0。sigma太小没效果,太大图像会模糊得像近视眼。

3.1.2 中值滤波

中值滤波就更有意思了。它不是算平均,而是取邻域内所有像素的「中位数」。

你想想看,如果图像里有椒盐噪声(黑白点),用高斯滤波会怎样?噪声点会被「平均」到周围,虽然变淡了,但还是存在。但中值滤波直接把这个异常点替换成中位数,干净利落。

# 中值滤波,核大小3
median = cv2.medianBlur(img, 3)
注意:中值滤波对椒盐噪声效果极好,但会损失边缘细节。我曾经在车道线检测中过度使用中值滤波,结果车道线边缘变得模糊,导致后续拟合偏差很大。

3.2 边缘检测:Canny算子详解

边缘检测是视觉感知的基石。车道线、行人轮廓、车辆边界——这些都需要边缘信息。Canny算子是目前最经典的方案,没有之一。

3.2.1 Canny的四个步骤

  1. 高斯滤波去噪——先磨平毛刺
  2. 计算梯度幅值和方向——用Sobel算子算x和y方向的梯度
  3. 非极大值抑制——只保留梯度方向上的局部最大值
  4. 双阈值检测——高阈值确定强边缘,低阈值连接弱边缘

嗯,这里要注意第三步。非极大值抑制说白了就是「瘦身」——把边缘变细,只保留最亮的那条线。否则你得到的边缘会像毛笔画的一样粗。

# Canny边缘检测
edges = cv2.Canny(img, 50, 150)
# 参数:低阈值50,高阈值150

阈值怎么调?我一般用「2:1」或「3:1」的比例。比如低阈值50,高阈值150。高阈值太高会丢失边缘,太低会引入大量噪声。

我的习惯做法:先用低阈值看整体边缘,再逐步提高高阈值,直到噪声消失。

3.3 形态学操作:让边缘更干净

做完边缘检测,你会发现图像里有很多「碎渣」——孤立的小点、断裂的线条。形态学操作就是用来处理这些问题的。

3.3.1 腐蚀与膨胀

这两个操作是基础。腐蚀是「收缩」,膨胀是「扩张」。

  • 腐蚀:消除小白点,让物体边界向内收缩
  • 膨胀:填补小空洞,让物体边界向外扩张
kernel = np.ones((3,3), np.uint8)
erosion = cv2.erode(img, kernel, iterations=1)
dilation = cv2.dilate(img, kernel, iterations=1)

3.3.2 开运算与闭运算

开运算 = 先腐蚀后膨胀。用来消除小噪点,同时保持物体大小不变。

闭运算 = 先膨胀后腐蚀。用来填补物体内部的小空洞。

# 开运算
opening = cv2.morphologyEx(img, cv2.MORPH_OPEN, kernel)
# 闭运算
closing = cv2.morphologyEx(img, cv2.MORPH_CLOSE, kernel)
避坑指南:我曾经在夜间场景处理时,直接用开运算去噪,结果把远处的车灯轮廓也去掉了。后来我改用「先闭运算填补车灯内部空洞,再开运算去除背景噪声」的顺序,效果好了很多。

3.4 实战组合拳

在实际的自动驾驶感知系统中,这些操作通常是组合使用的。我分享一个常用的预处理流水线:

  1. 高斯滤波去噪(核5x5,sigma=1.5)
  2. Canny边缘检测(阈值50/150)
  3. 形态学闭运算(核3x3,填补边缘断裂)
  4. 形态学开运算(核3x3,去除孤立噪点)
def preprocess_pipeline(img):
    # 1. 高斯滤波
    blurred = cv2.GaussianBlur(img, (5, 5), 1.5)
    # 2. Canny边缘检测
    edges = cv2.Canny(blurred, 50, 150)
    # 3. 闭运算
    kernel = np.ones((3,3), np.uint8)
    closed = cv2.morphologyEx(edges, cv2.MORPH_CLOSE, kernel)
    # 4. 开运算
    cleaned = cv2.morphologyEx(closed, cv2.MORPH_OPEN, kernel)
    return cleaned
重要提醒:预处理参数不是通用的!白天和夜晚、晴天和雨天、高速和城市道路,参数都需要调整。我建议你在代码里预留参数配置接口,方便根据不同场景切换。

好了,这一讲的内容就到这里。图像预处理看似基础,但它是整个感知系统的「地基」。地基没打好,后面的目标检测、跟踪都会出问题。下一讲我们会进入更核心的目标检测环节,到时候你就知道预处理做得好有多重要了。