4. 数据预处理:图像去畸变、数据归一化、数据增强

数据预处理这步,说实话,是很多初学者容易忽略的环节。我刚开始做嵌入式AI的时候,也犯过这个错——模型在服务器上跑得好好的,一上板子就崩。后来排查了半天,发现是数据预处理没对齐。嗯,这里面的坑,我一个个给你讲清楚。

核心观点:数据预处理不是可有可无的“洗菜”步骤,它直接决定了模型能不能收敛、泛化能力好不好、以及最终能不能在嵌入式设备上稳定运行。

4.1 图像去畸变

先说说图像去畸变。你想想看,摄像头拍出来的照片,尤其是广角镜头,边缘是不是有点弯曲?这就是畸变。我遇到过最夸张的一次,是客户用鱼眼镜头采集数据,模型训练时准确率90%,一上实车测试直接掉到60%。为什么?因为训练时没做去畸变,模型学到的全是“弯的”特征。

去畸变的原理其实不复杂。说白了,就是用一个数学模型把弯曲的像素点“拉直”。常用的模型是径向畸变和切向畸变,OpenCV里直接有现成的函数:

import cv2
import numpy as np

# 假设你已经标定好了相机内参和畸变系数
mtx = np.array([[fx, 0, cx],
                [0, fy, cy],
                [0,  0,  1]])
dist = np.array([k1, k2, p1, p2, k3])

# 去畸变
img_undistorted = cv2.undistort(img, mtx, dist, None, mtx)

这里有个坑:去畸变后的图像边缘会被裁剪掉一部分。我建议你保留完整的图像区域,用黑色填充边缘,这样不会丢失信息。当然,如果你对实时性要求高,也可以用cv2.initUndistortRectifyMap提前算好映射表,运行时直接查表,速度能快不少。

我的经验:在嵌入式设备上,去畸变最好在采集端就完成,不要放到推理时做。因为畸变校正涉及插值运算,算力消耗不小。我曾在树莓派上试过实时去畸变,1080p图像只能跑15帧,后来改成离线预处理,推理帧率直接翻倍。

4.2 数据归一化

数据归一化,这个我多说两句。很多新手觉得归一化就是“除以255”,其实没那么简单。

为什么要归一化?因为神经网络的激活函数(比如ReLU、Sigmoid)对输入范围很敏感。如果输入像素值在0~255之间,梯度会变得很大,训练时容易震荡。归一化之后,数据范围缩到0~1或者-1~1,梯度更稳定,收敛也更快。

常用的归一化方式有两种:

方法 公式 适用场景
Min-Max归一化 (x - min) / (max - min) 图像像素值(0~255)
Z-score标准化 (x - mean) / std 特征分布未知的情况

我个人习惯用Min-Max归一化,简单直接。但要注意:归一化的参数(min、max、mean、std)必须从训练集计算,然后应用到验证集和测试集。千万别每个数据集单独算,否则数据分布不一致,模型就废了。

# 正确的做法
train_mean = np.mean(train_images) / 255.0
train_std = np.std(train_images) / 255.0

# 训练、验证、测试都用同一组参数
train_norm = (train_images / 255.0 - train_mean) / train_std
val_norm = (val_images / 255.0 - train_mean) / train_std
test_norm = (test_images / 255.0 - train_mean) / train_std

避坑指南:我曾经在项目里犯过一个低级错误——把归一化写在了数据增强之后。结果每次随机裁剪后,图像的均值和标准差都变了,模型训练了三天都没收敛。后来才发现,归一化应该放在数据增强之后,但归一化参数必须在增强之前计算好。

4.3 数据增强

数据增强,说白了就是“无中生有”。你只有1000张图片,但通过随机裁剪、翻转、色彩抖动,可以变出10000张。模型见过的花样多了,泛化能力自然就强。

