数据压缩原理与应用 JPEG解码

来源:互联网 发布:seo一专员 编辑:程序博客网 时间:2024/05/22 07:51

一、实验原理

这里写图片描述

1. JPEG编码原理

  • DCT变换:能量守恒,能量集中,去相关

从框图可以看出先进行电平偏移再进行8x8块的DCT变换。level offset的原因在于Y的范围为(0,255),而U、V的范围为(-128,128),所以Y的电平应该减去128,再分为8x8的块进行DCT变换。
DCT变换的基本思路是将图像分解为8×8的子块或16×16的子块,并对每一个子块进行单独的DCT变换,然后对变换结果进行量化、编码。随着子块尺寸的增加,算法的复杂度急剧上升,因此,实用中通常采用8×8的子块进行变换。
这里写图片描述
对每个单独的彩色图像分量,把整个分量图像分成8×8的图像块,如图所示,并作为两维离散余弦变换。
这里写图片描述

  • 基于人眼特性量化DCT系数

选择的是中平型均匀量化器,因为人眼对亮度信号比对色差信号更敏感,因此使用了两种量化表:亮度量化值和色差量化值。
根据人眼的视觉特性(对低频敏感,对高频不太敏感)对低频分量采取较细的量化,对高频分量采取较粗的量化。如果原始图象中细节丰富,则去掉的数据较多,量化后的系数与量化前差别较大。
这里写图片描述

  • 对量化后的DCT系数F(u,v)数据进行熵编码

DC系数编码

由于直流系数 F(0,0)反映了该子图像中包含的直流成分,通常较大,又由于两个相邻的子图像的直流系数通常具有较大的相关性,所以对 DC 系数采用差值脉冲编码(DPCM),即对本像素块直流系数与前一像素块直流系数的差值进行无损编码。
这里写图片描述

AC系数编码

AC编码进行了之字形扫描:由于经DCT变换后,系数大多数集中在左上角,即低频分量区,因此采用Z字形按频率的高低顺序读出,可以出现很多连零的机会。可以使用游程编码。尤其在最后,如果都是零,给出EOB (End of Block)即可。
这里写图片描述
游程编码:系数序列分组,将非零系数和它前面的相邻的全部零系数分在一组内;每组用两个符号表示[(Run,Size),(Amplitude)],Amplitude:表示非零系数的幅度值;Run:表示零的游程即零的个数;Size:表示非零系数的幅度值的编码位数;

2. JPEG解码

与编码相反

过程 解码Huffman数据 解码DC差值 重构量化后的系数 DCT逆变换 丢弃填充的行/列 反0偏置 对丢失的CbCr分量差值(下采样的逆过程) YCbCr→RGB

3. JPEG文件格式

格式 全称 说明 标记代码 固定值 SOI Start of Image 图像开始 2字节 0xFFD8 APP0 Application 应用程序保留标记0 2字节 0xFFE0 DQT Define Quantization Table 定义量化表 2字节 0xFFDB SOF0 Start of Frame 帧图像开始 2字节 0xFFC0 DHT Define Huffman Table 定义哈夫曼表 2字节 0xFFC4 SOS Start of Scan 扫描开始 12字节 2字节 0xFFDA EOI End of Image 图像结束 2字节 0xFFD9

这里写图片描述

DQT 定义量化表

具体字段 字节数 说明 数据长度 2字节 字段①和多个字段②的总长度 量化表 数据长度-2字节 具体内容包括以下两项 精度及量化表ID 1字节 高4位:精度,只有两个可选值(0:8位;1:16位) 低4位:量化表ID,取值范围为0~3 表项 (64×(精度+1))字节 例如8位精度的量化表,其表项长度为64×(0+1)=64字节

DHT,定义哈夫曼表

具体字段 字节数 说明 数据长度 2字节 huffman表 数据长度-2字节 具体内容包括以下内容 表ID和表类型 1字节 高4位:类型,只有两个值可选(0:DC直流;1:AC交流) 低4位:哈夫曼表ID,注意,DC表和AC表分开编码 不同位数的码字数量 16字节 编码内容 16个不同位数的码字数量之和(字节)

