(一)实验目的

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

JPEG( Joint Photographic Experts Group)是最常用的图像文件格式,其主要是采用预测编码(DPCM)、离散余弦变换(DCT)以及熵编码的联合编码方式,以去除冗余的图像和彩色数据,属于有损压缩格式,它能够将图像压缩在很小的储存空间,一定程度上会造成图像数据的损伤。

(二)JPEG编解码原理

编码器

解码器为编码器的逆过程

2.1 JPEG文件格式介绍

符号 含义
SOI Start of Image,图像开始
EOI End of Image,图像结束 2字节
APP0 Application,应用程序保留标记0
DQT Define Quantization Table,定义量化表
SOF Start of Frame,帧图像开始
DHT Define Huffman Table,定义哈夫曼表
SOS Start of Scan,扫描开始 12字节

2.1.1 SOI & EOI

SOI ,Start of Image, 图像开始
标记代码 2字节 固定值0xFFD8
EOI,End of Image, 图像结束 2字节
标记代码 2字节 固定值0xFFD9

2.1.2 APP0 应用程序保留标记0

标记代码 2字节 固定值0xFFE0
① 数据长度 2字节 ①~⑨9个字段的总长度
② 标识符 5字节 固定值0x4A46494600,即字符串“JFIF0”
③ 版本号 2字节 一般是0x0102,表示JFIF的版本号1.2
④ X和Y的密度单位 1字节 只有三个值可选 0:无单位;1:点数/英寸;2:点数/厘米
⑤ X方向像素密度 2字节 取值范围未知
⑥ Y方向像素密度 2字节 取值范围未知
⑦ 缩略图水平像素数目 1字节 取值范围未知
⑧ 缩略图垂直像素数目 1字节 取值范围未知
⑨ 缩略图RGB位图 长度可能是3的倍数 缩略图RGB位图数据

2.1.3 DQT 定义量化表

标记代码 2字节 固定值0xFFDB
包含9个具体字段: ① 数据长度 2字节 字段①和多个字段②的总长度 ② 量化表 数据长度-2字节

a) 精度及量化表ID   1字节    高4位:精度,只有两个可选值    0:8位;1:16位     低4位:量化表ID,取值范围为0~3
b) 表项           (64×(精度+1))字节例如8位精度的量化表,其表项长度为64×(0+1)=64字节

本标记段中,字段②可以重复出现,表示多个量化表,但最多只能出现4次

2.1.4 SOF0 帧图像开始

标记代码 2字节 固定值0xFFC0
① 数据长度 2字节 ①~⑥六个字段的总长度
② 精度 1字节 每个数据样本的位数 通常是8位,一般软件都不支持 12位和16位
③ 图像高度 2字节 图像高度(单位:像素)
④ 图像宽度 2字节 图像宽度(单位:像素)
⑤ 颜色分量数 1字节 只有3个数值可选 1:灰度图;3:YCrCb或YIQ;4:CMYK 而JFIF中使用YCrCb,故这里颜色分量数恒为3
⑥颜色分量信息 颜色分量数×3字节(通常为9字节) a)颜色分量ID 1字节 b)水平/垂直采样因子 1字节 高4位:水平采样因子 低4位:垂直采样因子 c) 量化表 1字节 当前分量使用的量化表的ID

2.1.5 DHT 定义哈夫曼表

标记代码 2字节 固定值0xFFC4
包含2个具体字段:
① 数据长度 2字节
② huffman表 数据长度-2字节

表ID和表类型 1字节
高4位:类型,只有两个值可选
0:DC直流;1:AC交流 低4位:哈夫曼表ID,
注意,DC表和AC表分开编码
不同位数的码字数量 16字节
编码内容 16个不同位数的码字数量之和(字节)本标记段中,字段②可以重复出现(一般4次),也可以只出现1次

2.1.6 SOS 扫描开始

