3. OpenCV基础(二):色彩空间转换、几何变换与图像滤波
各位同学,欢迎来到第三章。上一章我们把OpenCV的安装、图像读写和像素操作捋了一遍。这一章,咱们要深入几个真正在YOLO实战中高频使用的核心操作:色彩空间转换、几何变换和图像滤波。
说实话,这三个东西在YOLO的预处理和后处理里几乎无处不在。你训练模型时,数据增强要用到几何变换;你部署模型时,输入图像的尺寸和色彩格式必须对齐;你调试模型时,滤波能帮你剔除噪声。所以,这一章的内容,说白了就是YOLO流水线上的“基本功”。
3.1 色彩空间转换:BGR、RGB、HSV
先问大家一个问题:OpenCV默认读进来的图像是什么颜色顺序?
答案是BGR,不是RGB。这一点坑过无数新手,也包括当年的我。我第一次用OpenCV显示图像,发现颜色偏蓝,查了半天才发现是通道顺序搞反了。
3.1.1 BGR与RGB的互转
YOLO模型训练时,大部分框架(比如PyTorch)用的是RGB顺序。所以你在做推理前,必须把OpenCV读进来的BGR图像转成RGB。
转换方式有两种:
- 方法一:使用
cv2.cvtColor() - 方法二:手动翻转通道
import cv2
# 读取图像,默认BGR
img_bgr = cv2.imread('image.jpg')
# 方法一:cvtColor转换
img_rgb = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2RGB)
# 方法二:手动翻转通道(性能略高)
img_rgb_manual = img_bgr[:, :, ::-1]
cvtColor,因为代码可读性更好。但在需要极致性能的嵌入式设备上,我会用切片翻转,能省下几毫秒。
3.1.2 BGR与HSV的转换
HSV(色调、饱和度、明度)在目标检测中非常有用。为什么?因为HSV把颜色和亮度分开了。你在做颜色阈值分割时,HSV比BGR稳定得多。
我记得有一次做交通标志检测,红色标志在BGR空间里受光照影响很大,换成HSV后,只用H通道就能稳定提取红色区域。
# BGR转HSV
img_hsv = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2HSV)
# HSV各通道范围
# H: 0-179, S: 0-255, V: 0-255
3.1.3 色彩空间对比
| 色彩空间 | 通道含义 | YOLO中的典型用途 |
|---|---|---|
| BGR | 蓝、绿、红 | OpenCV默认读取格式 |
| RGB | 红、绿、蓝 | 模型训练输入格式 |
| HSV | 色调、饱和度、明度 | 颜色阈值分割、目标跟踪 |
| 灰度 | 单通道亮度 | 轻量模型输入、边缘检测 |
3.2 图像缩放与几何变换
YOLO模型要求输入图像是固定尺寸的,比如640x640。但实际拍摄的图像千奇百怪,所以缩放是必须的。除此之外,数据增强时还会用到旋转、平移、仿射变换。
3.2.1 图像缩放:cv2.resize()
resize是最常用的函数。但插值方法的选择,直接影响图像质量和推理速度。
# 缩放到640x640
img_resized = cv2.resize(img, (640, 640))
# 按比例缩放(宽度不变,高度缩放)
h, w = img.shape[:2]
new_w = 640
new_h = int(h * new_w / w)
img_resized = cv2.resize(img, (new_w, new_h))
插值方法的选择:
- INTER_NEAREST:最近邻插值,速度快,但锯齿明显。适合做分类任务的数据增强。
- INTER_LINEAR:双线性插值,默认方法。质量适中,速度也快。我大部分场景都用这个。
- INTER_CUBIC:双三次插值,质量高,但慢。适合放大图像。
- INTER_AREA:区域插值,适合缩小图像。YOLO预处理中,我推荐用这个,能减少混叠效应。
INTER_AREA缩小,再用INTER_LINEAR放大到目标尺寸。这样既能保持细节,又能减少锯齿。
3.2.2 仿射变换:cv2.warpAffine()
仿射变换是YOLO数据增强的核心。旋转、平移、缩放、剪切,本质上都是仿射变换。
它的原理很简单:用一个2x3的变换矩阵,把原图上的点映射到新图上。
import numpy as np
# 旋转30度,中心点不变
h, w = img.shape[:2]
center = (w // 2, h // 2)
M = cv2.getRotationMatrix2D(center, 30, 1.0)
img_rotated = cv2.warpAffine(img, M, (w, h))
# 平移 (向右50像素,向下30像素)
M_trans = np.float32([[1, 0, 50], [0, 1, 30]])
img_translated = cv2.warpAffine(img, M_trans, (w, h))
这里有个坑:旋转后,图像边缘会被裁剪掉。如果你不想丢失信息,需要手动计算新画布尺寸。
3.2.3 透视变换:cv2.warpPerspective()
透视变换比仿射变换更灵活,可以模拟视角变化。比如,把一张倾斜拍摄的文档矫正为正视图。
# 定义四个源点和四个目标点
src_pts = np.float32([[0, 0], [w-1, 0], [0, h-1], [w-1, h-1]])
dst_pts = np.float32([[50, 0], [w-50, 0], [0, h], [w, h]])
M_persp = cv2.getPerspectiveTransform(src_pts, dst_pts)
img_persp = cv2.warpPerspective(img, M_persp, (w, h))
3.3 图像滤波:去噪与保边
滤波在YOLO预处理中,主要用于去除噪声。但要注意:滤波会损失细节,所以不能过度使用。
3.3.1 高斯滤波
高斯滤波是最常用的平滑滤波器。它用高斯核做卷积,能有效去除高斯噪声。
# 高斯滤波,核大小5x5,标准差1.5
img_gaussian = cv2.GaussianBlur(img, (5, 5), 1.5)
核大小必须是奇数。标准差越大,图像越模糊。我一般用(5,5)和1.5的组合,效果比较均衡。
3.3.2 中值滤波
中值滤波对椒盐噪声特别有效。它用邻域内像素的中值代替中心像素值。
# 中值滤波,核大小5
img_median = cv2.medianBlur(img, 5)
中值滤波的核大小也必须是奇数。我建议在图像有大量椒盐噪声时使用,比如摄像头传输不稳定导致的噪点。
3.3.3 双边滤波
双边滤波是“保边去噪”的神器。它同时考虑空间距离和像素值差异,所以能在平滑噪声的同时保留边缘。
# 双边滤波,d=9, sigmaColor=75, sigmaSpace=75
img_bilateral = cv2.bilateralFilter(img, 9, 75, 75)
参数说明:
- d:邻域直径
- sigmaColor:颜色空间标准差,值越大,颜色差异大的像素也会被纳入平滑
- sigmaSpace:空间标准差,值越大,距离远的像素也会被纳入平滑
3.3.4 三种滤波对比
| 滤波类型 | 适用噪声 | 边缘保留 | 速度 | YOLO中的典型用途 |
|---|---|---|---|---|
| 高斯滤波 | 高斯噪声 | 差 | 快 | 预处理去噪 |
| 中值滤波 | 椒盐噪声 | 中等 | 中等 | 摄像头噪点去除 |
| 双边滤波 | 高斯噪声+保留边缘 | 好 | 慢 | 后处理局部增强 |
3.4 本章知识体系
下面这张图,把本章的核心知识点串起来了。你可以把它当作一个快速参考。
嗯,这一章的内容就到这里。色彩空间转换、几何变换和图像滤波,这三个东西在YOLO的整个生命周期里都会反复用到。你可以在自己的项目里多试试不同的参数组合,找到最适合你场景的那一套。