JPEG压缩的部分细节
- 最近在探究JPEG压缩的过程,一开始在网上找,琳琅满目个抄个的,又有很多不一致的地方(特别是YCbCr的公式看过好几个版本),也有些细节丢失。由于网上的不准确,根据我自己摸索和查阅总结出来的应该算正确JPEG压缩细节是这样的:
- RGB图片转YCbCr:从广泛使用的jpeglib这个cpp库找到了YCbCr转换的代码,其注释内容如下:
(这里的MAXJSAMPLE是255,CENTERJSAMPLE是128)
# * YCbCr is defined per CCIR 601-1, except that Cb and Cr are
# * normalized to the range 0..MAXJSAMPLE rather than -0.5 .. 0.5.
# * The conversion equations to be implemented are therefore
# * Y = 0.29900 * R + 0.58700 * G + 0.11400 * B
# * Cb = -0.16874 * R - 0.33126 * G + 0.50000 * B + CENTERJSAMPLE
# * Cr = 0.50000 * R - 0.41869 * G - 0.08131 * B + CENTERJSAMPLE
-
这里有一个细节是padding,从而导致Y分量的dct系数的block数量实际上并不一定是Cb/Cr分量的2倍
Y分量的padding是到8的倍数,而CbCr的padding要到16的倍数,才能保证降采样后是8的倍数
也就是说,当原图的尺寸是width=16n+1时,Y分量的padding是7,从而有width_in_blocks = 2n+1
而Cb的padding是15,从而有width_in_blocks = 2n+2,比Y多出1
padding应该是在RGB转YCbCr前做的,padding的应该是0
然后这部分padding在解码的时候,由于JPEG文件中记录了原始图片的长宽值,因此解码库一般都会切掉,从而还原成原始的size,所以其实padding啥无所谓 -
然后是CbCr的降采样,取的是奇数列,然后Cb取奇数行Cr取偶数行,所以到这一步得到的Cb Cr和行列都是Y的1/2 或1/2+1(由于上述情况)
然后是分8x8的block进行DCT变换,Y Cb Cr都是单独分的,所以实际上Cb和Cr的一个block对应到原图是16x16的block(由于降采样过),而Y则是对应原图8x8的block -
DCT变换后,每个8x8的block得到了8x8的DCT系数,除以8x8的量化矩阵得到8x8的被量化DCT系数
然后取整,zigzag编码拉直成64,这样每个block对应了一个64维的向量,接下来要做的就是对这个向量进行压缩编码
注意从转成YCbCr开始,Y Cb Cr这三个分量就是各自计算互不相干了。除了降采样,大家都经历相同的过程 -
压缩编码首先是提取第一个值,也就是DCT的直流分量,有多少个block就有多少个直流分量,对这些直流分量单独编码,用是差分脉冲编码
由于相邻块亮度值变化不大,因此差分脉冲编码可以得到一些很小的值,使用变长整数编码来保存差分脉冲编码的结果可以极大降低码长
然后是每个block内部的64维度向量,使用行程编码,由于取整后有很多零值,这一步的行程编码将很大程度降低码长,另外,同样使用了变长整数编码来保存行程编码的结果 -
最后是对得到的最终编码结果使用Huffman编码,对亮度、色度、直流、交流分量使用不同的huffman码表。一共是4张。
-
以上内容有很多是参考 https://zhuanlan.zhihu.com/p/601614313,讲得很清楚也提供了代码,我个人仅补充了jpeglib和padding的部分