数字图像处理(冈萨雷斯第三版)学习笔记 - Chaper 2 Image Compression(2)

我们现在要讲解建立图像压缩算法的每一个环节,而且会特别详细讲解静态图像压缩标准 (JPEG)。

一、 霍夫曼编码(Huffman coding)

我们先从符号编码器讲起。在完成所有变换和量化之后,我们希望进一步压缩图像,量化会提供一定程度的压缩,但我们仍希望进一步压缩。我们需要存储量化后的数据 我们需要保证精确无误地存储。

为什么可以压缩呢以及如何压缩呢?如下图这张叫 Lena 的非常著名的图片,右边是此图的直方图,直方图记录每一特定的灰度值在图像中出现的次数。 我们由直方图可以看出,有些灰度值出现很多次,而有些灰度值甚至没有出现过。因此,我们要采用的方法的基本思路是,给出现次数很多的像素值一个非常简短的编码,而给出现次数较少的像素值相对较长的编码。
数字图像处理(冈萨雷斯第三版)学习笔记 - Chaper 2 Image Compression(2)_第1张图片
下面来看一个应用上述方法的例子。以前面一节中见过的那个五角星图像为例子,这是比较特殊的图像,它只有四个不同的灰度值,而灰度值为87的像素占了整个图像的25%,或者说灰度值87出现的概率是 0.25。灰度值为128的像素几乎占了图像的一半, 灰度值186 而 255 出现的次数极少,其它的灰度值并没有出现。所以表示这个图像的标准方式如下图。这里用到了二维数组,对于每一个像素,如果使用八位二进制码表示其像素值,比如,我们用87的二进制码来表示灰度值87,用128的二进制码来表示灰度值128,以此类推。在最后,如果我们不进行压缩,那每个像素都要占8比特。但是现在来假设一下,如果我想办法构造了如图所示的编码,其中 87是用二进制码01来表示的,128 用二进制码1来表示,186用二进制码000表示,而255用二进制码001 。这就意味着,如果我们想保存87、128、186、255四个像素,这里只需要2+1+3+3 ,一共9bit,而不是32bit。
数字图像处理(冈萨雷斯第三版)学习笔记 - Chaper 2 Image Compression(2)_第2张图片
现在,解码器会根据这个表格解码。所以解码器看到01时,会在这个像素的位置输出 87,以此类推。如此就完成了压缩,我们可以来看看这到底压缩了多少。我们来做一个非常简单的计算,我们有0.25的概率,需要用到长度为2的二进制码01,有0.47的概率,使用长度是1的二进制码,有0.28的概率,要用到长度为3的二进制码。
0.25 ∗ 2 + 0.47 ∗ 1 + 0.25 ∗ 3 + 0.03 ∗ 3 = 1.81 0.25*2+0.47*1+0.25*3+0.03*3=1.81 0.252+0.471+0.253+0.033=1.81
所以现在整个图像中平均每个像素只占1.81比特,而不是之前的8比特。从8减到了1.81 ,也就是说,压缩了四倍以上。这里我们用到的编码方式其实就是霍夫曼编码,接下来我们将学习怎样实现编码。
数字图像处理(冈萨雷斯第三版)学习笔记 - Chaper 2 Image Compression(2)_第3张图片
解释霍夫曼编码最好的办法是举个例子,接下来用上图这个例子说明。现在假设我们用六个不同的符号,比如说 a2可能代表像素值100 符号 a6 可能代表像素值77 等等。**首先,按概率大小排列这些符号,概率从高到低,**如果有些概率相同,那么无所谓谁先谁后。**然后,从下往上,取最底下的两个数,把它们的概率相加。**用0.04加上0.06 得到了0.1的概率,**最后,将这一列概率重新排列一遍。**上面都没有变,唯一改变了的是最后一个,最后一个概率0.1对应a3或a5。所以此时我们就需要用一些办法来区分a3和a5。现在,重复一下刚才的步骤,将最低的两个值相加,现在 这个值是0.2,记住每次都要重新排序。此时的 0.2的概率对应的 a4、a3或者a5,我们现在还不能确定是哪个符号。 以此类推,再重复几次,直到只剩下两个数。
数字图像处理(冈萨雷斯第三版)学习笔记 - Chaper 2 Image Compression(2)_第4张图片
下面我们来进行编码,上图是依据前一幻灯片中图表的一个重绘。现在,从后往前,依次赋值编码,给0.6赋上0,给0.4赋上1。 此时当解码器看到0,它会明白这是从 0.6对应的这一组来的,当它看到一个 1,就会知道是 0.4对应的这一组的。然后要添加更多的比特位来区分每组中的各个数值,这就是为什么要从后往前走。接下来继续往回推进,把这个 0 抄送回来,这个0.6是由0.3加0.3得来的,所以要在后面分别加上0和1,当然我们还要把0.4抄送回来,给它赋上从上一列得来的码值,这里不用加任何数字,所以它会一直不加任何比特位被抄送到前面。继续往前抄送,以此类推,最终得到的这就是它们的编码。