标记代码 2字节 固定值0xFFDA
包含2个具体字段:
①数据长度 2字节 ①~④两个字段的总长度
②颜色分量数 1字节 应该和SOF中的字段⑤的值相同,即: 1:灰度图是;3: YCrCb或YIQ;4:CMYK。
③ 颜色分量信息
a) 颜色分量ID 1字节
b) 直流/交流系数表号 1字节
高4位:直流分量使用的哈夫曼树编号
低4位:交流分量使用的哈夫曼树编号
④ 压缩图像数据
a)谱选择开始 1字节 固定值0x00
b)谱选择结束 1字节 固定值0x3F
c)谱选择 1字节 在基本JPEG中总为00

2.2 JPEG编码步骤

  1. 零偏置(Level Offset)
    对于灰度级是2n的像素,通过减去2(n-1),将无符号的整数值变成有符号数,使像素的绝对值出现3位10进制的概率大大减少
  2. DCT变换
    对每个单独的彩色图像分量,把整个分量图像分成8×8的图像块,并作为二维离散余弦变换DCT的输入。将原本的像素数值进行DCT变换,后续直接对DCT分量进行处理。
  3. 量化
    对DCT分量进行量化,由于人眼对亮度信号比对色差信号更敏感,因此使用了两种量化表:亮度量化值和色差量化值。
    根据人眼的视觉特性(对低频敏感,对高频不太敏感)对低频分量采取较细的量化,对高频分量采取较粗的量化。
    如果原始图象中细节丰富,则去掉的数据较多,量化后的系数与量化前差别。反之,细节少的原始图象在压缩时去掉的数据少些。
  4. DC系数差分编码
    DC直流系数有两个特点:系数的数值比较大,相邻DC系数值变化不大。
    根据上述两个特点,JPEG算法使用了差分脉冲调制编码(DPCM)技术,对相邻图像块之间量化DC系数的差值DIFF进行编码:
    DIFF_k=DC_k-DC_{k-1}
  5. AC系数之字形扫描
    由于经DCT变换后,系数大多数集中在左上角,即低频分量区,因此采用Z字形按频率的高低顺序读出,可以出现很多连零的机会。可以使用游程编码。尤其在最后,如果都是零,给出 EOB (End of Block)即可。
    6. AC系数的游程编码

在JPEG和MPEG编码中规定为:(run,level)
表示连续run个0,后面跟值为level的系数
如:0,2,0,0,3,0,-4,0,0,0,-6,0,0 ,5,7
表示为(1, 2), (2, 3)

2.3 JPEG文件解码流程

1.读入文件的相关信息
2.初步了解图像数据流的结构
3.颜色分量单元的内部解码
4.直流系数的差分编码
5.反量化 & 反Zig-zag编码
6.反离散余弦变换

3 JPEG文件解析

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

  1. struct huffman_table:存储Huffman码表
/* tinyjpeg-internal.h */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];
};
  1. struct component:储存当前8×8像块中有关解码的信息
/* tinyjpeg-internal.h */struct component
{unsigned int Hfactor; // 水平采样因子unsigned int Vfactor; // 垂直采样因子float* Q_table;   // 指向该8×8块使用的量化表struct huffman_table *AC_table;   // 指向该块使用的AC Huffman表struct huffman_table *DC_table;   // 指向该块使用的DC Huffman表short int previous_DC;    // 前一个块的直流DCT系数short int DCT[64];    // DCT系数数组#if SANITY_CHECKunsigned int cid;
#endif
};
  1. struct jdec_private:JPEG数据流结构体,用于存储JPEG图像宽高、数据流指针、Huffman码表等内容,并包含struct huffman_table和struct component
/* tinyjpeg-internal.h */struct jdec_private
{/* Public variables */uint8_t *components[COMPONENTS];  /* 分别指向YUV三个分量的三个指针 */unsigned int width, height;  /* 图像宽高 */unsigned int flags;/* Private variables */const unsigned char *stream_begin, *stream_end;unsigned int stream_length;const unsigned char *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];jmp_buf jump_state;/* Internal Pointer use for colorspace conversion, do not modify it !!! */uint8_t *plane[COMPONENTS];
};

