2025年3月22日 星期六 甲辰(龙)年 月廿一 设为首页 加入收藏
rss
您当前的位置:首页 > 计算机 > 文件格式与编码

JPEG原理分析 & 文件格式分析 & 解码原理 & JPEG解码器的调试

时间:02-25来源:作者:点击数:69

1 JPEG原理

1.1 JPEG简述

JPEG(Joint Photographic Experts Group)是联合图像专家组的英文缩写。 该组织从1986年正式开始制订静止数字图像的压缩编码标准,该标准于1992年正式通过,称为JPEG标准。

JPEG是第一个数字图像压缩的国际标准,它不仅适于静止图像的压缩,对 于电视图像序列的帧内压缩也常采用JPEG算法,因此JPEG是一个适用范围广泛的通用标准。


1.2 JPEG编码过程

在这里插入图片描述
1.2.0 RGB to YUV

为了减少各分量之间的相关性,减少数据的冗余,通常会把RGB颜色空间转换成YUV来进行各分量的编码。

在这里插入图片描述

1.2.1 Level Offset 零电平偏置下移

该步骤的作用是,图像内容平均亮度较高,将0电平移到中间,平均亮度降低, 便于DCT变换量化后直流的系数大大降低,也就降低了数据量。

将灰度级2 n 2^n2n的像素值,全部减去2 n − 1 2^{n-1}2n−1,数据形式由无符号数变为有符号数(补码),单极性数据变为双极性数据。


1.2.2 8x8 DCT变换

该步骤主要是用于去除图像数据之间的相关性,便于量化过程去除图像数据的空间冗余。

将图像分为8×8的像块;对于宽(高)不是8的整数倍的图像,使用图像边缘像素填充,以不改变频谱分布。然后对每一个子块进行DCT(Discrete Cosine Transform,离散余弦变换)。

DCT变换:

在这里插入图片描述

其中,C是8x8的DCT变换二维核矩阵,f ( x , y ) f(x,y)f(x,y)是原始的数据。由于DCT变换是一个正交变换,故CT = C−1

变换核矩阵如下所示:

在这里插入图片描述

需要特别强调的是,DCT是一种无损变换,也无法对图像进行压缩,这样做的目的是在为下一步的量化做准备。


1.2.3 Uniform scalar quantization 均匀标量量化

量化器主要是利用人眼视觉特性设计而成的矩阵量化DCT系数,减少视觉冗余。

将DCT变换后的临时结果,除以各自量化步长并四舍五入后取整,得到量化系数。

JPEG系统分别规定了亮度分量和色度分量的量化表,色度分量相应的量化步长比亮度分量大。

在量化步骤中,JPEG采用了中平型(Midtread)的均匀量化器。

量化是编码流程中唯一会引入误差也是唯一会带来压缩的步骤。Y、UV各一张表,共两张表。


1.2.4 直流:DPCM + VLC 可变长熵编码(采用Huffman)

8×8图像块经过DCT变换之后得到的DC直流系数有两个特点:

  • 系数的数值比较大
  • 相邻8×8图像块的DC系数值变化不大,冗余

根据这个特点,JPEG算法使用了差分脉冲调制编码(DPCM)技术,对相邻图像块之间量化DC系数的差值DIFF进行编码:

在这里插入图片描述

DPCM是对于DC系数处理时所需要用到的预测编码方法。此方法在之前的实验中已经有详细说明。

对DPCM后算出的DIFF差值使用Huffman编码。

将其分成类别,类似于指数的Golomb编码(只不过Golomb是一元码+定长码),也就是类别ID使用规范哈夫曼编码,类内索引使用定长码(自然码)。

所以,DC系数会产生一张长度为16的Huffman码表。

在这里插入图片描述

1.2.5 交流:ZigZag Scan + Run Length Encoding+VLC

对于量化后的数据,我们将其分为两路进行处理。一路是AC通路,一路是DC通路。ZigZag Scan+RLE是用于AC通路的,这是因为AC分量出现较多的0。JPEG采用对0系数的游程长度编码。而对非0值,则要保存所需数和实际值。