这些是变长编码,用来表示各个符号的编码长度并不完全一样,各个编码是有不同长度的,而这个长度取决于概率,概率越大,编码越短。这很容易看出,因为低概率值在不断被相加,当往回推导的时候,就要不断分开,每次分开的时候都要加一个符号, 因此要为这个符号加一个比特位,所以,编码变得越来越长。

这里有几件非常重要的事情要注意:

  1. 霍夫曼编码是一个递归的过程,所以如果知道了怎么从一列进行到下一列,就知道了怎么完成它。

  2. 霍夫曼编码得到的编码叫做无前缀编码,这是非常重要的。以序号为2的列举例,当第一位是1,解码器看到一个1,它就知道对应的是a2。当这里有一个0,编码器就会看看下一个符号是什么再做判断。但是没有任何一段码完全是其它编码的前缀 ,这是非常重要的,不然解码器是无法重建图像的。举个反例,符号a1用码字1表示, 符号a2用码字0表示,符号a3用码字01表示。此时,a2完全是a3编码的前缀,当解码器看到01,会出现两种可能,一种可能是a3,另一种可能是a2后面跟着a1。重建过程就出现了很大的歧义,因为这种编码并不是无前缀编码。霍夫曼码在构建时,我们这样一次又一次地进行分裂,它的这种构建过程就决定了它是无前缀编码。

  3. 霍夫曼编码是最优编码方法。即编码过程能使编码平均长度达到最短。基本思路是,当我们面对一组概率时,能够在多大程度上对其进行压缩? 在构建霍夫曼码之前,我们想要知道,这值不值得我们花费精力去构建这种编码? 可以压缩很多吗?而这就要通过计算**熵(entropy)**来得到答案。熵使用符号 H 来表示,它的计算公式:
    在这里插入图片描述
    这里b取2,所以在这里就是0.4乘以0.4以2为底的对数,加上0.3乘以0.3以2为底的对数,依次类推。香农第一定理在说,编码长度大于等于信息熵,因此我们的编码长度可以渐渐逼近这个熵,也就是理想的最短码长。可以证明,霍夫曼编码正可以逼近于这个长度。

    实际上熵的公式可以这样理解,这个概率就是I出现的概率,而 I 对应的理想编码长度,也就是说,理想的编码长度就是这个概率的对数。但是编码长度不能只能是整数,所以熵只是编码长度的理想值。这就是为什么它是平均期望值,也是编码长度的理想值。

二、JPEG的8x8块

假设现在我们有一张图片,JPEG 或者有损变换编解码压缩,首先做的是把图片分成一些小块,每一个小块有 n x n 个像素点。JPEG 使用的是 8 x 8 的小块。所以,JPEG几乎是将一幅图像中的每个 8 x 8 小块独立编码,小块之间都是不重叠的。如果图片的宽度或者长度不是8的倍数的情况,现在不用担心这个,这些只是技术细节。但是这只是针对灰度图像。

如果是一张RGB图片怎么办呢?JPEG 其实是个色盲,JPEG不知道如何处理色彩,它本身不理解颜色标准。但是我们可以想一下怎么来处理这个问题?最原始的一个想法是,首先把红色通道取出来,因为每一个通道都是一个二维数组,于是红色通道看起来是这样的,将红色通道分成 8 x 8 的小块,之后对红色通道编码。然后对绿色、蓝色通道做同样的处理。看起来这个方法非常合理,但是实际情况是,通道之间有很多相关性,RGB它将色调,亮度,饱和度三个量放在一起表示,很难分开。

