目录

  • JEPG原理
    • 简述
      • 优点
      • 缺点
    • JPEG文件格式
      • 常用标记码
    • 编解码原理
      • 编码原理
        • Level offset-零偏置
        • DCT变换
        • 量化
        • DC系数差分编码
        • AC系数的之字形扫描+游程编码
      • 解码原理
  • JPEG解码器调试
    • JPEG parser分析解码后的jpeg文件
    • jpeg_mindec解码器
      • tinyjpeg-internal.h头文件
        • ①struct huffman_table
        • ②struct component
        • ③struct jdec_private
      • 主要解码函数convert_one_image
        • ①初始化结构体tinyjpeg_init()
        • ②tinyjpeg_parse_header函数
        • ③parse_JFIF( )
        • ④tinyjpeg_decode函数
        • ⑤解码MCU
      • main函数
      • TRACE
  • 实验过程
    • 修改write_yuv并用YUVViewer查看输出图像
    • 输出量化矩阵和huffman码表
      • 代码修改
      • 输出结果
    • 输出DC系数和AC系数图像并进行概率统计
      • 代码修改
      • 输出结果
  • 实验总结

JEPG原理

简述

JPEG( Joint Photographic Experts Group)即联合图像专家组,是用于连续色调静态图像压缩的一种标准,文件后缀名为.jpg或.jpeg,是最常用的图像文件格式。
其主要是采用预测编码(DPCM)、离散余弦变换(DCT)以及熵编码的联合编码方式,以去除冗余的图像和彩色数据,属于有损压缩格式,它能够将图像压缩在很小的储存空间,一定程度上会造成图像数据的损伤。尤其是使用过高的压缩比例,将使最终解压缩后恢复的图像质量降低,如果追求高品质图像,则不宜采用过高的压缩比例。

JPEG的性能,用质量与比特率之比来衡量,是相当优越的。

优点

  • 支持极高的压缩率,因此JPEG图像的下载速度大大加快。
  • 能够轻松地处理16.8M颜色,可以很好地再现全彩色的图像。
  • 在对图像的压缩处理过程中,该图像格式可以允许自由地在最小文件尺寸(最低图像质量)和最大文件尺寸(最高图像质量)之间选择。
  • 该格式的文件尺寸相对较小,下载速度快,有利于在带宽并不“富裕”的情况下传输。

缺点

  • 并非所有的浏览器都支持将各种JPEG图像插入网页。
  • 压缩时,可能使图像的质量受到损失,因此不适宜用该格式来显示高清晰度的图像。

JPEG文件格式

JPEG文件使用的数据存储方式有多种,最常用的是JPEG文件交换格式(JPEG File Interchange Format, JFIF),JPEG文件大体上分为两部分:标记码(Tag)和压缩数据,遵循JPEG Marker + Compressed Data格式,具有以下特点:

  • 均以 0xFF 开始,后跟 1 字节的标记标识符(Marker)和 2 字节的标记长度(即Segment length,包含表示Length本身所占用的 2 字节,不含“0xFF” + “Marker” 所占用的2字节)
  • 保存时高位在前,低位在后
  • Data部分中,0xFF后若为0x00,则跳过此字节不予处理

常用标记码

名称 标记码固定值 负载 含义
SOI
(start of image)
0xFFD8 表示图像的开始
APP0
(application 0)
0xFFE0 9个具体字段 ①2字节数据长度
②5字节标识符,固定值0x4A46494600,即字符串“JFIF0”
③2字节版本号
④1字节表示X和Y的密度单位
⑤2字节X方向像素密度
⑥2字节Y方向像素密度
⑦1字节缩略图水平像素数据
⑧1字节缩略图垂直像素数据
⑨缩略图RGB位图
APPn
(application n)
0XFFE1–0XFFFF 2个字段 2字节数据长度和详细信息(内容不定)
DQT 0XFFDB 9个具体字段 定义量化表,其包含9个具体字段,其中2字节为数据长度,数据长度-2字节为量化表,量化表包含精度及量化表ID和表项,一般为两个量化表,即亮度和色度各一张
SOF0 0XFFC0 9个具体字段 表示帧图像开始,包括:
①2字节数据长度
②1字节精度
③2字节图像高度
④2字节图像宽度
⑤1字节颜色分量数
⑥颜色分量数×3字节颜色分量信息,依次表示1字节颜色分量ID,1字节水平/垂直采样因子(高4位代表水平采样因子,低4位代表垂直采样因子),1字节量化表
DHT 0XFFC4 2个字段 定义Huffman表,其包含的2字段分别为2字节数据长度,数据长度-2字节的Huffman表,一般有一个或多个Huffman表
DRI 0xFFDD 2个字段 定义差分编码累计复位的间隔,其包含的2字段分别为2字节数据长度和2字节MCU块的单元中重新开始间隔
SOS 0xFFDA 2个字段 扫描开始,其包含字段的具体内容分别为数据长度、颜色分量数和颜色分量信息
EOI(end of image) 0xFFD9 表示图像的结束

