🧠 第六章 自然语言处理
📄 一句话概括

三张不同尺寸、不同模式的图,走同一条流水线:转 RGB、中心裁方、缩放到 512×512、 归一化到 [-1, 1]。 100×80 变成 80×80 再放大,120×40 只剩中间 40×40,均值 -0.157 说明条纹图偏暗。

📝 本节和前面几节的关系

上一节算显存,这一节处理输入。 所有视觉模型的训练数据都要先过这一道,而且这一步做错,后面全白费。

一、预处理要解决的是「不齐」

原始图片有四个不齐:尺寸不齐(100×80、64×64、120×40)、通道数不齐(RGB、灰度)、 色彩空间不齐、数值范围不齐。 模型只能吃固定形状的张量。 所以预处理的目标不是「让图变好看」,是「让所有输入变成同一个形状、同一个范围」。 Demo 的四步对应四个不齐:convert 统一通道、crop 统一长宽比、resize 统一尺寸、归一化统一数值范围。 顺序不能换。先缩放再裁剪,会把边缘内容放大之后再切掉,等于白算。

二、为什么是中心裁剪

100×80 的图,裁成 80×80 会丢掉左右各 10 列。Demo 从中间裁,两边各丢 10。 为什么从中间?因为拍照的人通常把主体放在中间。这是个经验假设,不成立的时候会切掉主体。 真实系统有三种应对:随机裁剪(训练时做数据增强)、按内容检测框裁、 直接拉伸(sacrifice 比例换内容完整)。 拉伸最省事,但会把圆脸拉成椭圆脸。做人物、商品类模型时不能这么干。

三、归一化到 [-1, 1] 的理由

像素原始范围是 0 到 255。Demo 先除 127.5 再减 1,落到 [-1, 1]。 为什么要减到负数?因为模型权重初始化在 0 附近,输入全正会让梯度方向偏向一边。 均值在 0 附近训练更稳。 为什么是 [-1, 1] 而不是 [0, 1]?因为扩散模型的训练数据就是这么处理的,推理时必须对齐。 对不齐,出来的图会发灰。 这个坑很常见:模型没错、提示词没错,就是图发灰,最后发现是归一化没对齐。

四、灰度图那一步容易出错

第二张是单通道灰度,convert 成 RGB 之后三个通道值一样。 均值 -0.012 接近 0,标准差 0.580 不小,说明这张渐变图的灰度铺得很开。 如果直接把单通道张量喂进三通道模型,形状对不上,立刻报错——这种错好查。 难查的是「通道数对上了但含义错了」,比如把 BGR 当 RGB 用。 OpenCV 读图默认是 BGR,PIL 默认是 RGB。两者混用,颜色会整个错位,而且不报错。 这是历史上最常见的一类事故。

五、动手 Demo:图像预处理四步:转色彩、裁方、缩放、归一化

用 numpy 造三张合成图(100×80 的 RG 渐变、64×64 的单通道横条、120×40 的彩色竖条); 每张都走同一条流水线——转 RGB、按短边中心裁方、缩放到 512×512、除以 127.5 再减 1; 打出每步的尺寸和最终的均值、标准差。

im2 = im.convert('RGB')                      # 统一通道
side = min(w, h)
box = ((w - side) // 2, (h - side) // 2, (w + side) // 2, (h + side) // 2)
im3 = im2.crop(box).resize((512, 512), Image.LANCZOS)
arr = np.asarray(im3, dtype=np.float32) / 127.5 - 1.0

实跑输出:

--------------------------------------------------------------------
图像预处理:转色彩、裁方、缩放、归一化
--------------------------------------------------------------------
  输入张数                 3
  目标边长                 512 × 512
  归一化                   像素除以 127.5 再减 1,落到 [-1, 1]
  编号   原始尺寸   模式   裁剪后   缩放后      均值   标准差
  ----------------------------------------------------------
  图 1   100×80     RGB    80×80    512×512   -0.001    0.432
  图 2   64×64      L      64×64    512×512   -0.012    0.580
  图 3   120×40     RGB    40×40    512×512   -0.157    0.682

  第 2 张原本是单通道灰度,convert 之后三通道值一样,所以均值偏低但不为零。这一步的目的不是「变好看」,而是让所有输入张量的形状和数值范围统一。
  归一化用 [-1, 1] 而不是 [0, 1],是因为扩散模型的训练数据也是这么做的,推理时必须对齐;对不齐出来的图会发灰。

第一张 100×80 裁成 80×80,左右各丢 10 列。缩放用 LANCZOS,比默认的双线性锐一点,代价是慢。 第二张 64×64 本来就是正方形,裁剪没丢东西,直接从 64 放大到 512——放大了 8 倍,细节全是插值出来的, 不增加信息。 第三张 120×40 损失最大:裁完只剩中间 40×40,丢了三分之二的画面。 这就是中心裁剪的代价,长条图特别吃亏。 均值 -0.001/-0.012/-0.157,标准差 0.432/0.580/0.682。 第三张均值明显偏负,因为它有一半像素是暗色的(40 那个灰阶)。

30 秒实验

  1. 把中心裁剪改成直接拉伸到 512×512,看第三张图会不会变形、均值变多少。
  2. 把归一化改成除以 255 落到 [0, 1],看均值标准差怎么变。
  3. 想一想:为什么 BGR 和 RGB 混用不会报错,却会导致事故?

常见误区

  1. 「预处理只是缩放」——通道、色彩空间、数值范围一个都不能漏。
  2. 「先缩放再裁剪一样」——先缩放会把要丢掉的部分也算一遍,白费。
  3. 「归一化随便定」——和训练时不一致,出来的图会发灰。
自测三问(点开对答案) 1. 预处理要解决的四个「不齐」是什么? —— 尺寸、通道数、色彩空间、数值范围。 2. 为什么裁剪要放在缩放之前? —— 先缩放再裁剪,等于把最终要丢掉的像素也缩放了一遍,白算。 3. 归一化到 [-1, 1] 而不是 [0, 1] 的原因是什么? —— 要和扩散模型训练时的数据处理对齐,否则推理出来的图发灰。

图整理好了。下一节把它变成向量,做图像检索。

相关笔记