3.2 理解整体框架

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; //JPEG数据数据流unsigned char *components[3];fp = fopen(infilename, "rb");//读取JPEG图像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");if (tinyjpeg_parse_header(jdec, buf, length_of_file)<0)//读取头文件,检查图像格式exitmessage(tinyjpeg_get_errorstring(jdec));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);//将compoents结构体的数据传入jdecswitch (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() */ctinyjpeg_free(jdec);/* else called just free(jdec); */free(buf);return 0;
}

tinyjpeg_init初始化函数:动态分配存储内存

struct jdec_private *tinyjpeg_init(void)
{struct jdec_private *priv;//声明JPEG数据结构体priv = (struct jdec_private *)calloc(1, sizeof(struct jdec_private));//在内存的动态存储区中分配1个struct jdec_private大小的连续空间if (priv == NULL)return NULL;return priv;
}

tinyjpeg_parse_header函数:解码JPEG头信息(FFD8-SOI)
读取完前两字节后,开始指针后移两位priv->stream_begin=buffer+2,文件未读长度-2,接parse_JFIF函数遍历整个文件,找到不同的标识码,并解析相应标识码对应的信息

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:判断各种标签

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)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:}stream = next_chunck;}
}

DHT量化表解析

static int parse_DQT(struct jdec_private *priv, const unsigned char *stream)
{int qi;float *table;const unsigned char *dqt_block_end;
#if TRACEfprintf(p_trace,"> DQT marker\n");在文件中写出正在解析DQTfflush(p_trace);
#endifdqt_block_end = stream + be16_to_cpu(stream);stream += 2; /* Skip length */while (stream < dqt_block_end){qi = *stream++;table = priv->Q_tables[qi];build_quantization_table(table, stream);///建立量化表stream += 64;}
#if TRACEfprintf(p_trace,"< DQT marker\n");表示DQT解析结束fflush(p_trace);
#endifreturn 0;
}

反之字形建立标准量化表

static void build_quantization_table(float *qtable, const unsigned char *ref_table)
{int i, j;static const double aanscalefactor[8] = {1.0, 1.387039845, 1.306562965, 1.175875602,1.0, 0.785694958, 0.541196100, 0.275899379};const unsigned char *zz = zigzag;for (i=0; i<8; i++) {for (j=0; j<8; j++) {*qtable++ = ref_table[*zz++] * aanscalefactor[i] * aanscalefactor[j];}}}

DHT表解析:建立Huffman码表(读取对应码长的码字对应的权重)

static int parse_DHT(struct jdec_private *priv, const unsigned char *stream)
{unsigned int count, i;unsigned char huff_bits[17];int length, index;length = be16_to_cpu(stream) - 2;stream += 2;   /* Skip length */
#if TRACEfprintf(p_trace,"> DHT marker (length=%d)\n", length);fflush(p_trace);
#endifwhile (length>0) {index = *stream++;huff_bits[0] = 0;count = 0;for (i=1; i<17; i++) {huff_bits[i] = *stream++;count += huff_bits[i];}
#if TRACEfprintf(p_trace,"Huffman table %s[%d] length=%d\n", (index&0xf0)?"AC":"DC", index&0xf, count);fflush(p_trace);
#endif
#endifif (index & 0xf0 )build_huffman_table(huff_bits, stream, &priv->HTAC[index&0xf]);elsebuild_huffman_table(huff_bits, stream, &priv->HTDC[index&0xf]);length -= 1;length -= 16;length -= count;stream += count;}
#if TRACEfprintf(p_trace,"< DHT marker\n");fflush(p_trace);
#endifreturn 0;
}