在编码之前,需要把二维的变换系数矩阵转换为一维序列,由于量化之后右下角高频系数大部分为零,采用ZigZag Scan读取可以制造较长的零游程,提高编码效率。在扫描中,如果后续的系数全部为零,则用“EOB”表示块结束。

在这里插入图片描述

在扫描后,采用RLE进行编码。

1.2.5.1 RLE编码的过程:
  1. RLE编码。在JPEG编码中,RLE的含义就同其原有的意义略有不同。在JPEG编码中,假设RLE编码之后得到了一个(M,N)的数据对,其中M是两个非零AC系数之间连续的0的个数(即,行程长度),N是下一个非零的AC系数的值。采用这样的方式进行表示,是因为AC系数当中有大量的0,而采用Zigzag扫描也会使得AC系数中有很多连续的0的存在,这样就很适合RLE编码。

例:例如,现有一个字符串,如下所示:

57,45,0,0,0,0,23,0,-30,-8,0,0,1,000…

经过RLE之后,将呈现出以下的形式:

(0,57) ; (0,45) ; (4,23) ; (1,-30) ; (0,-8) ; (2,1) ; (0,0)

注意,如果AC系数之间连续0的个数超过16,则用一个扩展字节(15,0)来表示16连续的0。

  1. Huffman编码。
    在进行了RLE后,仍然需要进行Huffman编码。对于任何一个RLE的数据对,如(0,57),都可以表示成(RRRR,SSSS)的形式。其中前面的0-16采用自然码RRRR,后面的SSSS则是与DC一致的Huffman分组的编码方式,存储索引。放到码流里的是其组内编码。
    在这里插入图片描述

所以,最后总共有4张Huffman码表(亮度DC,亮度AC,色度DC,色度AC)。

那么,这些码表如何存储?源数据又放在哪里?针对这些未解之谜,接下来我们就分析JPEG的存储结构。


1.3 一个JPEG编码的示例

某个图象的一个8*8方块的亮度值:

在这里插入图片描述

Level Offset 后:

在这里插入图片描述

DCT变换后:

在这里插入图片描述

量化后:

在这里插入图片描述

其中,参照的量化表是:

在这里插入图片描述

随后,对于这个8*8方块的亮度量化后的数据分别进行AC和DC两路的编码。


2 JPEG文件格式的解析

由于后面需要导出码表等操作,势必需要掌握jpeg的存储格式。因此,我们针对实验所用的testrgb-2x2.jpg,作一个完整解析。


2.0 文件概览

在这里插入图片描述

JPEG以segment组成。每个segment都有一个名字,为其segment marker.我们逐一对每个marker进行分析。

整个文件以SOI开始,EOI结束。中间包含了APP0字段,两个DQT字段,一个SOF0字段,四个DHT字段,然后包含所有的ImageData数据。每个字段的作用都会在下面详细解释。


2.1 SOI(Start of Image)

图像以SOI(Start of Image)标志图像开始,内容为固定值FFD8

在这里插入图片描述

2.2 APP0

接下来是APP0字段。APP0字段是应用程序保留标记0。

该字段以FFE0开启,后面包含信息:

在这里插入图片描述

2.3 DQT[0]

DQT就是DCT后的两张量化表,一张AC,一张DC。每张量化表都由FFDB字段开始。随后的4个字节说明了该字段的长度,然后存放的就是量化表。

量化表中的第一个字节被分成了高四位和低四位来用。高四位表示了该量化表的精度,0:8位;1:16位;低四位表示了量化表ID,取值范围为0~3;接下来是所有的表项,数量为(64×(精度+1))字节,里面都是量化的系数。量化表中的数据按照Z字形保存量化表内8x8的数据

在这里插入图片描述

该jpeg文件存放了0和1两张码表。

2.4 SOF0[0]

SOF0[0]为帧图像开始marker.以FFC0为开始标记

在这里插入图片描述

