JPEG原理分析及JPEG解码器调试

来源:互联网 发布:霍元甲 歌词 知乎 编辑:程序博客网 时间:2024/05/22 12:14

一.实验内容

1.编解码原理

编码过程如上图所示,解码是编码的逆过程。
1)零偏置
对于灰度级是2n的像素,通过减去2n-1,将无符号的整数值变成有符号数;
对于n=8,即将0~255的值域,通过减去128,转换为值域在-128~127之间的值,可以使像素的绝对值出现3位10进制的概率大大减少。
2)将图像按8*8(pixel)划分成MCU,再进行DCT变换,二维正反离散余弦变换的算式:

3) 因为人眼对亮度信号比对色差信号更敏感,因此使用了两种量化表:亮度量化值和色差量化值。
根据人眼的视觉特性(对低频敏感,对高频不太敏感)对低频分量采取较细的量化,对高频分量采取较粗的量化;如果原始图象中细节丰富,则去掉的数据较多,量化后的系数与量化前差别;反之,细节少的原始图象在压缩时去掉的数据少些。
4)按Z字形把量化后的数据读出,例:

5)游程编码
由于经DCT变换后,系数大多数集中在左上角,即低频分量区,因此采用Z字形按频率的高低顺序读出,可以出现很多连零的机会。可以使用游程编码。尤其在最后,如果都是零,给出EOB (End of Block)即可。
6)差分编码
 8×8图像块经过DCT变换之后得到的DC直流系数有两个特点:系数的数值比较大和相邻8×8图像块的DC系数值变化不大,所以采用差分编码。
DC=8,上一DC=5,则DIFF=8-5=3,类别ID=2,类内索引=3,则码流=10011。


2.JPEG文件格式介绍

标记名称

全写

表示的意义

标记代码(固定值)

SOI

Start of Image

图像开始

0xFFD8

APP0

Application

应用程序保留标记0

0xFFE0

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

图像结束

0xFFD9


#APP0,Application,应用程序保留标记0
u  标记代码                                 2字节     固定值0xFFE0
u  包含9个具体字段:
  ① 数据长度                         2字节     ①~⑨9个字段的总长度
                                                            即不包括标记代码,但包括本字段
  ② 标识符                             5字节    固定值0x4A46494600,即字符串“JFIF0”
  ③ 版本号                             2字节    一般是0x0102,表示JFIF的版本号1.2
                                                            可能会有其他数值代表其他版本
  ④ X和Y的密度单位           1字节     只有三个值可选
                                                            0:无单位;1:点数/英寸;2:点数/厘米
  ⑤ X方向像素密度               2字节     取值范围未知
  ⑥ Y方向像素密度               2字节     取值范围未知   
  ⑦ 缩略图水平像素数目        1字节     取值范围未知
  ⑧ 缩略图垂直像素数目        1字节     取值范围未知
  ⑨ 缩略图RGB位图             长度可能是3的倍数           缩略图RGB位图数据

# DQT,Define Quantization Table,定义量化表
  标记代码                          2字节            固定值0xFFDB
  包含9个具体字段:
  ① 数据长度                  2字节            字段①和多个字段②的总长度
                                                            即不包括标记代码,但包括本字段
  ② 量化表        数据长度-2字节
a)         精度及量化表ID   1字节            高4位:精度,只有两个可选值
                                                              0:8位;1:16位
                                                低4位:量化表ID,取值范围为0~3
b)        表项       (64×(精度+1))字节              例如8位精度的量化表
                                                其表项长度为64×(0+1)=64字节
 本标记段中,字段②可以重复出现,表示多个量化表,但最多只能出现4次。

# SOF0,Start of Frame,帧图像开始
  标记代码                   2字节     固定值0xFFC0
  包含9个具体字段:
  ① 数据长度           2字节     ①~⑥六个字段的总长度
                                              即不包括标记代码,但包括本字段
  ② 精度                 1字节     每个数据样本的位数
                                              通常是8位,一般软件都不支持 12位和16位
  ③ 图像高度           2字节     图像高度(单位:像素),如果不支持 DNL 就必须 >0
  ④ 图像宽度           2字节     图像宽度(单位:像素),如果不支持 DNL 就必须 >0
  ⑤ 颜色分量数        1字节     只有3个数值可选
                                              1:灰度图;3:YCrCb或YIQ;4:CMYK
                                              而JFIF中使用YCrCb,故这里颜色分量数恒为3
  ⑥颜色分量信息      颜色分量数×3字节(通常为9字节)
