跳转到内容

JPEG - 想法和实践/彩色图片的两个程序

来自维基教科书,开放的书籍,开放的世界

现在需要在文件中写入另外两个组件。RGB 颜色值通过线性变换 RGB → YCbCr 转换为 YCbCr 颜色值,这样三个组件分别是 Y 组件、Cb 组件和 Cr 组件。但正如“帧段 SOF”部分所述,组件可以彼此之间进行子采样,这种子采样由三组组件的 (Hi, Vi) (i = 1, 2, 3) 对决定。通常 Y 组件不进行子采样,而两个颜色组件以相同的方式进行子采样。我们这里假设这种情况。这意味着 (Hi, Vi) = (1, 1) 用于颜色组件,而 (H1, V1) 则为 (1, 1)、(2, 1)、(1, 2) 或 (2, 2) 之一。我们首先假设 (H1, V1) = (1, 1),然后假设 (H1, V1) = (2, 2),并将最后一种情况公式化,以便公式和程序可以不加修改地应用于所有四种情况。

(H1, V1) = (1, 1)  在这种情况下,没有子采样。对于每个 8x8 方格,我们每个组件都有一个与用于灰度图像相同的编码和写入过程 - 唯一的区别是,我们对 Y 组件和两个颜色组件使用不同的量化和霍夫曼表。写入文件由数字 cp 控制,它分别代表 Y 组件、Cb 组件和 Cr 组件的 1、2 和 3。

与灰度情况类似,文件读取和图像绘制在一个循环中进行,但由于在读取三个数据序列之前无法绘制 8x8 方格,因此必须存储一些内容,即每次读取的结果 64 数组。我们让读取由数字 cp 控制:对于 cp = 1、2 和 3,分别使用 Y 组件、Cb 组件和 Cr 组件的数据来形成 64 数组 w,并将其存储在变量 wy、wb 和 wr 中。然后将 cp 设置为 4,当 cp = 4 时,数组 wy、wb 和 wr 将转换为 8x8 矩阵,并进行反量化和逆离散余弦变换,得到三个 8x8 矩阵(整数),这些矩阵可以看作是 8x8 的 YCbCr 三元组矩阵。YCbCr 三元组通过 RGB → YCbCr 变换的逆变换转换为 RGB 三元组。如果我们设置 wid8 = width div 8 和 hei8 = height div 8,则 8x8 方格可以分配坐标集 (i0, j0),i0 = 0, ..., wid8-1, j0 = 0, ..., hei8-1,并且要以 RGB 三元组(在 8x8 矩阵中)着色的点具有坐标集 (i, j) (i, j = 0, ... 7),在图像中具有坐标集 (i0*8 + i, j0*8 + j)。

(H1, V1) = (2, 2)  这意味着,对于两个颜色组件,构成 2x2 方格的四个像素通过取颜色的平均值被视为一个像素。因此,对于颜色组件,一个 8x8 方格对应于图像中的一个 16x16 方格,并且必须与 Y 组件的四个 8x8 方格组合在一起。这些四个 8x8 方格的编码数据按通常的顺序(从左到右,从上到下)一个接一个地写入文件。之后,两个颜色组件的 8x8 方格的数据被编码并写入文件,然后我们转到下一个 16x16 方格。我们现在假设图像的宽度和高度可以被 16 整除。我们设置 wid8 = width div (H1*8) 和 hei8 = height div (V1*8),这样 Y 组件划分(在本例中为 16x16 方格)的矩形具有坐标集 (i0, j0),i0 = 0, ..., wid8-1, j0 = 0, ..., hei8-1。

此过程(创建文件)很简单,但相反的过程,即读取文件和绘制图像并不那么简单,因为必须以正确的方式存储和组合事物。读取和解码的结果是 64 个数字的数组,现在必须存储六个这样的数组,然后才能绘制一个 16x16 方格:四个数组用于 Y 组件,每个颜色组件一个数组。为了以一致的方式进行组合(对于 (H1, V1) = (1, 1)、(2, 1)、(1, 2) 或 (2, 2)),我们让 Y 组件的 64 数组成为 64 数组的矩阵,即(在我们当前假设 (H1, V1) = (2, 2) 的情况下)一个 2x2 的 64 数组矩阵(或等效地:一个 64 数组的 2x2 矩阵)。我们称之为 wy,这样四个 64 数组就是 wy[0, 0][l]、wy[1, 0][l]、wy[0, 1][l] 和 wy[1, 1][l] (l = 1, ..., 64)。

