JPEG 原理分析及 JPEG 解码器的调试
文章目錄
- 前言
- 一、JPEG
- 1、JPEG概述
- 2、文件結(jié)構(gòu)
- 3、編碼流程
- 4、解碼流程
- 二、實(shí)驗(yàn)內(nèi)容
- 1、tinyjpeg-internal.h定義結(jié)構(gòu)體
- 2、JPEG文件主要操作代碼
- 三、實(shí)驗(yàn)結(jié)果
- 1、YUV圖像
- 2、量化表
- 3、霍夫曼碼表
- 4、分量圖像
前言
? JPEG( Joint Photographic Experts Group)是用于連續(xù)色調(diào)靜態(tài)圖像壓縮的一種標(biāo)準(zhǔn),文件后綴名為.jpg或.jpeg,是最常用的圖像文件格式。其主要是采用預(yù)測編碼(DPCM)、離散余弦變換(DCT)以及熵編碼的聯(lián)合編碼方式,以去除冗余的圖像和彩色數(shù)據(jù),屬于有損壓縮格式。它的特點(diǎn)在于夠?qū)D像壓縮在很小的儲存空間,但不可避免地一定程度上會造成圖像數(shù)據(jù)的損傷。尤其是使用過高的壓縮比例。使用JPEG方法將使最終解壓縮后恢復(fù)的圖像質(zhì)量降低,如果追求高品質(zhì)圖像,則不宜采用過高的壓縮比例。
? 然而,JPEG編碼可以用有損壓縮方式去除冗余的圖像數(shù)據(jù)。換句話說,就是可以用較少的磁盤空間得到較好的圖像品質(zhì)。而且JPEG是一種很靈活的格式,具有調(diào)節(jié)圖像質(zhì)量的功能,它允許用不同的壓縮比例對文件進(jìn)行壓縮,支持多種壓縮級別,壓縮比率通常在10:1到40:1。JPEG格式壓縮的主要是高頻信息,對色彩的信息保留較好,適合應(yīng)用于互聯(lián)網(wǎng);它可減少圖像的傳輸時間,支持24位真彩色;也普遍應(yīng)用于需要連續(xù)色調(diào)的圖像中。
? 本實(shí)驗(yàn)則在已經(jīng)對JPEG的原理進(jìn)行學(xué)習(xí)理解的基礎(chǔ)上,對C語言實(shí)現(xiàn)的JPEG分析和解碼程序進(jìn)行分析,進(jìn)行JPEG向YUV的轉(zhuǎn)換,對JPEG文件的等信息進(jìn)行分析。
一、JPEG
1、JPEG概述
? JPEG文件格式是JPEG(聯(lián)合圖像專家組)標(biāo)準(zhǔn)的產(chǎn)物,該標(biāo)準(zhǔn)由ISO與CCI TT(國際電報電話咨詢委員會)共同制定,是面向連續(xù)色調(diào)靜止圖像的一種壓縮標(biāo)準(zhǔn)。其壓縮步驟大致分為四步:
-
顏色轉(zhuǎn)換
? 由于JPEG只支持YUV顏色模式,而不支持RGB顏色模式,所以在將彩色圖像進(jìn)行壓縮之前,必須先對顏色模式進(jìn)據(jù)轉(zhuǎn)換。轉(zhuǎn)換完成之后還需要進(jìn)行數(shù)據(jù)采樣。一般采用的采樣比例是2:1:1或4:2:2。由于在執(zhí)行了此項(xiàng)工作之后,每兩行數(shù)據(jù)只保留一行,因此,采樣后圖像數(shù)據(jù)量將壓縮為原來的一半。 -
DCT變換
? DCT(DiscreteConsineTransform)是將圖像信號在頻率域上進(jìn)行變換,分離出高頻和低頻信息的處理過程。然后再對圖像的高頻部分(即圖像細(xì)節(jié))進(jìn)行壓縮,以達(dá)到壓縮圖像數(shù)據(jù)的目的。首先將圖像劃分為多個8*8的矩陣。然后對每一個矩陣作DCT變換(變換公式此略)。變換后得到一個頻率系數(shù)矩陣,其中的頻率系數(shù)都是浮點(diǎn)數(shù)。 -
量化
? 由于在后面編碼過程中使用的碼本都是整數(shù),因此需要對變換后的頻率系數(shù)進(jìn)行量化,將之轉(zhuǎn)換為整數(shù)。由于進(jìn)行數(shù)據(jù)量化后,矩陣中的數(shù)據(jù)都是近似值,和原始圖像數(shù)據(jù)之間有了差異,這一差異是造成圖像壓縮后失真的主要原因。 -
編碼
? 編碼采用兩種機(jī)制:一是0值的行程長度編碼;二是熵編碼(EntropyCoding)。在JPEG中,采用曲徊序列,即以矩陣對角線的法線方向作“之”字排列矩陣中的元素。這樣做的優(yōu)點(diǎn)是使得靠近矩陣左上角、值比較大的元素排列在行程的前面,而行程的后面所排列的矩陣元素基本上為0值。行程長度編碼是非常簡單和常用的編碼方式,在此不再贅述。編碼實(shí)際上是一種基于統(tǒng)計(jì)特性的編碼方法。在JPEG中允許采用HUFFMAN編碼或者算術(shù)編碼。
? 本實(shí)驗(yàn)則對JPEG的DCT變換、量化和編碼操作進(jìn)行學(xué)習(xí)與代碼分析。
?
2、文件結(jié)構(gòu)
? 此實(shí)驗(yàn)使用實(shí)驗(yàn)素材中的圖片進(jìn)行 .jpg文件結(jié)構(gòu)分析。使用HexFlex軟件進(jìn)行十六進(jìn)制分析。
? JPEG文件數(shù)據(jù)以數(shù)據(jù)段(Segment)為單位,以高位在前低位在后的形式進(jìn)行存儲,不同的段以首部兩個字節(jié)進(jìn)行標(biāo)志,且大多段的有效數(shù)據(jù)長度(不含段頭)均在段頭后的2個字節(jié)進(jìn)行標(biāo)識。不同的段其段頭兩字節(jié)有特殊標(biāo)識。
? JPEG文件不同段類型分為:
-
文件開始標(biāo)記 SOI
?標(biāo)記固定為0xFFD8,是所有的JPEG文件的文件頭。
-
應(yīng)用程序保留標(biāo)記 APP0
?固定標(biāo)記為0xFFE0,其中包含如圖所示字段。
?同時部分JPEG文件可能還含有APPn標(biāo)記,以攜帶圖片對應(yīng)的應(yīng)用程序信息(攝制設(shè)備等)。其中n的值為1-15,對應(yīng)的固定標(biāo)記為0xFFE1-0xFFEF。 -
定義量化表 DQT
?固定標(biāo)記為0xFFDB,在JPEG文件中通常存在兩個,分別為亮度信息的量化表與色度信息的量化表。每個量化表的長度一般為0x0043(若文件中兩量化表合在一起長度則為0x0084)。
-
幀圖像開始 SOF0
?固定標(biāo)記為0xFFC0,其內(nèi)容如下。
-
定義霍夫曼表 DHT
?固定標(biāo)記為0xFFC4,后面兩字節(jié)為DHT段長度。
?DHT段第五字節(jié)則是霍夫曼表ID和類型。其中高四位為表類型,0和1分別代表當(dāng)前表為直流分量霍夫曼表和交流分量霍夫曼表。低四位則為表ID,表示當(dāng)前表為第幾個表。
?其后的16字節(jié)則代表霍夫曼編碼后對應(yīng)碼長的碼字個數(shù)。霍夫曼碼長為1至16的碼字個數(shù)分別由這16個字節(jié)進(jìn)行表示。
?再其后的字節(jié)則為編碼內(nèi)容,表示每個符字對應(yīng)的權(quán)值。
?上圖即為示例文件中某個ID為0的直流分量霍夫曼表。可看到此表中2-16位碼字應(yīng)共有1+2+5+3+3+3+2+5+3+4+2+2+2+1+5=43個。選中后面表示每種碼字的權(quán)值,可看到的確位43個。
?而其中權(quán)值含義則為在解碼過程中,此碼字后的數(shù)據(jù)碼字的位長。根據(jù)位長讀取緊隨其后的碼字,即可進(jìn)行解碼。 -
定義掃描起點(diǎn) SOS
?固定標(biāo)志為0xFFDA,除段長度2字節(jié)外,存儲包含顏色分量信息和壓縮圖像數(shù)據(jù)。
圖中0xFFDA后即為此段數(shù)據(jù)。
3、編碼流程
? JPEG編碼的過程如下圖所示。而解碼則是編碼的逆過程。
? 如圖所示。過程分別為:
-
Level Offset 零偏置
? 對于灰度級峰值為2n的圖片,通過將像素減去2n-1,將無符號數(shù)轉(zhuǎn)換為有符號數(shù),從而使像素的絕對值出現(xiàn)3位10進(jìn)制的概率大大減少,縮小數(shù)據(jù)范圍。 -
8×8 DCT變換
?對每個單獨(dú)的圖像分量,進(jìn)行8×8塊為單位的DCT變換,將系數(shù)向矩陣左上位置進(jìn)行集中,以適合后續(xù)的交流直流分量分別編碼的方式。 -
Uniform Scalar Quantization 量化表量化
?對亮度信息和色度信息分別根據(jù)量化表進(jìn)行量化。 -
編碼
?編碼分為上下兩路,分別對量化后的系數(shù)矩陣作直流分量和交流分量的編碼。
?其中直流分量采用DPCM(差分脈沖調(diào)制編碼)的形式進(jìn)行編碼之后,再對編碼所得的預(yù)測差值進(jìn)行霍夫曼熵編碼。
?而對量化矩陣中的交流分量,由于DCT變換后的系數(shù)矩陣能量大多數(shù)存在于左上角,故使用之字形掃描。
?而對之字形掃描之后的數(shù)據(jù)進(jìn)行游程編碼得到(RRRR,SSSS)格式的游程碼數(shù)據(jù),再對數(shù)據(jù)進(jìn)行固定格式的霍夫曼編碼,從而得到最終編碼數(shù)據(jù)。
4、解碼流程
?解碼則是對編碼的逆操作,其步驟大致為:
- 解碼霍夫曼編碼碼字?jǐn)?shù)據(jù)
- 分別解碼直流分量和交流分量
- 矩陣反量化
- DCT逆變換
- 丟棄補(bǔ)充的行和列(在編碼過程中防止圖像行列無法被8整除而進(jìn)行邊緣拓展)
- 反0偏置
- 對插值Cb Cr分量進(jìn)行插值
- 還原圖像數(shù)據(jù)信息(向RGB轉(zhuǎn)換)
二、實(shí)驗(yàn)內(nèi)容
?實(shí)驗(yàn)通過對JPEG文件解碼工程進(jìn)行調(diào)試,對工程運(yùn)行邏輯進(jìn)行分析和理解,并加入實(shí)驗(yàn)內(nèi)容,以實(shí)現(xiàn)實(shí)驗(yàn)?zāi)康摹?br /> ?工程jpeg_dec共有三個頭文件 stdint.h、tinypeg.h和tinyjpeg-internal.h,以及三個代碼代碼.c文件 jidctflc.c、loadjpeg.c和tinyjpeg.c。
1、tinyjpeg-internal.h定義結(jié)構(gòu)體
① 霍夫曼碼表結(jié)構(gòu)體
//霍夫曼碼表 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]; //存儲特定權(quán)值對應(yīng)的碼字,可實(shí)現(xiàn)快速查找/* code size: give the number of bits of a symbol is encoded */unsigned char code_size[HUFFMAN_HASH_SIZE]; //存儲碼長對應(yīng)的權(quán)值/* 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];};② 圖像塊結(jié)構(gòu)體
//一個8×8的塊(DCT變換及量化編碼單元) struct component{unsigned int Hfactor; //水平采樣因子unsigned int Vfactor; //垂直采樣因子float* Q_table; /* Pointer to the quantisation table to use */struct huffman_table* AC_table; //交流DCT系數(shù)的哈夫曼碼表指針struct huffman_table* DC_table; //直流DCT系數(shù)的哈夫曼碼表指針short int previous_DC; //前一個塊的直流系數(shù)(用于進(jìn)行預(yù)測差分編碼)short int DCT[64]; //存儲當(dāng)前塊的8×8 DCT系數(shù) #if SANITY_CHECKunsigned int cid; #endif};③ 解碼信息結(jié)構(gòu)體
//文件基本解碼信息 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; //文件最為流式數(shù)據(jù)的開始和結(jié)束unsigned int stream_length;/* Pointer to the current stream */const unsigned char* stream; //指向文件流的指針unsigned int reservoir, nbits_in_reservoir;struct component component_infos[COMPONENTS];/* quantization tables */float Q_tables[COMPONENTS][64]; //量化表/* Huffman Tables */struct huffman_table HTDC[HUFFMAN_TABLES]; //直流系數(shù)霍夫曼表struct huffman_table HTAC[HUFFMAN_TABLES]; //交流系數(shù)霍夫曼表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]; //存儲反DCT變換之后的分量(4:2:0)jmp_buf jump_state;/* Internal Pointer use for colorspace conversion, do not modify it !!! */uint8_t* plane[COMPONENTS];/* 添加:用于存放直流系數(shù)(DCT[0])和交流系數(shù)(DCT[1])的數(shù)組 */int* dc, * ac; //用于讀取的指針unsigned char* outdc, * outac; //用于輸出的指針};2、JPEG文件主要操作代碼
?實(shí)驗(yàn)中對JPEG進(jìn)行解碼等主要操作均在tinyjpeg.c文件中。此實(shí)驗(yàn)通過代碼運(yùn)行邏輯順序進(jìn)行分析理解,并在過程中通過添加代碼完成實(shí)驗(yàn)要求。
① 解析JPEG文件數(shù)據(jù)段
?對讀取過程中的文件段頭(兩字節(jié))標(biāo)志進(jìn)行讀取,并判斷段的種類,調(diào)用相應(yīng)的函數(shù)進(jìn)行操作。
?讀取函數(shù)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;//為了實(shí)現(xiàn)塊的系數(shù)的預(yù)測編碼,定義臨近塊指針/* 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)//按照段頭標(biāo)志進(jìn)行識別{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; //下一個數(shù)據(jù)塊,繼續(xù)進(jìn)行掃描}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;}② DQT段操作函數(shù)
?對DQT段進(jìn)行解析,以得到量化表信息。同時加入代碼,來輸出所有的量化矩陣。
③ SOF段操作函數(shù)
/* 解析SOF0(FFC0),得到圖像基本數(shù)據(jù) */ static int parse_SOF(struct jdec_private* priv, const unsigned char* stream){int i, width, height, nr_components, cid, sampling_factor;int Q_table;struct component* c;#if TRACEfprintf(p_trace, "> SOF marker\n");fflush(p_trace); #endifprint_SOF(stream);//不包含F(xiàn)FC0頭情況下,第三四字節(jié)為圖像高度,第五六字節(jié)為圖像寬度,后面緊跟一字節(jié)為顏色分量數(shù)(03)height = be16_to_cpu(stream + 3);width = be16_to_cpu(stream + 5);nr_components = stream[7];#if SANITY_CHECKif (stream[2] != 8)snprintf(error_string, sizeof(error_string), "Precision other than 8 is not supported\n");if (width > JPEG_MAX_WIDTH || height > JPEG_MAX_HEIGHT)snprintf(error_string, sizeof(error_string), "Width and Height (%dx%d) seems suspicious\n", width, height);if (nr_components != 3)snprintf(error_string, sizeof(error_string), "We only support YUV images\n");if (height % 16)snprintf(error_string, sizeof(error_string), "Height need to be a multiple of 16 (current height is %d)\n", height);if (width % 16)snprintf(error_string, sizeof(error_string), "Width need to be a multiple of 16 (current Width is %d)\n", width); #endifstream += 8; //指針繼續(xù)向后走for (i = 0; i < nr_components; i++) {cid = *stream++; //顏色分量IDsampling_factor = *stream++; //水平和垂直采樣因子Q_table = *stream++; //顏色分量的量化表序號c = &priv->component_infos[i]; //指向該分量的結(jié)構(gòu)體指針#if SANITY_CHECKc->cid = cid;if (Q_table >= COMPONENTS)snprintf(error_string, sizeof(error_string), "Bad Quantization table index (got %d, max allowed %d)\n", Q_table, COMPONENTS - 1); #endifc->Vfactor = sampling_factor & 0xf; //取低四位為垂直采樣因子c->Hfactor = sampling_factor >> 4; //取高四位為水平采樣因子c->Q_table = priv->Q_tables[Q_table]; //以量化表序號獲取量化表#if TRACEfprintf(p_trace, "Component:%d factor:%dx%d Quantization table:%d\n",cid, c->Hfactor, c->Hfactor, Q_table);fflush(p_trace); #endif}//存儲圖像寬高信息priv->width = width;priv->height = height;#if TRACEfprintf(p_trace, "< SOF marker\n");fflush(p_trace); #endifreturn 0;}④ DHT段操作函數(shù)
?通過解析DHT數(shù)據(jù)段,來得到霍夫曼碼表信息,同時加入代碼輸出所有霍夫曼碼表。
⑤ SOS段操作函數(shù)
/* 解析SOS(0xFFDA),以得到顏色分量的霍夫曼表序號(對應(yīng)DHT中的序號數(shù)據(jù)) */ static int parse_SOS(struct jdec_private* priv, const unsigned char* stream){unsigned int i, cid, table;unsigned int nr_components = stream[2]; //讀取顏色分量數(shù)(僅支持3)#if TRACEfprintf(p_trace, "> SOS marker\n");fflush(p_trace); #endif#if SANITY_CHECKif (nr_components != 3)snprintf(error_string, sizeof(error_string), "We only support YCbCr image\n"); #endifstream += 3;for (i = 0; i < nr_components; i++) { //對每個顏色進(jìn)行表查找和處理cid = *stream++; //表IDtable = *stream++; //表數(shù)據(jù)#if SANITY_CHECKif ((table & 0xf) >= 4)snprintf(error_string, sizeof(error_string), "We do not support more than 2 AC Huffman table\n");if ((table >> 4) >= 4)snprintf(error_string, sizeof(error_string), "We do not support more than 2 DC Huffman table\n");if (cid != priv->component_infos[i].cid)snprintf(error_string, sizeof(error_string), "SOS cid order (%d:%d) isn't compatible with the SOF marker (%d:%d)\n",i, cid, i, priv->component_infos[i].cid); #if TRACEfprintf(p_trace, "ComponentId:%d tableAC:%d tableDC:%d\n", cid, table & 0xf, table >> 4);fflush(p_trace); #endif #endif//查找的量化表數(shù)據(jù)進(jìn)行取值priv->component_infos[i].AC_table = &priv->HTAC[table & 0xf]; //低四位為ACpriv->component_infos[i].DC_table = &priv->HTDC[table >> 4]; //高四位為DC}priv->stream = stream + 3;#if TRACEfprintf(p_trace, "< SOS marker\n");fflush(p_trace); #endifreturn 0;}⑥ 對讀取結(jié)構(gòu)體數(shù)據(jù)進(jìn)行處理
?在此函數(shù)中,對每個塊的水平和垂直采樣情況進(jìn)行解析,計(jì)算MCU。同時通過調(diào)用不同的MCU讀取和處理函數(shù)進(jìn)行數(shù)據(jù)處理。
?同時在此函數(shù)中添加代碼以實(shí)現(xiàn)輸出原JPEG圖像的直流分量圖像和其中一個交流分量圖像。
三、實(shí)驗(yàn)結(jié)果
?對工程進(jìn)行生成和運(yùn)行,可得到實(shí)驗(yàn)輸出結(jié)果:
1、YUV圖像
2、量化表
?實(shí)驗(yàn)中向txt文件中對JPEG文件的兩個量化表進(jìn)行了輸出。
3、霍夫曼碼表
?實(shí)驗(yàn)中向txt文件中對JPEG文件的所有霍夫曼碼表進(jìn)行了輸出。
?其中包括:
- 直流碼表0:
- 直流碼表1:
- 交流碼表0
- 交流碼表1
?同時在huffman文件中存儲了所有碼表的完整信息。
4、分量圖像
?實(shí)驗(yàn)中輸出了原圖像中的直流分量圖像和交流分量圖像,并對兩圖像的概率密度分布進(jìn)行了繪圖。
| 輸出圖像 | ||
| 概率分布 |
總結(jié)
以上是生活随笔為你收集整理的JPEG 原理分析及 JPEG 解码器的调试的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: java的duplicate用法_Jav
- 下一篇: GSM和GPRS有什么区别