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

目录

JPEG编码原理

JPEG文件格式

Segment 的组织形式

JPEG 的 Segment Marker

JPEG 的解码流程

实验要求及实现


实验名称:JPEG原理分析及JPEG解码器的调试

实验目的:掌握JPEG编解码系统的基本原理。初步掌握复杂的数据压缩算法实现,并能根据理论分析需要实现所对应数据的输出。

 


JPEG编码原理

  1. 图像分块:由于后面的DCT变换是是对8x8的子块进行处理的,因此,必须先将原始图像数据分成8*8的小块。
  2. 零偏置(Level Offset):将所有像素的电平减去128,电平范围从[0,255]变成[-128,127]
  3. 8×8 DCT变换:将低频部分集中在每个8*8块的左上角,高频部分在右下角。实现能量集中和去相关,去除图像的空间冗余。
  4. 量化:根据人眼视觉特性,低频细量化,高频粗量化,减小视觉冗余;JPEG使用的颜色是YUV格式。Y分量代表了亮度信息,UV分量代表了色差信息。根据人眼对亮度信息更敏感,对色度信息没那么敏感,我们还可以对Y采用细量化,对UV采用粗量化,可进一步提高压缩比。
  5. 编码:直流系数进行差分和VLC编码,交流系数进行之字形扫描、游程编码和VLC编码,减少数据冗余。
  • DC 系数编码:由于直流系数 F(0,0)反映了该子图像中包含的直流成分,通常较大,又由于两个相邻的子图像的直流系数通常具有较大的相关性,所以对 DC 系数采用差值脉冲编码(DPCM,即对本像素块直流系数与前一像素块直流系数的差值进行无损编码。
  • AC 系数编码:首先,进行游程编码(RLC),并在最后加上块结束码(EOB);然后,系数序列分组,将非零系数和它前面的相邻的全部零系数分在一组内;每组用两个符号表示[RunSize),(Amplitude]                                                  Amplitude:表示非零系数的幅度值;Run:表示零的游程即零的个数;Size表示非零系数的幅度值的编码位数;

JPEG文件格式

 

Segment 的组织形式

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

JPEG Segment Marker

 

 

 

 

JPEG 的解码流程

1. 读取文件
2. 解析 Segment Marker
  2.1 解析 SOI
  2.2 解析 APP0
       检查标识“ JFIF ”及版本
       得到一些参数
  2.3 解析 DQT
       得到量化表长度(可能包含多张量化表)
       得到量化表的精度
       得到及检查量化表的序号(只能是 0 —— 3
       得到量化表内容( 64 个数据)
  2.4 解析 SOF0
       得到每个 sample 的比特数、长宽、颜色分量数
        得到每个颜色分量的 ID 、水平采样因子、垂直采样因子、使用的量化表 序号(与 DQT 中序号对应)
  2.5 解析 DHT
       得到 Huffman 表的类型( AC DC )、序号
        依据数据重建 Huffman
  2.6 解析 SOS
      得到解析每个颜色分量的 DC AC 值所使用的 Huffman 表序号(与 DHT 中序号对应)
3. 依据每个分量的水平、垂直采样因子计算 MCU 的大小,并得到每个 MCU 8*8 宏块的个数
4. 对每个 MCU 解码(依照各分量水平、垂直采样因子对 MCU 中每个分量宏块解 码)
  4.1 对每个宏块进行 Huffman 解码,得到 DCT 系数
  4.2 对每个宏块的 DCT 系数进行 IDCT ,得到 Y Cb Cr
  4.3 遇到 Segment Marker RST 时,清空之前的 DC DCT 系数
5. 解析到 EOI ,解码结束
6. Y Cb Cr 转化为需要的色彩空间并保存。

实验要求及实现

1.逐步调试JPEG解码器程序。将输入的JPG文件进行解码,将输出文件保存为可供YUVViewer观看的YUV文件。

为输出YUV文件,需要在write_yuv函数下增加如下代码:

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

调试: 

用YUV播放器打开test.yuv 

 

2. 程序调试过程中,应做到:

  • 理解程序设计的整体框架
  • 理解三个结构体的设计目的
    • struct  huffman_table
    • struct  component
    • struct  jdec_private
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 
   * 快速查找表,使用HUFFMAN_HASH_NBITS位我们可以直接得到符号,            
   * 如果符号小于0,则需要查看慢速查找表 */
   
  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;//指向交流Huffman码表
  struct huffman_table *DC_table;//指向直流Huffman码表
  short int previous_DC;	 /* Previous DC coefficient */
  short int DCT[64];		 /* DCT coef ,即DCT系数 */
#if SANITY_CHECK
  unsigned int cid;
#endif
};

struct jdec_private      //保存着图像的基本信息,各通道的信息及需要用的量化表,解码后分量的值等
{
  /* Public variables */
  uint8_t *components[COMPONENTS];                   //一个MCU指向它包含的块
  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];              //解码后Y,Cr,Cb分量的值

  jmp_buf jump_state;
  /* Internal Pointer use for colorspace conversion, do not modify it !!! */
  uint8_t *plane[COMPONENTS];
};
  • 理解在视音频编解码调试中TRACE的目的和含义

        TRACE随着文件解析,输出中间信息,方便了解整个程序运行的过程。

 

  • 会打开和关闭TRACE

输出txt文件在loadjpeg.h中打开和关闭。

#define  snprintf _snprintf          //add by nxn
#define TRACE 1                      //add by nxn,若要关闭TRACE,则将TRACE设为0
#define  TRACEFILE "trace_jpeg.txt"  //add by nxn
  • 会根据自己的要求修改TRACE

