一、基本原理
1.JPEG文件格式介绍
1.1 SOI(Start of Image)图像开始
标记代码:固定值0xFFD8(2字节)
1.2 APP0(Application)应用程序保留标记
- 标记代码 :固定值0xFFE0(2字节)
- 包含9个具体字段:
序号 内容 字节数 取值 1 数据长度 2字节 ①~⑨9个字段的总长度 2 标识符 5字节 固定值0x4A46494600,即字符串“JFIF0” 3 版本号 2字节 一般是0x0102,表示JFIF的版本号1.2 4 X和Y的密度单位 1字节 只有三个值可选 0:无单位;1:点数/英寸;2:点数/厘米 5 X方向像素密度 2字节 取值范围未知 6 Y方向像素密度 2字节 取值范围未知 7 缩略图水平像素数目 1字节 取值范围未知 8 缩略图垂直像素数目 1字节 取值范围未知 9 缩略图RGB位图 长度可能是3的倍数 缩略图RGB位图数据
1.3 DQT(DefineQuantization Table)定义量化表
- 标记代码:固定值0xFFDB(2字节)
- 包含6个具体字段:
序号 内容 分层 字节数 取值 1 数据长度 2字节 字段①和多个字段②的总长度 2.1 量化表 精度及量化表ID 1字节 高4位:精度,只有两个可选值 0:8位;1:16位 低4位:量化表ID,取值范围为0~3 2.2 量化表 表项 1字节 (64×(精度+1))字节 * 本标记段中,字段②可以重复出现,表示多个量化表,但最多只能出现4次
1.4 SOF0(Start of Frame)帧图像开始
- 标记代码:固定值0xFFC0(2字节)
- 包含6个具体字段:
序号 内容 分层 字节数 取值 1 数据长度 2字节 ①~⑥六个字段的总长度 2 精度 1字节 通常是8位,一般软件都不支持 12位和16位 3 图像高度 2字节 图像高度(单位:像素) 4 图像宽度 2字节 图像宽度(单位:像素) 5 颜色分量数 1字节 只有3个数值可选-1:灰度图;3:YCrCb或YIQ;4:CMYK 而JFIF中使用YCrCb,故这里颜色分量数恒为3 6.1 颜色分量信息 颜色分量ID 1字节 只有3个数值可选-1:灰度图;3:YCrCb或YIQ;4:CMYK ,而JFIF中使用YCrCb,故这里颜色分量数恒为3 6.2 颜色分量信息 水平/垂直采样因子 1字节 高4位:水平采样因子/低4位:垂直采样因子 6.3 颜色分量信息 量化表 1字节 当前分量使用的量化表的ID
1.5 DHT(Define Huffman Table)定义哈夫曼表
- 标记代码:固定值0xFFC4(2字节)
- 包含2个具体字段:
序号 内容 字节数 1 数据长度 2字节 2 Huffman表数据长度 2字节 - 表ID和表类型 1字节
- 高4位:只有两个值可选(0:DC直流;1:AC交流)+低4位:哈夫曼表ID
- 不同位数的码字数量 16字节
- 编码内容:16个不同位数的码字数量之和(字节)
- 本标记段中,字段②可以重复出现(一般4次),也可以只出现1次。
1.6 SOS(Start of Scan)-扫描开始 12字节
- 标记代码:固定值0xFFDA(2字节)
- 包含4个具体字段:
序号 内容 分层 字节数 取值 1 数据长度 2字节 ①~④两个字段的总长度 2 颜色分量数 1字节 应该和SOF中的字段⑤的值相同- 1:灰度图是;3: YCrCb或YIQ;4:CMYK 3.1 颜色分量信息 颜色分量ID 1字节 3.2 颜色分量信息 直流/交流系数表号 1字节 高4位:直流分量使用的哈夫曼树编号+低4位:交流分量使用的哈夫曼树编号 4.1 压缩图像数据 谱选择开始 1字节 固定值0x00 4.2 压缩图像数据 谱选择结束 1字节 固定值0x3F 4.3 压缩图像数据 谱选择 1字节 在基本JPEG中总为00
1.7 EOI(End of Image)图像结束
标记代码:固定值0xFFD9(2字节)
2.JPEG编码原理
2.1 零偏置-Level Offset
- 目的:使像素的绝对值出现3位10进制的概率大大减少
- 对于灰度级是
2n
的像素,通过减去
2n−1
,将无符号的整数值变成有符号数
- eg:对于n=8,即将0~255的值域,通过减去128,转换为值域在-128~127之间的值
2.2 8x8DCT变换
- 对每个单独的彩色图像分量,把整个分量图像分成8×8的图像块,如图所示,并作为两维离散余弦变换DCT的输入
2.3 量化
- 中平型均匀量化器
- 量化步距是按照系数所在的位置、颜色分量来确定
- 因为人眼对亮度信号比对色差信号更敏感,因此使用了两种量化表:亮度量化值和色差量化值
- 根据人眼的视觉特性(对低频敏感,对高频不太敏感)对低频分量采取较细的量化,对高频分量采取较粗的量化
- 如果原始图象中细节丰富,则去掉的数据较多,量化后的系数与量化前差别大。反之,细节少的原始图象在压缩时去掉的数据少些
2.4 DC系数的差分编码
- 8×8图像块经过DCT变换之后得到的DC直流系数有两个特点:
- 系数的数值比较大
- 相邻8×8图像块的DC系数值变化不大:冗余
根据这个特点,JPEG算法使用了差分脉冲调制编码(DPCM)技术,对相邻图像块之间量化DC系数的差值DIFF进行编码:
- DIFFk=DCk−DCk−1
对DIFF用Huffman编码:分成类别,类似指数Golomb编码
- 类别ID:一元码编码
- 类内索引:采用定长码
2.5 AC系数的Z字扫描
- 由于经DCT变换后,系数大多数集中在左上角,即低频分量区,因此采用Z字形按频率的高低顺序读出,可以出现很多连零的机会。可以使用游程编码。尤其在最后,如果都是零,给出EOB (End of Block)即可。
2.6 AC系数的游程编码
- 在JPEG和MPEG编码中规定为:(run, level)
- 表示连续run个0,后面跟值为level的系数
- 如:0,2,0,0,3,0,-4,0,0,0,-6,0,0,5,7表示为(1, 2), (2, 3) ,…
- 编码:
- Run: 最多15个,用4位表示RRRR
- Level:类似DC
-分成16个类别,用4位表示SSSS表示类别号
-类内索引 - 对(RRRR, SSSS)联合用Huffman编码
- 对类内索引用定长码编码
二、JPEG解码流程
1. 读取文件
2. 解析 Segment Marker
- 1.解析 SOI
- 2.解析 APP0
- 检查标识“JFIF”及版本
- 得到一些参数
- 3.解析 DQT
- 得到量化表长度(可能包含多张量化表)
- 得到量化表的精度
- 得到及检查量化表的序号(只能是 0 —— 3)
- 得到量化表内容(64 个数据)
- 4.解析 SOF0
- 得到每个 sample 的比特数、长宽、颜色分量数
- 得到每个颜色分量的 ID、水平采样因子、垂直采样因子、使用的量表序号(与 DQT 中序号对应)
- 5.解析 DHT
- 得到 Huffman 表的类型(AC、DC) 、序号
- 依据数据重建 Huffman 表
- 6.解析 SOS
- 得到解析每个颜色分量的 DC、AC 值所使用的 Huffman 表序号(与DHT中序号对应)
3. 依据每个分量的水平、 垂直采样因子计算MCU的大小个 , 并得到每个MCU中8*8宏块的个数
4. 对每个MCU解码(依照各分量水平、垂直采样因子对MCU中每个分量宏块解码)
- 对每个宏块进行 Huffman 解码,得到 DCT 系数
- 对每个宏块的 DCT 系数进行 IDCT,得到 Y、Cb、Cr
- 遇到 Segment Marker RST 时,清空之前的 DC DCT 系数
5. 解析到EOI,解码结束
6. 将Y、Cb、Cr转化为需要的色彩空间并保存
三、代码分析
1.逐步调试JPEG解码器程序。将输入的JPG文件进行解码,将输出文件保存为可供YUVViewer观看的YUV文件。
- tinyjpeg.h中增加YUV类型:
enum tinyjpeg_fmt {
TINYJPEG_FMT_GREY = 1,
TINYJPEG_FMT_BGR24,
TINYJPEG_FMT_RGB24,
TINYJPEG_FMT_YUV420P,
//Add by ts
TINYJPEG_FMT_YUV,
//End
};
- loadjpeg.c中判断输出格式时增加YUV类型:
switch (output_format)
{
case TINYJPEG_FMT_RGB24:
case TINYJPEG_FMT_BGR24:
write_tga(outfilename, output_format, width, height, components);
break;
case TINYJPEG_FMT_YUV420P:
write_yuv(outfilename, width, height, components);
break;
//Add by ts
case TINYJPEG_FMT_YUV:
write_YUV(outfilename, width, height, components);
break;
//End
case TINYJPEG_FMT_GREY:
write_pgm(outfilename, width, height, components);
break;
}
- loadjpeg.c的main函数中增加相应代码
input_filename = argv[current_argument];
if (strcmp(argv[current_argument+1],"yuv420p")==0)
output_format = TINYJPEG_FMT_YUV420P;
else if (strcmp(argv[current_argument+1],"rgb24")==0)
output_format = TINYJPEG_FMT_RGB24;
else if (strcmp(argv[current_argument+1],"bgr24")==0)
output_format = TINYJPEG_FMT_BGR24;
else if (strcmp(argv[current_argument+1],"grey")==0)
output_format = TINYJPEG_FMT_GREY;
//Add by ts
else if (strcmp(argv[current_argument+1],"yuv")==0)
output_format = TINYJPEG_FMT_YUV;
//End
else
exitmessage("Bad format: need to be one of yuv420p, rgb24, bgr24, grey\n");
output_filename = argv[current_argument+2];
- 在tinyjpeg.c增加case TINYJPEG_FMT_YUV项
decode_mcu_table = decode_mcu_3comp_table;//
switch (pixfmt) {
case TINYJPEG_FMT_YUV420P:
……;
case TINYJPEG_FMT_RGB24:
……;
case TINYJPEG_FMT_BGR24:
……;
case TINYJPEG_FMT_GREY:
……;
//Add by ts
case TINYJPEG_FMT_YUV:
colorspace_array_conv = convert_colorspace_yuv420p;
if (priv->components[0] == NULL)
priv->components[0] = (uint8_t *)malloc(priv->width * priv->height);
if (priv->components[1] == NULL)
priv->components[1] = (uint8_t *)malloc(priv->width * priv->height/4);
if (priv->components[2] == NULL)
priv->components[2] = (uint8_t *)malloc(priv->width * priv->height/4);
bytes_per_blocklines[0] = priv->width;
bytes_per_blocklines[1] = priv->width/4;
bytes_per_blocklines[2] = priv->width/4;
bytes_per_mcu[0] = 8;
bytes_per_mcu[1] = 4;
bytes_per_mcu[2] = 4;
break;
//End
default:
2.程序调试过程中,应做到:
- 2.1 理解程序设计的整体框架
- 2.2 理解以下三个结构体的设计目的
/* 存放DC系数、AC系数的哈夫曼码表 */
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];
};
/* 存放8*8数据块组成的MCU数据值 */
struct component
{
unsigned int Hfactor;//水平采样
unsigned int Vfactor;//垂直采样
float *Q_table; /* Pointer to the quantisation table to use */
struct huffman_table *AC_table;//交流huffman结构体
struct huffman_table *DC_table;//直流huffman结构体
short int previous_DC; /* Previous DC coefficient */
short int DCT[64]; /* DCT coef */
#if SANITY_CHECK//SANITY_CHECK为1
unsigned int cid;//???
#endif
};
/* 包含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 YUV*/
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
- 会根据自己的要求修改TRACE
四、实验结果
1.原始图片
2.TXT输出结果
3.输出的DC、AC图像
图像 | Freq | |
---|---|---|
DC | ||
AC |