编解码原理

编码原理

Level offset-零偏置

JPEG编码将图像分为8×8的块作为数据处理的最小单位,对于灰度级为2n的像素,通过减去 2n-1,将无符号数变成有符号数。以灰度级n=8为例,原来图像的灰度范围是[0,255],减去128之后变为了[-128,127]。经过零偏置后,图像平均亮度降低,像素灰度的绝对值被控制在较小的范围内,有利于后续编码。

DCT变换

对零偏置后的图像进行DCT变换,以进行能量集中和去相关,同时去除图像的空间冗余,变换后图像的能量都集中在右上角,同时DCT变换是一种无损变换,在变换过程中没有精度损失。如果有些图片并非8×8的整数倍,那么就需要在边缘进行像素填充。

量化

因为人眼对亮度信号比对色差信号更敏感,因此使用了两种量化表,分别是亮度量化值和色差量化值。同时因为人眼对低频敏感高频不太敏感,因此对低频分量采取较细的量化,对高频分量采取较粗的量化,因此细节少的原始图像在压缩时去掉的数据要少

DC系数差分编码

8×8的图像块经过DCT变换之后得到的DC直流系数有两个特点:系数的数值比较大;相邻8×8图像块的DC系数值变化不大(存在冗余),因此可以采用DPCM方法,对相邻图像块之间量化DC系数的差值进行编码,编码方式采用熵编码(Huffman编码),亮度信号与色度信号的DC系数采用不同的Huffman编码表。

AC系数的之字形扫描+游程编码

由于经过DCT变换后,系数大多集中在左上角,也就是低频分量区,因此采用Z字形扫描,按频率的高低顺序读出,这样会出现很多的连零,可以使用RLE游程编码,尤其是在最后,如果都是零,直接给出EOB(End of Block)即可,亮度信号和色度信号的AC系数也有不同的Huffman码表。

在JPEG编码中,游程编码的形式为:(run,level),表示连续run个0后有值为level的系数
注:①run最多15个,用4位表示RRRR
②level,类似DC,分成16个类别,用4位SSSS表示类别号,类内索引
③对(RRRR,SSSS)采用Huffman编码,对类内索引采用定长编码

综上可以知道,在JPEG编码的过程中用到了两张量化表(亮度和色度)以及四张Huffman码表(亮度DC、亮度AC、色度DC和色度AC)

解码原理

解码就是编码的逆过程,其大致流程如下:

  • 1.读取文件
  • 2.解析文件segment
    解析SOI,判断是否是JPEG文件
    解析SOF
    解析DQT,获取量化表相关信息
    解析SOS
    解析DHT,获取Huffman码表相关信息
    解析DRI
  • 3.以MCU为单位进行解码
    解码Huffman数据
    解码DC差值
    重构量化后的系数
    DCT逆变换
  • 4.丢弃填充的行/列
  • 5.反0偏置
  • 6.对丢失的CbCr分量差值(下采样的逆过程)
  • 7.YCbCr->RGB

JPEG解码器调试

本次实验共两个部分的代码,Jpeg parser是jpeg码流分析软件,主要帮助我们理解jpeg码流结构;Jpeg_mindec是本次实验调试的程序,包含基本的jpeg解码过程。

JPEG parser分析解码后的jpeg文件

打开JPEGParser项目,设置命令行参数如下:

其中1.jpg为本次实验分析的jpeg文件,如下:

Parse-Report-1.txt是分析图片得到的分析报告,如下:

其中红色部分表示图像中有3个分量YCbCr,黄色部分表示Y对应的量化表索引值为00,蓝色部分表示UV对应的量化表索引值为01。
其他具体的分析可见 《JPEG解码》调试报告

jpeg_mindec解码器

本实验利用该部分代码进行JPEG文件的解码,并将输出的文件保存为可供YUVViewer观看的YUV文件,同时输出DC图像、某一个AC值图像以及以txt文件输出所有的量化矩阵和Huffman码表。

tinyjpeg-internal.h头文件

该文件中包含huffman_table、component、jdec_private三个重要结构体

①struct huffman_table

创建一个快速查找表用于快速解码,如果查找失败则用慢速查找表,目的是加快解码过程。

struct huffman_table
{/* 快速查表,用HUFFMAN_HASH_TABLE个比特可以快速找到符号,如果符号<0,那么需要查看慢速查找表*/short int lookup[HUFFMAN_HASH_SIZE];/* code size: 给出码字长度,输出权值对应的码长 */unsigned char code_size[HUFFMAN_HASH_SIZE];/* 给出在慢速查找表中没有出现的码字,计算256值是否足以存储所有的值*/uint16_t slowtable[16-HUFFMAN_HASH_NBITS][256];
};

②struct component

用于存储一个MCU块的信息,每处理一个新的MCU块后,信息都会更新。用它来保存一些解码过程中需要用到的信息,如量化表、前一个直流的值和当前MCU的DCT块从而使解码更加简单。

struct component
{unsigned int Hfactor; /* 水平采样情况 */unsigned int Vfactor; /* 垂直采样情况 */float *Q_table;        /* 指向量化表 */struct huffman_table *AC_table; /* 指向直流HUFFMAN表 */struct huffman_table *DC_table; /* 指向交流HUFFMAN表 */short int previous_DC;   /* 前一个直流系数 */short int DCT[64];     /* DCT系数 */
#if SANITY_CHECKunsigned 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]; /* 各通道信息 */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每次递增 *//* IDCT之后用于存储每个component的临时空间 */uint8_t Y[64*4], Cr[64], Cb[64];jmp_buf jump_state;/* 内部指针用于颜色空间的转换,请勿修改 !!! */uint8_t *plane[COMPONENTS];
};

主要解码函数convert_one_image

这一部分是解码用到的核心函数,这一部分的主要操作是将输入的jpeg文件内容进行读取并存入buf,之后利用tinyjpeg_parse_header函数解码JPEG文件的头信息,调用tinyjpeg_get_size函数获取文件大小,最后再调用tinyjpeg_decode函数进行解码,最后根据需要保存为不同形式的文件并释放空间。

/*** Load one jpeg image, and decompress it, and save the result.*/
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);  //将文件内容读入buffclose(fp);/* Decompress it */jdec = tinyjpeg_init();  //初始化if (jdec == NULL)exitmessage("Not enough memory to alloc the structure need for decompressing\n");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_init()

存在于tinyjpeg.c中,主要用于初始化一个jdec_private的结构体,并赋给jdec,之后围绕该结构体展开后续的解码操作。具体结构体如下:

struct jdec_private *tinyjpeg_init(void)
{struct jdec_private *priv;priv = (struct jdec_private *)calloc(1, sizeof(struct jdec_private));if (priv == NULL)return NULL;return priv;
}

②tinyjpeg_parse_header函数

主要用于判断是否是jpeg文件,解析文件信息,通过指针移动进入parse_JFIF函数,parse_JFIF函数遍历整个文件,找到不同的标识码,并解析相应标识码对应的信息

/*** Initialize the tinyjpeg object and prepare the decoding of the stream.** Check if the jpeg can be decoded with this jpeg decoder.* Fill some table used for preprocessing.*/
int tinyjpeg_parse_header(struct jdec_private *priv, const unsigned char *buf, unsigned int size)
{int ret;/* Identify the file */if ((buf[0] != 0xFF) || (buf[1] != SOI))  //判断是否是jpeg文件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);  //解析JFIF(解析各种不同的标签)return ret;
}

③parse_JFIF( )

在到达SOS之前,循环调用parse_SOF()、parse_DQT()、parse_SOS()、parse_DHT()、parse_DRI()。

