目录
一、内容概述
1. JPEG编解码原理
JPEG编码器系统框图
1.1 零偏置电平下移
① 方法:对8*8像块进行偏置电平下移(Level Offset),对灰度级为
2
n
2^n
2n的像素,通过减去
2
n
−
1
2^{n-1}
2n−1,将无符号整数变为有符号整数,使其值域变为
[
−
2
n
−
1
,
2
n
−
1
−
1
]
[-2^{n-1},2^{n-1}-1]
[−2n−1,2n−1−1]。
② 目的:减小绝对值大的数出现概率,提高编码效率。
1.2 8*8 DCT变换(无损不压缩)
① 方法:将图像划分为8*8像块(对于宽高不满足的,使用图像边缘像素填充,以不改变频谱分布),对每个子块进行DCT变换,具体原理可见实验4
② 目的:实现去相关、能量集中,便于去除空间冗余,提高编码效率。
1.3 均匀标量量化(会引入误差)
① 方法:利用人眼视觉特性采用特定的矩阵量化DCT系数,采用了中平型均匀量化器。
② 目的:减少视觉冗余。
1.4 DC: DPCM
① 方法:把所有的颜色分量单元按颜色分量(Y、Cr、Cb)分类。每一种颜色分量内,相邻的两个颜色分量单元的直流变量是以差分来编码的。计算直流系数DC差值DIFF进行DPCM变换,Diff为差分校正变量,也就是直接解码出来的直流系数。
D
C
n
=
D
C
n
−
1
+
D
i
f
f
DC_n=DC_{n-1}+Diff
DCn=DCn−1+Diff
② 目的:提高压缩上限。
1.5 AC: RLE
① 前提:DCT后,系数大多数集中在左上角低频分量区,因此采用Zig-Zag扫描,将系数按频率的高低顺序读出,这样可以出现很多连零,便于进行RLE(Run Length Encoding,游程编码),在最后如果都是零,给出EOB (End of Block)即可。
② 游程编码:
RLE(Run Length Encoding)行程长度压缩算法(也称游程长度压缩算法),是最早出现、也是最简单的无损数据压缩算法。基本思路是把数据按照线性序列分成两种情况:一种是连续的重复数据块,另一种是连续的不重复数据块。
① 对于第一种情况,对连续的重复数据块进行压缩,压缩方法就是用一个表示块数的属性加上一个数据块代表原来连续的若干块数据。
② 对于第二种情况,RLE算法有两种处理方法,一种处理方法是用和第一种情况一样的方法处理连续的不重复数据块,仅仅是表示块数的属性总是1;另一种处理方法是不对数据进行任何处理,直接将原始数据作为压缩后的数据。
对于字符串“AAABBBBBCD”,用这种RLE算法的Rle_Encode_P()函数压缩后的数据就是:0xC3,0x41,0xC5,0x42,0x43,0x44共6个字节,比原始长度10个字节少了4个字节。
1.6 Huffman编码
对DC系数DPCM的结果和AC系数RLE的结果进行Huffman编码,类别ID采用一元码编码,类内索引采用定长码编码。
1. DC Huffman编码
如DIFF为8-5=3,类别ID为2,类内索引为3,码流10011
2. AC Huffman编码
对于任何一个RLE的数据对,都可以表示成(RRRR,SSSS)的形式。其中前面的0-16采用自然码RRRR,后面的SSSS则是与DC一致的Huffman分组的编码方式,存储索引。放到码流里的是其组内编码。
2. JPEG文件格式
JPEG文件以segment形式组织,它具有以下特点
①每个segment均以0xFF开始,后跟1byte 的Marker和2byte的Segment length(包含表示Length本身所占用的2byte,不含“0xFF” + “Marker” 所占用的2byte);
②采用Motorola序(相对于Intel序),保存时高位在前,低位在后;
③Data 部分中,0xFF后若为0x00,则跳过此字节不予处理。
JPEG的segment marker格式
以testrgb_1×1.jpg为例
2.1 SOI
SOI:标志图像开始,固定FFD8。
EOI:标志图像结束,固定FFD9。
2.2 APP0
标志符:固定值0x4A46494600,即字符串“JFIF0”;
版本号:一般是0x0102,表示JFIF的版本号1.2;在此文件中为0101,表示版本号1.1。
2.3 DQT(两张,DC+AC)
量化表长度:0×0043
量化表精度(高4位):0:8位
量化表序号(低4位):0&1
量化表内容为后面的64个数据,共(64*(精度+1)字节)。
2.4 SOF0[0]
以下图为例,得到每个sample的比特数0×0011、长宽(0×0400*0×0400)、颜色分量数(3,对应YCrCb);
得到每个颜色分量的 ID01、水平采样因子1、垂直采样因子1、使用的量化表序号(00,与DQT序号对应);
2.5 DHT[0]
定义Huffman表
获得Huffman表类型及相关数据
2.6 SOS
在图像数据开始前,有SOS字段,标明扫描开始,该字段以FFDA开始,而后表明了字段的长度,然后说明了颜色分量数,该与SOF字段中的数据是保持一致的。
二、实验思路
反向进行以下操作:
- 逐步调试JPEG解码器程序。将输入的JPG文件进行解码,将输出文件保存为可供YUVViewer观看的YUV文件。
- 程序调试过程中:
①理解程序设计的整体框架;理解三个结构体的设计目的:struct huffman_table
struct component
struct jdec_private
②理解在视音频编解码调试中TRACE的目的和含义
③会打开和关闭TRACE,会根据自己的要求修改TRACE - 以txt文件输出所有的量化矩阵和所有的HUFFMAN码表。
- 输出DC图像并统计其概率分布。
- 输出某一个AC值图像并统计其概率分布。
三、代码分析
以testjpg-1×1.jpg
为例,根据main
函数,确定输入三个参数分别为输入文件(带jpg后缀)、转换格式(yuv420p
、rgb24、bgr24、grey)、输出文件(不带yuv后缀),首先修改命令参数如下图所示:
1. 将输入的jpg文件转换为yuv文件
只需要在write_yuv函数中添加以下代码:
//输出yuv文件
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);
2. 程序调试
2.1 整体框架
从main
函数开始分析,知晓命令参数,跳转到convert_one_image
函数,其中定义了文件长度、图像长宽,开辟buffer,定义了结构体struct jdec_private *jdec
,用于存储JPEG图像宽高、数据流指针、Huffman码表等内容,后期程序基本上都是调用jdec。
2.2 结构体
① struct huffman_table
:存储Huffman码表。
struct huffman_table
{
/* Fast look up table, using HUFFMAN_HASH_NBITS bits we can have directly the symbol,
* if the symbol is <0, then we need to look into the tree table */
short int lookup[HUFFMAN_HASH_SIZE];
/* code size: give the number of bits of a symbol is encoded */
unsigned char code_size[HUFFMAN_HASH_SIZE];
/* some place to store value that is not encoded in the lookup table
* FIXME: Calculate if 256 value is enough to store all values
*/
uint16_t slowtable[16-HUFFMAN_HASH_NBITS][256];
};
② struct component
:存储当前8×8像块中有关解码的信息。
struct component
{
unsigned int Hfactor;
unsigned int Vfactor;
float *Q_table; /* Pointer to the quantisation table to use */
struct huffman_table *AC_table;
struct huffman_table *DC_table;
short int previous_DC; /* Previous DC coefficient */
short int DCT[64]; /* DCT coef */
#if SANITY_CHECK
unsigned int cid;
#endif
};
③ struct jdec_private
:JPEG数据流结构体,是每一个JPEG的码流中的一小块的结构体,包含了所有完整的内容的定义,用于存储JPEG图像宽高、数据流指针、Huffman码表等内容,并包含struct huffman_table
和struct component
。
struct jdec_private
{
/* Public variables */
uint8_t *components[COMPONENTS];
unsigned int width, height; /* Size of the image */
unsigned int flags;
/* Private variables */
const unsigned char *stream_begin, *stream_end;
unsigned int stream_length;
const unsigned char *stream; /* Pointer to the current stream */
unsigned int reservoir, nbits_in_reservoir;
struct component component_infos[COMPONENTS];
float Q_tables[COMPONENTS][64]; /* quantization tables */
struct huffman_table HTDC[HUFFMAN_TABLES]; /* DC huffman tables */
struct huffman_table HTAC[HUFFMAN_TABLES]; /* AC huffman tables */
int default_huffman_table_initialized;
int restart_interval;
int restarts_to_go; /* MCUs left in this restart interval */
int last_rst_marker_seen; /* Rst marker is incremented each time */
/* Temp space used after the IDCT to store each components */
uint8_t Y[64*4], Cr[64], Cb[64];
jmp_buf jump_state;
/* Internal Pointer use for colorspace conversion, do not modify it !!! */
uint8_t *plane[COMPONENTS];
};
2.3 TRACE
作用:在代码中的任意地方进行条件编译TRACE文件输出调试信息,方便检查。
例如,可以获得输入文件大小和实际bit数:
#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
设定在tinyjpeg.h
:
#define TRACE 1//设为0时不输出
#define TRACEFILE "trace_jpeg.txt"//设置名称
3. 量化矩阵及Huffman码表
解码器的量化矩阵由parse_DQT()
找到了对应的量化表序号,调用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
类似,AC和DC的Huffman码表是在build_huffman_table
函数中完成建立的,由trace文件可得:
4. DC图像及概率分布
5. 一个AC图像及概率分布
四、总结
这真的是我写过最长的博客了,大哭,太不容易了!