在前面的步骤,我们已经将图像的亮度与色度分离开来,通过将色度降采样,使得数据量减少了很多,但是还有很大的下降空间。

冗余信息

现实存在的图像,相邻的像素经常是相关的。比如一片蓝天,左边的像素很可能和右边的像素颜色相近。

大部分情况下,我们面对的是这样的序列:

100, 101, 102, 103, 103, 104, 105, 105

而不是:

100, 250, 20, 240, 10, 255, 30, 230

当我们得知一个像素的值时,与它邻近的像素的值很可能也是可以预测的。这就意味着,图像中存在大量的冗余信息。

比如,前面提到的序列:

100, 101, 102, 103, 103, 104, 105, 105

我们可以用一些手段去减少冗余信息,比如:

100, +1, +1, +1, 0, +1, +1, 0

这样除了第一个数需要存储,后续的数都可以用一个比较小的差值来代替,那后续的数就可以用更少的比特数来存储了。当然,JPEG 并不会直接对相邻像素采用这种简单的差分编码,而是先通过离散余弦变换(DCT)重新组织信息。

正如我们前面所了解到的,JPEG 压缩算法通常认为是一种有损压缩算法。虽然 DCT 本身并不会丢失信息,但它把图像信息重新组织到不同频率的系数中,使得后续的量化阶段可以优先保留重要的低频信息,舍弃掉部分高频细节。因此,DCT 是 JPEG 有损压缩能够高效工作的关键基础。

1D DCT

这里的“1D”表示数据只有一个采样方向,与线性代数中向量空间的维数不是同一个概念。例如,一个包含8个样本的一维序列可以表示为一个8维向量,但它仍然是一维信号。

我们先绘制一条普通的余弦曲线 f1(x)=cos(x)f_1(x)=\cos(x)

我们将[0,π][0, \pi]区间三等分,并取每个区间的中点,分别为:

f1(π6)=cos(π6)=32f1(π2)=cos(π2)=0f1(5π6)=cos(5π6)=32\begin{align*} f_1(\frac{\pi}{6}) &= \cos(\frac{\pi}{6}) = \frac{\sqrt{3}}{2} \\ f_1(\frac{\pi}{2}) &= \cos(\frac{\pi}{2}) = 0 \\ f_1(\frac{5\pi}{6}) &= \cos(\frac{5\pi}{6}) = -\frac{\sqrt{3}}{2} \end{align*}

得到的值可以用一个向量表示:

[32032]\begin{bmatrix} \frac{\sqrt{3}}{2} \\ 0 \\ -\frac{\sqrt{3}}{2} \end{bmatrix}

同样的做法应用在f0(x)=cos(0x)f_0(x)=\cos(0\cdot x)上,可以得到:

[111]\begin{bmatrix} 1 \\ 1 \\ 1 \end{bmatrix}

应用在f2(x)=cos(2x)f_2(x)=\cos(2\cdot x)上,可以得到:

[12112]\begin{bmatrix} \frac{1}{2} \\ -1 \\ \frac{1}{2} \end{bmatrix}

如此,我们便得到了一组向量:

[111][32032][12112]\begin{bmatrix} 1 \\ 1 \\ 1 \end{bmatrix} \qquad \begin{bmatrix} \frac{\sqrt{3}}{2} \\ 0 \\ -\frac{\sqrt{3}}{2} \end{bmatrix} \qquad \begin{bmatrix} \frac{1}{2} \\ -1 \\ \frac{1}{2} \end{bmatrix}

目前这三个向量的长度还不一致,我们可以在维持向量方向不变的情况下,想办法让它们的长度都变成1,即所谓的归一化:

[333333][22022][666366]\begin{bmatrix} \frac{\sqrt{3}}{3} \\ \frac{\sqrt{3}}{3} \\ \frac{\sqrt{3}}{3} \end{bmatrix} \qquad \begin{bmatrix} \frac{\sqrt{2}}{2} \\ 0 \\ -\frac{\sqrt{2}}{2} \end{bmatrix} \qquad \begin{bmatrix} \frac{\sqrt{6}}{6} \\ -\frac{\sqrt{6}}{3} \\ \frac{\sqrt{6}}{6} \end{bmatrix}

可是这样的一组向量有什么用呢?

让我们随便指定一个一维离散信号。它包含3个样本,因此可以表示为一个3维列向量:

v=[v1v2v3]\bm{v} = \begin{bmatrix} v_1 \\ v_2 \\ v_3 \end{bmatrix}

我们可以将它表示为3个基向量的线性组合:

v=v1[100]+v2[010]+v3[001]\bm{v} = v_1 \begin{bmatrix} 1 \\ 0 \\ 0 \end{bmatrix} + v_2 \begin{bmatrix} 0 \\ 1 \\ 0 \end{bmatrix} + v_3 \begin{bmatrix} 0 \\ 0 \\ 1 \end{bmatrix}

这里面选用的基向量是:

[100][010][001]\begin{bmatrix} 1 \\ 0 \\ 0 \end{bmatrix} \qquad \begin{bmatrix} 0 \\ 1 \\ 0 \end{bmatrix} \qquad \begin{bmatrix} 0 \\ 0 \\ 1 \end{bmatrix}

但是想要得到v\bm{v},我们并不一定非要使用这组基向量,我们也可以使用前面归一化后的那组向量作为基向量:

v=u1[333333]+u2[22022]+u3[666366]\bm{v} = u_1 \begin{bmatrix} \frac{\sqrt{3}}{3} \\ \frac{\sqrt{3}}{3} \\ \frac{\sqrt{3}}{3} \end{bmatrix} + u_2 \begin{bmatrix} \frac{\sqrt{2}}{2} \\ 0 \\ -\frac{\sqrt{2}}{2} \end{bmatrix} + u_3 \begin{bmatrix} \frac{\sqrt{6}}{6} \\ -\frac{\sqrt{6}}{3} \\ \frac{\sqrt{6}}{6} \end{bmatrix}

只不过这样基向量前面的系数u1,u2,u3u_1, u_2, u_3会和原先的v1,v2,v3v_1, v_2, v_3不一样。但是把系数从v1,v2,v3v_1, v_2, v_3变换成u1,u2,u3u_1, u_2, u_3,到底有什么作用呢?

原先的系数v1,v2,v3v_1, v_2, v_3分别对应各个采样位置的取值,它们的大小主要反映对应位置的明暗,并没有把信号中的整体趋势与局部细节区分开来。如果想要进行有损压缩,我们就需要进一步判断:哪些信息构成了信号的整体面貌,应该尽量保留;哪些信息只是局部细节,可以用较低的精度表示,甚至直接舍弃。

但是u1,u2,u3u_1, u_2, u_3却不一样,u1u_1后面跟着的是频率最低的基向量,u2u_2后面跟着的是频率中等的基向量,u3u_3后面跟着的是频率最高的基向量。虽然这些基向量在数学上的地位相同,但对于自然图像,低频分量通常承载整体明暗和缓慢变化,高频分量则更多表现为边缘和细节。因此从图像压缩的角度看,低频分量通常更为重要,其系数往往更大;高频系数则往往更小。这就给我们带来了一些启发,如果我们只是去掉一些占比不大的“细节”,是不是就可以使用更少的存储空间来得到跟原先的v\bm{v}差不多的向量呢?

为了方便,我们可以把刚才得到的新系数拼成一个向量:

u=[u1u2u3]\bm{u} = \begin{bmatrix} u_1 \\ u_2 \\ u_3 \end{bmatrix}

我们还可以把刚才得到的3个基向量组合成一个矩阵:

T=[33226633063332266]T = \begin{bmatrix} \frac{\sqrt{3}}{3} & \frac{\sqrt{2}}{2} & \frac{\sqrt{6}}{6} \\ \frac{\sqrt{3}}{3} & 0 & -\frac{\sqrt{6}}{3} \\ \frac{\sqrt{3}}{3} & -\frac{\sqrt{2}}{2} & \frac{\sqrt{6}}{6} \end{bmatrix}

线性代数中有一个技巧,那就是矩阵左乘一个向量,可以理解为是矩阵的各个列向量的线性组合。如此一来,我们就可以用一种更为简洁的方式来表达刚才的过程了:

v=[v1v2v3]=v1[100]+v2[010]+v3[001]\begin{align*} \bm{v} &= \begin{bmatrix} v_1 \\ v_2 \\ v_3 \end{bmatrix} \\ &= v_1 \begin{bmatrix} 1 \\ 0 \\ 0 \end{bmatrix} + v_2 \begin{bmatrix} 0 \\ 1 \\ 0 \end{bmatrix} + v_3 \begin{bmatrix} 0 \\ 0 \\ 1 \end{bmatrix} \end{align*} =u1[333333]+u2[22022]+u3[666366]\begin{align*} &= u_1 \begin{bmatrix} \frac{\sqrt{3}}{3} \\ \frac{\sqrt{3}}{3} \\ \frac{\sqrt{3}}{3} \end{bmatrix} + u_2 \begin{bmatrix} \frac{\sqrt{2}}{2} \\ 0 \\ -\frac{\sqrt{2}}{2} \end{bmatrix} + u_3 \begin{bmatrix} \frac{\sqrt{6}}{6} \\ -\frac{\sqrt{6}}{3} \\ \frac{\sqrt{6}}{6} \end{bmatrix} \end{align*} =[33226633063332266][u1u2u3]=Tu\begin{align*} &= \begin{bmatrix} \frac{\sqrt{3}}{3} & \frac{\sqrt{2}}{2} & \frac{\sqrt{6}}{6} \\ \frac{\sqrt{3}}{3} & 0 & -\frac{\sqrt{6}}{3} \\ \frac{\sqrt{3}}{3} & -\frac{\sqrt{2}}{2} & \frac{\sqrt{6}}{6} \end{bmatrix} \begin{bmatrix} u_1 \\ u_2 \\ u_3 \end{bmatrix} \\ &= T \bm{u} \end{align*}

这个例子中的一维信号可以表示为一个3维向量,因此我们在每条余弦曲线上取3个样本,并构造3个基向量。我们也可以增加一维信号的样本数量。比如,将[0,π][0, \pi]区间八等分,并取每个区间的中点(即π16,3π16,5π16,,15π16\frac{\pi}{16}, \frac{3\pi}{16}, \frac{5\pi}{16}, \ldots, \frac{15\pi}{16}),在这8个点上分别计算cos(0x),cos(1x),,cos(7x)\cos(0x), \cos(1x), \ldots, \cos(7x),得到8个基向量;再像前面一样,把每个基向量都归一化到长度1。这样我们就可以将一个8维向量表示为8个归一化基向量的线性组合:

v=[v1v2v8]=[2412cos(1π16)12cos(2π16)12cos(7π16)2412cos(3π16)12cos(23π16)12cos(73π16)2412cos(15π16)12cos(215π16)12cos(715π16)][u1u2u8]\bm{v} = \begin{bmatrix} v_1 \\ v_2 \\ \vdots \\ v_8 \end{bmatrix} = \begin{bmatrix} \frac{\sqrt{2}}{4} & \frac{1}{2}\cos(\frac{1\pi}{16}) & \frac{1}{2}\cos(\frac{2\pi}{16}) & \cdots & \frac{1}{2}\cos(\frac{7\pi}{16}) \\ \frac{\sqrt{2}}{4} & \frac{1}{2}\cos(\frac{3\pi}{16}) & \frac{1}{2}\cos(\frac{2\cdot 3\pi}{16}) & \cdots & \frac{1}{2}\cos(\frac{7\cdot 3\pi}{16}) \\ \vdots & \vdots & \vdots & \ddots & \vdots \\ \frac{\sqrt{2}}{4} & \frac{1}{2}\cos(\frac{15\pi}{16}) & \frac{1}{2}\cos(\frac{2\cdot 15\pi}{16}) & \cdots & \frac{1}{2}\cos(\frac{7\cdot 15\pi}{16}) \end{bmatrix} \begin{bmatrix} u_1 \\ u_2 \\ \vdots \\ u_8 \end{bmatrix}

其中v\bm{v}u\bm{u}是8维列向量,TT是由8个基向量(作为列)拼成的8×88 \times 8矩阵。这其实就意味着,我们可以用这8个基向量来表示这样排成一行的8个像素点的亮度值,并且我们可以修改高频分量的系数来降低这一行的“细节”,为后续压缩编码做好准备。

但我们的图像通常不会只有一行,有没有办法将这个思路从一行像素扩展到一大块像素呢,比如说,从1×81 \times 8的像素扩展到8×88 \times 8的像素?

2D DCT

为了解决这个问题,我们来看看线性代数中另一个技巧:行向量右乘矩阵,可以理解为是矩阵的各个行向量的线性组合。也就是说,我们不仅有:

[33226633063332266][u1u2u3]=u1[333333]+u2[22022]+u3[666366]\begin{bmatrix} \frac{\sqrt{3}}{3} & \frac{\sqrt{2}}{2} & \frac{\sqrt{6}}{6} \\ \frac{\sqrt{3}}{3} & 0 & -\frac{\sqrt{6}}{3} \\ \frac{\sqrt{3}}{3} & -\frac{\sqrt{2}}{2} & \frac{\sqrt{6}}{6} \end{bmatrix} \begin{bmatrix} u_1 \\ u_2 \\ u_3 \end{bmatrix} = u_1 \begin{bmatrix} \frac{\sqrt{3}}{3} \\ \frac{\sqrt{3}}{3} \\ \frac{\sqrt{3}}{3} \end{bmatrix} + u_2 \begin{bmatrix} \frac{\sqrt{2}}{2} \\ 0 \\ -\frac{\sqrt{2}}{2} \end{bmatrix} + u_3 \begin{bmatrix} \frac{\sqrt{6}}{6} \\ -\frac{\sqrt{6}}{3} \\ \frac{\sqrt{6}}{6} \end{bmatrix}

还可以有:

[u1u2u3][33333322022666366]=u1[333333]+u2[22022]+u3[666366]\begin{bmatrix} u_1 & u_2 & u_3 \end{bmatrix} \begin{bmatrix} \frac{\sqrt{3}}{3} & \frac{\sqrt{3}}{3} & \frac{\sqrt{3}}{3} \\ \frac{\sqrt{2}}{2} & 0 & -\frac{\sqrt{2}}{2} \\ \frac{\sqrt{6}}{6} & -\frac{\sqrt{6}}{3} & \frac{\sqrt{6}}{6} \end{bmatrix} = u_1 \begin{bmatrix} \frac{\sqrt{3}}{3} & \frac{\sqrt{3}}{3} & \frac{\sqrt{3}}{3} \end{bmatrix} + u_2 \begin{bmatrix} \frac{\sqrt{2}}{2} & 0 & -\frac{\sqrt{2}}{2} \end{bmatrix} + u_3 \begin{bmatrix} \frac{\sqrt{6}}{6} & -\frac{\sqrt{6}}{3} & \frac{\sqrt{6}}{6} \end{bmatrix}

利用这两个技巧,我们可以分别将图像块的行与列重新用新的基向量表达:

X=TMTTX = T M T^T

我们单看后半部分的MTTM T^T,它实际上就是遍历MM的每一行,把每一行都用新的基向量重新组合一遍,得到了一个中间矩阵MM';然后再用左边的TT去遍历这个中间矩阵MM'的每一列,把每一列也重新组合一遍,得到最终结果XX

在 1D DCT 中,我们能清晰地看到,v1,v2,v3v_1, v_2, v_3之所以会变成u1,u2,u3u_1, u_2, u_3,是因为我们更换了基向量,整个变换过程可以完全视为一个线性映射。可是在 2D DCT 中,虽然我们能猜到XXMM之间肯定是有联系的,但一时之间似乎看不太出 X=TMTTX = T M T^T到底干了什么。

现在我们将图像块XX的各个列拆开,上下拼在一起,形成一个特别长的列向量:

vec(X)=vec([x11x12x13x18x21x22x23x28x31x32x33x38x81x82x83x88])=[x11x21x31x81x12x22x32x88]\operatorname{vec}(X) = \operatorname{vec}(\begin{bmatrix} x_{11} & x_{12} & x_{13} & \cdots & x_{18} \\ x_{21} & x_{22} & x_{23} & \cdots & x_{28} \\ x_{31} & x_{32} & x_{33} & \cdots & x_{38} \\ \vdots & \vdots & \vdots & \ddots & \vdots \\ x_{81} & x_{82} & x_{83} & \cdots & x_{88} \end{bmatrix}) = \begin{bmatrix} x_{11} \\ x_{21} \\ x_{31} \\ \vdots \\ x_{81} \\ x_{12} \\ x_{22} \\ x_{32} \\ \vdots \\ x_{88} \end{bmatrix}

这样写未免有些啰嗦,我们不妨将XX的每一列都用一个向量符号表示:

xi=[x1ix2ix8i]\bm{x_i} = \begin{bmatrix} x_{1i} \\ x_{2i} \\ \vdots \\ x_{8i} \end{bmatrix}