static int parse_JFIF(struct jdec_private *priv, const unsigned char *stream)
{int chuck_len;int marker;int sos_marker_found = 0;int dht_marker_found = 0;const unsigned char *next_chunck;/* Parse marker */while (!sos_marker_found){if (*stream++ != 0xff)  //获取0xff之后的字符,确定标识符类型goto bogus_jpeg_format;/* Skip any padding ff byte (this is normal) */while (*stream == 0xff)stream++;marker = *stream++;chuck_len = be16_to_cpu(stream);next_chunck = stream + chuck_len;switch (marker){case SOF:if (parse_SOF(priv, stream) < 0)return -1;break;case DQT:if (parse_DQT(priv, stream) < 0)return -1;break;case SOS:if (parse_SOS(priv, stream) < 0)return -1;sos_marker_found = 1;break;case DHT:if (parse_DHT(priv, stream) < 0)return -1;dht_marker_found = 1;break;case DRI:if (parse_DRI(priv, stream) < 0)return -1;break;default:
#if TRACEfprintf(p_trace,"> Unknown marker %2.2x\n", marker);fflush(p_trace);
#endifbreak;}stream = next_chunck;}if (!dht_marker_found) {#if TRACEfprintf(p_trace,"No Huffman table loaded, using the default one\n");fflush(p_trace);
#endifbuild_default_huffman_tables(priv);}#ifdef SANITY_CHECKif (   (priv->component_infos[cY].Hfactor < priv->component_infos[cCb].Hfactor)|| (priv->component_infos[cY].Hfactor < priv->component_infos[cCr].Hfactor))snprintf(error_string, sizeof(error_string),"Horizontal sampling factor for Y should be greater than horitontal sampling factor for Cb or Cr\n");if (   (priv->component_infos[cY].Vfactor < priv->component_infos[cCb].Vfactor)|| (priv->component_infos[cY].Vfactor < priv->component_infos[cCr].Vfactor))snprintf(error_string, sizeof(error_string),"Vertical sampling factor for Y should be greater than vertical sampling factor for Cb or Cr\n");if (   (priv->component_infos[cCb].Hfactor!=1) || (priv->component_infos[cCr].Hfactor!=1)|| (priv->component_infos[cCb].Vfactor!=1)|| (priv->component_infos[cCr].Vfactor!=1))snprintf(error_string, sizeof(error_string),"Sampling other than 1x1 for Cr and Cb is not supported");
#endifreturn 0;
bogus_jpeg_format:
#if TRACEfprintf(p_trace,"Bogus jpeg format\n");fflush(p_trace);
#endifreturn -1;
}

其中parse_*( ),即parse_SOF()、parse_DQT()、parse_SOS()、parse_DHT()、parse_DRI()分别用于解析各字段,具体内部结构略。

④tinyjpeg_decode函数

在此函数中,对每个块的水平和垂直采样情况进行解析,计算MCU,同时对不同MCU进行处理。

/*** Decode and convert the jpeg image into @pixfmt@ image** Note: components will be automaticaly allocated if no memory is attached.*/
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) {  //根据不同的输出格式确定MCUcase 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;case TINYJPEG_FMT_RGB24:colorspace_array_conv = convert_colorspace_rgb24;if (priv->components[0] == NULL)priv->components[0] = (uint8_t *)malloc(priv->width * priv->height * 3);bytes_per_blocklines[0] = priv->width * 3;bytes_per_mcu[0] = 3*8;break;case TINYJPEG_FMT_BGR24:colorspace_array_conv = convert_colorspace_bgr24;if (priv->components[0] == NULL)priv->components[0] = (uint8_t *)malloc(priv->width * priv->height * 3);bytes_per_blocklines[0] = priv->width * 3;bytes_per_mcu[0] = 3*8;break;case TINYJPEG_FMT_GREY:decode_mcu_table = decode_mcu_1comp_table;colorspace_array_conv = convert_colorspace_grey;if (priv->components[0] == NULL)priv->components[0] = (uint8_t *)malloc(priv->width * priv->height);bytes_per_blocklines[0] = priv->width;bytes_per_mcu[0] = 8;break;default:
#if TRACEfprintf(p_trace,"Bad pixel format\n");fflush(p_trace);
#endifreturn -1;}xstride_by_mcu = ystride_by_mcu = 8;   //初始化MCU宽高8px(4:4:4)if ((priv->component_infos[cY].Hfactor | priv->component_infos[cY].Vfactor) == 1) {//若水平垂直采样因子均为1decode_MCU = decode_mcu_table[0];  //MCU包含1个Yconvert_to_pixfmt = colorspace_array_conv[0];
#if TRACEfprintf(p_trace,"Use decode 1x1 sampling\n");fflush(p_trace);
#endif} else if (priv->component_infos[cY].Hfactor == 1) {//若水平采样因子均为1,垂直采样因子均为2decode_MCU = decode_mcu_table[1]; //MCU包含2个Yconvert_to_pixfmt = colorspace_array_conv[1];ystride_by_mcu = 16;  //MCU高16px,宽8px
#if TRACEfprintf(p_trace,"Use decode 1x2 sampling (not supported)\n");fflush(p_trace);
#endif} else if (priv->component_infos[cY].Vfactor == 2) {//若水平垂直采样因子均为2decode_MCU = decode_mcu_table[3]; //MCU包含4个Yconvert_to_pixfmt = colorspace_array_conv[3];xstride_by_mcu = 16;ystride_by_mcu = 16;//MCU高宽均为16px
#if TRACE fprintf(p_trace,"Use decode 2x2 sampling\n");fflush(p_trace);
#endif} else {  //若水平采样因子均为2,垂直采样因子均为1decode_MCU = decode_mcu_table[2]; //MCU包含2个Yconvert_to_pixfmt = colorspace_array_conv[2];xstride_by_mcu = 16; //MCU宽16px,高8px
#if TRACEfprintf(p_trace,"Use decode 2x1 sampling\n");fflush(p_trace);
#endif}resync(priv);/* Don't forget to that block can be either 8 or 16 lines */bytes_per_blocklines[0] *= ystride_by_mcu;bytes_per_blocklines[1] *= ystride_by_mcu;bytes_per_blocklines[2] *= ystride_by_mcu;bytes_per_mcu[0] *= xstride_by_mcu/8;bytes_per_mcu[1] *= xstride_by_mcu/8;bytes_per_mcu[2] *= xstride_by_mcu/8;/* 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;}}}}
#if TRACEfprintf(p_trace,"Input file size: %d\n", priv->stream_length+2);fprintf(p_trace,"Input bytes actually read: %d\n", priv->stream - priv->stream_begin + 2);fflush(p_trace);
#endifreturn 0;
}

