模型看到的不是照片:一文搞懂像素、通道与张量

第 02 讲《图像数字化:像素、通道与张量》公众号整理版

整理说明:本文基于 B 站视频《第02讲〈图像数字化:像素、通道与张量〉》的公开信息、课程课件主线,并结合 OpenCV、PIL、PyTorch 与 YOLO 输入预处理常见流程进行原创化整理。本文不是逐字稿,重点是把“图片如何变成模型能算的数字”整理成科研小白可以照着学、照着查、照着跑的教程。

请添加图片描述

你在屏幕上看到一张图片,可能是一只猫、一辆车、一张医学影像,也可能是交通路口。

但模型看到的不是“猫”,不是“车”,也不是“病灶”。模型看到的是一组排好队的数字。

所以第 02 讲只解决一个核心问题:

模型看到的不是照片,而是一组怎样排列的数字?

这件事一旦讲清楚,后面学 CNN、YOLO26、数据增强、训练报错都会顺很多。因为几乎所有视觉模型的第一步,都是把图片变成张量。


02_图像数字化:像素、通道与张量

01 为什么小白总卡在 shape 上?

很多同学刚开始学 YOLO 或深度学习,最容易卡在 4 个地方:

  1. 图片明明能打开,为什么到模型里变成 3 x 640 x 640
  2. RGBBGRHWCCHWBCHW 到底谁在前谁在后?
  3. 图片尺寸、通道顺序、归一化写错,为什么代码不一定报错,但结果会很离谱?
  4. 同一张图在 OpenCV、PIL、PyTorch 里为什么读出来不一样?

这几个问题本质上都指向同一件事:

图片不是直接进入模型的。它要先被解码成像素,再整理成通道,再变成张量,最后按照模型要求的 shape 输入网络。

请添加图片描述

把这条链路记住:

图片文件 → 像素矩阵 → 通道排列 → 尺寸统一 → 数值缩放 → 维度转换 → batch 张量 → 进入网络

以后遇到输入错误,先沿着这条链往回查,不要第一反应就怀疑模型结构。


02 六个核心概念,一次说清楚

概念 严格一点的说法 小白理解
像素 Pixel 图像的最小采样单元,每个位置记录颜色或强度 一张图由很多小格子组成
通道 Channel 颜色或特征的维度 RGB 图像有红、绿、蓝三层
张量 Tensor 多维数组,是神经网络处理数据的基本容器 模型能计算的数字盒子
Shape 张量每个维度的长度 1 x 3 x 640 x 640 里的每个数字
Batch 一次送入模型的一组样本 batch=8 表示一次处理 8 张图
归一化 把数值缩放到稳定范围 常见做法是把 0-255 除以 255

初学阶段先别急着背复杂公式。你只要能回答三件事:

  1. 这个数字从哪里来?
  2. 它现在是什么形状?
  3. 下一步要送到哪里?

能把这三件事讲明白,shape 就不再是玄学。


03 像素:一张图首先是一张数字表

一张 640 x 480 的灰度图,可以理解为一个 480 行 x 640 列 的二维矩阵。

每个位置有一个数,表示这个位置的亮度。常见 8 位图像的像素范围是 0 到 255:

数值 含义
0 最暗,接近黑色
255 最亮,接近白色
中间值 不同程度的灰度

彩色图像多了颜色通道。最常见的 RGB 图像,每个像素不是一个数,而是三个数:

[R, G, B]

例如:

颜色 RGB 数值
红色 [255, 0, 0]
绿色 [0, 255, 0]
蓝色 [0, 0, 255]
白色 [255, 255, 255]

这就是“模型看到的是数字”的第一层含义:图片先变成一堆像素值。


04 2 x 2 RGB 小图,手算一次图片变张量

为了彻底看懂,我们构造一张最小彩色图:

位置 颜色 RGB
左上 红色 [255, 0, 0]
右上 绿色 [0, 255, 0]
左下 蓝色 [0, 0, 255]
右下 白色 [255, 255, 255]

请添加图片描述

如果用 HWC 表示,它可以写成:

[
  [[255, 0, 0],   [0, 255, 0]],
  [[0, 0, 255],   [255, 255, 255]]
]

这个 shape 是:

H x W x C = 2 x 2 x 3

其中:

字母 含义 这里的值
H Height,高度,有几行 2
W Width,宽度,有几列 2
C Channel,通道数 3

但深度学习框架里,模型常常更喜欢 CHWBCHW

HWC: 2 x 2 x 3
CHW: 3 x 2 x 2
BCHW: 1 x 3 x 2 x 2

注意,数字总量没有变,只是排列顺序变了。


05 HWC、CHW、BCHW 到底怎么记?

请添加图片描述

最简单的记法:

格式 常见场景 例子 读法
H x W x C 图片存储、OpenCV/PIL 转 NumPy 后常见 640 x 640 x 3 高、宽、通道
C x H x W 单张图送入 PyTorch 模型前常见 3 x 640 x 640 通道、高、宽
B x C x H x W 模型训练和推理常见 8 x 3 x 640 x 640 batch、通道、高、宽

为什么要加 B

因为训练时通常不是一张一张喂给模型,而是一批一批喂。8 x 3 x 640 x 640 的意思是:

维度 含义
8 一次处理 8 张图
3 每张图 3 个通道
640 高度 640
640 宽度 640

一句话总结:

HWC 像图片,CHW 像单张模型输入,BCHW 像一批模型输入。


06 OpenCV、PIL、PyTorch 为什么容易混?

最常见的坑是通道顺序。

工具 常见读取结果 通道习惯 小白提醒
OpenCV NumPy 数组 默认 BGR 显示或送模型前常需要转 RGB
PIL Image 对象 常见 RGB 转 NumPy 后通常是 HWC
PyTorch Tensor 常用 CHW/BCHW 通常要 permutetranspose

举个例子:

import cv2

img_bgr = cv2.imread("test.jpg")
img_rgb = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2RGB)

如果忘了 BGR 转 RGB,代码可能不报错,但颜色语义已经变了。对人来说只是颜色怪,对模型来说就是输入分布变了。

这也是为什么很多检测结果“看起来很玄”:模型还没开始推理,输入已经悄悄错了。


07 归一化:为什么要除以 255?

常见图片像素范围是 0 到 255,但神经网络训练更喜欢比较稳定的数值范围。

所以经常会做:

x_norm = x / 255.0

这样像素会从:

0-255

变成:

0-1

这一步不是为了“好看”,而是为了让后续计算更稳定。数值尺度差太大,可能让激活值、梯度和优化过程变得不舒服。

但要注意:

训练时怎么归一化,推理时也要保持一致。

如果训练时输入是 0-1,推理时却喂 0-255,模型结果很可能异常。


08 Resize 和 Letterbox:尺寸统一不是简单拉伸

模型通常要求统一输入尺寸,例如 640 x 640

但真实图片可能是横图、竖图、方图,尺寸各不相同。常见处理有两类:

方法 做法 风险
直接 resize 强行拉到指定大小 可能改变目标比例
letterbox 保持比例缩放,再补边 更适合检测任务

请添加图片描述

目标检测尤其要注意比例。因为检测任务不仅要认出类别,还要预测位置。如果几何比例乱了,框的位置学习也会被影响。

所以第一次做自己的数据集时,请记录:

  1. 原图尺寸是多少?
  2. 训练输入尺寸是多少?
  3. 是否使用 letterbox?
  4. 标签坐标是否和图片尺寸对应?
  5. 可视化标签时,框是否真的贴住目标?

09 实操教程:从 0 检查一张图的输入张量

下面这套流程适合科研小白跟着做。第一次不要急着训练 YOLO26,先把一张图怎么变成张量跑通。

第一步:创建环境

conda create -n image-tensor python=3.10 -y
conda activate image-tensor
pip install numpy pillow opencv-python matplotlib torch

如果暂时不想装 PyTorch,可以先跑 NumPy、PIL、OpenCV 部分。

第二步:手写 2 x 2 RGB 小图

新建 01_make_2x2_rgb_tensor.py

import numpy as np

img_hwc = np.array([
    [[255, 0, 0], [0, 255, 0]],
    [[0, 0, 255], [255, 255, 255]],
], dtype=np.uint8)

img_chw = np.transpose(img_hwc, (2, 0, 1))
img_bchw = np.expand_dims(img_chw, axis=0)
img_norm = img_bchw.astype(np.float32) / 255.0

print("HWC shape:", img_hwc.shape)
print(img_hwc)
print("CHW shape:", img_chw.shape)
print(img_chw)
print("BCHW shape:", img_bchw.shape)
print("normalized range:", img_norm.min(), img_norm.max())

你要看懂这三行:

np.transpose(img_hwc, (2, 0, 1))
np.expand_dims(img_chw, axis=0)
img_bchw.astype(np.float32) / 255.0

它们分别对应:

  1. HWC 变成 CHW
  2. 加一个 batch 维度,变成 BCHW
  3. 把 0-255 缩放到 0-1。

第三步:读取真实图片并打印 shape

准备一张 test.jpg,新建 02_inspect_image_shape.py

from pathlib import Path

import cv2
import numpy as np
from PIL import Image

image_path = Path("test.jpg")
if not image_path.exists():
    raise FileNotFoundError("请先放一张 test.jpg 到当前目录")

img_cv = cv2.imread(str(image_path))
print("OpenCV:", type(img_cv), img_cv.shape, img_cv.dtype)
print("OpenCV first pixel BGR:", img_cv[0, 0].tolist())