inyjpeg_decode函数
根据上面判断的参数对JPEG数据进行解码,得到解码后的数据,并且对每个宏块进行huffman解码,得到了DCT的系数在进行反DCT

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;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_YUV420P:   //这种格式使用的decode_mcu_table是decode_mcu_3comp_tablecolorspace_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;}//mcu的组织,对每个 MCU 解码xstride_by_mcu = ystride_by_mcu = 8;if ((priv->component_infos[cY].Hfactor | priv->component_infos[cY].Vfactor) == 1) {decode_MCU = decode_mcu_table[0];   //使用的函数为decode_MCU_1x1_3planesconvert_to_pixfmt = colorspace_array_conv[0];} else if (priv->component_infos[cY].Hfactor == 1) {decode_MCU = decode_mcu_table[1];convert_to_pixfmt = colorspace_array_conv[1];ystride_by_mcu = 16;
#if TRACEfprintf(p_trace,"Use decode 1x2 sampling (not supported)\n");fflush(p_trace);
#endif} else if (priv->component_infos[cY].Vfactor == 2) {decode_MCU = decode_mcu_table[3];convert_to_pixfmt = colorspace_array_conv[3];xstride_by_mcu = 16;ystride_by_mcu = 16;
#if TRACE fprintf(p_trace,"Use decode 2x2 sampling\n");fflush(p_trace);
#endif} else {decode_MCU = decode_mcu_table[2];convert_to_pixfmt = colorspace_array_conv[2];xstride_by_mcu = 16;
#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;//对每个宏块进行 Huffman 解码得到DCT系数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;}}}}return 0;
}

write_yuv:将输出文件保存为可供YUVViewer观看的YUV文件
在loadjpeg.c文件中定义了函数write_yuv()输出.Y、.U和.V三个通道的数据文件,仅需将这三个文件的数据保存在一个文件中即可得到可供YUVViewer观看的YUV文件。

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

输出量化表和huffman表
在头文件下trace语句后类似写好DHT和DQT输出定义

FILE *DQT_trace;//量化表
FILE *DHT_trace;//huffman表#define DQT_file "DQT.txt"
#define DHT_file "DHT.txt"

和trace的写法类似: 在主函数中找到trace打开的位置,类似的加入打开文件

#if TRACEp_trace=fopen(TRACEFILE,"w");if (p_trace==NULL){printf("trace file open error!");}DQT_trace = fopen(DQT_file, "w");if (DQT_trace == NULL){printf("Q_trace file open error!");}DHT_trace = fopen(DQT_file, "w");if (DHT_trace == NULL){printf("Q_trace file open error!");}
#endif

在DHT函数中打开trace时,表示边运行边写入

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

在build_quantization_table函数中将Huffman表打印出来

#if TRACEfprintf(p_trace,"val=%2.2x code=%8.8x codesize=%2.2d\n", val, code, code_size);fflush(p_trace);fprintf(DHT_trace, "val=%2.2x code=%8.8x codesize=%2.2d\n", val, code, code_size); fflush(DHT_trace);#endif

继续打印DQT表:(量化表的标签)

#if TRACEfprintf(p_trace,"< DQT marker\n");fflush(p_trace);fprintf(DQT_trace, "DQT marker:%d\n", qi);fflush(DQT_trace);
#endif

build_huffman_table函数中加入如下:打印出 量化表的具体内容

#if TRACEfor (i=0; i<8; i++) {for (j=0; j<8; j++) {fprintf(DQT_trace, "%d\t", ref_table[*zz]);*qtable++ = ref_table[*zz++] * aanscalefactor[i] * aanscalefactor[j];}}
#endif

最终得到两张量化表和四张huffman表
可以看出第二张量化表的量化步长更大,最终得到的质量也越不好,根据人眼对色度分量不敏感的特点,我们也能实现推断出第二张图为色度图。

亮度分量
DC

Huffman table DC[0] length=10
val=04 code=00000000 codesize=02
val=05 code=00000001 codesize=02
val=06 code=00000002 codesize=02
val=03 code=00000006 codesize=03
val=02 code=0000000e codesize=04
val=01 code=0000001e codesize=05
val=00 code=0000003e codesize=06
val=09 code=0000007e codesize=07
val=07 code=000000fe codesize=08
val=08 code=000001fe codesize=09

AC

