ImageSharp源码详解之JPEG编码原理(1)JPEG介绍

最近在看GitHub上的一个很火的项目是:ImageSharp。这是一个纯.net core的图像处理库,没有使用其他的任何依赖。在看这个项目过程中激发了我对图像文件编码解码的兴趣。于是从最简单的BMP图像开始看,到GIF格式卡了一段时间(主要卡在lzw编码过程和数据块中),到最后的JPEG格式(PNG格式不打算看了),经历了半个月时间才梳理出个大概。趁着这个热乎劲,我想写下关于JPEG格式的系列文章,文章目录暂定如下:

ImageSharp源码详解之JPEG编码原理(1)JPEG介绍

ImageSharp源码详解之JPEG编码原理(2)采样

ImageSharp源码详解之JPEG压缩原理(3)DCT变换

ImageSharp源码详解之JPEG压缩原理(4)量化

ImageSharp源码详解之JPEG压缩原理(5)熵编码

ImageSharp源码详解之JPEG压缩原理(6)C#源码解析及调试技巧

1.JPEG介绍

JPEG(Joint Photographic Experts Group)是联合图像专家小组的英文缩写。它由国际电话与电报咨询委员会CCITT(The International Telegraph and Telephone Consultative Committee)与国际标准化组织ISO于1986年联合成立的一个小组,负责制定静态数字图像的编码标准。

小组一直致力于标准化工作,开发研制出连续色调、多级灰度、静止图像的数字图像压缩编码方法,即JPEG算法。JPEG算法被确定为国际通用标准,其适用范围广泛,除用于静态图像编码外,还推广到电视图像序列的帧内图像压缩。而用JPEG算法压缩出来的静态图片文件称为JPEG文件,扩展名通常为*.jpg、*.jpe*.jpeg。

JPEG专家组开发了两种基本的压缩算法、两种数据编码方法、四种编码模式。具体如下:

压缩算法:

1有损的离散余弦变换(Discrete Cosine Transform,DCT);

2 无损的预测技术压缩。

数据编码方法:

1哈夫曼编码;

2算术编码;

编码模式:

1基于DCT顺序模式:编/解码通过一次扫描完成;

2基于DCT递进模式:编/解码需要多次扫描完成,扫描效果从粗糙到精细,逐级递进;

3无损模式:基于DPCM,保证解码后完全精确恢复到原图像采样值;

4层次模式:图像在多个空间多种分辨率进行编码,可以根据需要只对低分辨率数据作解码,放弃高分辨率信息。

在我阅读的源码中,我关注的是离散余弦变换、哈夫曼编码、基于DCT顺序模式的编码,这也是JPRG图像常用的技术。

整体文件的大致结构如下:

SOI(0xFFD8)

APP0(0xFFE0)

[APPn(0xFFEn)]可选

DQT(0xFFDB)

SOF0(0xFFC0)

DHT(0xFFC4)

SOS(0xFFDA)

压缩数据

EOI(0xFFD9)

我们解码的时候大致都是按照上面顺序进行解码,关于上面这些标记,大家可以从文章结尾参考资料中看到他们的详细信息,这里我不对这些标记展开描述,在后面用到的时候会提到。

2.JPEG压缩的大致过程

2.1 编码

对于一副图像,编码器首先需要填充这个图像的一些头信息,量化表,霍夫曼表。我们可以看ImageSharp中JpegEncoderCore这个类里面的的Encode方法如下:

 1            ...
 2            // Write the Start Of Image marker.
 3             this.WriteApplicationHeader(metadata);
 4             // Write Exif and ICC profiles
 5             this.WriteProfiles(metadata);
 6             // Write the quantization tables.
 7             this.WriteDefineQuantizationTables();
 8             // Write the image dimensions.
 9             this.WriteStartOfFrame(image.Width, image.Height, componentCount);
10             // Write the Huffman tables.
11             this.WriteDefineHuffmanTables(componentCount);
12             // Write the image data.
13             this.WriteStartOfScan(image);
14             // Write the End Of Image marker.
15             this.buffer[0] = JpegConstants.Markers.XFF;
16             this.buffer[1] = JpegConstants.Markers.EOI;
17             stream.Write(this.buffer, 0, 2);
18             stream.Flush();
19         }           
View Code

这一系列都是围绕着WriteStartOfScan这个方法展开,这个方法就是对于图像数据进行编码,过程如下图:

注意我们看到过程中YUV采样后面是DCT变换、量化、熵编码。实际过程中,YUV采样过程中就包含后面DCT变换、量化、熵编码这三个过程,只不过我们在描述的时候将其分开。在ImageSharp源码中我们可以看到它使用的是标准霍夫曼表来进行编码,这也是一般JPEG编码器常用的方法,但这样就和常规的霍夫曼编码不一致,我们可以通过其他资料学习到霍夫曼编码需要对原始数据遍历两次,一次构建霍夫曼树,一次进行编码。我在谷歌的guetzli项目中看到了针对不同图像数据,构建不同霍夫曼树结构的方法。

2.2 解码

作为编码的互逆过程,大致流程如下:

 

虽然我们了解了如何编码,就能大致知道如何解码,但是ImageSharp源码中,对于解码和编码在代码实现还是有区别的,后续我只分析JPEG的编码过程。

3.后续

后面计划是先把量化和熵编码的相关文章写完,然后把imagesharp这个项目中调试技巧做一下分析解读,最后再写DCT变换。DCT涉及到傅里叶变换,如果要深入理解,其背后复杂的数学知识不是我所能讲解的,在这一章尽量讲解代码技巧,数学公式不敢牵涉太多。

 参考文献:

学习JPEG编码需要参考大量的资料,这些资料侧重点都不一样,需要相互印证。对我来说看代码比看文献更快,我的方法就是通过调试代码来验证我的猜想,下面是我看过的一些资料,后面文章不在赘述:

1.itu-t81.pdf JPEG文件格式,标准的霍夫曼编码参考的是这个资料,在ImageSharp源码中可以下载获取。

2.impulseadventure 这个网站里面可以找到一个叫JPEGsnoop的开源软件,可以分析JPEG各个标记的具体数值,同时这个网站里面很多教程都是关于JPEG的编码和解码,我文章里面一些图片都来自上面。

3.JPEG压缩原理,一位大牛的博客,值得一看。

4.FluxJpeg 这个源码只针对JPEG格式进行编码与解析,所以看起来相对ImageSharp简单很多,我前期就是先对照资料看的这个源码,后面换成ImageSharp。

5.数据压缩导论(第4版) 这本书很好,理论讲解的很深入,例子举的也很容易看懂。

6.实用数字信号处理-从原理到应用  这本书关于DFT的讲解很深入,值得一看。

 

posted @ 2019-08-04 16:50  程序员小张  阅读(2886)  评论(2编辑  收藏  举报