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