后两个字节标注数据长度;然后一个字节标注了每个颜色分量每个像素的位数(8),然后表明了行数和每行的采样点数,然后附上了三个components体信息。每个component都是一个颜色分量,内含颜色索引ID、Sample factor(高四位水平因子、低四位垂直因子)、和采用的量化表号。

2.5 DHT[0] (Define Huffman Table)

DHT是存放Huffman码表的地方。该Marker以FFC4作为开始标记。然后是字段长度,类型(AC/DC),索引(Index),位表(bit table),值表(value table)。

在这里插入图片描述

表的内容如课件所示。

在这里插入图片描述
彩蛋:课件上的数据与分析的图像是一张图像。

一共有4张DHT,对应AC/DC的Y/UV。

2.6 ImageData[0]-SOS

在大量的图像数据开始前,还有一个SOS字段。该字段表明了扫描开始,说明了数据是如何组织的。该字段以FFDA开始,然后表明了字段的长度,然后说明了颜色分量数,该与SOF字段中的数据应该是保持一致的。然后针对于每一个颜色分量信息,给出了每个分量的DC/AC使用的哈夫曼表编号。

在这里插入图片描述
这中间有一些课件未提及的数据?
在这里插入图片描述

然后是)谱选择开始、谱选择结束和谱选择固定值003F00。然后就是正式的图像数据了。

3 程序架构和结构体分析

从main入口开始,我们观察到程序设定了两个模式,一个跑分模式(benchmark_mode)和一个转换图像模式(convert_one_image)。由于我们这次不涉及到跑分模式,我们只对转换图像模式(convert_one_image)进行分析。

我们跳入函数convert_one_image,查看里面干了什么。首先,将程序加载到内存中然后就关闭;然后解码jpeg(这是最主要的工作);然后获取图像大小;然后获取每个通道的内存地址;拥有了获取的这些信息后,才可以以想要的输出方式存储。然后系统对于解码后的文件进行了写出,以用户选择的模式进行写出。

在该函数中,设置了这些变量:

  • FILE *fp; //打开的文件
  • unsigned int length_of_file; //文件长度
  • unsigned int width, height; //宽、高
  • unsigned char *buf; //存储的buffer
  • struct jdec_private *jdec; //一个结构体指针
  • unsigned char *components[3]; //三个通道的指针

可以看到,后面的处理都是针对于jdec这个指针指向的jdec_private结构体进行处理的:

  • jdec = tinyjpeg_init(); //该函数用于初始化jdec结构体
  • tinyjpeg_parse_header(jdec, buf, length_of_file)//解析JPEG文件头
  • tinyjpeg_get_size(jdec, &width, &height); // 计算图像宽高
  • tinyjpeg_decode(jdec, output_format);// 解码实际数据
  • tinyjpeg_get_components(jdec, components); //获得每个通道的数据

所以,我们有必要对于jdec_private这个结构体进行一个分析。

3.1 结构体:jdec_private

jdec_private是每一个JPEG的码流中的一小块的结构体,包含了所有完整的内容的定义。

在jedc_private 中,定义了指向三个components的指针和三个components结构体(下面详述其意义);定义了图像的宽高;码流长度、始末指针;还有三张量化表(最终只用到两张,Y一张,UV一张);以及DC\AC各四张哈夫曼表(实际各用两张)。

3.2 结构体:components

components主要用于单个颜色通道的DCT变换后的值的存储;以及指明使用了哪个huffman_table和量化table。

3.3 结构体:huffman_table

huffman_table这个结构体主要用于存储所有的Huffman表。huffman表分为快速的查找表和慢速表。

4 解码过程更详细的解释

tinyjpeg_parse_header中,解析了JPEG的文件头。在读完SOI后,调用parse_JFIF对于每个Marker进行解析。这个解析过程持续到遇到了sos Marker,也就是扫描行开始。

  • while (!sos_marker_found)

4.1 DQT解码