⑤解码MCU

对每个MCU进行解码(依照各分量水平、垂直采样因子对 MCU中每个分量宏块解码)
①对每个宏块进行 Huffman解码,得到DCT系数
②对每个宏块的 DCT系数进行 IDCT,得到 Y、Cb 、Cr
③遇到 Segment Marker RST时,清空之前的DC 、DCT系数
以decode_MCU_2x3_3planes( )为例。

/** Decode a 2x2*  .-------.*  | 1 | 2 |*  |---+---|*  | 3 | 4 |*  `-------'*/
static void decode_MCU_2x2_3planes(struct jdec_private *priv)
{// Yprocess_Huffman_data_unit(priv, cY);IDCT(&priv->component_infos[cY], priv->Y, 16);process_Huffman_data_unit(priv, cY);IDCT(&priv->component_infos[cY], priv->Y+8, 16);process_Huffman_data_unit(priv, cY);IDCT(&priv->component_infos[cY], priv->Y+64*2, 16);process_Huffman_data_unit(priv, cY);IDCT(&priv->component_infos[cY], priv->Y+64*2+8, 16);// Cbprocess_Huffman_data_unit(priv, cCb);IDCT(&priv->component_infos[cCb], priv->Cb, 8);// Crprocess_Huffman_data_unit(priv, cCr);IDCT(&priv->component_infos[cCr], priv->Cr, 8);
}

其中process_Huffman_data_unit(priv, cCr)主要用于对包含DCT系数的单个块进行Huffman解码:

/**** Decode a single block that contains the DCT coefficients.* The table coefficients is already dezigzaged at the end of the operation.**/
static void process_Huffman_data_unit(struct jdec_private *priv, int component)
{unsigned char j;unsigned int huff_code;unsigned char size_val, count_0;struct component *c = &priv->component_infos[component];short int DCT[64];/* Initialize the DCT coef table */memset(DCT, 0, sizeof(DCT));/* DC coefficient decoding */huff_code = get_next_huffman_code(priv, c->DC_table);//trace("+ %x\n", huff_code);if (huff_code) {//解码得到当前块与前一块DC系数的差值get_nbits(priv->reservoir, priv->nbits_in_reservoir, priv->stream, huff_code, DCT[0]);DCT[0] += c->previous_DC;//差值与前一DC系数相加,得到当前块DC系数c->previous_DC = DCT[0];//更新前一DC系数值} else {DCT[0] = c->previous_DC;}/* AC coefficient decoding */j = 1;while (j<64){huff_code = get_next_huffman_code(priv, c->AC_table);//trace("- %x\n", huff_code);size_val = huff_code & 0xF;count_0 = huff_code >> 4;if (size_val == 0){ /* RLE */if (count_0 == 0)break;  /* EOB found, go out */else if (count_0 == 0xF)j += 16; /* skip 16 zeros */}else{j += count_0;    /* skip count_0 zeroes */if (__unlikely(j >= 64)){snprintf(error_string, sizeof(error_string), "Bad huffman data (buffer overflow)");break;}get_nbits(priv->reservoir, priv->nbits_in_reservoir, priv->stream, size_val, DCT[j]);j++;}}for (j = 0; j < 64; j++)c->DCT[j] = DCT[zigzag[j]];
}

main函数

对于main函数来说,主要是进行了输入输出文件的获取、输出格式和解码方式的选择等

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;
#if TRACE    //设定trace,边解码边写入文件p_trace=fopen(TRACEFILE,"w");if (p_trace==NULL){printf("trace file open error!");}
#endifif (argc < 3)usage();current_argument = 1;while (1){if (strcmp(argv[current_argument], "--benchmark")==0)benchmark_mode = 1;  //如果argv[current_argument]=--benchmark,则说明设置了benchmark,因此benchmark_mode设为1elsebreak;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;elseexitmessage("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);elseconvert_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 TRACEfclose(p_trace);
#endifreturn 0;
}

TRACE

trace定义在tinyjpeg.h末尾处,目的主要是输出中间变量或者错误信息,有利于方便高效地理解程序运行的过程及排查错误

#define TRACE 1//0为关闭,1为开启,开启trace表示边解码边将信息写入文件,
#define  TRACEFILE "trace_jpeg.txt"//输出文件

利用以下结构修改TRACE的内容,可用于输出中间变量和调试程序

#if TRACE    //设定TRACE,边解码边写入文件
......
#endif

实验过程

修改write_yuv并用YUVViewer查看输出图像

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);printf("write yuvfile begin...\n");snprintf(temp, 1024, "%s.yuv", filename);F = fopen(temp, "wb");fwrite(components[0], width, height, F);//写入Yfwrite(components[1], width * height / 4, 1, F);//写入Ufwrite(components[2], width * height / 4, 1, F);//写入Vfclose(F);printf("write yuvfile done!\n");
}

生成以下图像并用YUVViewer打开:

输出量化矩阵和huffman码表

代码修改

在tinyjpeg.h中添加如下声明:

FILE* q_trace;   //存储量化表
FILE* h_trace;   //存储Huffman码表#define Q_FILE "DQT.txt"   //输出量化表
#define H_FILE "Huffman.txt"  //输出Huffman码表

在主程序中添加如下打开文件的部分:

  //打开存储量化表文件q_trace = fopen(Q_FILE, "w");if (q_trace == NULL){printf("Q_trace file open error!");}//打开存储Huffman码表文件h_trace = fopen(H_FILE, "w");if (q_trace == NULL){printf("H_trace file open error!");}......fclose(q_trace);fclose(h_trace);

在parse_DQT的循环中添加以下用于写入量化表ID的代码:

  fprintf(q_trace, "Quantization_table_index:   %d \n", qi);fflush(q_trace);

在build_quantization_table函数中添加输出量化表的代码:

#if TRACEconst unsigned char* z = zigzag;   //以矩阵形式输出量化表for (i = 0; i < 8; i++) {for (j = 0; j < 8; j++) {if(j==7)fprintf(q_trace, "%-02d\n", ref_table[*z++]);else fprintf(q_trace, "%-02d\t", ref_table[*z++]);}}fflush(q_trace);
#endif