所以,JPEG 做的是它不对RGB编码,而是处理一种叫YCbCr的色彩空间,接下来我就来告诉大家怎么做这个。在这里,我们有Y、Cb和Cr三个通道,而不是RGB三个通道。Y是亮度通道 (luminance channel),这就是大家在黑白电视机上见到的通道。我们有一张彩色图片,但是只能看到黑白的部分,也就是它的亮度部分。Cb、Cr是色彩通道,Cb指蓝色色度,Cr指红色色度。实际上,我们把Y Cb Cr列成一个三维列向量,通过乘以一个 3 x 3 的矩阵就可以得到R G B组成的列向量。 所以Y、Cb、Cr都可以看作R、G、B的一个线性组合。
Y = 0.299 R + 0.587 G + 0.114 B Y=0.299R+0.587G+0.114B Y=0.299R+0.587G+0.114B

C b = 0.564 ( B − Y ) Cb=0.564(B-Y) Cb=0.564(BY)

C r = 0.713 ( R − Y ) Cr=0.713(R-Y) Cr=0.713(RY)

因此,JPEG对彩色图像首先会做的事,是将彩色图片的每一个像素乘上一个常数矩阵,公式如上。这样对图像的每一个像素点都进行处理,把红绿蓝 (RGB) 阵列转换成 YCbCr 阵列。接下来就是各个通道分开独立处理,先处理Y通道,将其分成 8 x 8 的小块,将这些小块分别独立地编码。

三、K-L变换( Karhunen-Loeve Transform )

现在对应 YCbCr 的每个域,我们都有 n x n 的方块,现在我们进入正变换这一节。首先,我们需要了解为什么我们要做正变换?

首先解释一下,在有损压缩时,我们是如何量度压缩图像所产生的误差,基本想法是,误差是由均方误差 (mean square error) ,简称MSE来量度的。均方误差的计算公式如下。
M S E = 1 n u m ∑ p i x e l s ( f 1 − f 0 ) 2 MSE=\sqrt{\frac{1}{num}\displaystyle \sum^{}_{pixels}{(f_1-f_0)^2}} MSE=num1pixelsf1f0)2
这就是我们测量误差的方式。那么,为什么我们需要做变换呢?想象一下,我们已经取了n x n 的方块,因为我们将对每个方块独立操作,每个方块尽管有 n x n (n=8时 是64) 个像素,我只对其中一个进行传输,我允许你只传输其中一个。那么,如果我们取一幅普通的图像,但只传输其中一个像素,误差将会非常大。

  • (上面这个地方老师是这么讲的,但是为什么不变换的时候误差就会非常大,我不是很理解,希望懂的读者可以在评论区解答一下,谢谢。)

所以,我们或许可以做一个变换。变换是什么意思?其实就是乘以一个矩阵,我有一个 8 x 8 模块,做了一些操作,得到一个新的 8 x 8 模块,也就是 8 x 8 的图像。这个新的图像称为变换域。其中,变换矩阵需要是可逆的,这样才可以恢复原状。

为了解决上述误差大的问题,存在这样一种变换,卡洛南-洛伊变换 (Karhunen-Loeve Transform),即 K-L变换,使我们可以只取第一个元素,均方误差就能取到最小。那么假如我刚才做的是 K-L变换(也称KLT变换),然后取64个元素中的第一个像素,计算均方误差,注意刚才只用了一个元素去做重建,去掉了其中的63个系数,然后反变换,并且计算均方误差,此时的误差即为只用一个元素情况下的最小误差。

如果我想要3个元素,我可以做完全一样的事。K-L变换,特别的地方在于,刚才我们不仅仅得到了在只用一个元素情况下最小的误差,而且那个元素还是第一个。所以可以想象,取第一个像素,那是你只能用一个元素的情况下,得到的最小误差。如果你想用三个系数得到最小误差, 那么就取前三个像素即可。所以这个变换很好。但是它有一个大问题,这个变换是依赖于图像的。即在我们看到图像之前,我们并不知道要乘的矩阵的系数。也就是说,我们需要先知道图像的信息, 然后我才可以计算这个矩阵的系数。这样实用性不强,这样很慢,因为我们不能一拿到图像就马上操作。

