一、实验目的
掌握JPEG编解码系统的基本原理。初步掌握复杂的数据压缩算法实现,并能根据理论分析需要实现所对应数据的输出。
二、实验内容
1、JPEG编解码原理
JPEG是最常用的图像文件格式。其主要是采用预测编码(DPCM)、离散余弦变换(DCT)以及熵编码的联合编码方式,以去除冗余的图像和彩色数据,属于有损压缩格式,它能够将图像压缩在很小的储存空间,一定程度上会造成图像数据的损伤。尤其是使用过高的压缩比例,将使最终解压缩后恢复的图像质量降低,如果追求高品质图像,则不宜采用过高的压缩比例。
-
零偏置 Level Offset:
U、V分量是有正有负的,将Y分量减去128,也变为正负形式。
对于灰度级是2n的像素,通过减去2n-1,将无符号的整数值变成有符号数。
对于n=8,即将0~ 255的值域,通过减去128,转换为值域在-128~ 127之间的值。
目的:使像素的绝对值出现3位十进制的概率大大增加 -
DCT变换:
对每个单独的彩色图像分量,把整个分量图像分成8*8的图像块,并作为两维离散余弦变换DCT的输入
-
量化:
因为人眼对亮度信号比对色差信号更敏感,因此使用了两种量化表:亮度量化值和色差量化值
根据人眼的视觉特性(对低频敏感,对高频不敏感),对低频分量采取较细的量化,对高频部分采取较粗的量化。 这样做会引入误差,如果原始图像中细节丰富,则去掉的数据较多。 -
DC系数的差分编码:
8*8图像块经过DCT变换之后得到的DC直流系数有两个特点,系数的数值较大,相邻的8 * 8图像块的DC系数值变化不大:冗余
根据这个特点,JPEG算法采用DCMP(差分脉冲调制编码),对相邻图像块之间量化DC系数的差值DIFF进行编码。 -
AC系数的Z字扫描:
由于经DCT变换后,系数大多数集中在左上角,即 低频分量区,因此采用Z字形按频率高低顺序读出,可以出现连零,可以使用游程编码,如果都是0,可以直接给出EOB
-
Huffman编码:
对DC系数DPCM的结果和AC系数RLE的结果进行Huffman编码,类别ID采用一元码编码,类内索引采用定长码编码。
以DC系数为例,例如差值DIFF = 3 ,对应的类别ID = 2,类内索引 = 3,则码字为100 11。
共有亮度DC、亮度AC、色差DC、色差AC四张Huffman编码表。
解码是编码的逆过程
2、JPEG文件格式的解析
JPEG以segment组成,每个segment以一个marker开始。maker均以0xFF开始,后跟 1字节的标记标识符 和 2字节的标记长度 以及 该标记所对应的payload
- 标记长度部分高位在前,低位在后,不包含该标记的头两个字节
- 熵编码部分的数据在0xFF后由编码器插入0x00,解码器解码时跳过此字节不予处理
- SOI( Start Of Image)和EOI( End Of Image)标记没有payload
- 一定以0xFFD8开始,即表示图像开始SOI
一定以0xFFD9结束,表示图像结束EOI
使用下面这张图片作为测试图像
-
SOI和EOI
-
APP0
APP0块的长度:16字节(不含0xFFE0)
-
量化表DQT,数值0xDB
一般为两张量化表,即亮度和色度各一张
以0xFFDB开始,量化表长度一般为0043(或0084),此图片为0043。
量化表信息(1字节)
Bit 0~3 QT号(只能取值0 ~ 3,否则错误)
Bit 4~7 QT精度(0为8bit,否则表示16 bit )
-
帧图像开始SOF0,数值0xFFC0
-
Huffman表,数值0xFFC4
一共有4张Huffman表,长度分别为29、62、30、47
表ID和表类型:0x00 DC 0号表,0x10 AC 0号表,0x01 DC 1号表,0x11 AC 1号表
不同位数的码字数量(16个字节):以DC 0号表为例,表示没有1位的Huffman码字,2位的Huffman码字有3个,3~9位的Huffman码字均有1个,没有10位及以上的Huffman码字。
编码内容(10个字节):以DC 0号表为例,由上一部分知道此表该字段有10项。这段数据表示10个叶子结点按从小到大排列,其权值(Huffval)依次为04、 05、 06、 03、 02、 01、 00、09、 07、 08 (16进制)。 -
SOS,数值为0xFFDA
以FFDA开始,表明字段的长度,说明了颜色分量数
谱选择开始结束和固定值 003F00。后面是正式的图像数据了。
三、实验代码
1、结构体
存储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
{
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
{
/* 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.解码
int convert_one_image(const char *infilename, const char *outfilename, int output_format)
{
FILE *fp;
unsigned int length_of_file;
unsigned int width, height;
unsigned char *buf;
struct jdec_private *jdec;
unsigned char *components[3];
/* Load the Jpeg into memory */
fp = fopen(infilename, "rb");
if (fp == NULL)
exitmessage("Cannot open filename\n");
length_of_file = filesize(fp);
buf = (unsigned char *)malloc(length_of_file + 4);
if (buf == NULL)
exitmessage("Not enough memory for loading file\n");
fread(buf, length_of_file, 1, fp);
fclose(fp);
/* Decompress it */
jdec = tinyjpeg_init();
if (jdec == NULL)
exitmessage("Not enough memory to alloc the structure need for decompressing\n");
if (tinyjpeg_parse_header(jdec, buf, length_of_file)<0)
exitmessage(tinyjpeg_get_errorstring(jdec));
/* Get the size of the image */
tinyjpeg_get_size(jdec, &width, &height);
snprintf(error_string, sizeof(error_string),"Decoding JPEG image...\n");
if (tinyjpeg_decode(jdec, output_format) < 0)
exitmessage(tinyjpeg_get_errorstring(jdec));
/*
* Get address for each plane (not only max 3 planes is supported), and
* depending of the output mode, only some components will be filled
* RGB: 1 plane, YUV420P: 3 planes, GREY: 1 plane
*/
tinyjpeg_get_components(jdec, components);
/* Save it */
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;
case TINYJPEG_FMT_GREY:
write_pgm(outfilename, width, height, components);
break;
}
/* Only called this if the buffers were allocated by tinyjpeg_decode() */
tinyjpeg_free(jdec);
/* else called just free(jdec); */
free(buf);
return 0;
}
JPEG文件头解析
int tinyjpeg_parse_header(struct jdec_private *priv, const unsigned char *buf, unsigned int size)
{
int ret;
/* Identify the file */
if ((buf[0] != 0xFF) || (buf[1] != SOI))
snprintf(error_string, sizeof(error_string),"Not a JPG file ?\n");
priv->stream_begin = buf+2;
priv->stream_length = size-2;
priv->stream_end = priv->stream_begin + priv->stream_length;
ret = parse_JFIF(priv, priv->stream_begin);
return ret;
}
解析marker标志
static int parse_JFIF(struct jdec_private *priv, const unsigned char *stream)
{
int chuck_len;
int marker;
int sos_marker_found = 0;
int dht_marker_found = 0;
const unsigned char *next_chunck;
/* Parse marker */
while (!sos_marker_found)
{
if