Huffman table AC[0] length=43
val=00 code=00000000 codesize=02
val=01 code=00000002 codesize=03
val=03 code=00000003 codesize=03
val=02 code=00000008 codesize=04
val=04 code=00000009 codesize=04
val=05 code=0000000a codesize=04
val=11 code=0000000b codesize=04
val=21 code=0000000c codesize=04
val=22 code=0000001a codesize=05
val=31 code=0000001b codesize=05
val=61 code=0000001c codesize=05
val=06 code=0000003a codesize=06
val=12 code=0000003b codesize=06
val=a1 code=0000003c codesize=06
val=32 code=0000007a codesize=07
val=41 code=0000007b codesize=07
val=62 code=0000007c codesize=07
val=13 code=000000fa codesize=08
val=51 code=000000fb codesize=08
val=23 code=000001f8 codesize=09
val=42 code=000001f9 codesize=09
val=71 code=000001fa codesize=09
val=81 code=000001fb codesize=09
val=91 code=000001fc codesize=09
val=15 code=000003fa codesize=10
val=52 code=000003fb codesize=10
val=63 code=000003fc codesize=10
val=07 code=000007fa codesize=11
val=14 code=000007fb codesize=11
val=33 code=000007fc codesize=11
val=53 code=000007fd codesize=11
val=16 code=00000ffc codesize=12
val=43 code=00000ffd codesize=12
val=08 code=00001ffc codesize=13
val=b1 code=00001ffd codesize=13
val=34 code=00003ffc codesize=14
val=c1 code=00003ffd codesize=14
val=24 code=00007ffc codesize=15
val=d1 code=0000fffa codesize=16
val=09 code=0000fffb codesize=16
val=72 code=0000fffc codesize=16
val=f0 code=0000fffd codesize=16
val=a2 code=0000fffe codesize=16

色度分量
DC

Huffman table DC[1] length=11
val=04 code=00000000 codesize=02
val=05 code=00000001 codesize=02
val=06 code=00000002 codesize=02
val=03 code=00000006 codesize=03
val=02 code=0000000e codesize=04
val=01 code=0000001e codesize=05
val=00 code=0000003e codesize=06
val=07 code=0000007e codesize=07
val=0a code=000000fe codesize=08
val=09 code=000001fe codesize=09
val=08 code=000003fe codesize=10

AC

Huffman table AC[1] length=28
val=00 code=00000000 codesize=02
val=04 code=00000001 codesize=02
val=01 code=00000004 codesize=03
val=02 code=00000005 codesize=03
val=03 code=00000006 codesize=03
val=31 code=0000001c codesize=05
val=61 code=0000001d codesize=05
val=11 code=0000003c codesize=06
val=12 code=0000003d codesize=06
val=05 code=0000007c codesize=07
val=21 code=0000007d codesize=07
val=13 code=000000fc codesize=08
val=14 code=000000fd codesize=08
val=41 code=000000fe codesize=08
val=51 code=000001fe codesize=09
val=06 code=000007fc codesize=11
val=22 code=000007fd codesize=11
val=32 code=000007fe codesize=11
val=07 code=00001ffc codesize=13
val=15 code=00001ffd codesize=13
val=42 code=00001ffe codesize=13
val=08 code=0000fff8 codesize=16
val=71 code=0000fff9 codesize=16
val=23 code=0000fffa codesize=16
val=24 code=0000fffb codesize=16
val=33 code=0000fffc codesize=16
val=81 code=0000fffd codesize=16
val=a1 code=0000fffe codesize=16

3.3 输出DC图像和AC图像并统计其概率分布

  1. 输出DC和AC图像
    在tinyjpeg_decode函数中修改~这里注意,DC和AC系数都是指的是DCT变换后,存储DCT变量的数据,DCT[0]即DC图像,DCT[1] ~ DCT[63]为AC系数,修改好的代码如下:
int tinyjpeg_decode(struct jdec_private *priv, int pixfmt)
{FILE* DCFile;///DC系数的存储指针FILE* ACFile_1, * ACFile_10, * ACFile_20;AC系数的存储指针DCFile = fopen("DC.yuv", "w");ACFile_1 = fopen("AC1.yuv", "w");ACFile_10 = fopen("AC10.yuv", "w");ACFile_20 = fopen("AC10.yuv", "w");unsigned char* uvbuf = 128;     //将DC系数和AC系数认为是Y分量,uv分量统一设置为128unsigned char* DCbuf, * ACbuf_1, * ACbuf_10, * ACbuf_20;int count = 0;    //统计Y分量的数量.......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);//加入DC,AC的数据接入文件//DC系数的范围是-512~512,为了使图像能显示,手动+512/4DCbuf = (unsigned char)((priv->component_infos->DCT[0] + 512) / 4.0);fwrite(&DCbuf, 1, 1, DCFile);//AC系数,手动+128,调成正值ACbuf_1 = (unsigned char)((priv->component_infos->DCT[1] + 128));fwrite(&ACbuf_1, 1, 1, ACFile_1);ACbuf_10 = (unsigned char)((priv->component_infos->DCT[10] + 128));fwrite(&ACbuf_10, 1, 1, ACFile_10);ACbuf_20 = (unsigned char)((priv->component_infos->DCT[20] + 128));fwrite(&ACbuf_20, 1, 1, ACFile_20);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;}}}}.......//uv分量写进文件for (int j = 0; j < count * 0.25 * 2; j++){fwrite(&uvbuf, sizeof(unsigned char), 1, DCFile);fwrite(&uvbuf, sizeof(unsigned char), 1, ACFile_1);fwrite(&uvbuf, sizeof(unsigned char), 1, ACbuf_10);fwrite(&uvbuf, sizeof(unsigned char), 1, ACbuf_20);}fclose(DCFile);fclose(ACFile_1);fclose(ACbuf_10);fclose(ACbuf_20);return 0;
}

运行程序,输出.yuv文件

  1. 统计概率分布
    从对比可以看出,DC图像的方差更大,信息熵更大,包含着更多的图像信息

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

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

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

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

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

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

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

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

    目录 JEPG原理 简述 优点 缺点 JPEG文件格式 常用标记码 编解码原理 编码原理 Level offset-零偏置 DCT变换 量化 DC系数差分编码 AC系数的之字形扫描+游程编码 解码原理 ...

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

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

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

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

  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. PAT 显示格式错误
  2. BRCM5.02编译十:cmake: command not found
  3. Java REST框架一览
  4. Python之PIL库
  5. 法学学士学位的完整形式是什么?
  6. CCF201709-1 打酱油
  7. 【华为云技术分享】云小课 | SAP S/4HANA高可用之实战演练
  8. 'yasm' 不是内部或外部命令
  9. 京东物流研发岗位会背景调查吗_【秋招资讯】京东健康于港交所主板上市 | 京东健康2021校园招聘火热进行中!...
  10. 前端必须掌握30个CSS3选择器
  11. paip. erlang语法C++语法对比attilax总结
  12. 主题模型:LDA原理详解与应用
  13. 【Sniffer和网络执法官软件助你维护网络(转网络安全)】
  14. 时间序列相似性度量-DTW
  15. EasyAR笔记01 检测云识别是否存在相似图片
  16. 新概念英语第二册61-96课(转)
  17. 【thinkphp5.1】htmlentities() expects parameter 1 to be string, array given
  18. JavaBeans分类
  19. 美团“互联网下半场”的筹码:豪赌B端业务
  20. 生产者/消费者模式的理解及实现

热门文章

  1. 腾达u6无线网卡 linux,腾达u6驱动-腾达u6无线网卡驱动下载 v1.0官方版--pc6下载站...
  2. 标准信号转高电压高电流输出放大转换器0-5v/0-24v转4-20mA/0-500mA
  3. August 4th 羽毛球拍
  4. 学生php实训个人总结300字,实训总结300字左右
  5. 电商评论数据聚类实验报告
  6. FME自动连接转换器
  7. KALI的三种安装方式
  8. 电商卖家玩转社群裂变的3大致胜秘诀,社群营销裂变增长!
  9. qq推广移动端无法唤醒qq“显示需要更新QQ”
  10. PHP 执行shell 脚本,常见问题