四、DCT变换( The Discrete Cosine Transform )

为了替代K-L变换,我们采用一种 离散余弦变换 (discrete cosine transform) ,这种变换是真正在 JPEG 中使用的。

但是要记住,我们其实是想要做 K-L变换,K-L变换所做的事情是消除图像元素之间的相关性。它把很多信息都放进了第一个系数,然后更多一点的信息放进了第二个系数, 两个系数间是相互独立的,之后的系数以此类推。而且我们计算得到的均方误差是最优的。

但是用离散余弦变换,我们将会得到次优的结果。但这个变换有一个固定的矩阵,有固定的系数,所以是通用的。我们可以对任意图片使用,而不需要做额外计算。

变换用连续的图像表达式 f(x,y) 来解释,我们有一张图像,记之为 f(x,y),来写下这个变换,我们会进入一个新的变换域 T(u,v) 。设我们有n个元素,所以我们的x和y将从 0 操作到 n-1,并用图像 f(x,y) 乘以变换系数 r(x,y,u,v)。变换公式和反变换公式如下。
T ( u , v ) = ∑ x = 0 n − 1 ∑ y = 0 n − 1 f ( x , y ) r ( x , y , u , v ) T(u,v)=\sum^{n-1}_{x=0}\sum^{n-1}_{y=0}{f(x,y)r(x,y,u,v)} T(u,v)=x=0n1y=0n1f(x,y)r(x,y,u,v)

f ( x , y ) = ∑ u = 0 n − 1 ∑ v = 0 n − 1 T ( u , v ) s ( x , y , u , v ) f(x,y)=\sum^{n-1}_{u=0}\sum^{n-1}_{v=0}{T(u,v)s(x,y,u,v)} f(x,y)=u=0n1v=0n1T(u,v)s(x,y,u,v)