那刚才的式子就可以写成下面这种更简洁的形式:

vec(X)=vec([x1x2x3x8])=[x1x2x3x8]\operatorname{vec}(X) = \operatorname{vec}(\begin{bmatrix} \bm{x_1} & \bm{x_2} & \bm{x_3} & \cdots & \bm{x_8} \end{bmatrix}) = \begin{bmatrix} \bm{x_1} \\ \bm{x_2} \\ \bm{x_3} \\ \vdots \\ \bm{x_8} \end{bmatrix}

我们再回过头研究一下 2D DCT 中通过系数块重建图像块的过程:

X=TMTT=T[m1m2m3m8]TT\begin{align*} X &= T M T^T \\ &= T \begin{bmatrix} \bm{m_1} & \bm{m_2} & \bm{m_3} & \cdots & \bm{m_8} \end{bmatrix} T^T \end{align*} =T[t11m1+t12m2++t18m8t21m1+t22m2++t28m8t81m1+t82m2++t88m8]=[T(t11m1+t12m2++t18m8)T(t21m1+t22m2++t28m8)T(t81m1+t82m2++t88m8)]\begin{align*} &= T \begin{bmatrix} t_{11}\bm{m_1} + t_{12}\bm{m_2} + \cdots + t_{18}\bm{m_8} \qquad t_{21}\bm{m_1} + t_{22}\bm{m_2} + \cdots + t_{28}\bm{m_8} \qquad \cdots \qquad t_{81}\bm{m_1} + t_{82}\bm{m_2} + \cdots + t_{88}\bm{m_8} \end{bmatrix} \\ &= \begin{bmatrix} T(t_{11}\bm{m_1} + t_{12}\bm{m_2} + \cdots + t_{18}\bm{m_8}) \qquad T(t_{21}\bm{m_1} + t_{22}\bm{m_2} + \cdots + t_{28}\bm{m_8}) \qquad \cdots \qquad T(t_{81}\bm{m_1} + t_{82}\bm{m_2} + \cdots + t_{88}\bm{m_8}) \end{bmatrix} \end{align*}

如果我们把XX的各列堆叠起来,就可以得到:

vec(X)=[T(t11m1+t12m2++t18m8)T(t21m1+t22m2++t28m8)T(t31m1+t32m2++t38m8)T(t81m1+t82m2++t88m8)]\begin{align*} \operatorname{vec}(X) &= \begin{bmatrix} T(t_{11}\bm{m_1} + t_{12}\bm{m_2} + \cdots + t_{18}\bm{m_8}) \\ T(t_{21}\bm{m_1} + t_{22}\bm{m_2} + \cdots + t_{28}\bm{m_8}) \\ T(t_{31}\bm{m_1} + t_{32}\bm{m_2} + \cdots + t_{38}\bm{m_8}) \\ \cdots \\ T(t_{81}\bm{m_1} + t_{82}\bm{m_2} + \cdots + t_{88}\bm{m_8}) \end{bmatrix} \end{align*} =[t11Tt12Tt18Tt21Tt22Tt28Tt31Tt32Tt38Tt81Tt82Tt88T][m1m2m3m8]\begin{align*} &= \begin{bmatrix} t_{11}T & t_{12}T & \cdots & t_{18}T \\ t_{21}T & t_{22}T & \cdots & t_{28}T \\ t_{31}T & t_{32}T & \cdots & t_{38}T \\ \cdots \\ t_{81}T & t_{82}T & \cdots & t_{88}T \end{bmatrix} \begin{bmatrix} \bm{m_1} \\ \bm{m_2} \\ \bm{m_3} \\ \vdots \\ \bm{m_8} \end{bmatrix} \end{align*} =[t11t11t11t12t11t18t18t11t18t12t18t18t11t21t11t22t11t28t18t21t18t22t18t28t11t81t11t82t11t88t18t81t18t82t18t88t81t11t81t12t81t18t88t11t88t12t88t18t81t21t81t22t81t28t88t21t88t22t88t28t81t81t81t82t81t88t88t81t88t82t88t88]vec(M)\begin{align*} &= \begin{bmatrix} t_{11}t_{11} & t_{11}t_{12} & \cdots & t_{11}t_{18} & \cdots & t_{18}t_{11} & t_{18}t_{12} & \cdots & t_{18}t_{18} \\ t_{11}t_{21} & t_{11}t_{22} & \cdots & t_{11}t_{28} & \cdots & t_{18}t_{21} & t_{18}t_{22} & \cdots & t_{18}t_{28} \\ \vdots & \vdots & \ddots & \vdots & \ddots & \vdots & \vdots & \ddots & \vdots \\ t_{11}t_{81} & t_{11}t_{82} & \cdots & t_{11}t_{88} & \cdots & t_{18}t_{81} & t_{18}t_{82} & \cdots & t_{18}t_{88} \\ \vdots & \vdots & \ddots & \vdots & \ddots & \vdots & \vdots & \ddots & \vdots \\ t_{81}t_{11} & t_{81}t_{12} & \cdots & t_{81}t_{18} & \cdots & t_{88}t_{11} & t_{88}t_{12} & \cdots & t_{88}t_{18} \\ t_{81}t_{21} & t_{81}t_{22} & \cdots & t_{81}t_{28} & \cdots & t_{88}t_{21} & t_{88}t_{22} & \cdots & t_{88}t_{28} \\ \vdots & \vdots & \ddots & \vdots & \ddots & \vdots & \vdots & \ddots & \vdots \\ t_{81}t_{81} & t_{81}t_{82} & \cdots & t_{81}t_{88} & \cdots & t_{88}t_{81} & t_{88}t_{82} & \cdots & t_{88}t_{88} \end{bmatrix} \operatorname{vec}(M) \end{align*}

