一、实验原理
1.JPEG编解码原理
JPEG(Joint Photographic Experts Group)采用有损压缩方式去除冗余的图像和彩色数据,在获得极高压缩率的同时能展现十分丰富生动的图像。
① Level Offset(零偏置)
为了使使像素的绝对值出现3位10进制的概率大大减少,对于灰度级是2的n次方的像素,通过减去2的n-1次方,将无符号的整数值变成有符号数。
② 8✖️8 DCT变换
变换公式如下:
DCT变换的优点:1、能量守恒 2、能量集中 3、去相关。
经过DCT变换后,能量集中在左上角,使得左上角数值较大,右下角数值较小。
③Uniform scalar quantization(量化)
因为人眼对亮度信号比对色差信号更敏感,因此使用了两种量化表:亮度量化值和色差量化值。由于人眼对低频敏感,对高频不太敏感,因此对低频分量采取较细的量化,对高频分量采取较粗的量化。
④dc quantization indices Differential coding(DC系数的差分编码)
8×8图像块经过DCT变换之后得到的DC直流系数有两个特点:系数的数值比较大; 相邻8×8图像块的DC系数值变化不大(产生冗余)。
根据这个特点,JPEG算法使用了差分脉冲调制编码(DPCM)技术,对相邻图像块之间量化DC系数的差值DIFF进行编码:DIFFk =DCk - DCk-1。
码表如下(此编码表并不是一成不变的,不同的文件有不同的码表。以此码表为例,DC=8,上一DC=5时,则DIFF=8-5=3,类别ID=2,类内索引=3,则码流为10011):
⑤Zig-zag scan(AC系数的Z字扫描)
由于量化之后右下角高频系数大部分为0,在编码是为了制造更长的0游程提高编码效率,采用之字形扫描读取法。经过之字形扫描读出后把二维系数矩阵转换为一维数据序列。在最后, 如果都是零,给出 EOB (End of Block) 即可。
zig-zag序
⑥AC系数的游程编码(Run-level coding)
编码规则:(run,level)
表示连续run个0,后面跟着值为level的AC系数。
run:最多15个,故用4位表示。
level:类似DC系数,分为类别ID与类内索引。用4位表示类别ID。
(run,level的类别ID)合成1字节存在AC系数码表的权值中
AC系数的之字形序列编码中有两个特殊符号——(0,0)和(15,0)。第一个特殊符号指的是块的结束(end-of-block,EOB),用来表明在之字形块中剩余的元素都是零。另一个特殊符号是指零游程长度(zero-run-length,ZRL),用来表明16个零游程。基线(baselinesequential)JPEG算法允许的零游程最大长度是16个。如果这里的零超过16个,那么这个游程分成几个长度为16的零游程。
经过RLE编码的AC系数可以映射成两个标志(RUNLENGTH,CATEGORY)和(AMPLITUDE),前者采用的是霍夫曼编码,而后者采用的是VLI编码(直接二进制编码)。同理经过DPCM编码的DC系数同样可以映射成两个标志(CATEGORY)和(AMPLITUDE),,前者采用霍夫曼编码,后者采用VLI编码。
解码过程是编码过程的反过程
2. JPEG文件格式
英文缩写 | 英文全称 | 说明 | 标志代码 |
---|---|---|---|
SOI | Start of Image | 图像开始 | 0xFFD8 |
APPn | Application | 应用程序保留标记n | 0xFFEn |
DQT | Define Quantization Table | 定义量化表 | 0XFFDB |
SOF0 | Start of Frame | 帧图像开始 | 0xFFC0 |
DHT | Define Huffman Table | 定义哈夫曼表 | 0xFFC4 |
SOS | Start of Scan | 扫描开始,12字节 | 0xFFDA |
EOI | End of Image | 图像结束,2字节 | 0xFFD9 |
JPEG文件以FFD8开头
APPn是文件细节信息,看图片时,没有点进去就显示出来的缩略图就是存在这部分里,下图FFE0为APP0,从FFE0依次向后读:00 10表示该部分占16字节;4A 46 49 4600表示JFIF0;01 01表示版本号;00 表示X和Y方向的密度单位(0:无单位、1:1像素/英寸、2:2像素/厘米);00 01表示X方向像素密度;00 01表示Y方向像素密度;最后的00和00分别表示缩略图的X和Y方向像素数,由于该文件没有缩略图,后面就没有缩略的RGB位图的数据。
文件中有两个FFDB,说明有两张量化表(DQT),以第一张量化表为例,从FFDB向后读:00 43表示量化表的长度为67字节;00的低四位表示第0张量化表,高四位表示8bit的量化精度(非0则16bit);最后是8*8的量化表实际数据,按之字形保存。
FFC0代表帧图像的开始(SOF0),向后读:00 11表示该部分的长度17字节;08表示每个颜色分量每个像素占8bit;04 00 04 00表示图像的高和宽分别为1024和1024像素;03 表示有3个颜色分量(YCbCr);后面9个字节,每3个一组,分别表示Y、Cb、Cr的如下信息,1字节的颜色ID(01、02、03)、1字节的采样因子(高四位水平 1、低四位垂直 1,说明MCU为8*8)、该分量使用的量化表(00、01、01)。
该文件有4个FFC4,表示有4张huffman码表,以第一张为例:001D表示这张表的长度为29字节;00高四位表示直流(1为交流),低四位表示第0张表;后面16字节为不同位数码字的数量,00 03 01 01……00等表示码字为1、2、3……16位的个数分别为0、3、1、1……0个,相加得到码字总数为10;最后是这10个码字对应的权重,表示解码时需要再读入的位数。
FFDA表示扫描开始(SOS):00 0C表示这部分长度12字节;03表示颜色分量数为3; 后面6个字节。每2个一组表示一种颜色分量的ID和该分量使用的直流与交流量化表,01 00表示Y分量,交流使用第0张量化表,直流使用第0张量化表,02 11表示Cb分量,交流直流均使用第1张量化表,03 11表示Cr分量,交流直流均使用第1张量化表;后面是压缩图像数据,00是固定值,表示谱选择开始,3F是固定值,表示谱选择结束,00是固定值,表示谱选择。
后面是图像流的组成部分,需要进行译码,直至遇到FF D9(EOI)结束。
二、代码分析
任务一:调试JPEG解码器程序,将输出文件保存为可供YUVViewer观看的YUV文件。
1. 定义输入参数的枚举类型enum tinyjpeg_fmt中增加TINYJPEG_FMT_YUV一项
enum tinyjpeg_fmt {
TINYJPEG_FMT_GREY = 1,
TINYJPEG_FMT_BGR24,
TINYJPEG_FMT_RGB24,
TINYJPEG_FMT_YUV420P,
/*******************/
TINYJPEG_FMT_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;
/***************************************************************/
case TINYJPEG_FMT_YUV:
write_YUV(outfilename, width, height, components);
break;//增加
/***************************************************************/
case TINYJPEG_FMT_GREY:
write_pgm(outfilename, width, height, components);
break;
}
3. main函数,增加解析命令行。
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;
/*********************************************************/
else if (strcmp(argv[current_argument+1],"yuv")==0)
output_format = TINYJPEG_FMT_YUV;//增加
/*********************************************************/
else
exitmessage("Bad format: need to be one of yuv420p, rgb24, bgr24, grey\n");
switch (pixfmt) {
......
//增加start
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
......
}
任务二:
以txt文件输出所有的量化矩阵、所有的HUFFMAN码表。
1. 定义指向txt文件的指针
FILE *p_ytxt;
#define YTXT 1
#define YTXTFILE "ld.txt"
#if YTXT
p_ytxt = fopen(YTXTFILE, "w");
if (p_ytxt == NULL)
{
printf("trace file open error!");
}
#endif
static void build_quantization_table(float *qtable, const unsigned char *ref_table)
{
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++) {
#if YTXT
fprintf(p_ytxt, "%d ", ref_table[*zz]);//输出以zigzag矩阵所规定的顺序的量化矩阵。原因是ref_table指针中存着的着量化表值是以z字形扫描的次序来进行存储的,因此需要利用zigzag进行正确的排序
fflush(p_ytxt);
#endif
*qtable++ = ref_table[*zz++] * aanscalefactor[i] * aanscalefactor[j];
}
#if YTXT
fprintf(p_ytxt, "\n");
fflush(p_ytxt);
#endif
}
}
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
};
static int parse_DHT(struct jdec_private *priv, const unsigned char *stream)
{
unsigned int count, i;
unsigned char huff_bits[17];
int length, index;
length = be16_to_cpu(stream) - 2;
stream += 2; /* Skip length */
#if TRACE
fprintf(p_trace,"> DHT marker (length=%d)\n", length);
fflush(p_trace);
#endif
#if YTXT
fprintf(p_ytxt, "> DHT marker (length=%d)\n", length);
fflush(p_ytxt);
#endif
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 SANITY_CHECK
if (count >= HUFFMAN_BITS_SIZE)
snprintf(error_string, sizeof(error_string),"No more than %d bytes is allowed to describe a huffman table", HUFFMAN_BITS_SIZE);
if ( (index &0xf) >= HUFFMAN_TABLES)
snprintf(error_string, sizeof(error_string),"No more than %d Huffman tables is supported (got %d)\n", HUFFMAN_TABLES, index&0xf);
#if TRACE
fprintf(p_trace,"Huffman table %s[%d] length=%d\n", (index&0xf0)?"AC":"DC", index&0xf, count);
fflush(p_trace);
#endif
#if YTXT
fprintf(p_ytxt, "Huffman table %s[%d] length=%d\n", (index & 0xf0) ? "AC" : "DC", index & 0xf, count);
fflush(p_ytxt);
#endif
#endif
if (index & 0xf0 )
build_huffman_table(huff_bits, stream, &priv->HTAC[index&0xf]);
else
build_huffman_table(huff_bits, stream, &priv->HTDC[index&0xf]);
length -= 1;
length -= 16;
length -= count;
stream += count;
}
static void build_huffman_table(const unsigned char *bits, const unsigned char *vals, struct huffman_table *table)
{
unsigned int i, j, code, code_size, val, nbits;
unsigned char huffsize[HUFFMAN_BITS_SIZE+1], *hz;
unsigned int huffcode[HUFFMAN_BITS_SIZE+1], *hc;
int next_free_entry;
/*
* Build a temp array
* huffsize[X] => numbers of bits to write vals[X]
*/
hz = huffsize;
for (i=1; i<=16; i++)
{
for (j=1; j<=bits[i]; j++)
*hz++ = i;
}
*hz = 0;
memset(table->lookup, 0xff, sizeof(table->lookup));
for (i=0; i<(16-HUFFMAN_HASH_NBITS); i++)
table->slowtable[i][0] = 0;
/* Build a temp array
* huffcode[X] => code used to write vals[X]
*/
code = 0;
hc = huffcode;
hz = huffsize;
nbits = *hz;
while (*hz)
{
while (*hz == nbits)
{
*hc++ = code++;
hz++;
}
code <<= 1;
nbits++;
}
/*
* Build the lookup table, and the slowtable if needed.
*/
next_free_entry = -1;
for (i=0; huffsize[i]; i++)
{
val = vals[i];
code = huffcode[i];
code_size = huffsize[i];
#if TRACE
fprintf(p_trace,"val=%2.2x code=%8.8x codesize=%2.2d\n", val, code, code_size);
fflush(p_trace);
#endif
#if YTXT
fprintf(p_ytxt, "val=%2.2x code=%8.8x codesize=%2.2d\n", val, code, code_size);
fflush(p_ytxt);
#endif
table->code_size[val] = code_size;
if (code_size <= HUFFMAN_HASH_NBITS)
{
/*
* Good: val can be put in the lookup table, so fill all value of this
* column with value val
*/
int repeat = 1UL<<(HUFFMAN_HASH_NBITS - code_size);
code <<= HUFFMAN_HASH_NBITS - code_size;
while ( repeat-- )
table->lookup[code++] = val;
}
else
{
/* Perhaps sorting the array will be an optimization */
uint16_t *slowtable = table->slowtable[code_size-HUFFMAN_HASH_NBITS-1];
while(slowtable[0])
slowtable+=2;
slowtable[0] = code;
slowtable[1] = val;
slowtable[2] = 0;
/* TODO: NEED TO CHECK FOR AN OVERFLOW OF THE TABLE */
}
}
}
任务三:输出DC图像并经过huffman统计其概率分布,并输出某一个AC值图像并统计其概率分布。
FILE *dc_file;
FILE *ac_file;
#if DC
dc_file = fopen(DCFILE, "wb");
if (dc_file == NULL)
{
printf("trace file open error!");
}
#endif
#if AC
ac_file = fopen(ACFILE, "wb");
if (ac_file == NULL)
{
printf("trace file open error!");
}
#endif
1
struct jdec_private
{
int *acimage,*dcimage;
}
priv->dcimage = (int *)malloc(sizeof(int)*priv->width * priv->height/64);
priv->acimage = (int *)malloc(sizeof(int)*priv->width * priv->height / 64);
static void DC_image(struct jdec_private *priv)
{
static int i=0;
if ( i < (priv->height*priv->width / 64))
{
priv->dcimage[i] = priv->component_infos[cY].DCT[0];
}
i++;
}
static void AC_image(struct jdec_private *priv)
{
static int i = 0;
if (i < (priv->height*priv->width / 64))
{
priv->acimage[i] = priv->component_infos[cY].DCT[1];
}
i++;
}
int acmax, dcmax, acmin, dcmin;
int tmp;
acmax = priv->acimage[0];
acmin = priv->acimage[0];
dcmax = priv->dcimage[0];
dcmin = priv->dcimage[0];
for (i = 0; i < priv->width*priv->height / 64; i++)
{
if (priv->acimage[i] >=acmax)
acmax = priv->acimage[i];
if (priv->dcimage[i] >= dcmax)
dcmax = priv->dcimage[i];
if (priv->acimage[i] <= acmin)
acmin = priv->acimage[i];
if (priv->dcimage[i] <=dcmin)
dcmin = priv->dcimage[i];
}
for (i = 0; i < priv->width*priv->height / 64; i++)
{
tmp = priv->acimage[i] - acmin;
acfileout[i] = (unsigned char)(255 *(priv->acimage[i]-acmin)/ (acmax - acmin));
}
fwrite(acfileout, 1, priv->width*priv->height / 64, ac_file);
if (acfileout)
free(acfileout);
for (i = 0; i < priv->width*priv->height / 64; i++)
{
dcfileout[i] = (unsigned char)(255 *(priv->dcimage[i]-dcmin)/ (dcmax - dcmin));
}
fwrite(dcfileout, 1, priv->width*priv->height / 64, dc_file);
if (dcfileout)
free(dcfileout);
输出量化表
输出的Huffman码表:
原始图像YUV:
输出的DC图像及其概率分布图:
输出的AC图像及其概率分布图:
蚊子噪声的图像
压缩程度越剧烈,量化的步长越大,高频系数为零的可能性就越大,即高频截止,于是轮廓周围会出现许多蚊子似的点,即空间域出现蚊子噪声。