现在我需要说明这两个函数 r 和 s ,其中的 r(x,y,u,v)和s(x,y,u,v)也被称作是基函数或者是基图像。理想化情况下r和s会使均方误差极小。对于K-L变换,这些系数r(x,y,u,v)完全依赖于图像,不仅依赖于所取的位置,还依赖于你图像上实际的灰度值。所以我们使用DCT来代替,那么DCT中 r 和 s 是什么呢?公式如下。
r ( x , y , u , v ) = s ( x , y , u , v ) = α ( u ) α ( v ) c o s [ ( 2 x + 1 ) u π 2 n c o s [ ( 2 y + 1 ) v π 2 n ] r(x,y,u,v)=s(x,y,u,v)=\alpha(u)\alpha(v)cos[\frac{(2x+1)u\pi}{2n}cos[\frac{(2y+1)v\pi}{2n}] r(x,y,u,v)=s(x,y,u,v)=α(u)α(v)cos[2n(2x+1)uπcos[2n(2y+1)vπ]

归 一 化 系 数 α ( u ) = { 1 n u = 0 2 n u = 1 , 2 , . . . , n − 1 归一化系数\alpha(u)=\left\{ \begin{aligned} &\sqrt\frac{1}{n} & u=0\\ &\sqrt\frac{2}{n} & u=1,2,...,n-1 \end{aligned} \right. α(u)=n1 n2 u=0u=1,2,...,n1

如果把上述反变换公式写成矩阵的形式,则得到以下公式。F是一个包含g(x,y)的像素的、大小是n*n的矩阵。
F = ∑ u = 0 n − 1 ∑ v = 0 n − 1 T ( u , v ) S u v {\bf F}= \sum^{n-1}_{u=0}\sum^{n-1}_{v=0}{T(u,v){\bf S_{uv}}} F=u=0n1v=0n1T(u,v)Suv

S u v = [ s ( 0 , 0 , u , v ) s ( 0 , 0 , u , v ) ⋯ s ( 0 , 0 , u , v ) s ( 0 , 0 , u , v ) ⋮ ⋯ ⋮ ⋯ ⋮ ⋯ ⋮ s ( 0 , 0 , u , v ) s ( 0 , 0 , u , v ) ⋯ s ( 0 , 0 , u , v ) ] {\bf S_{uv}}= \left[ \begin{matrix} s(0,0,u,v) &s(0,0,u,v) &\cdots &s(0,0,u,v)\\ s(0,0,u,v) &\vdots &\cdots &\vdots\\ \cdots &\vdots &\cdots &\vdots\\ s(0,0,u,v) &s(0,0,u,v) &\cdots &s(0,0,u,v)\\ \end{matrix} \right] Suv=s(0,0,u,v)s(0,0,u,v)s(0,0,u,v)s(0,0,u,v)s(0,0,u,v)s(0,0,u,v)s(0,0,u,v)

如下图这个例子, 为了使演示简单一些,我们取n=4。对于每一个基函数,它之前是关于u、v、x、y的函数,但如果固定u和v的值,我们则得到一个只与 x 和 y 有关的函数。由公式可以推出,当 v=0 且 u=0 时,基函数与x,y无关且为一常数1/4。当 v=1 且 u=0 时,基函数则只与y相关,以此类推。最终得到的基函数(基图像)如下图所示。
数字图像处理(冈萨雷斯第三版)学习笔记 - Chaper 2 Image Compression(2)_第5张图片
我们是在试着将 n×n 大小的小图像,分解成这些基函数的线性组合。如果是纯色图像,这里的T(u,v) 只要 u和v 的值都为0即可。如果图像中变化频繁,那么就需要一些系数,如T(3,3)。这样,每一个 T(u,v),都会被描述为有多少这样的部分组成,现在我们已经用此类图像的线性组合来表示 n × n 的图像了。

很重要的一点是,这些图像是不变的。而我们提到过,在K-L变换中它们并非如此,因此很难有高效的硬件实现可以实时完成那种变换。而此处的基图像是固定不变的,你给我一张图,我就会用这些基图像的一种线性组合来表示它。 T(u,v) 可以告诉我们,在 n × n 的图像里,有多少这些基图像组成,也就是提供基图像前面的系数,这就是变换。

为什么选择离散余弦变换? 为什么不选择傅里叶变换或其他变换方法,譬如哈达玛变换 (Hadamard Transform)? 变换方法有很多种,我们选择离散余弦变换有几个理由。

  1. 第一,其实在一些特定情况下,离散余弦变换完全等同于卡洛南-洛伊变换。这些特例中的图像,其像素排列符合马尔可夫链,即每个像素都以一种特殊的形式,依赖于相邻像素,然后相邻像素也是如此。 这就是所谓的一阶马尔可夫图像源( Markovian)。如果可以假设一张这样的图像,就可以证明卡洛南-洛伊变换最终就是离散余弦变换,所以最终,像我们上面看到的一样,它的基图像是固定不变的。这就是我们使用离散余弦变换的一个原因。

  2. 第二,为什么不用傅里叶变换之类的方法呢? 因为离散傅里叶变换对周期性有一个潜在的假设,假设图像如下图一样在自我重复。这个假设适用于每个方向,以及整个二维平面。也就是说,我们需要一个小块中第一个像素,与下一个小块中的第一个像素(n个之后的像素)完全相同,这就是潜在的假设。但是这种假设存在的可能性很低。
    数字图像处理(冈萨雷斯第三版)学习笔记 - Chaper 2 Image Compression(2)_第6张图片
    然而,离散余弦变换对周期性所作的假设是不同的,离散余弦变换假设边界处有镜面对称,如下图,即是说图像是在重复,但是翻折了。这个假设是说,假定这里的像素与相邻的这个像素类似,不是说像素与八个像素之后的那一个相同,而是与紧接着它的那个像素相同。这个假设更合理,这就是我们选用离散余弦变换的两个原因。其一是对一类图像而言,进行离散余弦变换就是在进行卡洛南-洛伊变换。其二是周期性,在我们从 n × n 或 8 × 8 小块开始处理图像时,离散余弦变换对周期性的假设更为实用。
    数字图像处理(冈萨雷斯第三版)学习笔记 - Chaper 2 Image Compression(2)_第7张图片
    另外,现在可以解释一下我们怎么得到 8 × 8 这个值的? 其实,JEPG是在大量研究之后被设定为 8 × 8 规格的。对于下面这张图,如果我们取了整个图像,即是说我们没有做分割,进行离散余弦变换,并取系数的25%,也就是说我们只取了所有系数的四分之一,并将其它系数设为零。 之后我们进行逆变换,下图a是我们得到的结果。
    数字图像处理(冈萨雷斯第三版)学习笔记 - Chaper 2 Image Compression(2)_第8张图片

我们之前介绍过误差,而离散余弦变换所做的是,尽可能把我们引入一个变换域,在其中会去掉一些系数以实现压缩,将它们设为 0 或引入一些差值,这会产生误差,但只会造成很不显眼的误差。比如在这里我们取了25%的离散余弦变换系数,并且我们取的是1/4的值最大的那些系数。

那么如果这里我们取 2 × 2 的小块,就是说我们这里把此图分割为 2 × 2 的小块,即对每个 2 × 2 小块进行离散余弦变换,然后取系数的25%,也就是只用了1个系数(4*0.25),然后逆变换,上图b是我们得到的结果,很显然2 × 2的结果并不好。那么我们对 4 × 4 的分块方式以及 8 × 8 的分块方式,分别做了同样的处理,分别得到图c和图d,我们可以看到图像效果越来越好。 从实验结果中可以看出,块分得约大,误差就会越小,也就是说如果不分块,即一整块时,误差最小。而且,从上图可得, 8 × 8的分块方式已经和不分块的方式结果很接近了。

但是为什么不选择 16 × 16 或 32 × 32 呢?有这样几个原因。第一,在计算方面,进行多个小型离散余弦变换会比一个大型变换更划算。比如你有一个 16 × 16 的图像,对四个 8 × 8 小块进行离散余弦变换,比对一个 16 × 16 大图做变换更方便。第二,DCT变换与KLT变换奇妙的相似性。上文说过,在一阶马尔可夫图像源这个特殊的条件下,DCT变换可以近似为KLT变换。如果是在 8 × 8 小块里的一个小范围内,马尔可夫链条件可能成立,但如果在一个很大的图像上操作,整张图不会满足马尔可夫链条件。也就是说,马尔可夫链条件小范围中假设可能成立,但在很大范围中则不行。
数字图像处理(冈萨雷斯第三版)学习笔记 - Chaper 2 Image Compression(2)_第9张图片
上句话可以这样理解, 比如上图,你会看到很多相关性,比如肩膀附近的灰度值就具有相关性,它们灰度值几乎一样,但如果向比较远的地方看,比如帽子上的一点,那么这两个像素值之间就没有相关性了,Lena可以换一个帽子改变其灰度,但不需要考虑肩膀上灰度问题。即当图中的两个像素离得很远的时候,它们就没有相关性了。

所以对于分块的大小,我们需要折中选择。一方面你希望有很小的小块以实现高效计算,另一方面你不希望小块太小,因为小块中没有足够的信息,没有足够的关联性在变换中会被去相关,实验结果也表明产生的误差会很大。 然而如果你选了太大的模块,我们将要面对低效的计算,并且还忽视了 DCT 变换优势的设定条件。所以深入研究后最终确定 8 × 8 是个不错且折中的选择,并被 JPEG 采纳。所以这是一个很有意义的成果。
数字图像处理(冈萨雷斯第三版)学习笔记 - Chaper 2 Image Compression(2)_第10张图片
最后总结一下,我们先按 8 × 8 分块,进行离散余弦变换,我们保留 12.5%的系数,然后我们进行逆变换,还没有真正进行量化,这个下一节会讲,这里我们只进行离散余弦变换和它的逆变换。但是我们已经成功压缩了,因为我们只保留了像素的12.5%,但得到了看起来和原图几乎一样的效果,如上图。这是因为我们是在离散余弦变换的变换域中进行处理的,如果我们只是扔掉大量像素,只保留它的12.5%,我们不能得到这样质量的图像。上右图是原图和重建的图像之间的误差,可以看到,这是个很小的误差。所以说单单一个离散余弦变换,去除大量系数,然后逆变换,就已经完成了相当不错的压缩。接下来,我们将在 DCT域中,通过量化,实现更大程度的压缩。

你可能感兴趣的:(数字图像处理(冈萨雷斯第三版)学习笔记 - Chaper 2 Image Compression(2))