a)         颜色分量ID                 1字节    
b)        水平/垂直采样因子      1字节            高4位:水平采样因子
                                                       低4位:垂直采样因子
                                                       (曾经看到某资料把这两者调转了)
c)        量化表                         1字节            当前分量使用的量化表的ID
本标记段中,字段⑥应该重复出现,有多少个颜色分量(字段⑤),就出现多少次(一般为3次)。

# DHT,Difine Huffman Table,定义哈夫曼表
标记代码                                 2字节            固定值0xFFC4
包含2个具体字段:
 ①数据长度                             2字节            字段①和多个字段②的总长度
                                                                   即不包括标记代码,但包括本字段
 ② 哈夫曼表              数据长度-2字节
a)       表ID和表类型            1字节            高4位:类型,只有两个值可选
                                                                     0:DC直流;1:AC交流
                                                        低4位:哈夫曼表ID,
                                                                     注意,DC表和AC表分开编码
b)      不同位数的码字数量    16字节
c)      编码内容       16个不同位数的码字数量之和(字节)
本标记段中,字段②可以重复出现(一般4次),也可以只出现1次。

 #SOS,Start of Scan,扫描开始 12字节
  标记代码                          2字节     固定值0xFFDA
  包含2个具体字段:
 ①数据长度                      2字节     ①~④两个字段的总长度
                                                     即不包括标记代码,但包括本字段
 ②颜色分量数                 1字节     应该和SOF中的字段⑤的值相同,即:
                                                     1:灰度图是;3: YCrCb或YIQ;4:CMYK。
                                                         而JFIF中使用YCrCb,故这里颜色分量数恒为3
 ③颜色分量信息
        a) 颜色分量ID           1字节
        b) 直流/交流系数表号 1字节     高4位:直流分量使用的哈夫曼树编号
                                                        低4位:交流分量使用的哈夫曼树编号
 ④ 压缩图像数据
        a)谱选择开始                     1字节     固定值0x00
        b)谱选择结束                     1字节     固定值0x3F
        c)谱选择                            1字节     在基本JPEG中总为00

3.JPEG文件解码流程
1)读入文件的相关信息 
JPEG文件解码流程:
SOI(0xFFD8)
APP0(0xFFE0)
[APPn(0xFFEn)]可选
DQT(0xFFDB)
SOF0(0xFFC0)
DHT(0xFFC4)
SOS(0xFFDA)
压缩数据
EOI(0xFFD9)
读出哈夫曼表数据
a)理论说明
在标记段DHT内,包含了一个或者多个的哈夫曼表。对于单一个哈夫曼表,应该包括了三部分:
l 哈夫曼表ID和表类型
这个字节的值为一般只有四个0x00、0x01、0x10、0x11。
0x00表示DC直流0号表;
0x01表示DC直流1号表;
0x10表示AC交流0号表;
0x11表示AC交流1号表。
l 不同位数的码字数量
JPEG文件的哈夫曼编码只能是1~16位。这个字段的16个字节分别表示1~16位的编码码字在哈夫曼树中的个数。
l 编码内容
这个字段记录了哈夫曼树中各个叶子结点的权。所以,上一字段(不同位数的码字数量)的16个数值之和就应该是本字段的长度,也就是哈夫曼树中叶子结点个数。
b)举例说明
以下面一段哈夫曼表数据举例说明(数据全部以16进制表示):
FFC4 00 1D 00 00 03 01 01 01 01 01 01 01 00  0000 00 00 0004 05 06 03 02 01 00 09 07 08
红色部分 为哈夫曼表ID和表类型,其值0x00表示此部分数据描述的是DC交流0号表。
蓝色部分(16个字节)为不同位数的码字的数量。这16个数值实际意义为:没有1位的哈夫曼码字;2位的码字有3个;3位-9位的码字各有1个;没有9位或以上的码字。
绿色部分(10个字节)为编码内容。由蓝色部分数据知道,此哈夫曼树有0+3+1+1+1+1+1+1+1=10个叶子结点,即本字段应该有10个字节。这段数据表示10个叶子结点按从小到大排列,其权值依次为04、 05、 06、 03、 02、 01、 00、09、 07、 08 (16进制)

建立哈夫曼树
a)理论说明
在读出哈夫曼表的数据后,就要建立哈夫曼树。具体方法为:
1)第一个码字必定为0。
如果第一个码字位数为1,则码字为0;
如果第一个码字位数为2,则码字为00;
如此类推。
2)从第二个码字开始,
如果它和它前面的码字位数相同,则当前码字为它前面的码字加1;
如果它的位数比它前面的码字位数大,则当前码字是前面的码字加1后再在后边添若干个0,直至满足位数长度为止。
b)举例说明
利用第一个例子:

第一个码字必定为0,当前码长是两位,00 03表示前三个码字码长为2,权值依次为04 05 06。01表示码长为3位的码字只有一个,码字等于10+1=11,再补零即110,权值为03,以此类推。
2)初步了解图像数据流的结构 
3)颜色分量单元的内部解码 
颜色分量单元指的是MCU中某个颜色分量中的一个8*8数据块。
刚才建立的是DC(0)huffman表,其权值就是解码时再需要读入的bit位数。这个再次读入的位数通过查表得到真正的码值。例如:0110101011根据刚建立的huffman表分解: 01,10101011.码字01 的权值为05.则再读取5位。 01,10101,011这5位10101进行译码为:21. 表示直流系数为21。
对于交流系数,用交流哈夫曼树/表查得该码字对应的权值。权值的高4位表示当前数值前面有多少个连续的零,低4位表示该交流分量数值的二进制位数,也就是接下来需要读入的位数。例如:权值为0X31.可表示为(3,1)。表明此交流系数前有3个0,而此交流系数的具体值还需要再读入1个bit的码字,才能得到。
4)直流系数的差分编码 
把所有的颜色分量单元按颜色分量(Y、Cr、Cb)分类。每一种颜色分量内,相邻的两个颜色分量单元的直流变量是以差分来编码的。也就是说,通过步骤3解码出来的直流变量数值只是当前颜色分量单元的实际直流变量减去前一个颜色分量单元的实际直流变量。也就是说,当前直流变量要通过前一个颜色分量单元的实际(非解码)直流分量来校正:DCn=DCn-1+Diff
其中Diff为差分校正变量,也就是直接解码出来的直流系数。但如果当前颜色分量单元是第一个单元,则解码出来的直流数值就是真正的直流变量。
5)反量化 & 反Zig-zag编码 
6)反离散余弦变换 

二.实验步骤

1.逐步调试JPEG解码器程序。将输入的JPG文件进行解码,将输出文件保存为可供 
YUVViewer观看的YUV文件。 
2. 程序调试过程中,应做到: 
- 理解程序设计的整体框架 
- 理解三个结构体的设计目的 
struct huffman_table 
struct component 
struct jdec_private 
- 理解在视音频编解码调试中TRACE的目的和含义 
- 会打开和关闭TRACE 
- 会根据自己的要求修改TRACE 
3. 以txt文件输出所有的量化矩阵和所有的HUFFMAN码表。 
4. 输出DC图像并经过huffman统计其概率分布(使用第三个实验中的Huffman编码器)。 
5. 输出某一个AC值图像并统计其概率分布(使用第三个实验中的Huffman编码器)。

三.代码分析