与之前一样,解码由数字 cp 控制,它分别代表三个组件的读取的 1、2 和 3,以及 16x16 方格的计算和绘制的 4。

cp = 1  cp = 1 的读取过程运行四次:分别针对 (i1, j1) = (0, 0)、(0, 1)、(1, 0) 和 (1, 1)。这样的一对 (i1, j1) 被表示为 pos,找到下一对 pos 的函数被称为nextpos(pos),因此,如果 pos = (1, 1),那么nextpos(pos) 就是 (0, 0)。nextpos 的程序如下所示。

DC 数 dcy(用于 Y 组件)是通过将数字 m(通过decodedy(给出数字 val)后跟num(从 val 计算 m)找到)添加到存储在 dcy0 中的先前 DC 数中得到的 - 这是针对先前对 pos 的,当 pos = (0, 0) 时,它是 (1, 1)(用于下一个 16x16 方格)。Y 组件的四个 DC 数构成一个 2x2 矩阵 wy1[i1, j1] (i1, j1 = 0, 1) - 被表示为 wy1,因为它是一个 2x2 矩阵的 64 数组 wy 的 DC 项:wy[1] = wy1。

63 个 AC 数(用于 (i1, j1))是通过decodeay(给出数字 nz 和 val)后跟下面显示的formac 过程找到的。formac 的结果是一个数组 w[l],l = 2, ..., 64(第一项未指定),该数组存储在 wy[i1, j1] 中:wy[i1, j1] = w。

wy[i1, j1] 的 DC 项是 wy1[i1, j1],但它的固定可以等到 cp = 4 时:wy[i1, j1][1] = wy1[i1, j1]。

完成四个 8x8 方格(构成 16x16 方格)的读取后,将对 (i1, j1) 设置为 (0, 0),当 (i1, j1) = (0, 0) 时,将 cp 设置为 2 (= cp + 1),以读取对应于 Y 组件 16x16 方格的 Cb 颜色组件 8x8 方格。

cp = 2, 3  对两个颜色组件形成数组 wb 和 wr 与适用于灰度过程的相似。例如,对于 wb,它以这种方式进行:通过将数字 m(通过decodedc(给出数字 val)后跟num(从 val 计算 m)找到)添加到存储在 dcb0 中的先前 DC 数中,找到 DC 数 dcb。然后,通过decodeac(给出数字 nz 和 val)后跟下面显示的formac 过程,找到 63 个 AC 数。formac 的结果是一个数组 w[l],l = 2, ..., 64(第一项未指定),该数组存储在 wb 中:wb = w。wb 的 DC 项是 dcb,但它的固定可以等到 cp = 4 时:wb[1] = dcb。

cp = 4  cp = 1 生成了一个 2x2 的 64 数组矩阵 wy[i1, j1] (i1, j1 = 0, 1),cp = 2 生成了一个 64 数组 wb,cp = 3 生成了一个 64 数组 wr。此后,将 cp 设置为 4,当 cp = 4 时,这六个数组将进行反量化和逆离散余弦变换,结果数字是颜色值,需要以正确的方式组合以对 16x16 方格进行着色。16x16 方格的坐标集是 (i0, j0) (i0 = 0, ..., wid8-1, j0 = 0, ..., hei8-1)。在这样的 16x16 方格内,四个 8x8 方格的坐标集是 (i1, j1),i1, j1 = 0, 1,因此,图像中 8x8 方格 (i1, j1) 的左上角具有坐标集 (i2, j2),其中 i2 = (i0*H1 + i1) * 8 且 j2 = (j0*V1 + j1) * 8。在一个 8x8 方格内,坐标集是 (i, j),i, j = 0, ..., 7。对于具有坐标集 (i1, j1) 的 8x8 方格(在具有坐标集 (i0, j0) 的 16x16 方格中),点 (i, j) 对应于 1)在图像中,对应于具有坐标集 (i2 + i, j2 + j) 的点,以及 2)在对应于 16x16 方格的颜色组件的 8x8 方格中,对应于具有坐标集 (i3, j3) 的点,其中 i3 = 4*i1 + i div H1 且 j3 = 4*j1 + j div V1。