在parse_DHT函数的trace中添加写入Huffman码表的代码:

    fprintf(h_trace, "Huffman table %s  index:%d  length=%d\n", (index & 0xf0) ? "AC" : "DC", index & 0xf, count);fflush(h_trace);

在build_huffman_table函数的trace中添加输出Huffman码表的代码:

    fprintf(h_trace, "val=%2.2x code=%8.8x codesize=%2.2d\n", val, code, code_size);fflush(h_trace);

输出结果

index=0表示亮度,index=1表示色度
1.量化表DQT.txt


2.Huffman码表Huffman.txt



输出DC系数和AC系数图像并进行概率统计

DC图像就是直流分量对应的图像,AC图像就是交流分量对应的图像。
对于8×8的宏块来说,其左上角的值就是直流分量的值即DC分量,若原图是1024×1024,则输出的DC图像的大小就是128×128。
在实验的过程中设定了存储DCT分量的数组,因此从中取出DCT[0]组成图像即为DC图像,从后面取出交流系数的值就是AC图像,实验中取DCT[2]查看AC分量值组成的图像。
因为DC分量的值范围是[-512,512],为了使得图像可以显示,将其调整到0~255的范围,因为AC值比较小,因此直接+128使其能够显示

代码修改

在tinyjpeg.h中添加如下声明:

FILE* DCfile;
FILE* ACfile;

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

  DCfile = fopen("DC_pic.yuv", "w");ACfile = fopen("AC_pic.yuv", "w");......fclose(DCfile);fclose(ACfile);

在tinyjpeg_decode函数中添加取出系数并写入文件代码:

int tinyjpeg_decode(struct jdec_private *priv, int pixfmt)
{unsigned char *DCbuf, *ACbuf; unsigned char *uvbuf = 128;int count=0;...
//添加在decode_MCU(priv)后DCbuf= (unsigned char)((priv->component_infos->DCT[0] + 512.0) / 4 + 0.5);  ACbuf= (unsigned char)(priv->component_infos->DCT[1] + 128);   fwrite(&DCbuf, 1, 1,DCfile);fwrite(&ACbuf, 1, 1,ACfile);count++;...for (int j = 0; j < count *0.25 * 2; j++){fwrite(&uvbuf, sizeof(unsigned char), 1, DCfile);fwrite(&uvbuf, sizeof(unsigned char), 1, ACfile);}return 0;
}

利用python代码绘制概率分布图:

import numpy as np
import matplotlib.pyplot as plt
import mathf = open(r"G:\2022春季学期-疫情在家-大三下\数据压缩\实验\实验5JPEG编码\code\jpeg_mindec\test_images\AC_pic.yuv", "rb")#DC_pic or AC_pic
data = f.read()
f.close()
data = [int(x) for x in data]
data_Y=[]for i in range(0,128*128):data_Y.append(data[i])count_Y=np.zeros(255)
for i in data_Y:count_Y[i-1]=count_Y[i-1]+1for i in range(0,255):count_Y[i] = count_Y[i] / (128*128)plt.plot(count_Y)
plt.title("AC Frequency")#AC Frequency or DC Frequency
plt.grid()
plt.show()

输出结果

1.DC系数和AC系数图像

2.DC系数和AC系数概率分布图

实验总结

本次实验主要调试了JPEG图像的解码程序,理解了JPEG编解码的基本过程,学习到了在数据压缩实验中TRACE的用途和使用方法。

总体来说,JPEG编码是利用了人眼的一些视觉特性将一些人眼不敏感的数据和一些冗余数据去掉从而达到压缩的目的。通常会将高度相关的RGB分量转换成相关性较小的YUV分量,然后采用零偏置使像素的绝对值减小,之后利用DCT变换进行能量集中,进一步提高数据压缩的空间,由于DCT变换后DC系数和AC系数的不同特点,量化后对DC系数采取差分+Huffman编码的方式,对AC系数采取Z字形扫描+游程编码+Huffman编码的方式,从而进一步提高压缩率。