我们输入的JPEG文件需要先解码DQT,也就是量化表。在这一步里,构建起了对应的编号的量化表。通过信息的剥离,在parse_DQT()首先找到了对应的需要创建的是哪张量化表,然后就开始调用build_quantization_table开始创建了。

  • static int parse_DQT(struct jdec_private *priv, const unsigned char *stream)
  • {
  • int qi; //该参数记录了采用的量化表号码
  • float *table; //指针,指向量化表开始
  • const unsigned char *dqt_block_end;//指针,指向量化表结束
  • #if TRACE
  • fprintf(p_trace,"> DQT marker\n");
  • fflush(p_trace);
  • #endif
  • dqt_block_end = stream + be16_to_cpu(stream);
  • stream += 2; /* Skip length */
  • while (stream < dqt_block_end)
  • {
  • qi = *stream++; //读入stream中的一个字节,该字节是流中的量化表系数,并赋值给qi
  • #if SANITY_CHECK
  • if (qi>>4)
  • snprintf(error_string, sizeof(error_string),"16 bits quantization table is not supported\n");
  • if (qi>4)
  • snprintf(error_string, sizeof(error_string),"No more 4 quantization table is supported (got %d)\n", qi);
  • #endif
  • table = priv->Q_tables[qi];
  • build_quantization_table(table, stream); //开始构建量化表
  • stream += 64;
  • }
  • #if TRACE
  • fprintf(p_trace,"< DQT marker\n");
  • fflush(p_trace);
  • #endif
  • return 0;
  • }

我们进入build_quantization_table进行创建量化表的查看:

  • static void build_quantization_table(float *qtable, const unsigned char *ref_table)
  • {
  • /* Taken from libjpeg. Copyright Independent JPEG Group's LLM idct.
  • * For float AA&N IDCT method, divisors are equal to quantization
  • * coefficients scaled by scalefactor[row]*scalefactor[col], where
  • * scalefactor[0] = 1
  • * scalefactor[k] = cos(k*PI/16) * sqrt(2) for k=1..7
  • * We apply a further scale factor of 8.
  • * What's actually stored is 1/divisor so that the inner loop can
  • * use a multiplication rather than a division.
  • */
  • int i, j;
  • static const double aanscalefactor[8] = {
  • 1.0, 1.387039845, 1.306562965, 1.175875602,
  • 1.0, 0.785694958, 0.541196100, 0.275899379
  • };
  • const unsigned char *zz = zigzag;
  • for (i=0; i<8; i++) {
  • for (j=0; j<8; j++) {
  • *qtable++ = ref_table[*zz++] * aanscalefactor[i] * aanscalefactor[j];
  • }
  • }
  • }

从中我们可以看出,量化表的建立传入了两个参数,一个是正式的要写入的量化表qtable,另一个是参考表(reftable),是从流中读取的数据。

对于这个数据,首先程序设定了一个比例因子, scalefactor[0] = 1

;scalefactor[k] = cos(k*PI/16) * sqrt(2) for k=1…7,且存的是倒数,这是便于进行乘法运算的。

这里主要是因为,在编码的时候,进行了人眼视觉特性设计而成的矩阵量化DCT系数,解码的时候需要进行反操作。

且采用了zigzag扫描:

  • static const unsigned char zigzag[64] =
  • {
  • 0, 1, 5, 6, 14, 15, 27, 28,
  • 2, 4, 7, 13, 16, 26, 29, 42,
  • 3, 8, 12, 17, 25, 30, 41, 43,
  • 9, 11, 18, 24, 31, 40, 44, 53,
  • 10, 19, 23, 32, 39, 45, 52, 54,
  • 20, 22, 33, 38, 46, 51, 55, 60,
  • 21, 34, 37, 47, 50, 56, 59, 61,
  • 35, 36, 48, 49, 57, 58, 62, 63
  • };

因此,我们如果想要输出量化表,其实在这一步后面把ref_table表输出就可以了。码流里直接读出来的量化表是真正的量化表。后来计算出来的量化表,乘了比例因子,是为了辅助浮点dct ,idct 运算用的。如果是浮点运算,在编码端要乘比例因子,所以在解码端也要乘。具体的代码添加见下面。

