数据压缩实验 | JPEG原理分析及JPEG解码器的调试

实验原理:

1.JPEG解码原理

【图】
(1)预处理
像块分割:把原图像分割成若干个8x8互相不重叠矩形块,每一个像块作为一个独立的单元进行变换和熵编码。
零偏置(Level Offset):对于灰度级是2n的像素,通过减去2n-1,将无符号的整数值变成有符号数。如对于n=8,即将0~255的值域,通过减去
128,转换为值域在-128~127之间的值。其目的是使像素的绝对值出现3位10进制的概率大大减少。
(2)8x8DCT
经过DCT变换后,前后图像能量不变,图像的低频分量会集中在左上角,故而左上角数值大,相关性小。
(3)量化
根据人眼对低频敏感,对高频不太敏感的视觉特性,对低频分量采取比较细的量化,对高频分量采取比较粗的量化。
因为人眼对亮度信号比对色差信号更敏感,所以使用了两种量化表:亮度量化值和色差量化值。
(4)编码
对DC系数的差分编码:
8x8图像快经过DCT变换后得到的DC直流系数有两个特点:系数的数值比较大、相邻8x8图像块的DC系数值变化不大–冗余。根据这个特点,JPEG算法使用了DPCM技术,对相邻图像块之间量化DC系数的差值DIFF进行编码:
【图片】
码字=类别ID+类内索引
类别ID同时也是解码类内索引时,还需读几位的码流,存在DC系数Huffman码表的全值中。
如:DC=8,上一DC=5,则DIFF=8-5=3,类别ID=2,类内索引=3,则码流=10011。
对交流系数AC采用之字形扫描、游程编码和VLC编码。
AC系数的Z字Sam扫描:
由于经DCT变换后,系数大多数集中在左上角,即低频分量区,因此采用Z字形按频率的高低顺序读出,可以出现很多连零的机会。可以使用游程编码。在最后,如果都是0,给出EOB(End of Block)即可。
【图片】
AC系数的游程编码:(run,level)
连续run个0,后面跟值为level的系数。
如:0,2,0,0,3,0,-4,0,0,0,-6,0,0,5,7表示为(1,2),(2,3),(1,-4),…
Run:最多15个,用4位表示RRRR
Level:类似DC。分成16个类别,用4位表示SSSS表示类别号与类内索引。

2.JPEG文件格式

【文件格式】
(1)Segment的组织形式
JPEG在文件中以Segment的形式组织,具有以下特点:
均以0xFF开始,后跟 1 byte 的 Marker 和 2 byte 的 Segment length(包含表示 Length 本身所占用的 2 byte,不含“0xFF” + “Marker” 所占用的 2 byte);
采用 Motorola 序(相对于 Intel 序),即保存时高位在前,低位在后;
Data 部分中,0xFF 后若为 0x00,则跳过此字节不予处理
(2)JPEG的Segment Maker
【Segment】

3.JPEG的解码流程

1.读取文件
2.解析 Segment Marker
①解析 SOI
②解析 APP0 

  • 检查标识“JFIF”及版本 
  • 得到一些参数

③解析 DQT 

  • 得到量化表长度(可能包含多张量化表) 
  • 得到量化表的精度 
  • 得到及检查量化表的序号(只能是 0 —— 3)
  • 得到量化表内容(64 个数据)
    ④解析 SOF0 
  • 得到每个 sample 的比特数、长宽、颜色分量数 
  • 得到每个颜色分量的 ID、水平采样因子、垂直采样因子、使用的量化表 序号(与 DQT 中序号对应)
    ⑤解析 DHT 
  • 得到 Huffman 表的类型(AC、DC)、序号
  • 依据数据重建 Huffman 表 3.2.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文件。
在loadjpeg.c的write_yuv函数中添加如下代码,将单独的Y、U、V数据合并输出到.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);

原图像:
【图】
输出的YUV图像:
【YUV】
2.调试程序:
理解程序设计框架

main函数:初始化,对文件进行读取

 int main(int argc, char *argv[])
{
  //初始化输出文件格式为YUV420P
  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;
  #if TRACE
  p_trace=fopen(TRACEFILE,"w");
  if (p_trace==NULL)
  {
   printf("trace file open error!");
  }
#endif
  if (argc < 3)
    usage();
  current_argument = 1;
  while (1)
   {
     if (strcmp(argv[current_argument], "--benchmark")==0)
       benchmark_mode = 1;
     else
       break;
     current_argument++;//下一次读的时候可以读到下一串字符
   }
  if (argc < current_argument+2)
    usage();
  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;
  else
    exitmessage("Bad format: need to be one of yuv420p, rgb24, bgr24, grey\n");
  output_filename = argv[current_argument+2];
  start_time = clock();
  if (benchmark_mode)
    load_multiple_times(input_filename, output_filename, output_format);
  else
    convert_one_image(input_filename, output_filename, output_format);
  finish_time = clock();
  duration = finish_time - start_time;
  snprintf(error_string, sizeof(error_string),"Decoding finished in %u ticks\n", duration);
#if TRACE
  fclose(p_trace);
#endif
  return 0;
}