【数据压缩-实验5】JPEG原理分析及JPEG解码器的调试相关推荐

  1. JPEG原理分析及JPEG解码器的解析

    文章目录 JPEG原理分析及JPEG解码器的调试 原理分析 JPEG编解码流程图 DC系数编码 AC系数编码 JPEG文件格式 Segment组织形式 JPEG 的 Segment Marker no ...

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

    文章目录 数据压缩实验(五) 一.JPEG原理分析 1.概述 优点 缺点 2.JPEG编解码原理 (1)彩色空间 (2)Level offset--零偏置电平下移 (3)8x8 DCT--离散余弦变换 ...

  3. 【数据压缩】实验:JPEG原理分析及JPEG解码器的调试

    一.JPEG编解码原理 1.1 JPEG图像压缩标准基本介绍 JPEG是Joint Photographic Experts Group(联合图像专家小组)的缩写,文件后缀名为.jpg或.jpeg,是 ...

  4. 实验五—JEPG 原理分析及 JPEG 解码器的调试

    文章目录 一.JPEG编码原理 1.Level Offset(水平偏移)能量的重新分配 2.DCT变换 3.Uniform scalar quantization 均匀标量量化 4.DC系数差分编码 ...

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

    JPEG格式简介 JPEG( Joint Photographic Experts Group)即联合图像专家组,是用于连续色调静态图像压缩的一种标准,文件后缀名为.jpg或.jpeg,是最常用的图像 ...

  6. 【数据压缩】实验五——JPEG原理分析及JPEG解码器的调试

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

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

    一.JPEG编码原理 1.将RGB转换为YUV空间.(相关性较小) 2.零偏置.(使最大的绝对值大的无符号数变为绝对值小的有符号数,减小数据平均的位数,例如0~255变为-128~127) 3.做8* ...

  8. 数据压缩实验五 JPEG原理分析JPEG解码器的调试

    一.实验原理 1.JPEG原理及编码流程   JPEG是常见的一种图像格式,由ISO与CCITT建立并开发,是一个国际数字图像压缩标准.JPEG文件的扩展名为.jpg或.jpeg,用有损方式去除冗余的 ...

  9. 数据压缩实验五:JPEG文件解码实验分析

    一:实验原理 1.JPEG编码原理 JPEG 是Joint Photographic Experts Group(联合图像专家小组)的缩写,是第一个国际图像压缩标准. .jpeg/.jpg是最常用的图 ...

最新文章

  1. 脑电分析系列[MNE-Python-12]| 注释连续数据
  2. 【Kali渗透全方位实战】Metasploitable2系统介绍
  3. leetcode 450. 删除二叉搜索树中的节点 c语言实现
  4. 人力资源oracle,Oracle Connections
  5. 2.4亿!知乎到底牛X在哪里!
  6. 一张图,看懂阿里云12年的“飞天日记”
  7. LVS三种模式的区别及负载均衡算法
  8. 《四世同堂》金句摘抄(十四)
  9. Cracer渗透-windows基础(系统目录,服务,端口,注册表)
  10. 【Java中级篇】使用itextpdf生成PDF
  11. c语言随机抽取小程序_C语言整人小程序,慎用,谨记!
  12. Json的反序列化 .net Newtonsoft.Json
  13. tornado 重定向404
  14. Python xlwt 操作 excel 表格基础(一):单元格写入、合并、插入位图等
  15. CSS绝对定位使用left:50%实现水平居中偏左问题
  16. 设计模式之里氏宗青出于蓝而胜于蓝
  17. html如何修改title前的小图标
  18. 《考博英语词汇重难点20天冲刺》
  19. Android LottieAnimation使用---踩坑篇
  20. 企业管理概论试题库【1】

热门文章

  1. 三星R463无线网卡驱动,声卡驱动,FN快捷键驱动,Easy_Display_Manager
  2. qt中glMultiTexCoord2fARB报错
  3. 破坏网络可信身份认证,黑灰产业链正在兴起
  4. 应用程序操作word文件报“RPC服务器不可用,HRESULT:0x800706BA”
  5. Java之多线程Runnable(2)卖烤鸭-yellowcong
  6. 微积分同济大学第三版下
  7. Batch Normalization反方向传播求导
  8. 服务器报错 http error 503.the service is unavailable怎么解决
  9. 记录一个关于oracle数据库us7ascii字符集解决的方法
  10. 为什么包装类型间的相等判断应该用 equals