qinghuan
← 回到知识探索

AI 直觉 / 动手实验

电脑怎样把一张图变成一串数字?

用六个彩色小方格,弄清像素、坐标和 RGB 数字怎样对应。

展开本文目录
  1. 先动手看看
  2. 先给每个方格一个地址
  3. 一个颜色,可以分别记录红绿蓝
  4. 把网格排成一列,再找到原来的像素
  5. 有了数字,还没有认出物体
  6. 记住这三点
  7. 别把例子用过头
  8. 自己试一小步
  9. 查原始资料
  10. 找一份资料继续练
开始前,会这些就够了
  • 会数行列,认识 Python 列表和下标即可

你把一张猫的照片交给识图模型,程序收到的并不是“猫”这个词,而是按约定排列的数字。先把照片缩小到只有六个色块,就容易看清这一步:我们先记录哪里是什么颜色,再讨论这些颜色可能组成什么东西。

本文只看已经解码的像素数据,不比较图片文件格式。想象桌上摆着两行、每行三个小方格;每个位置都能写下一组数字,这就是接下来要亲手做的小图片。

点一个像素,看看它对应哪三个数

左上角是 (0, 0),x 向右、y 向下。点选一个位置,再调它的红、绿、蓝三个通道。

当前坐标 (2, 1),先取第 1 行,再取第 2 列(下标从 0 开始)。

一维起始下标:(1 × 3 + 2) × 3 = 15。

RGB:[64, 128, 192];各自除以 255 后:[0.251, 0.502, 0.753](显示到小数后三位)。

这是一张两行三列、共十八个通道数的小图。这里只展示已解码的八位 RGB 样本;没有训练或调用识图模型。除以 255 是本例的缩放约定。

先给每个方格一个地址

像素可以先理解为这张数字图片中的一个采样位置。本文把左上角记作坐标 (0, 0),x 向右增加,y 向下增加。因此右下角是 (2, 1),不是 (1, 2)。图片宽三格、高两格,合起来是六个像素。

代码把每一行放进一个列表,所有行再装进外层列表,所以取像素时写 image[y][x]:先找哪一行,再找这一行的哪一列。坐标习惯和数组写法并不相同,处理图片时先写明约定,能避开很多“转了九十度”的困惑。

一个颜色,可以分别记录红绿蓝

这里选择常见的八位 RGB 表示。R、G、B 分别是红、绿、蓝三个通道,每个通道用 0 到 255 的整数记录。我们用 (255, 0, 0) 表示红色,(0, 0, 255) 表示蓝色,三个通道都为零表示黑色。这是加色表示,不是三罐颜料的混合规则。

六个像素各放三个通道,一共十八个数。若紧凑地按每通道一个字节保存,纯像素部分就是十八字节;Python 列表实际占用的内存远不止这些。图片也可以有灰度、透明度或更高精度,不能把“三个整数”当成所有图片的固定格式。

把网格排成一列,再找到原来的像素

下面从上到下逐行读取,每个像素依次写入 R、G、B。右下角前面已有五个像素,每个占三个数,所以它从列表下标十五开始。先自己找出这三个数,再运行程序核对。只要同时保留宽高与排列约定,这样排成一列不会丢失原网格的信息;若只剩数字而不知道尺寸,就无法确定原来怎样分行。

最后把右下角的通道都除以 255,让数值落在 0 到 1。这里只是演示一种缩放约定,没有训练模型,也没有把颜色变成含义。真实模型还可能要求其他缩放、尺寸和通道顺序,要按它的输入说明准备。

小实验 · python
image = [
    [(255, 0, 0), (0, 255, 0), (0, 0, 255)],
    [(255, 255, 255), (0, 0, 0), (64, 128, 192)],
]
height, width = len(image), len(image[0])
flat = [channel for row in image for pixel in row for channel in pixel]
x, y = 2, 1
start = (y * width + x) * 3
print(f'size: {width} x {height}; values: {len(flat)}')
print('pixel:', image[y][x])
print('from flat:', tuple(flat[start:start + 3]))
print('scaled:', [round(v / 255, 3) for v in image[y][x]])

怎样运行:保存为 pixels.py,用 Python 3 运行:python pixels.py。只用内置功能,不用安装图像库。

运行后对照结果
size: 3 x 2; values: 18
pixel: (64, 128, 192)
from flat: (64, 128, 192)
scaled: [0.251, 0.502, 0.753]

有了数字,还没有认出物体

把红蓝通道弄反,数字数量和图片尺寸仍然没变,颜色却会改变;把宽高写错,同一串数字也可能被排成另一张图。可见只把数据装进去还不够,读取顺序和形状也是输入约定的一部分。

接着打开来源里的 3Blue1Brown 第一课,找到 Neurons 小节,对照手写数字图和输入节点。它用二十八乘二十八的灰度图,每个像素对应一个亮度值,共七百八十四个输入。圈出其中一个位置,沿图找到对应节点:这一动作练习的是“图怎样变成输入”,并不要求先看完视频或成功识别手写数字。

记住这三点

  • 像素需要位置和数值;先说明坐标方向、宽高与通道顺序。
  • 八位 RGB 的一个像素含三个 0 到 255 的通道值,但图片还有其他表示。
  • 把像素缩放成模型要求的输入,只是准备数据,还不是理解图片。

别把例子用过头

  • 这里的十八字节是紧凑原始像素的计算,不是 Python 对象内存或图片文件大小。
  • RGB 数值要结合颜色空间等约定解释;除以 255 也不表示人眼感受到的亮度与数值成正比。

自己试一小步

如果一张小图只把红蓝通道交换,哪些东西会变,哪些不会?

  1. 另造一张两行四列的小图,写出宽、高和通道数。
  2. 保留原图,生成一个只交换每个像素 R、B 的新列表。
  3. 选一个非灰色像素,分别按二维下标和平铺下标取出前后数值。

做到这些,就算完成

  • 像素总数和通道总数没有混为一谈。
  • 原列表没有被意外修改,交换后的坐标仍对应同一位置。
  • 能解释为什么灰色像素可能看不出这次交换。

查原始资料

想确认一个细节,先看这份资料对应的部分,不必从头读完所有文档。

找一份资料继续练

先选一项,按下面的范围继续学;不必从头读完整门课程。