【数据压缩】实验五——JPEG原理分析及JPEG解码器的调试
(一)实验目的
掌握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编码步骤
- 零偏置(Level Offset)
对于灰度级是2n的像素,通过减去2(n-1),将无符号的整数值变成有符号数,使像素的绝对值出现3位10进制的概率大大减少 - DCT变换
对每个单独的彩色图像分量,把整个分量图像分成8×8的图像块,并作为二维离散余弦变换DCT的输入。将原本的像素数值进行DCT变换,后续直接对DCT分量进行处理。 - 量化
对DCT分量进行量化,由于人眼对亮度信号比对色差信号更敏感,因此使用了两种量化表:亮度量化值和色差量化值。
根据人眼的视觉特性(对低频敏感,对高频不太敏感)对低频分量采取较细的量化,对高频分量采取较粗的量化。
如果原始图象中细节丰富,则去掉的数据较多,量化后的系数与量化前差别。反之,细节少的原始图象在压缩时去掉的数据少些。 - DC系数差分编码
DC直流系数有两个特点:系数的数值比较大,相邻DC系数值变化不大。
根据上述两个特点,JPEG算法使用了差分脉冲调制编码(DPCM)技术,对相邻图像块之间量化DC系数的差值DIFF进行编码:
DIFF_k=DC_k-DC_{k-1} - 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 理解三个结构体的设计目的
- 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];
};
- 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
};
- 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图像并统计其概率分布
- 输出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文件
- 统计概率分布
从对比可以看出,DC图像的方差更大,信息熵更大,包含着更多的图像信息
【数据压缩】实验五——JPEG原理分析及JPEG解码器的调试相关推荐
- 实验五—JEPG 原理分析及 JPEG 解码器的调试
文章目录 一.JPEG编码原理 1.Level Offset(水平偏移)能量的重新分配 2.DCT变换 3.Uniform scalar quantization 均匀标量量化 4.DC系数差分编码 ...
- JPEG原理分析及JPEG解码器的解析
文章目录 JPEG原理分析及JPEG解码器的调试 原理分析 JPEG编解码流程图 DC系数编码 AC系数编码 JPEG文件格式 Segment组织形式 JPEG 的 Segment Marker no ...
- JPEG原理分析 及 JPEG解码器的调试
文章目录 数据压缩实验(五) 一.JPEG原理分析 1.概述 优点 缺点 2.JPEG编解码原理 (1)彩色空间 (2)Level offset--零偏置电平下移 (3)8x8 DCT--离散余弦变换 ...
- 【数据压缩-实验5】JPEG原理分析及JPEG解码器的调试
目录 JEPG原理 简述 优点 缺点 JPEG文件格式 常用标记码 编解码原理 编码原理 Level offset-零偏置 DCT变换 量化 DC系数差分编码 AC系数的之字形扫描+游程编码 解码原理 ...
- 【数据压缩】实验:JPEG原理分析及JPEG解码器的调试
一.JPEG编解码原理 1.1 JPEG图像压缩标准基本介绍 JPEG是Joint Photographic Experts Group(联合图像专家小组)的缩写,文件后缀名为.jpg或.jpeg,是 ...
- JPEG原理分析及JPEG解码器调试
JPEG格式简介 JPEG( Joint Photographic Experts Group)即联合图像专家组,是用于连续色调静态图像压缩的一种标准,文件后缀名为.jpg或.jpeg,是最常用的图像 ...
- JPEG原理分析及JPEG解码器的调试
一.JPEG编码原理 1.将RGB转换为YUV空间.(相关性较小) 2.零偏置.(使最大的绝对值大的无符号数变为绝对值小的有符号数,减小数据平均的位数,例如0~255变为-128~127) 3.做8* ...
- 数据压缩实验五 JPEG原理分析JPEG解码器的调试
一.实验原理 1.JPEG原理及编码流程 JPEG是常见的一种图像格式,由ISO与CCITT建立并开发,是一个国际数字图像压缩标准.JPEG文件的扩展名为.jpg或.jpeg,用有损方式去除冗余的 ...
- 数据压缩实验五:JPEG文件解码实验分析
一:实验原理 1.JPEG编码原理 JPEG 是Joint Photographic Experts Group(联合图像专家小组)的缩写,是第一个国际图像压缩标准. .jpeg/.jpg是最常用的图 ...
最新文章
- PAT 显示格式错误
- BRCM5.02编译十:cmake: command not found
- Java REST框架一览
- Python之PIL库
- 法学学士学位的完整形式是什么?
- CCF201709-1 打酱油
- 【华为云技术分享】云小课 | SAP S/4HANA高可用之实战演练
- 'yasm' 不是内部或外部命令
- 京东物流研发岗位会背景调查吗_【秋招资讯】京东健康于港交所主板上市 | 京东健康2021校园招聘火热进行中!...
- 前端必须掌握30个CSS3选择器
- paip. erlang语法C++语法对比attilax总结
- 主题模型:LDA原理详解与应用
- 【Sniffer和网络执法官软件助你维护网络(转网络安全)】
- 时间序列相似性度量-DTW
- EasyAR笔记01 检测云识别是否存在相似图片
- 新概念英语第二册61-96课(转)
- 【thinkphp5.1】htmlentities() expects parameter 1 to be string, array given
- JavaBeans分类
- 美团“互联网下半场”的筹码:豪赌B端业务
- 生产者/消费者模式的理解及实现
热门文章
- 腾达u6无线网卡 linux,腾达u6驱动-腾达u6无线网卡驱动下载 v1.0官方版--pc6下载站...
- 标准信号转高电压高电流输出放大转换器0-5v/0-24v转4-20mA/0-500mA
- August 4th 羽毛球拍
- 学生php实训个人总结300字,实训总结300字左右
- 电商评论数据聚类实验报告
- FME自动连接转换器
- KALI的三种安装方式
- 电商卖家玩转社群裂变的3大致胜秘诀,社群营销裂变增长!
- qq推广移动端无法唤醒qq“显示需要更新QQ”
- PHP 执行shell 脚本,常见问题