4.2 SOF解码

接下来该JPEG头中是SOF,故解析了SOF,帧图像开始marker。这段解析较为简单,主要读出了图像的通道数量、图像的宽和高、每个通道的ID、水平垂直采样因子和使用的量化表等。

4.3 DHT解码

接下来就到了Huffman码表的解析。

  • length = be16_to_cpu(stream) - 2; // 表长(可能包含多张表) stream += 2; /* Skip length */
  • while (length>0) { // 是否还有表
  • index = *stream++;
  • /* We need to calculate the number of bytes 'vals' will takes */
  • huff_bits[0] = 0; count = 0;
  • for (i=1; i<17; i++) {
  • huff_bits[i] = *stream++; count += huff_bits[i];
  • }
  • if(index&0xf0)//AC 表
  • // (index&0xf), Huffman 表序号
  • build_huffman_table(huff_bits, stream, &priv->HTAC[index&0xf]); else // DC 表
  • build_huffman_table(huff_bits, stream, &priv->HTDC[index&0xf]);
  • length -= 1; length -= 16; length -= count; stream += count;
  • }

5 修改程序输出为YUV

想要修改程序的输出,我们需要先对程序进行一个逻辑解读。从main入口开始,我们观察到程序设定了两个模式,一个跑分模式(benchmark_mode)和一个转换图像模式(convert_one_image)。由于我们要做的事情是修改程序输出为YUV,在这里我们忽略benchmark_mode

我们跳入函数convert_one_image,查看里面干了什么。首先,将程序加载到内存中然后就关闭;然后解码jpeg(这是最主要的工作);然后获取图像大小;然后获取每个通道的内存地址;拥有了获取的这些信息后,才可以以想要的输出方式存储。

我们想要的存储方式是TINYJPEG_FMT_YUV420P,对应的处理函数是write_yuv。因此我们跳入这个函数进行查看:

  • static void write_yuv(const char *filename, int width, int height, unsigned char **components)
  • {
  • FILE *F;
  • char temp[1024];
  • snprintf(temp, 1024, "%s.Y", filename);
  • F = fopen(temp, "wb");
  • fwrite(components[0], width, height, F);
  • fclose(F);
  • snprintf(temp, 1024, "%s.U", filename);
  • F = fopen(temp, "wb");
  • fwrite(components[1], width*height/4, 1, F);
  • fclose(F);
  • snprintf(temp, 1024, "%s.V", filename);
  • F = fopen(temp, "wb");
  • fwrite(components[2], width*height/4, 1, F);
  • fclose(F);
  • }

因此,想要完成这个任务非常容易,只需要将所有内容写入到一个文件中,输出一个.yuv文件即可。

  • static void write_yuv(const char *filename, int width, int height, unsigned char **components)
  • {
  • FILE *F;
  • char temp[1024];
  • snprintf(temp, 1024, "%s.YUV", filename);
  • F = fopen(temp, "wb");
  • fwrite(components[0], width, height, F);
  • fwrite(components[1], width*height/4, 1, F);
  • fwrite(components[2], width*height/4, 1, F);
  • fclose(F);
  • }

将所有内容全部写入一个文件中,再次运行程序,已成功生成了我们所需的文件,打开进行查看:

在这里插入图片描述

至此,修改程序输出为YUV已完成。



6 TRACE输出所有的量化矩阵和所有的Huffman码表

6.1 TRACE:输出TXT

我们想要在程序执行的过程中获得一些调试信息时候,可以使用之前打开的TRACE文件进行输出。TRACE可以在代码中的任意地方进行条件编译。比如下面这段:

  • #if TRACE
  • fprintf(p_trace,"< DQT marker\n");
  • fflush(p_trace);
  • #endif

也就是说,当TRACE被设定为1,程序的if条件成立,就会执行这段宏内的条件编译内容,完成对于trace文件的写入。

至于TRACE和输出文件的设定,是在cpp头宏定义的:

  • #define TRACE 1
  • #define TRACEFILE "trace_jpeg.txt"//add by nxn