二、实验流程

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

三、关键代码分析

  • 将输出文件保存为可供YUVViewer观看的YUV文件

tinyjpeg.h

    enum tinyjpeg_fmt {       TINYJPEG_FMT_GREY = 1,       TINYJPEG_FMT_BGR24,       TINYJPEG_FMT_RGB24,       TINYJPEG_FMT_YUV420P,    TINYJPEG_FMT_YUV420ALL,//////////////////////////////////edit by lee 2017 5 16    };

loadjpeg.c

int main(int argc, char *argv[])    {      int output_format = TINYJPEG_FMT_YUV420ALL;//将输出的格式换为yuv文件格式      ...      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 if (strcmp(argv[current_argument + 1], "yuv420all") == 0)//////add yuv file 2017 5 16 bu lee          output_format = TINYJPEG_FMT_YUV420ALL;      else        exitmessage("Bad format: need to be one of yuv420p, rgb24, bgr24, grey\n");      ...    }
    int load_multiple_times(const char *filename, 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);        case TINYJPEG_FMT_YUV420ALL://            write_pgm(outfilename, width, height, components);//add yuv file 2017 5 16 by lee          break;       }    ......    }
    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:            /////////////////////////////////////////you can create yourself/////////////////////////////////          write_yuv(outfilename, width, height, components);          break;        case TINYJPEG_FMT_GREY:          write_pgm(outfilename, width, height, components);        case TINYJPEG_FMT_YUV420ALL:            write_yuv_file(outfilename, width, height, components);/////////add by lee           break;       }    }
    int tinyjpeg_decode(struct jdec_private *priv, int pixfmt)    {     ......      switch (pixfmt) {         case TINYJPEG_FMT_YUV420ALL://add by lee         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;   ......    }

输出yuv文件

    static void write_yuv_file(const char *filename, int width, int height, unsigned char **components)//output yuv file 2017 5 16 by lee    {        FILE *F;        char temp[1024];        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);    }
  • 以txt文件输出所有的量化矩阵和所有的HUFFMAN码表

tinyjpeg.h

    #define QTXT 1//add by lee 2017 5 16    #define QTXTFILE "quan_jpeg.txt"//add by lee 2017 5 16

tinyjpeg.c

输出huffman码表

    static void build_huffman_table(const unsigned char *bits, const unsigned char *vals, struct huffman_table *table)    {      ......        #if TRACE         fprintf(p_trace,"val=%2.2x code=%8.8x codesize=%2.2d\n", val, code, code_size);         fflush(p_trace);        #endif        #if QTXT         fprintf(quan, "val=%2.2x code=%8.8x codesize=%2.2d\n", val, code, code_size);         fflush(quan);        #endif       ......    }

输出量化码表

    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 QTXT////////////////////edit by lee 2017 5 16             fprintf(quan,"%d ",ref_table[*zz]);    //ref_table[*zz]存的即为量化码表,直接输出              fflush(quan);            #endif           *qtable++ = ref_table[*zz++] * aanscalefactor[i] * aanscalefactor[j];         }        #if QTXT/////////////////////////////////edit by lee 2017 5 16         fprintf(quan,"\n");         fflush(quan);        #endif       }       ......    }

输出txt文件中DQT码表的头尾

    static int parse_DQT(struct jdec_private *priv, const unsigned char *stream)    {    ......    #if TRACE      fprintf(p_trace,"> DQT marker\n");      fflush(p_trace);    #endif    #if QTXT////////////////////edit by lee 2017 5 16      fprintf(quan, "> DQT marker\n");      fflush(quan);    #endif    #if TRACE      fprintf(p_trace,"< DQT marker\n");      fflush(p_trace);    #endif    #if QTXT//edit by lee 2017 5 16      fprintf(quan, "< DQT marker\n");      fflush(quan);    #endif    ......    }
  • 输出DC图像和某一个AC值图像

tinyjpeg.h

    void DCiamge(struct jdec_private *priv);//存储DC系数    void ACiamge(struct jdec_private *priv);//存储AC系数    void outputDCAC(struct jdec_private *priv);//输出图像

tinyjpeg.c

开空间以及调用函数

    int tinyjpeg_decode(struct jdec_private *priv, int pixfmt)    {    ..................................................      priv->DCImage = (int *)malloc(sizeof(int)*priv->width * priv->height/64);//edit by lee 2017 5 18      priv->ACImage = (int *)malloc(sizeof(int)*priv->width * priv->height/64);//edit by lee 2017 5 18      outputDCAC(priv);//edit by lee 2017 5 20      .......................................    }
    /*     * output DC     * edit by lee 2017 5 20     */    static void DCiamge(struct jdec_private *priv)    {        static long i = 0;//i表示图像中DCT块的个数        if (i<priv->height*priv->width / 64)//8x8的块        {            priv->DCImage[i] = priv->component_infos[cY].DCT[0];//cY=0        }        i++;    }
    /*    * output AC    * edit by lee 2017 5 20    */    static void ACiamge(struct jdec_private *priv)    {        static long int i = 0;        if (i<priv->height*priv->width / 64)            priv->ACImage[i] = priv->component_infos[cY].DCT[2];//此处选取的是DCT[64]的第二个ac系数,而其实取值为(1,63)皆可        i++;    }
    /*    * output DCACimage    * edit by lee 2017 5 20    */    static void outputDCAC(struct jdec_private *priv)    {        int i = 0;        int dcmax, dcmin;//记录下DC值中最大最小值方便归一化        int acmax, acmin;//记录下AC值中最大最小值方便归一化        //此处归一化有两种方法,一种全部除以8,一种用最大最小值归一化,此处选择后者,保证比例准确        unsigned char *temp;//记录归一化后的值方便输出        /*设置初值*/        dcmin = priv->DCImage[0];        dcmax = priv->DCImage[0];        acmin = priv->ACImage[0];        acmax = priv->ACImage[0];        temp = (unsigned char*)malloc(priv->height*priv->width / 64);//找出最大最小值并临时存放值        for (i = 0; i < (priv->height*priv->width / 64); i++)        {            if (priv->DCImage[i] > dcmax)                dcmax = priv->DCImage[i];            if (priv->DCImage[i] < dcmin)                dcmin = priv->DCImage[i];            if (priv->ACImage[i] > acmax)                acmax = priv->ACImage[i];            if (priv->ACImage[i] < acmin)                acmin = priv->ACImage[i];        }//循环找出最大最小值        for (i = 0; i < (priv->height*priv->width / 64); i++)        {            temp[i] = (unsigned char) 255*(priv->DCImage[i] - dcmin) / (dcmax - dcmin);        }        fwrite(temp, 1, priv->width*priv->height / 64, DFILE);//DC系数以及写出dc系数进DFILE        for (i = 0; i < (priv->height*priv->width / 64); i++)        {            temp[i] = (unsigned char)255*(priv->ACImage[i] - acmin)/ (acmax - acmin);        }        fwrite(temp, 1, priv->width*priv->height / 64, AFILE);//DC系数以及写出dc系数进DFILE        if (temp) free(temp);    }

类似的函数都需要在Y分量中加入DCiamge和ACiamge函数

    static void decode_MCU_1x1_1plane(struct jdec_private *priv)    {      // Y      process_Huffman_data_unit(priv, cY);      DCiamge(priv);//edit by lee 2017 5 20      ACiamge(priv);//edit by lee 2017 5 20      IDCT(&priv->component_infos[cY], priv->Y, 8);      ........     }

下省略


四、实验结果

  • DQT表和huffman编码码表
    这里写图片描述
    这里写图片描述
    这里写图片描述
    这里写图片描述

  • DC系数的YUV图像和AC系数之一的图像

这里写图片描述
这里写图片描述
这里写图片描述

  • 蚊子噪声

高频锐利截止,因为变换后的高频系数很小,量化后值为0,高频截止,导致蚊子噪声。
原始图片:
这里写图片描述
压缩后的图像
这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述
可以看出压缩比越大,蚊子噪声越明显。

原创粉丝点击