convert_one_image函数:解码

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");
  //解析Segment Maker
  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;
}

tinyjpeg_parse_header函数:解析JPEG文件的文件头

int tinyjpeg_parse_header(struct jdec_private *priv, const unsigned char *buf, unsigned int size)
{
  int ret;
  /* Identify the file */
  //判断JPEG文件头
  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;
}

理解三个结构体的设计目的

  • struct huffman_table
    存放DC系数、AC系数的哈夫曼码表。
 struct huffman_table
{
  //快速查找表,使用HUFFMAN_HASH_NBITS的比特可以直接得到符号,如果符号小于0,那么需要在树表中查找
  short int lookup[HUFFMAN_HASH_SIZE];
  //给出编码符号的位数
  unsigned char code_size[HUFFMAN_HASH_SIZE];
 //存储未在查找表中编码的值的位置,通过计算判断256值是否足以存储所有值
  uint16_t slowtable[16-HUFFMAN_HASH_NBITS][256];
};
  • struct component
    存放8x8数据块组成的最小数据单元的数据值,每访问下一个像块,该结构体就会更新一次值。
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
    最上层结构体,支配整个图像
 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];
  //对YUV进行量化的量化表
  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 */
  //临时存储反DCT之后存三个分量的空间
  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];
};

3.以txt文件输出所有的量化矩阵和所有的HUFFMAN码表。
输出量化矩阵:

tinyjpeg.h

#define TABLES 1 
FILE *f_tables;

loadjpeg.c中的main函数中添加(打开关闭文件):

#if TABLES
  char temp[1024];
  snprintf(temp, 1024, "%s_tables.txt", output_filename)
#endif
#if TABLES
  fclose(f_tables);
#endif

tinyjpeg.c
parse_DQT函数:

while (stream < dqt_block_end)
   {
     qi = *stream++;
#if TRACE
     PrintQtable(table, qi);
#endif;
#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
#if TABLES
     fprintf(f_tables, "Quantization_table [%d]:\n", qi);
     fflush(f_tables);
#endif
     table = priv->Q_tables[qi];
     build_quantization_table(table, stream);
     stream += 64;
   }

build_quantization_table函数:

while (stream < dqt_block_end)
   {
     qi = *stream++;
#if TRACE
     PrintQtable(table, qi);
#endif;
#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
#if TABLES
     fprintf(f_tables, "Quantization_table [%d]:\n", qi);
     fflush(f_tables);
#endif
     table = priv->Q_tables[qi];
     build_quantization_table(table, stream);
     stream += 64;
   }

build_quantization_table函数:

for (i=0; i<8; i++) 
  {
     for (j=0; j<8; j++) 
     {
        //add by susu
#if TABLES
         fprintf(f_tables, "%d\t", ref_table[*zz]);
         fflush(f_tables);
         if (j == 7)
         {
            fprintf(f_tables,"\n");
            fflush(f_tables);
         }
#endif

输出哈夫曼码表:
tinyjpeg.c
build_huffman_table函数:

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 TABLES
     fprintf(f_tables, "val=%2.2x code=%8.8x codesize=%2.2d\n", val, code, code_size);
     fflush(f_tables);
#endif
     table->code_size[val] = code_size;
     ......
   }

输出结果:
【量化矩阵】
在这里插入图片描述
4.输出DC图像与某一个AC值并统计其概率分布。
在tinyjpeg.h文件中声明:

FILE *DC_FILE;
FILE *AC_FILE;
static void output_DC_AC_Image(struct jdec_private *priv, int temp);
static void write_DC_AC_Image(struct  jdec_private *priv);

在main函数中添加文件打开关闭代码:

  char temp[1024];
  snprintf(temp, 1024, "%output_dc.yuv", output_filename);
  
  DC_FILE = fopen(temp, "wb");
  snprintf(temp, 1024, "%output_ac.yuv", output_filename);
  AC_FILE = fopen(temp, "wb");
  
  fclose(AC_FILE);
  fclose(DC_FILE);

写出DC、AC图像write_DC_AC_Image:

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);
}

图像输出
DC图像:
在这里插入图片描述
AC图像:
在这里插入图片描述
5.概率分布:
在这里插入图片描述
在这里插入图片描述

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值