所以,我们想关闭TraceFile的输出,设置TRACE=0即可。

6.2 输出量化矩阵

在4.1小节我们已经说明,量化矩阵会在哪里产生,也就是build_quantization_table里。现在我们在该函数末尾加上一个TRACE,写上输出它的代码:

  • #if TRACE
  • const unsigned char* anotherzz = zigzag;
  • for (int i = 0; i < 8; i++) {
  • for (int j = 0; j < 8; j++) {
  • fprintf(p_trace, "%-6d", ref_table[*anotherzz++]);
  • if (j == 7) {
  • fprintf(p_trace, "\n");
  • }
  • }
  • }
  • #endif

输出结果:

在这里插入图片描述

6.3 输出Huffman码表

默认的代码中已经输出了所有的Huffman码表。在3.3中我们可以看出,AC和DC的Huffman码表都是在build_huffman_table函数中完成建立的。因此我们进入这两个函数,然后对他们进行查看,并输出码表。函数中已经写好:

  • #if TRACE
  • fprintf(p_trace,"val=%2.2x code=%8.8x codesize=%2.2d\n", val, code, code_size);
  • fflush(p_trace);
  • #endif
在这里插入图片描述

我们可以在TRACE文件里看到所有码表内容。完整的输出可以在附录中查询到。

7 输出DC/AC图像

首先采用类似TRACEFILE文件的方式添加宏定义:

  • #define snprintf _snprintf//add by nxn
  • #define TRACE 1 1//add by nxn
  • #define TRACEFILE "trace_jpeg.txt"//add by nxn
  • #define OUTPUTACDC 1
  • #define OUTPUTACFILE "output_ac.yuv"
  • #define OUTPUTDCFILE "output_dc.yuv"

然后添加文件读写:

  • FILE *p_trace;//add by nxn
  • FILE* output_ac;
  • FILE* output_dc;
  • # if OUTPUTACDC
  • output_ac = fopen(OUTPUTACFILE, "wb");
  • output_dc = fopen(OUTPUTDCFILE, "wb");
  • # endif

图像的解码在解出jpeg头后的tinyjpeg_decode函数中完成。因此,我们跳转进该函数,首先添加需要开的buffer:

  • unsigned char* dcImgBuff;
  • unsigned char* acImgBuff;
  • unsigned char* uvBuff = 128;
  • int count = 0;

在循环里:

  • for (x=0; x < priv->width; x+=xstride_by_mcu)
  • {
  • decode_MCU(priv);
  • # if OUTPUTACDC
  • dcImgBuff = (unsigned char)((priv->component_infos->DCT[0] + 512.0) / 4 + 0.5); // DCT[0]为DC系数;DC系数范围-512~512;变换到0~255
  • acImgBuff = (unsigned char)(priv->component_infos->DCT[1] + 128); // 选取DCT[1]作为AC的observation;+128便于观察
  • fwrite(&dcImgBuff, 1, 1, output_dc);
  • fwrite(&acImgBuff, 1, 1, output_ac);
  • count++;
  • # endif
  • convert_to_pixfmt(priv);
  • priv->plane[0] += bytes_per_mcu[0];
  • priv->plane[1] += bytes_per_mcu[1];
  • priv->plane[2] += bytes_per_mcu[2];
  • if (priv->restarts_to_go>0)
  • {
  • priv->restarts_to_go--;
  • if (priv->restarts_to_go == 0)
  • {
  • priv->stream -= (priv->nbits_in_reservoir/8);
  • resync(priv);
  • if (find_next_rst_marker(priv) < 0)
  • return -1;
  • }
  • }
  • }
  • }
  • #if TRACE
  • fprintf(p_trace,"Input file size: %d\n", priv->stream_length+2);
  • fprintf(p_trace,"Input bytes actually read: %d\n", priv->stream - priv->stream_begin + 2);
  • fflush(p_trace);
  • #endif
  • return 0;
  • }

我们通过计数器,可以看到最后计数下来是4096个像素:

在这里插入图片描述