1.输出可观看的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);  //add on 5.23 生成可观看的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);   //add on 5.23}
2.以txt文件输出所有的量化矩阵和所有的HUFFMAN码表
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++;#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]; //add 5.23 量化矩阵输出 #if TRACE  fprintf(p_trace, "Quantization_table [%d]:\n", qi);  fflush(p_trace);#endif  //add 5.23     build_quantization_table(table, stream);     stream += 64;   } /*#if TRACE  fprintf(p_trace,"< DQT marker\n");  fflush(p_trace);#endif*/  return 0;}
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];  //add5.23 量化矩阵输出      #if TRACE fprintf(p_trace,"%d\t",  ref_table[*(zigzag+i * 8 + j)]);  fflush(p_trace);     #endif     } fprintf(p_trace,"\n");   }  //add 5.23}
atic 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  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#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;  }#if TRACE  fprintf(p_trace,"< DHT marker\n");  fflush(p_trace);#endif  return 0;}
static void build_huffman_table(const unsigned char *bits, const unsigned char *vals, struct huffman_table *table){ ....... for (i=0; huffsize[i]; i++)//HUFFMAN码表输出   {     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      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 */      }   }}

3.输出DC,AC图像

tinyjpeg-internal.h

struct jdec_private{ ......  //add    short int *DC_Img;   short int *AC_Img;  // add };
tinyjpeg.h

//add    FILE *DC_FILE;   FILE *AC_FILE;  static void write_DC_AC_images(struct jdec_private *priv);  static void output_DC_AC_images(struct jdec_private *priv);  //add 

loadjpeg.c

int main(int argc, char *argv[]){  int output_format = TINYJPEG_FMT_YUV420P;  char *output_filename, *input_filename;  clock_t start_time, finish_time;  unsigned int duration;  int current_argument;  int benchmark_mode = 0;   //add   char temp[1024];//add  ......  output_filename = argv[current_argument+2];   //add     snprintf(temp, 1024, "%s_dc.y", output_filename);    DC_FILE = fopen(temp, "wb");    snprintf(temp, 1024, "%s_ac.y", output_filename);    AC_FILE = fopen(temp, "wb"); //add    start_time = clock();.......  return 0; }
static void write_DC_AC_images(struct jdec_private *priv){int i;    short int DC_min, DC_max, AC_min, AC_max;    unsigned char *temp;    DC_min = priv->DC_Img[0];    DC_max = priv->DC_Img[0];    AC_min = priv->AC_Img[0];    AC_max = priv->AC_Img[0];    temp = (unsigned char*)malloc(priv->height*priv->width / 64);    for (i = 0;i < priv->height*priv->width/64;i++)    {         DC_max =(priv->DC_Img[i] > DC_max)?priv->DC_Img[i]:DC_max;         DC_min =(priv->DC_Img[i] < DC_min)?priv->DC_Img[i]:DC_min;      AC_max =(priv->AC_Img[i] > AC_max)?priv->AC_Img[i]:AC_max;      AC_min =(priv->AC_Img[i] < AC_min)?priv->AC_Img[i]:AC_min;    }    for (i = 0; i < (priv->height*priv->width/64); i++)    {        temp[i] = (unsigned char)255 * (priv->DC_Img[i] - DC_min) / (DC_max - DC_min);    }    fwrite(temp, 1, priv->width*priv->height / 64, DC_FILE);    for (i = 0; i < (priv->height*priv->width / 64); i++)    {        temp[i] = (unsigned char)255 * (priv->AC_Img[i] - AC_min) / (AC_max - AC_min);    }    fwrite(temp, 1, priv->width*priv->height / 64, AC_FILE);    if (temp) free(temp);}

以1x1为例

tinyjpeg.c 

static void decode_MCU_1x1_3planes(struct jdec_private *priv){  // Y  process_Huffman_data_unit(priv, cY);  output_DC_AC_images(priv);//add  IDCT(&priv->component_infos[cY], priv->Y, 8);   // Cb  process_Huffman_data_unit(priv, cCb);  IDCT(&priv->component_infos[cCb], priv->Cb, 8);  // Cr  process_Huffman_data_unit(priv, cCr);  IDCT(&priv->component_infos[cCr], priv->Cr, 8);}
//addstatic void output_DC_AC_images(struct jdec_private *priv){static long int i = 0;    if(i<priv->height*priv->width/64)       {   priv->DC_Img[i]=priv->component_infos[0].DCT[0];       priv->AC_Img[i]=priv->component_infos[0].DCT[1];}    i++;}//add
int tinyjpeg_decode(struct jdec_private *priv, int pixfmt){  ......  bytes_per_mcu[0] *= xstride_by_mcu/8;  bytes_per_mcu[1] *= xstride_by_mcu/8;  bytes_per_mcu[2] *= xstride_by_mcu/8;   //add   priv->DC_Img = (short int *)malloc(sizeof(short int)*priv->height*priv->width / 64);   priv->AC_Img = (short int *)malloc(sizeof(short int)*priv->height*priv->width / 64);   //add  /* Just the decode the image by macroblock (size is 8x8, 8x16, or 16x16) */  for (y=0; y < priv->height/ystride_by_mcu; y++)   {     //trace("Decoding row %d\n", y);     priv->plane[0] = priv->components[0] + (y * bytes_per_blocklines[0]);     priv->plane[1] = priv->components[1] + (y * bytes_per_blocklines[1]);     priv->plane[2] = priv->components[2] + (y * bytes_per_blocklines[2]);     for (x=0; x < priv->width; x+=xstride_by_mcu)      {decode_MCU(priv);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;    } }      }   }   write_DC_AC_images(priv);//add......  return 0;}
void tinyjpeg_free(struct jdec_private *priv){  int i;  for (i=0; i<COMPONENTS; i++) {     if (priv->components[i])       free(priv->components[i]);     priv->components[i] = NULL;  }  if (priv->AC_Img) free(priv->AC_Img);//add  if (priv->DC_Img) free(priv->DC_Img);//add  free(priv);}

 四.实验结果

以testrgb-1x1.jpg为例

阅读全文
0 0
原创粉丝点击