前言

最近学习了图像压缩的相关知识,准备在此方向上进行深入研究,最后以期能利用深度学习的方法来实现图像压缩。

图像压缩的基本原理

图像压缩的基本原理是将图像中的冗余信息去除,从而减小图像的大小。

图像压缩的方法有很多,包括有损压缩和无损压缩。有损压缩是指将图像中的冗余信息去除,从而减小图像的大小。无损压缩是指将图像中的冗余信息保留下来,依然可以减小图像的大小。

当我们在使用JPEG压缩图片时,我们可以选择不同的压缩质量。压缩质量越高,图片的质量就越好,但是图片的大小就越大。压缩质量越低,图片的质量就越差,但是图片的大小就越小。

质量取值合理的时候视觉上并不能看出差别,为什么丢失掉一些信息却还能让图片看起来和原图一样呢?

这里我们就要解释一下人眼的视觉原理了。人眼中有两种敏感的视觉细胞,一种是对亮度变化敏感的细胞,另一种是对颜色变化敏感的细胞。视感细胞有一亿个,而视锥细胞只有600万个。所以人眼对亮度变化非常敏感,而对颜色变化不敏感。也就是说,人眼对亮度变化的敏感度比颜色变化的敏感度要高。因此,JPEG算法会将亮度变化的元素保留下来,而将颜色变化的元素压缩掉。这样就可以让图片看起来和原图一样,同时又减小了图片的大小。

举个例子,这里有两张图片,分别是只显示亮度的黑白版本和完整的彩色版本。

保留亮度版本
完整彩色版本

可以看出只显示亮度的黑白版本,似乎与完整的彩色图像一样详细。但是当我们统一亮度之保留色彩时,图片的细节就会明显丢失掉。

保留色彩版本
保留亮度版本
完整彩色版本

JPEG压缩算法的基本流程如下:

  1. 色彩空间转换:将RGB颜色空间转换为YUV颜色空间
  2. 色差缩减取样
  3. 离散余弦变换(DCT)
  4. 量化
  5. 熵编码
  6. 将每个小块的熵编码结果合并成一个压缩图像

色彩空间转换

原始图像是由像素组成的,每个像素都有一个RGB值。RGB值是由红、绿、蓝三个颜色通道组成的。每个颜色通道的值都在0到255之间。这些RGB值组合起来就形成了一个像素的颜色。
当然,你也可以讲RGB三个通道的值单独提取出来,然后进行处理。

对于RGB颜色图像我们可以通过公式:
Y(亮度) = 0.299 R + 0.587 G + 0.114 B
Cb(蓝色色度) = -0.147 R - 0.289 G + 0.436 B
Cr(红色色度) = 0.615 R - 0.515 G - 0.100 B
将RGB颜色空间转换为YUV颜色空间。

YUV颜色空间是一种亮度和色度分离的颜色空间。YUV颜色空间的亮度通道是Y,色度通道是U和V。Y通道的值在0到255之间,U和V通道的值在-128到127之间。YUV色彩空间是一种表示颜色的方法,RGB才是真正的物理色彩,也就是说,不管你咋表示, 最后要显示的时候, 还得要依靠红、绿、蓝三色叠加而成。铁证就是各种屏幕都是由红、绿、蓝三种发光点组成的。

从RGB空间导YUV空间的过程是可逆的。但是在之后的“色度缩减取样“”步骤中,将会对数据进行删除。

色差缩减取样

色差缩减取样是JPEG压缩算法的一个重要步骤。色差缩减取样是指将YUV颜色空间中的色度通道进行缩减取样。

色差缩减取样的目的是为了减少色度通道的数据量,从而减小图像的大小。我们将Cb和Cr色度分离层上的像素按照2*2的块进行取样,然后对每个块的色度区平均值。

如图:

分成2*2的块
块内取平均值

之后我们就可以缩小图像了,将含有一个平均值的块代替原来的4个像素。到此为止,我们就将人眼不能敏锐感知的色彩缩减为原来的1/4,而保持了亮度不变。至此我们的图像已经压缩为原来大小的一半了。

Befor:1 + 1 + 1 = 3 (RGB)
After:1 + 1/4 + 1/4 = 1.5 (YUV*缩减CrCb)

当我们需要查看图片的时候,只要将Cb和Cr色度通道大小进行还原为完整大小,再计算为RGB值,就可以将图片还原为原来的RGB颜色空间。注意由于我们之前进行了取平均值的动作,所以当我们还原的时候RGB值会有变化。

离散余弦变换(DCT)

此步骤也会删除信息,但是这是基于人眼对于高频率元素的敏感度低的原理。

例如你在一个树林中,你会对一棵树,一块大石头的轮廓有感知,但是你很难分辨出一片树叶、一颗草上的纹路。此外,大多数风景照片的一部分会是失焦状态,为了更平滑的纹理去移除高频率的颜色变化的操作是人眼无法察觉的。