因此,在我们的打开图像时候应该选择64*64的4:2:0的图像。

可以看到效果:

在这里插入图片描述

8 概率统计

使用python对图像进行统计

  • import numpy as np
  • import matplotlib.pyplot as plt
  • import collections
  • import pandas as pd
  • from collections import Counter
  • fraw = open("output_ac.yuv", "rb")
  • frec = open("output_dc.yuv", "rb")
  • raw = []
  • rec = []
  • i = 0
  • while i < 64 * 64:
  • i += 1
  • buf1 = fraw.read(1)
  • buf2 = frec.read(1)
  • if buf1:
  • buf1 = int.from_bytes(buf1, byteorder='big')
  • buf2 = int.from_bytes(buf2, byteorder='big')
  • raw.append(buf1)
  • rec.append(buf2)
  • a = Counter(raw)
  • b = Counter(rec)
  • x=[]
  • y=[]
  • x_dc=[]
  • y_dc=[]
  • for i in a:
  • x.append(i);
  • y.append(a[i])
  • x_dc.append(i)
  • y_dc.append(b[i])
  • # x是值,y是计数
  • plt.rcParams['font.sans-serif'] = ['Songti SC'] # 指定默认字体
  • fig,rgb = plt.subplots()
  • rgb.bar(x,y)
  • rgb.bar(x_dc,y_dc)
  • rgb.set_xlabel('值')
  • rgb.set_ylabel('出现频率')
  • rgb.set_title('AC/DC系数统计图')
  • rgb.legend()
  • plt.show()
在这里插入图片描述

附录

