JPEG原理分析及JPEG解码器的调试
一、JPEG編碼原理
1.將RGB轉(zhuǎn)換為YUV空間。(相關(guān)性較小)
2.零偏置。(使最大的絕對(duì)值大的無符號(hào)數(shù)變?yōu)榻^對(duì)值小的有符號(hào)數(shù),減小數(shù)據(jù)平均的位數(shù),例如0~255變?yōu)?128~127)
3.做8*8DCT變換。(區(qū)分DC與AC成分,便于后續(xù)處理)
4.細(xì)量化低頻粗量化高頻。(利用人眼對(duì)高頻不敏感的特性節(jié)省空間)
5.針對(duì)DC系數(shù)使用DPCM編碼。(相鄰變化幅度較小,適合DPCM)
6.針對(duì)AC系數(shù)使用Z型掃描隨后使用游程編碼。(壓縮掉大量的0)
7.對(duì)DC與AC分別做哈夫曼編碼。(減少空間)
JPEG解碼為編碼的逆過程,了解編碼方式方便解碼。
二、JPEG解碼原理
1.JPEG文件格式
2.整體分析
查閱loadjpeg.c中的主函數(shù)得知該程序先通過輸入的參數(shù)來決定輸出的格式,然后load_multiple_times函數(shù)多次使用tinyjpeg_parse_header函數(shù)讀取jpeg文件頭,tinyjpeg_parse_header使用parse_JFIF函數(shù)來解析每個(gè)標(biāo)識(shí)符,依次解析SOF、DQT、SOS、DHT、DRI。
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: #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; }3.結(jié)構(gòu)體功能
huffman_table結(jié)構(gòu)體:
存儲(chǔ)快速查找表、碼字長(zhǎng)度和慢速查找表。
component結(jié)構(gòu)體:
存儲(chǔ)水平采樣因子、垂直采樣因子,前一個(gè)直流系數(shù),dct變換后的值,哈夫曼表和量化表。
jdec_private結(jié)構(gòu)體:
存儲(chǔ)jpeg中一個(gè)塊的定義和信息
4.TRACE的功能
#if XXX ... #endif該格式可根據(jù)if后面的條件是否為真來決定是否編譯#if與#endif中間的代碼,所以程序中的#if TRACE即為一部分代碼的開關(guān),TRACE為1時(shí)執(zhí)行那些代碼,輸出trace_jpeg.txt文件。
TRACE的定義在tinyjpeg.h中,將TRACE修改為0即可關(guān)閉部分代碼。
5.輸出量化矩陣
該程序使用build_quantization_table生成量化矩陣,在該函數(shù)下方添加輸出代碼即可。
#if TRACEconst unsigned char* outzigzag = zigzag;for (int i = 0; i < 8; i++) {for (int j = 0; j < 8; j++) {fprintf(p_trace, "%d ", ref_table[*outzigzag++]);}fprintf(p_trace, "\n");} #endif輸出的量化矩陣:
6.輸出哈夫曼碼表
該程序使用build_huffman_table生成哈夫曼碼表。
輸出的哈夫曼碼表:
7.輸出解碼圖像
原程序?qū)uv分別輸出到了三個(gè)文件中,更改后將yuv均寫入同一個(gè)文件即可得到y(tǒng)uv文件。
查閱實(shí)驗(yàn)所用jpg文件得知圖像分辨率為1024*1024,用該分辨率查看得到的yuv文件即可得到正確圖像。
8.輸出DC、AC圖像
由JPEG編碼過程得知DC分量經(jīng)過DPCM與哈夫曼編碼后變?yōu)閖peg直流數(shù)據(jù),解碼時(shí)jpeg數(shù)據(jù)經(jīng)過哈夫曼解碼與dpcm解碼后得到DC分量。該程序中tinyjpeg_decode用于解碼,所以修改tinyjpeg_decode函數(shù)。
tinyjpeg_decode:
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;/*---------------here------------------*/FILE* out_DC;FILE* out_AC;out_DC = fopen("out_DC.yuv", "wb");out_AC = fopen("out_AC.yuv", "wb");unsigned char* out_DC_Buf;unsigned char* out_AC_Buf;/*---------------here------------------*/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) {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;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;if ((priv->component_infos[cY].Hfactor | priv->component_infos[cY].Vfactor) == 1) {decode_MCU = decode_mcu_table[0];convert_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) {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;/* 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);/*-----------------------------------here-------------------------------------*/out_DC_Buf = (unsigned char)((priv->component_infos->DCT[0] + 512) / 4 + 0.5);out_AC_Buf = (unsigned char)(priv->component_infos->DCT[1] + 128);fwrite(&out_DC_Buf, 1, 1, out_DC);fwrite(&out_AC_Buf, 1, 1, out_AC);/*-----------------------------------here-------------------------------------*/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; }DC分量為原圖的88數(shù)據(jù)得到,原圖為10241024,所以得到的圖像為128*128。
因?yàn)橹挥衴分量沒有uv分量,所以得到的是黑白圖像,使用Luminance Only來顯示僅有亮度分量的圖片。
AC與DC同理。
所的圖片:
9.DC、AC圖像概率分布
將out_DC.yuv與out_AC.yuv放到matlab運(yùn)行目錄中,運(yùn)行以下代碼:
所得結(jié)果:
(藍(lán)色為DC分量,橙色為AC分量)
DC分量較為分散,AC分量較集中,兩者均大致關(guān)于中心位置對(duì)稱。
總結(jié)
以上是生活随笔為你收集整理的JPEG原理分析及JPEG解码器的调试的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: DPCM 压缩系统的实现和分析
- 下一篇: MPEG原理分析及MPEG音频编码器的调