实验五:JPEG解码

一、实验原理

1、JPEG(Joint Picture Expert Group),是联合图像专家组的英文缩写。

其编码使用的是标称Huffman编码。编码原理图如下:
编码原理图
解码是编码的逆过程。

2、JPEG文件格式

JPEG的每个标记都是由2个字节组成,其前一个字节是固定值0xFF。JFIF文件格式直接使用JPEG标准为应用程序定义的许多标记,因此JFIF格式成为事实上JPEG文件交换格式标准。将一张JPEG图片以二进制形式打开(部分数据),分析文件格式:
文件格式
0xFFD8:SOI,Start of Image,图像开始
0xFFE0:APP0,Application,应用程序保留标记0,在这里指JFIF应用数据块
0xFFDB:DQT,Define Quantization Table,定义量化表
0xFFC0:SOF0,Start of Frame,帧图像开始
0xFFC4:DHT,Define Huffman Table,定义哈夫曼表
0xFFDA:SOS,Start of Scan,扫描开始12字节
0xFFD9:EOI,End of Image,图像结束2字节

3、DCT变换

变换编码是将输入的信号进行变换,得到一些系数。它可以从三个角度分析,一个是因子分析,一个是对主程序分析(与K-L变换等价),第三个是信号的合成与分解,是变化域系数与对应向量的线性组合,这也是DCT变换的本质。一维DCT变换:Y=AX ;二维DCT变换:Y=AXB(B为A的转置)。
DCT变换的三个特点:①能量守恒;②能量集中;③去相关性,将有记忆信源变成无记忆,使数据易于处理。

4、量化

JPEG中的量化表是按之字形扫描的格式保存的,需要反之字形扫描才能得到真正的量化表。文件存有两张量化表,分别对亮度信号和色差信号进行量化。根据人眼的视觉特性对低频分量采取细量化,对高频分量采取粗量化。

5、DC系数的差分编码

8X8图像块经过DCT变换后得到的DC直流系数有两个特点:①系数的数值较大;②相邻图像块的DC变化不大。根据这一特点,JPEG使用了差分脉冲调制编码技术,即DPCM技术,对相邻块之间的量化DC系数的差值DIFF进行Huffman编码。

6、AC系数的Z字扫描

由于DCT变换后,系数大多集中在左上角,即低频分量区,所以采用Z字形扫描按照频率从高到低的顺序读出,可以得到很多连零的结果。
z字扫描

7、Huffman码表存储方式

JPEG图片存储需要四张Huffman码表,分别是亮度DC,亮度AC,色度DC和色度AC。以一个Huffman码表为例进行分析:
huffman
0xFFC4是Huffman码表的开始标志。
黄色的“00”为Huffman表ID和表类型,0x00表示此部分为DC交流0号表。
绿色部分(16个字节)为不同位数的码字数量。没有1位的Huffman码字,2位的有1个,3位的有5个,4、5、6、7、8、9位各有一个,一共12个码字。
红色部分(12个字节,由绿色部分决定)为编码内容。这段数据表示12个叶子节点按从小到大排列,其权值用十六进制表示,依次为00,01,02,03,04,05,06,07,08,09,0A,0B.

二、实验步骤

分析JPEG编解码过程,要注意模块的划分和数据的流向。用三个结构体实现层次的嵌套:
struct jdec_private是最上层的结构体,包含 struct huffman_table和struct component。 struct huffman_table用来存放DC系数和AC系数的Huffman表;struct component用来存放8x8数据块组成的MCU(最小数据单元)的数值。
JPEG文件解码过程:读取文件->分析数据流结构->颜色分量单元的内部解码->直流系数的差分编码->反量化和反z字编码->反DCT变换

三、相关代码

1、解析三个结构体
/*************存放DC系数和AC系数的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];
};
/*****************存放8x8数据块组成的MCU(最小数据单元)的数值*******************/
struct component 
{
  unsigned int Hfactor;//水平采样因子
  unsigned int Vfactor;//垂直采样因子
  float *Q_table;       /* Pointer to the quantisation table to use */
  struct huffman_table *AC_table;//指向AC系数的Huffman表
  struct huffman_table *DC_table;//指向DC系数的Huffman表
  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];//分别指向YUV分量结构体
  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];
  short int *DC_Image;//指向输出DC图像的指针
  short int *AC_Image;//指向输出AC图像的指针
  jmp_buf jump_state;
  /* Internal Pointer use for colorspace conversion, do not modify it !!! */
  uint8_t *plane[COMPONENTS];
};
2、将输出文件保存为可供YUVViewer观看的YUV文件

①在命令行中将输出格式设为yuv。
②修改相关代码:

/********tinyjpeg.h***********/
enum tinyjpeg_fmt {
   TINYJPEG_FMT_GREY = 1,
   TINYJPEG_FMT_BGR24,
   TINYJPEG_FMT_RGB24,
   TINYJPEG_FMT_YUV420P,
   TINYJPEG_FMT_YUV,//合成YUV分量并输出
};
/**********tinyjpeg.c***************/
int tinyjpeg_decode(struct jdec_private *priv, int pixfmt)
{
  unsigned int x, y, xstride_by_mcu, ystride_by_mcu;
  unsigned int bytes_per_blocklines[3], bytes_per_mcu[3];
  decode_MCU_fct decode_MCU;
  const decode_MCU_fct *decode_mcu_table;
  const convert_colorspace_fct *colorspace_array_conv;
  convert_colorspace_fct convert_to_pixfmt;

  if (setjmp(priv->jump_state))
    return -1;

  /* To keep gcc happy initialize some array */
  bytes_per_mcu[1] = 0;
  bytes_per_mcu[2] = 0;
  bytes_per_blocklines[1] = 0;
  bytes_per_blocklines[2] = 0;

  decode_mcu_table = decode_mcu_3comp_table;
  switch (pixfmt) {
     case TINYJPEG_FMT_YUV:
     ......//add yuv format condition
     case TINYJPEG_FMT_YUV420P:
       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;
       ......
  }
/**************loadjpeg.c******************/
int convert_one_image(const char *infilename, const char *outfilename, int output_format)
{
  ......
  /* 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;
    /******add yuv output**********/
    case TINYJPEG_FMT_YUV:
      write_add_yuv(outfilename, width, height, components);
      break;
   }
}
/**************YUV文件写出****************/
static void write_add_yuv(const char *filename, int width, int height, unsigned char **components)  
{  
 FILE *F;  
 char temp[1024];  
 snprintf(temp, 1024, "%s.yuv", filename);  
 F = fopen(temp, "wb");  
 fwrite(components[0], width, height, F);  
 snprintf(temp, 1024, "%s.yuv", filename);  
 fwrite(components[1], width*height/4, 1, F); 
 snprintf(temp, 1024, "%s.yuv", filename);   
 fwrite(components[2], width*height/4, 1, F);  
 fclose(F);    
}  
/*************主函数*************/
int main(int argc, char *argv[])
{
  ......
  if (strcmp(argv[current_argument + 1], "yuv420p") == 0)
      output_format = TINYJPEG_FMT_YUV420P;
 ......
  else if (strcmp(argv[current_argument + 1], "yuv") == 0)//add
      output_format = TINYJPEG_FMT_YUV;
  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
  ......
  return 0;
}
3、以txt文件输出量化矩阵和Huffman码表
/************tinyjpeg.c 输出量化表*************/
static int parse_DQT(struct jdec_private *priv, const unsigned char *stream)
{
  ......
  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];
     build_quantization_table(table, stream);
     stream += 64;
      #if TABLES
     fprintf(f_tables,"Quantization_table[%d]:\n",qi);//add
     fflush(f_tables);
      #endif
   }
   ......
}
/***********tinyjpeg.c 以矩阵形式输出量化表*******************/
static void build_quantization_table(float *qtable, const unsigned char *ref_table)
{
  ......
  for (i=0; i<8; i++) {
     for (j=0; j<8; j++) {
         #if TABLES
fprintf(f_tables,"%d\t",ref_table[*zz]);//add
fflush(f_tables);
if(j==7)
{
fprintf(f_tables,"%\n");
fflush(f_tables);
}
#endif
       *qtable++ = ref_table[*zz++] * aanscalefactor[i] * aanscalefactor[j];
     }
   }

}
/*******parse_DHT函数,输出DC和AC***************/
if{
 #if TABLES
         fprintf(f_tables, "Huffman table AC[%d] length=%d\n", index & 0xf, count);
         fflush(f_tables);
    #endif
    ...
    }
    else
    {
    ...
     #if TABLES
         fprintf(f_tables, "Huffman table DC[%d] length=%d\n", index & 0xf, count);
         fflush(f_tables);
    #endif
    }
/******build_huffman_table,输出Huffman码表**********/
 #if TABLES
     fprintf(f_tables, "val=%2.2x code=%8.8x codesize=%2.2d\n", val, code, code_size);
     fflush(f_tables);
    #endif
4、输出DC和某一AC图像及概率分布
/**************将DC AC图像写入Image中*******************/
static void output_DC_AC_Image(struct jdec_private *priv)
{
    static long int i=0;
    if(i<priv->height*priv->width/64)
        priv->DC_Image[i]=priv->component_infos[0].DCT[0];
        priv->AC_Image[i]=priv->component_infos[0].DCT[1];
    i++;
}
/**************输出DC AC图像***************/
static void write_DC_AC_Image(struct jdec_private *priv)
{
    int i;
    short int DC_min, DC_max, AC_min, AC_max;
    unsigned char *temp;
    DC_min = priv->DC_Image[0];
    DC_max = priv->DC_Image[0];
    AC_min = priv->AC_Image[0];
    AC_max = priv->AC_Image[0];
    temp = (unsigned char*)malloc(priv->height*priv->width / 64);
    for (i = 0; i < (priv->height*priv->width/64); i++)
    {
        if (priv->DC_Image[i] > DC_max)
            DC_max = priv->DC_Image[i];
        if (priv->DC_Image[i] < DC_min)
            DC_min = priv->DC_Image[i];
        if (priv->AC_Image[i] > AC_max)
            AC_max = priv->AC_Image[i];
        if (priv->AC_Image[i] < AC_min)
            AC_min = priv->AC_Image[i];
    }
    for (i = 0; i < (priv->height*priv->width/64); i++)
    {
        temp[i] = (unsigned char)255 * (priv->DC_Image[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_Image[i] - AC_min) / (AC_max - AC_min);
    }
    fwrite(temp, 1, priv->width*priv->height / 64, AC_FILE);
    if (temp) free(temp);
}

四、实验结果

1、输出YUV文件
yuv
2、输出量化矩阵
量化矩阵
3、输出DC[0],AC[1]的Huffman码表
码表
4、DC(左)、AC(右)的图像及概率分布图
图像
概率分布图

五、实验结论

1、在有需要的地方添加trace,通过初始化,在解码过程中会输出一些与图像相关的参数及信息,将tinyjpeg.h中的trace变量设为1,则关闭trace文件。
2、 JPEG有三种失真:
①蚊子噪声->锐利的高频截止。
蚊子噪声
②马赛克、块效应->高压缩比图像中量化噪声相邻块的直流分量相差太大。
③轮廓线不光滑->轮廓线跨越几个块,在不同的块取值不同。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值