trace_jpeg.txt

  • > Unknown marker e0
  • > DQT marker
  • 2 1 1 2 2 4 5 6
  • 1 1 1 2 3 6 6 6
  • 1 1 2 2 4 6 7 6
  • 1 2 2 3 5 9 8 6
  • 2 2 4 6 7 11 10 8
  • 2 4 6 6 8 10 11 9
  • 5 6 8 9 10 12 12 10
  • 7 9 10 10 11 10 10 10
  • < DQT marker
  • > DQT marker
  • 2 2 2 5 10 10 10 10
  • 2 2 3 7 10 10 10 10
  • 2 3 6 10 10 10 10 10
  • 5 7 10 10 10 10 10 10
  • 10 10 10 10 10 10 10 10
  • 10 10 10 10 10 10 10 10
  • 10 10 10 10 10 10 10 10
  • 10 10 10 10 10 10 10 10
  • < DQT marker
  • > SOF marker
  • > SOF marker
  • Size:1024x1024 nr_components:3 (????) precision:8
  • Component:1 factor:2x2 Quantization table:0
  • Component:2 factor:1x1 Quantization table:1
  • Component:3 factor:1x1 Quantization table:1
  • < SOF marker
  • > DHT marker (length=27)
  • Huffman table DC[0] length=10
  • val=04 code=00000000 codesize=02
  • val=05 code=00000001 codesize=02
  • val=06 code=00000002 codesize=02
  • val=03 code=00000006 codesize=03
  • val=07 code=0000000e codesize=04
  • val=02 code=0000001e codesize=05
  • val=01 code=0000003e codesize=06
  • val=00 code=0000007e codesize=07
  • val=09 code=000000fe codesize=08
  • val=08 code=000001fe codesize=09
  • < DHT marker
  • > DHT marker (length=60)
  • Huffman table AC[0] length=43
  • val=00 code=00000000 codesize=02
  • val=01 code=00000002 codesize=03
  • val=03 code=00000003 codesize=03
  • val=02 code=00000008 codesize=04
  • val=04 code=00000009 codesize=04
  • val=05 code=0000000a codesize=04
  • val=11 code=0000000b codesize=04
  • val=21 code=0000000c codesize=04
  • val=22 code=0000001a codesize=05
  • val=31 code=0000001b codesize=05
  • val=61 code=0000001c codesize=05
  • val=06 code=0000003a codesize=06
  • val=12 code=0000003b codesize=06
  • val=a1 code=0000003c codesize=06
  • val=32 code=0000007a codesize=07
  • val=41 code=0000007b codesize=07
  • val=62 code=0000007c codesize=07
  • val=13 code=000000fa codesize=08
  • val=51 code=000000fb codesize=08
  • val=23 code=000001f8 codesize=09
  • val=42 code=000001f9 codesize=09
  • val=71 code=000001fa codesize=09
  • val=81 code=000001fb codesize=09
  • val=91 code=000001fc codesize=09
  • val=15 code=000003fa codesize=10
  • val=52 code=000003fb codesize=10
  • val=63 code=000003fc codesize=10
  • val=07 code=000007fa codesize=11
  • val=14 code=000007fb codesize=11
  • val=33 code=000007fc codesize=11
  • val=53 code=000007fd codesize=11
  • val=16 code=00000ffc codesize=12
  • val=43 code=00000ffd codesize=12
  • val=08 code=00001ffc codesize=13
  • val=b1 code=00001ffd codesize=13
  • val=34 code=00003ffc codesize=14
  • val=c1 code=00003ffd codesize=14
  • val=24 code=00007ffc codesize=15
  • val=d1 code=0000fffa codesize=16
  • val=09 code=0000fffb codesize=16
  • val=72 code=0000fffc codesize=16
  • val=f0 code=0000fffd codesize=16
  • val=a2 code=0000fffe codesize=16
  • < DHT marker
  • > DHT marker (length=28)
  • Huffman table DC[1] length=11
  • val=05 code=00000000 codesize=02
  • val=06 code=00000001 codesize=02
  • val=07 code=00000002 codesize=02
  • val=04 code=00000006 codesize=03
  • val=03 code=0000000e codesize=04
  • val=02 code=0000001e codesize=05
  • val=00 code=0000007c codesize=07
  • val=01 code=0000007d codesize=07
  • val=0a code=0000007e codesize=07
  • val=08 code=000000fe codesize=08
  • val=09 code=000001fe codesize=09
  • < DHT marker
  • > DHT marker (length=43)
  • Huffman table AC[1] length=26
  • val=00 code=00000000 codesize=02
  • val=01 code=00000002 codesize=03
  • val=03 code=00000003 codesize=03
  • val=04 code=00000004 codesize=03
  • val=05 code=00000005 codesize=03
  • val=02 code=0000000c codesize=04
  • val=21 code=0000000d codesize=04
  • val=31 code=0000000e codesize=04
  • val=61 code=0000001e codesize=05
  • val=11 code=0000003e codesize=06
  • val=41 code=000000fc codesize=08
  • val=06 code=000001fa codesize=09
  • val=12 code=000001fb codesize=09
  • val=13 code=000001fc codesize=09
  • val=15 code=000001fd codesize=09
  • val=14 code=000003fc codesize=10
  • val=22 code=000003fd codesize=10
  • val=51 code=000003fe codesize=10
  • val=07 code=000007fe codesize=11
  • val=32 code=00000ffe codesize=12
  • val=23 code=00003ffc codesize=14
  • val=71 code=00003ffd codesize=14
  • val=08 code=00007ffc codesize=15
  • val=16 code=00007ffd codesize=15
  • val=33 code=00007ffe codesize=15
  • val=81 code=0000fffe codesize=16
  • < DHT marker
  • > SOS marker
  • ComponentId:1 tableAC:0 tableDC:0
  • ComponentId:2 tableAC:1 tableDC:1
  • ComponentId:3 tableAC:1 tableDC:1
  • < SOS marker
  • Use decode 2x2 sampling
  • Input file size: 111269
  • Input bytes actually read: 111268

参考资料

  1. 现代电视原理课件
  2. 《数据压缩》课件
  3. JPEG编解码原理及C++调试
  4. 实验提供的所有资料
  5. C条件编译
  6. 非常感谢老师的讲解!
方便获取更多学习、工作、生活信息请关注本站微信公众号城东书院 微信服务号城东书院 微信订阅号
推荐内容
相关内容
栏目更新
栏目热门