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()
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)
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])
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
3.4 实战:完整的预处理 Pipeline
最后,我把上面所有操作整合成一个完整的预处理函数。这个函数可以直接用在 tf.data.Dataset 的 map 方法里。
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 搭建一个图像分类模型。到时候这些预处理操作都会用上,所以这一章的基础一定要打牢。