img_rgb = cv2.cvtColor(img_cv, cv2.COLOR_BGR2RGB)
print("After BGR->RGB first pixel:", img_rgb[0, 0].tolist())

img_pil = Image.open(image_path).convert("RGB")
img_np = np.array(img_pil)
print("PIL to NumPy:", type(img_np), img_np.shape, img_np.dtype)
print("PIL first pixel RGB:", img_np[0, 0].tolist())

img_chw = np.transpose(img_np, (2, 0, 1))
img_bchw = np.expand_dims(img_chw, axis=0).astype(np.float32) / 255.0
print("CHW:", img_chw.shape)
print("BCHW normalized:", img_bchw.shape, img_bchw.min(), img_bchw.max())

如果你能读懂输出,就说明你已经掌握了图像数字化的核心。

第四步:做一次 letterbox 尺寸检查

新建 03_letterbox_check.py

import cv2
import numpy as np


def letterbox(img, new_shape=(640, 640), color=(114, 114, 114)):
    h, w = img.shape[:2]
    new_h, new_w = new_shape
    scale = min(new_w / w, new_h / h)
    resized_w, resized_h = int(round(w * scale)), int(round(h * scale))
    pad_w, pad_h = new_w - resized_w, new_h - resized_h
    left, right = pad_w // 2, pad_w - pad_w // 2
    top, bottom = pad_h // 2, pad_h - pad_h // 2

    resized = cv2.resize(img, (resized_w, resized_h))
    padded = cv2.copyMakeBorder(resized, top, bottom, left, right, cv2.BORDER_CONSTANT, value=color)
    return padded, scale, (left, top)


img = cv2.imread("test.jpg")
if img is None:
    raise FileNotFoundError("请先放一张 test.jpg 到当前目录")

out, scale, pad = letterbox(img)
print("original:", img.shape)
print("letterbox:", out.shape)
print("scale:", scale)
print("pad left/top:", pad)
cv2.imwrite("letterbox_preview.jpg", out)

运行后会生成 letterbox_preview.jpg。你要观察:图片有没有被强行拉伸,边缘是否补了灰边。


10 用这张表定位输入问题

请添加图片描述

异常现象 优先检查 常见原因
颜色明显不对 RGB/BGR OpenCV 读图后没转 RGB
shape 报错 HWC/CHW/BCHW 少了 batch 维或通道位置错
检测框乱飘 resize/letterbox/标签 几何比例或标签坐标对应不上
结果置信度很低 归一化 训练和推理数值范围不一致
训练显存爆掉 batch/imgsz batch 太大或图像尺寸太大
小目标变差 resize 策略 缩放后目标太小,信息丢失

排错顺序建议:

  1. 先打印原图 shape。
  2. 再打印预处理后 shape。
  3. 再检查通道顺序。
  4. 再检查数值范围。
  5. 最后可视化图片和标签。

这比盲目改模型、改学习率、改 epoch 更靠谱。


11 学完本讲,你要能回答这 5 个问题

  1. 一个 RGB 像素为什么有三个数?
  2. 8 x 3 x 640 x 640 每一维分别代表什么?
  3. 为什么 OpenCV 读取的图片经常要从 BGR 转 RGB?
  4. 为什么归一化会影响训练和推理稳定性?
  5. resize 和 letterbox 对检测任务有什么区别?

如果你能把这 5 个问题讲给别人听,第 02 讲就真正学进去了。


12 最后总结

本讲最重要的一句话:

模型看到的不是照片,而是按规则排列的数字张量。

一张图片进入模型前,通常要经过:

读取图片 → 解码成像素 → 拆分通道 → 调整尺寸 → 改变维度顺序 → 数值归一化 → 加 batch 维度 → 进入网络

后面学 YOLO26 时,只要遇到输入问题,就回到这条链路检查。

如果这篇文章帮你把 RGBHWCCHWBCHW 想清楚了,建议先关注、收藏,也可以转给正在被 shape 折磨的同学。

评论区留言:张量。下一篇继续整理第 03 讲:有了输入张量之后,标签和 Loss 如何告诉模型哪里做错。


参考资料

  1. B 站视频:《第02讲〈图像数字化:像素、通道与张量〉》
    https://www.bilibili.com/video/BV1GaJ56jEeM/
  2. PyTorch Tensor 文档
    https://docs.pytorch.org/docs/stable/tensors.html
  3. Pillow Image 文档
    https://pillow.readthedocs.io/en/stable/reference/Image.html
  4. OpenCV 图像读写文档
    https://docs.opencv.org/4.x/d4/da8/group__imgcodecs.html
  5. Ultralytics YOLO Predict 文档
    https://docs.ultralytics.com/modes/predict/
Logo

免费领 200 小时云算力,进群参与显卡、AI PC 幸运抽奖

更多推荐