3、图像处理基础:OpenCV 与 TensorFlow 图像加载、图像增强(翻转、旋转、裁剪)、数据标准化

好,咱们进入第三章。说实话,这一章是后面所有实战项目的「地基」。你想想看,不管是做目标检测还是OCR,第一步都是把图像喂给模型。如果连图像加载都搞不定,或者数据增强做得不对,模型训练效果肯定大打折扣。

我个人习惯把图像处理分成两大块:OpenCV 负责「看得见」的操作,比如翻转、旋转、裁剪;TensorFlow 负责「数据流」里的自动化处理,比如在训练时实时增强。两者配合好了,事半功倍。

3.1 图像加载:OpenCV vs TensorFlow

先说说加载图像。OpenCV 和 TensorFlow 都能加载,但细节上有坑。

3.1.1 OpenCV 加载

OpenCV 默认用 cv2.imread(),返回的是 BGR 格式。嗯,这里要注意:不是我们常见的 RGB。我第一次用 OpenCV 显示图像时,发现颜色偏蓝,查了半天才发现是通道顺序的问题。

import cv2

# 加载图像,默认 BGR
img_bgr = cv2.imread('cat.jpg')

# 转成 RGB
img_rgb = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2RGB)

# 显示
cv2.imshow('Cat', img_bgr)
cv2.waitKey(0)
cv2.destroyAllWindows()
我曾经踩过的坑:用 OpenCV 加载图像后直接传给 TensorFlow 模型,结果模型预测完全不对。原因就是通道顺序不一致。记住:OpenCV 是 BGR,TensorFlow 是 RGB。

3.1.2 TensorFlow 加载

TensorFlow 提供了 tf.io.read_file()tf.image.decode_image(),返回的是 RGB 格式,省去了转换的麻烦。

import tensorflow as tf

# 读取文件
image_raw = tf.io.read_file('cat.jpg')

# 解码成 RGB 张量
image_tensor = tf.image.decode_image(image_raw, channels=3)

# 查看形状
print(image_tensor.shape)  # (height, width, 3)

我个人更推荐在训练 pipeline 里用 TensorFlow 的方式加载,因为可以直接跟 tf.data.Dataset 配合,实现高效的数据流。

3.2 图像增强:翻转、旋转、裁剪

数据增强说白了就是「无中生有」。你想想看,一张猫的照片,左右翻转一下,模型就以为看到了另一只猫。这样训练出来的模型泛化能力更强。

3.2.1 翻转

翻转分两种:水平翻转和垂直翻转。水平翻转最常用,因为现实世界中物体左右对称的情况很多。

import cv2
import numpy as np

img = cv2.imread('cat.jpg')

# 水平翻转
flip_h = cv2.flip(img, 1)

# 垂直翻转
flip_v = cv2.flip(img, 0)

# 同时翻转
flip_hv = cv2.flip(img, -1)

用 TensorFlow 更简单:

# 随机水平翻转(训练时常用)
augmented = tf.image.random_flip_left_right(image_tensor)

# 随机垂直翻转(慎用,比如文字识别就不适合)
augmented = tf.image.random_flip_up_down(image_tensor)
我的建议:做 OCR 时不要用垂直翻转,文字倒过来就没意义了。但做目标检测时,水平翻转基本是标配。

3.2.2 旋转

旋转操作要小心。90度、180度、270度这种「硬旋转」没问题,但任意角度旋转会引入黑色填充区域,影响模型训练。

# OpenCV 旋转
(h, w) = img.shape[:2]
center = (w // 2, h // 2)

# 旋转45度,缩放1.0
M = cv2.getRotationMatrix2D(center, 45, 1.0)
rotated = cv2.warpAffine(img, M, (w, h))

# TensorFlow 旋转
rotated_90 = tf.image.rot90(image_tensor, k=1)  # 顺时针90度
rotated_180 = tf.image.rot90(image_tensor, k=2) # 180度

我记得有一次做车牌识别项目,发现旋转角度超过30度后,模型准确率直线下降。后来我限制了旋转范围在 ±15 度,效果就好了很多。

3.2.3 裁剪

裁剪有两种:中心裁剪和随机裁剪。中心裁剪适合固定输入尺寸的场景,随机裁剪则能增加数据多样性。

# OpenCV 中心裁剪
h, w = img.shape[:2]
crop_size = 224
start_x = w // 2 - crop_size // 2
start_y = h // 2 - crop_size // 2
crop_img = img[start_y:start_y+crop_size, start_x:start_x+crop_size]

# TensorFlow 随机裁剪
crop_img = tf.image.random_crop(image_tensor, size=[224, 224, 3])

# 调整大小到固定尺寸
resized = tf.image.resize(crop_img, [224, 224])
关键点:裁剪后一定要 resize 到模型输入尺寸。我见过有人直接拿裁剪后的图像训练,结果模型因为尺寸不一致报错,折腾了半天。

3.3 数据标准化

标准化就是把像素值从 [0, 255] 映射到某个固定范围,比如 [0, 1] 或 [-1, 1]。为什么要做?说白了,神经网络对输入数据的尺度很敏感。如果像素值范围太大,梯度更新会不稳定。

3.3.1 简单缩放

# 缩放到 [0, 1]
normalized = image_tensor / 255.0

# 缩放到 [-1, 1]
normalized = (image_tensor / 127.5) - 1.0

3.3.2 均值-标准差标准化

更常用的方法是减去均值再除以标准差。这样数据分布接近标准正态分布,模型收敛更快。

# 计算均值和标准差(通常用 ImageNet 的统计值)
mean = [0.485, 0.456, 0.406]
std = [0.229, 0.224, 0.225]

# 标准化
normalized = (image_tensor / 255.0 - mean) / std
我的经验:如果训练自己的数据集,可以先计算数据集的均值和标准差。但大多数情况下,直接用 ImageNet 的统计值就够用了。我做过对比,差别不大。

3.4 实战:完整的预处理 Pipeline

最后,我把上面所有操作整合成一个完整的预处理函数。这个函数可以直接用在 tf.data.Datasetmap 方法里。

def preprocess_image(image_path, target_size=(224, 224)):
    # 1. 加载
    image_raw = tf.io.read_file(image_path)
    image = tf.image.decode_image(image_raw, channels=3)
    
    # 2. 随机增强(训练时用)
    if training:
        # 随机翻转
        image = tf.image.random_flip_left_right(image)
        # 随机旋转(±15度)
        angles = tf.random.uniform([], -15, 15) * 3.14159 / 180
        image = tfa.image.rotate(image, angles)
        # 随机裁剪
        image = tf.image.random_crop(image, size=[*target_size, 3])
    else:
        # 验证/测试时只做中心裁剪
        image = tf.image.central_crop(image, central_fraction=0.875)
        image = tf.image.resize(image, target_size)
    
    # 3. 标准化
    image = tf.cast(image, tf.float32) / 255.0
    mean = tf.constant([0.485, 0.456, 0.406])
    std = tf.constant([0.229, 0.224, 0.225])
    image = (image - mean) / std
    
    return image

这个 pipeline 我用了好几年,从分类任务到检测任务都能直接套用。你只需要根据具体任务调整增强策略就行。

3.5 本章小结

这一章的内容其实不难,但细节很多。我建议你动手跑一遍代码,看看不同增强操作对图像的影响。特别是标准化这一步,很多人觉得无所谓,但实际训练时你会发现,标准化后的模型收敛速度明显更快。

下一章我们会进入真正的实战——用 TensorFlow 搭建一个图像分类模型。到时候这些预处理操作都会用上,所以这一章的基础一定要打牢。