DCT变换

老师要求在课堂上讲一种变换,感觉 DCT 还是挺有趣的,就决定讲它了!

DCT(简单易懂版)

  DCT,Discrete Cosine Transfrom,离散余弦变换,主要用于图像压缩中,比如 jpeg。

  一般来说,我们人眼对灰度没那么敏感,对颜色比较敏感。所以我们一般不用三原色(RGB)来表示图片,而是用 YCrCb,如下图所示:

YCbCr-color-spaces

  然后我们对灰度图进行压缩。根据前面的内容,我们知道可以用不同频率的余弦函数的线性组合来表示任意函数,在图片中也是一样,对于任意的 8x8 灰度图,我们都可以用下面一系列离散余弦函数来表示:

  我们取灰度图中的一个 8x8 矩阵的为例,数值越大,颜色越浅:

  然后我们对这个进行归一化,归一到 -128~127(因为cos的值在 -1~1 之间),也就是每个值减去 128

  然后进行 DCT 变换,也就是计算每个余弦函数的系数,得到:

  这里我们为了方便看,仅保留一位小数。注意到这个矩阵有个特点:左上角的数字比较大,右下角的数字比较小。这是因为图片中的低频分量大,高频分量比较小(PS. 人眼对高频分量不敏感,高频就是压缩中损失的部分)。

  注意此时还未进行压缩(不考虑舍去小数点),我们依然可以进行 IDCT 变换得到原图。那么如何压缩呢?我们先定义一个量化表(difine quantization table),如下左:

  然后将DCT变换后得到的矩阵中的每个数字,除以量化表对应的数,再取最近的整数,得到上面右边的矩阵。可以很明显的看出,最终只有左上角的数不为0,其余大部分数都为0。这说明量化表的数字越大,压缩率越高。然后我们以 Z字形的顺序,从左上到右下将这个矩阵保存为一个数组,再利用霍夫曼编码,就可以实现图片的压缩,得到 JPEG 图像。

  反编码很简单。乘以表格,然后再进行 IDCT 变换即可。最终得到图片如下:

原图 压缩图

  放大了看可以很明显的看出差异,但注意这只是一张图片中的 8x8 个像素,放在图片中我们是分辨不出差异的。

  总结一下得到 JPEG 的过程:

  1. RGB 转 YCrCb
  2. DCT
  3. 量化
  4. 霍夫曼编码
  5. JPEG!

  JPEG 图片中会存储量化表,每个图片的量化表是不同的。

#DCT实验 #Python
import matplotlib.pyplot as plt
import numpy as np
from scipy.fft import dct, idct

np.set_printoptions(precision=1, suppress=True)

origin_pic = np.array(
    [
        [62, 55, 55, 54, 49, 48, 47, 55],
        [62, 57, 54, 52, 48, 47, 48, 53],
        [61, 60, 52, 49, 48, 47, 49, 54],
        [63, 61, 60, 60, 63, 65, 68, 65],
        [67, 67, 70, 74, 79, 85, 91, 92],
        [82, 95, 101, 106, 114, 115, 112, 117],
        [96, 111, 115, 119, 128, 128, 130, 127],
        [109, 121, 127, 133, 139, 141, 140, 133],
    ]
)
plt.figure("origin picture")
plt.imshow(origin_pic, cmap="gray")
plt.show()

centered_pic = origin_pic - 128

dct_pic = dct(centered_pic, 2, norm="ortho")

table = np.array(
    [
        [6, 12, 14, 14, 18, 24, 49, 72],
        [11, 12, 13, 17, 22, 35, 64, 92],
        [10, 14, 16, 22, 37, 55, 78, 95],
        [16, 19, 24, 29, 56, 64, 87, 98],
        [24, 26, 40, 51, 68, 81, 103, 112],
        [40, 58, 57, 87, 109, 104, 121, 100],
        [48, 60, 69, 80, 103, 113, 120, 103],
        [51, 55, 56, 62, 77, 92, 101, 99],
    ]
)
compressed_pic = np.around(dct_pic / table)
print(compressed_pic)

decode_pic = idct(compressed_pic * table, 2, norm="ortho") + 128
plt.figure("compressed picture")
plt.imshow(decode_pic, cmap="gray")
plt.show()

参考