其中vec(X)\operatorname{vec}(X)是64维的列向量,vec(M)\operatorname{vec}(M)也是64维的列向量,而中间的

W=[t11t11t11t12t11t18t18t11t18t12t18t18t11t21t11t22t11t28t18t21t18t22t18t28t11t81t11t82t11t88t18t81t18t82t18t88t81t11t81t12t81t18t88t11t88t12t88t18t81t21t81t22t81t28t88t21t88t22t88t28t81t81t81t82t81t88t88t81t88t82t88t88]W = \begin{bmatrix} t_{11}t_{11} & t_{11}t_{12} & \cdots & t_{11}t_{18} & \cdots & t_{18}t_{11} & t_{18}t_{12} & \cdots & t_{18}t_{18} \\ t_{11}t_{21} & t_{11}t_{22} & \cdots & t_{11}t_{28} & \cdots & t_{18}t_{21} & t_{18}t_{22} & \cdots & t_{18}t_{28} \\ \vdots & \vdots & \ddots & \vdots & \ddots & \vdots & \vdots & \ddots & \vdots \\ t_{11}t_{81} & t_{11}t_{82} & \cdots & t_{11}t_{88} & \cdots & t_{18}t_{81} & t_{18}t_{82} & \cdots & t_{18}t_{88} \\ \vdots & \vdots & \ddots & \vdots & \ddots & \vdots & \vdots & \ddots & \vdots \\ t_{81}t_{11} & t_{81}t_{12} & \cdots & t_{81}t_{18} & \cdots & t_{88}t_{11} & t_{88}t_{12} & \cdots & t_{88}t_{18} \\ t_{81}t_{21} & t_{81}t_{22} & \cdots & t_{81}t_{28} & \cdots & t_{88}t_{21} & t_{88}t_{22} & \cdots & t_{88}t_{28} \\ \vdots & \vdots & \ddots & \vdots & \ddots & \vdots & \vdots & \ddots & \vdots \\ t_{81}t_{81} & t_{81}t_{82} & \cdots & t_{81}t_{88} & \cdots & t_{88}t_{81} & t_{88}t_{82} & \cdots & t_{88}t_{88} \end{bmatrix}

则是一个64×6464 \times 64的矩阵。

原来,2D DCT 和 1D DCT 一样,都是用“可以区分重要程度”的新的基向量,重新表达原来的数据。在上面的式子中,WW的每一列就是一个新的基向量,vec(M)\operatorname{vec}(M)中的每个值就是对应基向量前面的系数。

不过这样理解似乎过于抽象了些,我们再往回退一步,将堆叠起来的vec(X)\operatorname{vec}(X)还原回原来的XX,而矩阵中的每一列(也就是每个新的基向量),也从堆叠的状态还原到8×88 \times 8的矩阵状态。