JPEG就利用了人眼的这一特性,通过离散余弦变换和量化的两个步骤,遍历图像的各个部分,并找到具有高频的色度或者亮度的色素区域。然后将这些人眼无法感知的元素删除,例如拍摄天空,这个天空的很多部分都是天蓝色,那么可以利用这一特性对数据进行简化。

亮度、蓝色色度、红色色度三个图层都进行了一样的处理,接下来我们以亮度图层为例进行说明。

首先,将图像分成8*8的区块,每个区块都有64个像素。每个像素的取值是0-255代表其亮度。我们通过减去128的方式来重新标定各个像素的亮度值,这一范围就变成了-128-127。-128是黑色,127是白色。

如图:

然后,对每个区块进行离散余弦变换(DCT)。离散余弦变换是一种将图像从空间域转换到频率域的方法。它将图像的每个小块都转换为一组频率系数。

DCT步骤:
1.获得图像的二维数据矩阵f(x,y);
2.求离散余弦变换的系数矩阵\[A];
3.求系数矩阵对应的转置矩阵\[A]T;
4.根据公式 \[F(u,v)]=\[A][f(x,y)][A]T 计算离散余弦变换;

同时DCT会定义64个8*8像素的基础图像表,和一个常量表。这个常量表的元素对应的是基础图像每个元素使用的次数。

基础图像表如下:

DCT的原理是将每个区块的像素值与基础图像进行点乘,然后将结果相加。

需要知道DCT并不会减小图片的大小,我们需要在后面的“量化”步骤中进行操作。在DCT处理后,频谱左上角聚集的是低频信息。也就是对于图像来说最重要的元素信息。我们可以对右下部分的高频信息进行处理,使其大多数不重要的元素变成0,以方便表示。

这就是量化的思想。

量化

量化是JPEG压缩算法的一个重要步骤。量化是指将DCT变换后的结果进行量化。

量化的目的是为了减少DCT变换后的结果的数据量,从而减小图像的大小。我们将DCT变换后的结果按照8*8的块进行量化。

我们用刚才提到的常量表除以量化表中的对应值,并将结果取整。

量化表如下:

可以看出,右下角的数值较高,以便将人眼不擅长感知的高频信号归零处理。

处理后的区块数据如下所示:

可以看到主要的数据集中在左上角,而右下角多是一些连续的0。根据这种特性,我们通过“之”字形进行遍历,将二维数组拉成一条一维数组,这样处理的好处是可以将尽可能多的0集中在一维数组的后面,我们用EOF表示后面全部是0。

对于色度,我们使用了一个数值更大的量化表,使表中的0更多。

综上,我们使用了一张88的基础图像表,一张88的亮度量化表,一张8*8的色度量化表。完成了DCT和量化的操作。

熵编码

熵编码是无损压缩。

游程编码,是一种简单高效的无损数据压缩算法。具体来说,游程编码的基本思路是将数据视为一个线性序列,并将其分为两种情况:连续的重复数据块和连续的不重复数据块。在图像处理中,游程编码特别适合于二值图像的编码,通过用一个符号值或串长代替具有相同值的连续符号,使符号长度少于原始数据的长度,从而实现压缩。

例如,字符串 “AAABBBBBCCDDDD” 可以被游程编码为 “A3B5C2D4”。游程编码的优点包括对简单图像有压缩作用。

本例中,我们使用了游程编码的方法,将 “140,-14,-60,14,-8,-14,-3,14,4,0,0,-2,4,4,-1,02,-1,-2,0,0,0,0,0,0,-1,0,0,0,0,0,00,0,0,0,0,0,0,0,0,0,0,0,0,0,0,00,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0” 精简为–“140,-14,-60,14,-8,-14,-3, 14, 4, 0[x2]-2,4[x2],-1,0,2,-1,-2,0[x6],-1,0[x38]”。

之后,我们使用哈夫曼编码,将数据进行压缩。就完成了整个JPEG压缩算法的流程。

H.264

使用了色度缩减取样、离散余弦变换和量化技术的变种,H.264不是像JPEG压缩单一的静态图像,视频压缩必须在一秒内压缩24-60帧甚至更多。

以30帧举例,只会对其中的一帧进行类似JPEG的压缩操作,叫做“I帧”,然后对于剩下的29帧,它用预测或者双向预测,只对差异和运动进行编码,同时使用先前解码的帧作为参考。

需要注意的是I帧的频率变化很大,通常在每个场景变化的开始都有一个I帧。因为在不同的场景,你不能预测出下一帧的运动,所以I帧的压缩率很低。

重建图像

执行反向步骤。

  1. 熵解码
  2. 反量化
  3. 反DCT
  4. 反色差缩减取样
  5. 反色彩空间转换

JPEG的缺陷与备注

由于在量化中涉及到量化表的取值问题,如果取值过大就会丢失过多的细节信息。

JPEG算法在压缩相机图片时表现出色,但是对矢量图压缩效果不好。