常用的数据增强方法:

  • 随机裁剪:从原图中随机抠出一块,缩放到固定尺寸。我建议裁剪比例在0.8~1.0之间,太小了会丢失关键信息。
  • 色彩抖动:随机调整亮度、对比度、饱和度、色相。这个对光照变化大的场景特别有用。我在做自动驾驶项目时,就靠色彩抖动把雨天场景的识别率从70%提到了85%。
  • 随机翻转:水平翻转(左右镜像)是安全的,垂直翻转(上下颠倒)要慎用——你想想,路标倒过来还是路标吗?
  • 随机旋转:一般控制在±15°以内,旋转太大图像边缘会丢失信息。
import torchvision.transforms as transforms

# 定义数据增强流水线
train_transform = transforms.Compose([
    transforms.RandomResizedCrop(size=224, scale=(0.8, 1.0)),
    transforms.RandomHorizontalFlip(p=0.5),
    transforms.ColorJitter(brightness=0.2, contrast=0.2, saturation=0.2, hue=0.1),
    transforms.ToTensor(),
    transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
])

# 验证集和测试集只做标准化,不做增强
val_transform = transforms.Compose([
    transforms.Resize(256),
    transforms.CenterCrop(224),
    transforms.ToTensor(),
    transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
])

我的建议:数据增强不是越多越好。我见过有人把旋转、裁剪、色彩抖动、噪声、模糊全加上,结果模型训练了100个epoch还没收敛。一般来说,选2~3种增强方法就够了,关键是要跟你的应用场景匹配。比如做人脸识别,重点做光照和角度变化;做OCR,重点做透视变换和模糊。

4.4 数据集划分

数据集划分,这个看似简单,但很多人栽跟头。标准的划分比例是:训练集70%、验证集15%、测试集15%。但要注意几个细节:

  • 分层采样:如果数据集类别不平衡(比如猫1000张,狗100张),一定要按类别比例分层采样。否则验证集里可能一只狗都没有,模型过拟合了你也发现不了。
  • 时间顺序:如果是视频流数据,按时间顺序划分。不要随机打乱,否则模型会“偷看”未来的数据。
  • 人员/设备隔离:如果是多个人采集的数据,确保同一个人/设备的数据只出现在一个集合里。我踩过这个坑——训练集和测试集用了同一个人的数据,结果模型在测试集上准确率98%,一换新人直接掉到60%。
from sklearn.model_selection import train_test_split

# 分层采样
X_train, X_temp, y_train, y_temp = train_test_split(
    X, y, test_size=0.3, stratify=y, random_state=42
)
X_val, X_test, y_val, y_test = train_test_split(
    X_temp, y_temp, test_size=0.5, stratify=y_temp, random_state=42
)

避坑指南:测试集一旦划分好,就绝对不要碰。我见过有人调参调不好,偷偷拿测试集来验证,结果模型在测试集上过拟合了,上线后一塌糊涂。记住:测试集是用来评估最终模型的,不是用来调参的。

4.5 本章知识体系

下面这张图,是我自己总结的数据预处理流程。你可以把它当作一个检查清单,每次做项目时对照着来:

数据预处理流程 原始图像数据 图像去畸变 数据增强 数据归一化 数据集划分(训练/验证/测试) 预处理完成的数据 随机裁剪、色彩抖动、翻转 Min-Max / Z-score

这张图把整个流程串起来了。你从原始数据开始,先做去畸变,然后分两条路——一条做数据增强,一条做归一化。注意,这两步的顺序不能乱:先增强,再归一化。因为增强会改变像素值,归一化要基于增强后的数据重新计算。

最后,把处理好的数据按比例划分成训练集、验证集、测试集。记住:划分要在预处理之后做,否则你无法保证每个集合的数据分布是一致的。

总结一下:数据预处理不是“洗菜”,而是“配菜”。菜洗得再干净,配菜顺序错了,炒出来的菜也不好吃。去畸变、归一化、增强、划分,每一步都有它的道理。你按这个流程走,至少能避开80%的坑。


专注资料整理