我们分别用idcty(w) 和idctc(w) 表示对 Y 组件和颜色组件的 8x8 方格的 64 数组 w 进行反量化和逆离散余弦变换的函数。对于 8x8 方格 (i1, j1)(Y 组件的 16x16 方格),idcty 应用于 64 数组 wy[i1, j1]。我们称结果 8x8 矩阵为 fy (fy = idcty(wy[i1, j1])),并让 yy 是 fy 在点 (i, j) 中的值:yy = fy[i, j]。对于颜色组件的 8x8 方格(对应于 16x16 方格),idctc 应用于 64 数组 wb 和 wr。我们称结果 8x8 矩阵为 fb 和 fr (fb = idctc(wb) 且 fr = idctc(wr)),并让 cb 和 br 是 fb 和 fr 在对应于 (i, j)(和 (i1, j1))的点 (i3, j3) 中的值:cb = fb[i3, j3] 且 cr = fr[i3, j3]。

YCbCr 三元组 (yy, cb, cr) 通过 RGB → YCbCr 变换的逆变换转换为 RGB 三元组 (tr, tg, tb)。并且要以该 RGB 三元组着色的点具有坐标集 (i2 + i, j2 + j)

如果 cp = 1 那么
开始
如果 l = 1 那么
开始
dcy0 = dcy
decodedy
num
dcy = m + dcy0
wy1[i1, j1] = dcy
结束
decodeay
formac
如果 l = 64 那么
开始
l = 1
wy[i1, j1] = w
pos[0] = i1
pos[1] = j1
i1 = nextpos(pos)[0]
j1 = nextpos(pos)[1]
如果 (i1 = 0) 且 (j1 = 0) 那么
cp = cp + 1
结束
结束
如果 cp = 2 那么
开始
如果 l = 1 那么
开始
dcb0 = dcb
decodedc
num
dcb = m + dcb0
结束
decodeac
formac
如果 l = 64 那么
开始
l = 1
wb = w
cp = cp + 1
结束
结束
如果 cp = 3 那么
开始
如果 l = 1 那么
开始
dcr0 = dcr
decodedc
num
dcr = m + dcr0
结束
decodeac
formac
如果 l = 64 那么
开始
l = 1
wr = w
cp = cp + 1
结束
结束
如果 cp = 4 那么
开始
cp = 1
wb[1] = dcb
wr[1] = dcr
fb = idctc(wb)
fr = idctc(wr)
对于 j1 = 0 到 v1 - 1 做
对于 i1 = 0 到 h1 - 1 做
开始
wy[i1, j1][1] = wy1[i1, j1]
fy = idcty(wy[i1, j1])
i2 = (i0 * h1 + i1) * 8
j2 = (j0 * v1 + j1) * 8
对于 j = 0 到 7 做
对于 i = 0 到 7 做
开始
i3 = 4 * i1 + i div h1
j3 = 4 * j1 + j div v1
yy = fy[i, j]
cb = fb[i3, j3]
cr = fr[i3, j3]
tr = round(yy + 1.402 * cr + 128)
tg = round(yy - 0.3441 * cb - 0.71414 * cr + 128)
tb = round(yy + 1.772 * cb + 128)
如果 tr > 255 那么
tr = 255
如果 tr < 0 那么
tr = 0
如果 tg > 255 那么
tg = 255
如果 tg < 0 那么
tg = 0
如果 tb > 255 那么
tb = 255
如果 tb < 0 那么
tb = 0
setpixel(i2 + i, j2 + j, tr, tg, tb)
结束
结束
i1 = 0
j1 = 0
i0 = i0 + 1
如果 i0 * h1 * 8 >= width 那么
开始
i0 = 0
j0 = j0 + 1
结束
结束

函数nextpos(pos) 可以通过以下程序计算

i = pos[0]
j = pos[1]
i = i + 1
如果 (v1 = 2) 且 (j = 0) 且 (i = h1) 那么
开始
j = 1
i = 0
结束
如果 (j = v1 - 1) 且 (i = h1) 那么
开始
i = 0
j = 0
结束
nextpos[0] = i
nextpos[1] = j

对 Y 分量的 AC 部分和颜色分量的 AC 部分分别进行 decodeaydecodeac 解码后,用于 formac 的程序将形成 64 数组 w 的 AC 部分(即 l > 1 的 w[l]),生成两个数字 nz(零的个数)和 val(num 使用的数字个数),程序可能如下所示:

如果 val > 0 则
开始
如果 nz > 0 则
从 i = 1 到 nz 执行循环
开始
l = l + 1
w[l] = 0
结束
num
l = l + 1
w[l] = m
结束
如果 (nz = 15) 且 (val = 0) 则
从 i = 1 到 16 执行循环
开始
l = l + 1
w[l] = 0
结束
如果 (nz = 0) 且 (val = 0) 则
当 l < 64 时执行循环
开始
l = l + 1
w[l] = 0
结束
华夏公益教科书