比如,我们将WW的第一列还原回8×88 \times 8的矩阵状态:

w1=[t11t11t21t11t31t11t41t11t51t11t61t11t71t11t81t11t11t21t21t21t31t21t41t21t51t21t61t21t71t21t81t21t11t31t21t31t31t31t41t31t51t31t61t31t71t31t81t31t11t41t21t41t31t41t41t41t51t41t61t41t71t41t81t41t11t51t21t51t31t51t41t51t51t51t61t51t71t51t81t51t11t61t21t61t31t61t41t61t51t61t61t61t71t61t81t61t11t71t21t71t31t71t41t71t51t71t61t71t71t71t81t71t11t81t21t81t31t81t41t81t51t81t61t81t71t81t81t81]\bm{w_1} = \begin{bmatrix} t_{11}t_{11} & t_{21}t_{11} & t_{31}t_{11} & t_{41}t_{11} & t_{51}t_{11} & t_{61}t_{11} & t_{71}t_{11} & t_{81}t_{11} \\ t_{11}t_{21} & t_{21}t_{21} & t_{31}t_{21} & t_{41}t_{21} & t_{51}t_{21} & t_{61}t_{21} & t_{71}t_{21} & t_{81}t_{21} \\ t_{11}t_{31} & t_{21}t_{31} & t_{31}t_{31} & t_{41}t_{31} & t_{51}t_{31} & t_{61}t_{31} & t_{71}t_{31} & t_{81}t_{31} \\ t_{11}t_{41} & t_{21}t_{41} & t_{31}t_{41} & t_{41}t_{41} & t_{51}t_{41} & t_{61}t_{41} & t_{71}t_{41} & t_{81}t_{41} \\ t_{11}t_{51} & t_{21}t_{51} & t_{31}t_{51} & t_{41}t_{51} & t_{51}t_{51} & t_{61}t_{51} & t_{71}t_{51} & t_{81}t_{51} \\ t_{11}t_{61} & t_{21}t_{61} & t_{31}t_{61} & t_{41}t_{61} & t_{51}t_{61} & t_{61}t_{61} & t_{71}t_{61} & t_{81}t_{61} \\ t_{11}t_{71} & t_{21}t_{71} & t_{31}t_{71} & t_{41}t_{71} & t_{51}t_{71} & t_{61}t_{71} & t_{71}t_{71} & t_{81}t_{71} \\ t_{11}t_{81} & t_{21}t_{81} & t_{31}t_{81} & t_{41}t_{81} & t_{51}t_{81} & t_{61}t_{81} & t_{71}t_{81} & t_{81}t_{81} \end{bmatrix}

因为WW有64列,这就意味着我们可以得到64个图像块。这就意味着,原本的图像块XX,可以用64个图像块的线性组合来表示。

MM中的每个值,对应的就是各个pattern前面的系数。

前面我们也提到了,真实的图像中,低频分量的系数绝对值往往比较大,高频分量的系数绝对值往往比较小。这就造成MM的左上角的数值绝对值往往都比较大,而右下角的数值往往都比较小甚至接近于0。这就给我们带来了一个提示,如果我们舍弃掉右下角的那些接近于0的数值,是否就可以用更少的存储空间来表示原本的图像块XX呢?关于如何精简系数,我们将在下一节中详细介绍。

目前我们还剩下一个问题。虽然我们已经知道 2D DCT 产生的系数矩阵MM的含义,但是我们要怎么才能计算出MM呢?

我们回到前面的式子:

X=TMTTX = T M T^T

前面构造TT时使用的8个离散余弦基向量,两两正交,并且都不是零向量。而一组两两正交的非零向量必然线性无关。因此,TT满足可逆的条件。那么我们只需要在等式左边乘以T1T^{-1},再在右边乘以(TT)1(T^T)^{-1},就可以得到:

M=T1X(TT)1M = T^{-1} X (T^T)^{-1}

可是要如何计算T1T^{-1}呢?

我们再回忆前面,每个基向量已经都归一化,即TT的各个列向量长度都为1

根据矩阵乘法的定义,TTTT^TT的第ii行第jj列,刚好就是TT的第ii列与第jj列的内积:

(TTT)ij=(column i)(column j)={1,i=j0,ij(T^TT)_{ij} = (\text{column }i) \cdot (\text{column }j) = \begin{cases} 1, & i = j \\ 0, & i \ne j \end{cases}

这就说明:

TTT=IT^T T = I

接着在等号两侧同时右乘T1T^{-1}

TTTT1=IT1TT=T1T^T T T^{-1} = I\, T^{-1} \qquad\Longrightarrow\qquad T^T = T^{-1}

这就带来了一个非常好用的结果:我们只要将TT进行转置,就可以轻松得到它的逆矩阵。

所以计算MM的公式也变得清晰起来:

M=T1X(TT)1=TTXT\begin{align*} M &= T^{-1} X (T^T)^{-1} \\ &= T^T X T \end{align*}

回忆一下前面,TT 可以表示为:

[2412cos(1π16)12cos(2π16)12cos(7π16)2412cos(3π16)12cos(23π16)12cos(73π16)2412cos(15π16)12cos(215π16)12cos(715π16)]\begin{bmatrix} \frac{\sqrt{2}}{4} & \frac{1}{2}\cos(\frac{1\pi}{16}) & \frac{1}{2}\cos(\frac{2\pi}{16}) & \cdots & \frac{1}{2}\cos(\frac{7\pi}{16}) \\ \frac{\sqrt{2}}{4} & \frac{1}{2}\cos(\frac{3\pi}{16}) & \frac{1}{2}\cos(\frac{2\cdot 3\pi}{16}) & \cdots & \frac{1}{2}\cos(\frac{7\cdot 3\pi}{16}) \\ \vdots & \vdots & \vdots & \ddots & \vdots \\ \frac{\sqrt{2}}{4} & \frac{1}{2}\cos(\frac{15\pi}{16}) & \frac{1}{2}\cos(\frac{2\cdot 15\pi}{16}) & \cdots & \frac{1}{2}\cos(\frac{7\cdot 15\pi}{16}) \end{bmatrix}

这样书写未免过于繁琐,实际上 TT 的第 ii 行第 uu 列可以用一个统一的公式表示:

Tiu=12Cucos((2i1)(u1)π16)T_{iu} = \frac{1}{2} C_u \cos\left(\frac{(2i-1)(u-1)\pi}{16}\right)

其中

Cu={12,u=11,u=2,3,,8C_u = \begin{cases} \frac{1}{\sqrt{2}}, & u = 1 \\ 1, & u = 2, 3, \ldots, 8 \end{cases}

类似地,TTT^T 的第 vv 行第 jj 列为:

(TT)vj=Tjv=12Cvcos((2j1)(v1)π16)(T^T)_{vj}=T_{jv} = \frac{1}{2} C_v \cos\left(\frac{(2j-1)(v-1)\pi}{16}\right)

其中

Cv={12,v=11,v=2,3,,8C_v = \begin{cases} \frac{1}{\sqrt{2}}, & v = 1 \\ 1, & v = 2, 3, \ldots, 8 \end{cases}

这里其实就能看出,TiuT_{iu}(TT)vj(T^T)_{vj} 的结构实际上是一样的。

如果我们把 M=TTXTM=T^T X T 完全展开,MM 中第 vv 行、第 uu 列的系数可以写成:

Mvu=14CuCvi=18j=18Xijcos((2i1)(v1)π16)cos((2j1)(u1)π16)M_{vu} = \frac{1}{4} C_u C_v \sum_{i=1}^{8}\sum_{j=1}^{8} X_{ij} \cos\left(\frac{(2i-1)(v-1)\pi}{16}\right) \cos\left(\frac{(2j-1)(u-1)\pi}{16}\right)

其中:

Ck={12,k=11,k=2,3,,8C_k = \begin{cases} \frac{1}{\sqrt{2}}, & k = 1 \\ 1, & k = 2, 3, \ldots, 8 \end{cases}

这便是将一个8×88 \times 8图像块XX转换为 DCT 系数矩阵MM的计算公式。

到这里为止,我们已经完成了 JPEG 实现 DCT 的理论基础。实际上,JPEG 正是将 Y、Cb、Cr 三个分量分别划分成一个个8×88 \times 8的样本块,然后对每个样本块进行 DCT,得到对应的 DCT 系数矩阵。

另外需要注意的是,按照 JPEG 中的约定,DCT 变换前需要先将输入样本块的每个样本值减去128,使得样本值的范围从[0,255][0, 255]变为[128,127][-128, 127],也就是把数据范围的中点定在了0。