通过在系统各处添加trace,可以在需要的时候输出想要的中间结果,这些中间信息在tinyjpeg.c中输出。

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

tinyjpeg.h补充

FILE* Q_table;
FILE* Huffman_code;

loadjpeg.c的main中补充

Q_table=fopen("Q_table.txt","wb");
Huffman_code = fopen("Huffman_code.txt", "wb");

parse_DQT函数:解析量化表,得到量化表长度,检查量化表是否被支持,得到量化表内的数据

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
   {
     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;                                //指向下一个8*8的块
   }
#if TRACE                                         //输出量化表
     fprintf(Q_table, "scaled quantization table [%d]", qi);
     for (int i = 0; i < 64; i++)
     {
         if ((!(i % 8)))                         //8个一行输出
         {
             fprintf(Q_table, "\n%f", table[i]);
         }
         else
         {
             fprintf(Q_table, " %f", table[i]);
         }
     }
     fprintf(Q_table, "\n");
   }
#endif
#if TRACE
  fprintf(p_trace,"< DQT marker\n");
  fflush(p_trace);
#endif
  return 0;
}

 parse_DHT函数:得到 Huffman 表的类型( AC、 DC)、序号。添加如下代码:

#if TRACE    
     fprintf(p_trace,"Huffman table %s[%d] length=%d\n", (index&0xf0)?"AC":"DC", index&0xf, count);	 
     fflush(p_trace);	
     fprintf(Huffman_code,"Huffman table %s[%d] length=%d\n", (index&0xf0)?"AC":"DC", index&0xf, count);
     fflush(Huffman_code);
#endif

build_huffman_table函数:重建 Huffman 表 。添加如下代码:

#if TRACE
     fprintf(p_trace, "val=%2.2x \tcode=%8.8x \tcodesize=%2.2d\n", val, code, code_size);
     fflush(p_trace);
     fprintf(Huffman_code, "val=%2.2x \tcode=%8.8x \tcodesize=%2.2d\n", val, code, code_size);
     fflush(Huffman_code);
#endif

实验结果: 

 

 

 

 

 

 

4. 输出DC图像并统计其概率分布。

5. 输出某一个AC值图像并统计其概率分布。

tinyjpeg.h补充

FILE* AC_file, * DC_file;
void outputDCAC(struct jdec_private* priv);

 loadjpeg.c的main中补充

DC_file = fopen("DC_img.yuv", "w");
AC_file = fopen("AC_img.yuv", "w");

 tinyjpeg.c补充

static void outputDCAC(struct jdec_private* priv)
{     
    int dcmax, dcmin;       
    int acmax, acmin;      
    unsigned char* temp;           //记录归一化后的值          
    dcmin = priv->DCimg[0];       
    dcmax = priv->DCimg[0];       
    acmin = priv->ACimg[0];       
    acmax = priv->ACimg[0];        
    temp = (unsigned char*)malloc(sizeof(unsigned char) * (priv->height * priv->width / 64));                               
    for (int i = 0; i < (priv->height*priv->width / 64); i++)   //找出最大最小值    
    {           
        if (priv->DCimg[i] > dcmax)              
            dcmax = priv->DCimg[i];          
        if (priv->DCimg[i] < dcmin)           
            dcmin = priv->DCimg[i];         
        if (priv->ACimg[i] > acmax)               
            acmax = priv->ACimg[i];         
        if (priv->ACimg[i] < acmin)              
            acmin = priv->ACimg[i];       
    }、     
    for (int i = 0; i < (priv->height*priv->width / 64); i++)       
    { 
        temp[i] = (unsigned char)(255 * (priv->DCimg[i] - dcmin) / (dcmax - dcmin));
    }     
    fwrite(temp, 1, priv->width*priv->height / 64, DC_file);       
    for (int i = 0; i < (priv->height*priv->width / 64); i++)   //DC_file为128*128
    {          
        temp[i] = (unsigned char)(255*(priv->ACimg[i] - acmin)/ (acmax - acmin));      
    }       
    fwrite(temp, 1, priv->width*priv->height / 64, AC_file);   //AC_file为128*128     
}
 
 

int tinyjpeg_decode(struct jdec_private *priv, int pixfmt)
{
    priv->DCimg = (int*)malloc(sizeof(int) * priv->width * priv->height / 64);
    priv->ACimg = (int*)malloc(sizeof(int) * priv->width * priv->height / 64);
    static long i = 0;//i为DCT块的个数        
    if (i < priv->height * priv->width / 64)              //8x8块        
    {
        priv->DCimg[i] = priv->component_infos[cY].DCT[0];//直流DCT[0]
        priv->ACimg[i] = priv->component_infos[cY].DCT[1];//选取DCT[64]的第二个ac系数(取值范围(1,63))  
    }
    i++;
    outputDCAC(priv);

    ......

}

概率分布图调用DPCM编码博文中出现的Count函数处理输出的AC_img.yuv和DC_img.yuv

​void Count(unsigned char* Buff, double* freq, FILE* outfile)
{
    int num[256] = { 0 };
    for (int i = 0; i < 256; i++)
    {
        for (int j = 0; j < width * height; j++)
        {
            if (i == Buff[j])
            {
                num[i]++;
            }
        }
    }
    fprintf(outfile, "symbol\tfreq\n");
    for (int i = 0; i < 256; i++)
    {
        freq[i] = double(num[i]) / (width * height);
        fprintf(outfile, "%d\t%f\n", i, freq[i]);
    }
}​

得到AC_freq.txt和DC_freq.txt 

 

实验结果:

yuv打开方式:128*128   400格式

 yuv图像概率分布图
DC
AC

 

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值