JPEG(Joint Photographic Experts Group)是联合图像专家组的英文缩写。 该组织从1986年正式开始制订静止数字图像的压缩编码标准,该标准于1992年正式通过,称为JPEG标准。
JPEG是第一个数字图像压缩的国际标准,它不仅适于静止图像的压缩,对 于电视图像序列的帧内压缩也常采用JPEG算法,因此JPEG是一个适用范围广泛的通用标准。
为了减少各分量之间的相关性,减少数据的冗余,通常会把RGB颜色空间转换成YUV来进行各分量的编码。
该步骤的作用是,图像内容平均亮度较高,将0电平移到中间,平均亮度降低, 便于DCT变换量化后直流的系数大大降低,也就降低了数据量。
将灰度级2 n 2^n2n的像素值,全部减去2 n − 1 2^{n-1}2n−1,数据形式由无符号数变为有符号数(补码),单极性数据变为双极性数据。
该步骤主要是用于去除图像数据之间的相关性,便于量化过程去除图像数据的空间冗余。
将图像分为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是一种无损变换,也无法对图像进行压缩,这样做的目的是在为下一步的量化做准备。
量化器主要是利用人眼视觉特性设计而成的矩阵量化DCT系数,减少视觉冗余。
将DCT变换后的临时结果,除以各自量化步长并四舍五入后取整,得到量化系数。
JPEG系统分别规定了亮度分量和色度分量的量化表,色度分量相应的量化步长比亮度分量大。
在量化步骤中,JPEG采用了中平型(Midtread)的均匀量化器。
量化是编码流程中唯一会引入误差也是唯一会带来压缩的步骤。Y、UV各一张表,共两张表。
8×8图像块经过DCT变换之后得到的DC直流系数有两个特点:
根据这个特点,JPEG算法使用了差分脉冲调制编码(DPCM)技术,对相邻图像块之间量化DC系数的差值DIFF进行编码:
DPCM是对于DC系数处理时所需要用到的预测编码方法。此方法在之前的实验中已经有详细说明。
对DPCM后算出的DIFF差值使用Huffman编码。
将其分成类别,类似于指数的Golomb编码(只不过Golomb是一元码+定长码),也就是类别ID使用规范哈夫曼编码,类内索引使用定长码(自然码)。
所以,DC系数会产生一张长度为16的Huffman码表。
对于量化后的数据,我们将其分为两路进行处理。一路是AC通路,一路是DC通路。ZigZag Scan+RLE是用于AC通路的,这是因为AC分量出现较多的0。JPEG采用对0系数的游程长度编码。而对非0值,则要保存所需数和实际值。
在编码之前,需要把二维的变换系数矩阵转换为一维序列,由于量化之后右下角高频系数大部分为零,采用ZigZag Scan读取可以制造较长的零游程,提高编码效率。在扫描中,如果后续的系数全部为零,则用“EOB”表示块结束。
在扫描后,采用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。
所以,最后总共有4张Huffman码表(亮度DC,亮度AC,色度DC,色度AC)。
那么,这些码表如何存储?源数据又放在哪里?针对这些未解之谜,接下来我们就分析JPEG的存储结构。
某个图象的一个8*8方块的亮度值:
Level Offset 后:
DCT变换后:
量化后:
其中,参照的量化表是:
随后,对于这个8*8方块的亮度量化后的数据分别进行AC和DC两路的编码。
由于后面需要导出码表等操作,势必需要掌握jpeg的存储格式。因此,我们针对实验所用的testrgb-2x2.jpg,作一个完整解析。
JPEG以segment组成。每个segment都有一个名字,为其segment marker.我们逐一对每个marker进行分析。
整个文件以SOI开始,EOI结束。中间包含了APP0字段,两个DQT字段,一个SOF0字段,四个DHT字段,然后包含所有的ImageData数据。每个字段的作用都会在下面详细解释。
图像以SOI(Start of Image)标志图像开始,内容为固定值FFD8。
接下来是APP0字段。APP0字段是应用程序保留标记0。
该字段以FFE0开启,后面包含信息:
DQT就是DCT后的两张量化表,一张AC,一张DC。每张量化表都由FFDB字段开始。随后的4个字节说明了该字段的长度,然后存放的就是量化表。
量化表中的第一个字节被分成了高四位和低四位来用。高四位表示了该量化表的精度,0:8位;1:16位;低四位表示了量化表ID,取值范围为0~3;接下来是所有的表项,数量为(64×(精度+1))字节,里面都是量化的系数。量化表中的数据按照Z字形保存量化表内8x8的数据
。
该jpeg文件存放了0和1两张码表。
SOF0[0]为帧图像开始marker.以FFC0为开始标记
后两个字节标注数据长度;然后一个字节标注了每个颜色分量每个像素的位数(8),然后表明了行数和每行的采样点数,然后附上了三个components体信息。每个component都是一个颜色分量,内含颜色索引ID、Sample factor(高四位水平因子、低四位垂直因子)、和采用的量化表号。
DHT是存放Huffman码表的地方。该Marker以FFC4作为开始标记。然后是字段长度,类型(AC/DC),索引(Index),位表(bit table),值表(value table)。
表的内容如课件所示。
一共有4张DHT,对应AC/DC的Y/UV。
在大量的图像数据开始前,还有一个SOS字段。该字段表明了扫描开始,说明了数据是如何组织的。该字段以FFDA开始,然后表明了字段的长度,然后说明了颜色分量数,该与SOF字段中的数据应该是保持一致的。然后针对于每一个颜色分量信息,给出了每个分量的DC/AC使用的哈夫曼表编号。
然后是)谱选择开始、谱选择结束和谱选择固定值003F00。然后就是正式的图像数据了。
从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这个结构体进行一个分析。
jdec_private是每一个JPEG的码流中的一小块的结构体,包含了所有完整的内容的定义。
在jedc_private 中,定义了指向三个components的指针和三个components结构体(下面详述其意义);定义了图像的宽高;码流长度、始末指针;还有三张量化表(最终只用到两张,Y一张,UV一张);以及DC\AC各四张哈夫曼表(实际各用两张)。
components主要用于单个颜色通道的DCT变换后的值的存储;以及指明使用了哪个huffman_table和量化table。
huffman_table这个结构体主要用于存储所有的Huffman表。huffman表分为快速的查找表和慢速表。
在tinyjpeg_parse_header中,解析了JPEG的文件头。在读完SOI后,调用parse_JFIF对于每个Marker进行解析。这个解析过程持续到遇到了sos Marker,也就是扫描行开始。
- while (!sos_marker_found)
-
我们输入的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 运算用的。如果是浮点运算,在编码端要乘比例因子,所以在解码端也要乘。具体的代码添加见下面。
接下来该JPEG头中是SOF,故解析了SOF,帧图像开始marker。这段解析较为简单,主要读出了图像的通道数量、图像的宽和高、每个通道的ID、水平垂直采样因子和使用的量化表等。
接下来就到了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;
- }
-
想要修改程序的输出,我们需要先对程序进行一个逻辑解读。从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已完成。
我们想要在程序执行的过程中获得一些调试信息时候,可以使用之前打开的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即可。
在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
-
输出结果:
默认的代码中已经输出了所有的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文件里看到所有码表内容。完整的输出可以在附录中查询到。
首先采用类似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的图像。
可以看到效果:
使用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
-