關於圖像壓縮實驗

圖像壓縮實驗報告

一、背景介紹【1】

      圖像壓縮是數據壓縮技術在數字圖像上的應用,目的是減少圖像數據中的冗餘信息,從而用更加高效的格式存儲和傳輸數據。而圖像壓縮的方法可以是有損壓縮或無損壓縮,常見的應用有gif和tiff格式等。

      有損壓縮又稱“破壞性資料壓縮”、“不可逆壓縮”,通過這種壓縮方法,解壓後的數據會與原始數據不同但非常相近,藉由將次要的信息數據捨棄,犧牲一些質量來減少數據量、提高壓縮比。根據各種格式設計的不同,有損壓縮都會有代間損失——每次壓縮與解壓文件都會帶來漸進的質量下降。在很多情況下,數據會包含比必要的還多的信息。例如,一張分辨率過高的照片,其中的細節肉眼可能已無法辨識;同理,在一個音量很高的音頻片段中,一些細節可能是人耳難以察覺的。捨棄這些人類無法察覺的細節,就可以用更小的數據量,提供與原始數據相差無幾的感官體驗。有時也允許失去一部分可以察覺的細節,來達到更好的壓縮率。

       無損壓縮是指數據經過壓縮後,信息不受損失還能完全恢復到壓縮前的原樣。無損”一詞是相對於有損數據壓縮,有損數據壓縮只允許一個近似原始數據進行重建,以換取更好的壓縮率。無損壓縮通常用於嚴格要求“經過壓縮、解壓縮的數據必須與原始數據一致”的場合。我們可以藉由無損壓縮,在不失去任何信息的條件下,將數據壓縮得更小。例如,當一張圖片存儲成數字文件時,我們會將它轉換成一連串的點,再分別存儲每個點的顏色信息。如果某張圖片由200個紅點構成,我們會以類似“紅點、紅點、...(重複197次)...、紅點”的格式來存儲它。在這個例子中,我們可以改成用“200個紅點”這樣的格式來存儲這張圖片,就能不失去任何信息的完成壓縮。然而,若要保留源文件案的所有信息,信息論說明了,無論使用任何壓縮方法,文件大小都無法低於一個下界。一個直觀的例子:壓縮後得到的zip文件會比源文件案更小,但一直重複壓縮同一個文件並不會讓文件大小變成0,因爲源文件案終究含有一定量的信息。

 

二、回顧算法【2】

①遊程編碼(無損壓縮)【3】:Run-Length Coding,又稱行程長度編碼或變動長度編碼法,是一種與資料性質無關的無損數據壓縮技術。變動長度編碼法爲一種“使用變動長度的碼來取代連續重複出現的原始資料”的壓縮技術。舉例來說,一組資料串"AAAABBBCCDEEEE",由4個A、3個B、2個C、1個D、4個E組成,經過變動長度編碼法可將資料壓縮爲4A3B2C1D4E(由14個單位轉成10個單位)。簡言之,其優點在於將重複性高的資料量壓縮成小單位;然而,其缺點在於─若該資料出現頻率不高,可能導致壓縮結果資料量比原始資料大,例如:原始資料"ABCDE",壓縮結果爲"1A1B1C1D1E"(由5個單位轉成10個單位)。

      其算法步驟如下:先使用一個暫存函數Q讀取第一個資料,接着將下一個資料與Q值比,若資料相同,則計數器加1;若資料不同,則將計數器存的數值以及Q值輸出,再初始計數器爲,Q值改爲下一個資料。以此類推,完成資料壓縮。

 

②香農範諾編碼(無損壓縮)【4】:Shannon–Fanocoding,是一種基於一組符號集及其出現的或然率(估量或測量所得),從而構建前綴碼的技術。該編碼將符號從最大可能到最少可能排序,然後將排列好的符號分化爲兩大組,使兩組的概率和近於相同,並各賦予一個二元碼符號“0”和“1”。只要有符號剩餘,以同樣的過程重複這些集合以此確定這些代碼的連續編碼數字。依次下去,直至每一組的只剩下一個信源符號爲止。當一組已經降低到一個符號時,意味着符號的代碼是完整的,不會形成任何其他符號的代碼前綴。

      其算法步驟如下:1)對於一個給定的符號列表,制定概率相應的列表或頻率計數,得到每個符號的相對發生頻率。2)根據頻率的符號列表排序,最常出現的符號在左邊,最少出現的符號在右邊。3)清單分爲兩部分,使左邊部分的總頻率和儘可能接近右邊部分的總頻率和。4)該列表的左半邊分配二進制數字0,右半邊是分配的數字1。5)對左、右半部分遞歸應用步驟3和4,細分羣體,並添加位的代碼,直到每個符號已成爲一個相應的代碼樹的葉。

 

③哈夫曼編碼(無損壓縮)【5】:HuffmanCoding,是一種用於無損數據壓縮的熵編碼(權編碼)算法。它使用變長編碼表對源符號(如文件中的一個字母)進行編碼,其中變長編碼表是通過一種評估來源符號出現機率的方法得到的,出現機率高的字母使用較短的編碼,反之出現機率低的則使用較長的編碼,這便使編碼之後的字符串的平均長度、期望值降低,從而達到無損壓縮數據的目的。同時它又稱最優二叉樹,是一種帶權路徑長度最短的二叉樹。

      其算法步驟如下:1)爲每個符號建立一個葉子節點,並加上其相應的發生頻率。2)當有一個以上的節點存在時,進行下列循環:把這些節點作爲帶權值的二叉樹的根節點,左右子樹爲空;選擇兩棵根結點權值最小的樹作爲左右子樹構造一棵新的二叉樹,且新的二叉樹的根結點的權值爲其左右子樹上根結點的權值之和;把權值最小的兩個根節點移除;將新的二叉樹加入隊列中。3)最後剩下的節點暨爲根節點,此時二叉樹已經完成。

 

④算術編碼(無損壓縮)【6】:ArithmeticCoding,是一種無損數據壓縮方法,也是一種熵編碼的方法。和其它熵編碼方法不同的地方在於,其他的熵編碼方法通常是把輸入的消息分區爲符號,然後對每個符號進行編碼,而算術編碼是直接把整個輸入的消息編碼爲一個數,一個滿足(0.0 ≤ n < 1.0)的小數n。在給定符號集和符號概率的情況下,算術編碼可以給出接近最優的編碼結果。使用算術編碼的壓縮算法通常先要對輸入符號的概率進行估計,然後再編碼。這個估計越準,編碼結果就越接近最優的結果。

      算法步驟舉例如下:1)假設對一個簡單的信號源進行觀察,得到的統計模型如下: 60%的機會出現符號中性,20%的機會出現符號 陽性 ,10%的機會出現符號 陰性 ,10%的機會出現符號數據結束符. (出現這個符號的意思是解碼已完成)。2)則得到符號模型如下:中性對應的區間是[0, 0.6) 陽性對應的區間是[0.6,0.8) 陰性對應的區間是[0.8, 0.9) 數據結束符對應的區間是[0.9,1)。3)假設此時編碼爲0.538,則它落在子區間[0,0.6)中,這向我們提示編碼器所讀的第一個符號必然屬於中立的,這樣我們就可以將它作爲消息的第一個符號記下來。4)然後我們將區間[0,0.6)分成子區間: 中性 的區間是[0, 0.36) -- [0, 0.6)的60% ,陽性 的區間是[0.36, 0.48) -- [0, 0.6)的20% ,陰性 的區間是[0.48, 0.54) -- [0, 0.6)的10% ,數據結束符 的區間是[0.54, 0.6). -- [0, 0.6)的10% ,我們的分數.538在[0.48, 0.54)區間;所以消息的第二個符號一定屬於陰性的。5)我們再一次將當前區間劃分成子區間: 中性 的區間是[0.48, 0.516) 陽性的區間是[0.516, 0.528) 陰性 的區間是[0.528,0.534) 數據結束符 的區間是[0.534, 0.540). 我們的分數.538落在符號數據結束符的區間;所以,這一定是下一個符號。由於它也是內部的結束符號,這也就意味着編碼已經結束。

 

⑤離散餘弦變換編碼(有損壓縮)【7】:Discrete CosineTransform (DCT),是與傅里葉變換相關的一種變換,類似於離散傅里葉變換,但是隻使用實數。離散餘弦變換相當於一個長度大概是它兩倍的離散傅里葉變換,這個離散傅里葉變換是對一個實偶函數進行的(因爲一個實偶函數的傅里葉變換仍然是一個實偶函數),在有些變形裏面需要將輸入或者輸出的位置移動半個單位(DCT有8種標準類型,其中4種是常見的)。最常用的一種離散餘弦變換的類型是下面給出類型,通常我們所說的離散餘弦變換指的就是這種。

     

     

 

⑥K-L轉換(有損壓縮)【8】:Karhunen-Loève Transform,是建立在統計特性基礎上的一種轉換,它是均方差(MSE, Mean Square Error)意義下的最佳轉換,因此在資料壓縮技術中佔有重要的地位。在影像的壓縮上,目的是要將原始的影像檔用較少的資料量來表示,由於大部分的影像並不是隨機的分佈,相鄰的像素間存在一些相關性,如果我們能找到一種可逆轉換(reversible transformation),它可以去除數據的相關性,如此一來就能更有效地儲存資料,由於K-L轉換是一種線性轉換,它可以對輸入的向量x做一個正交變換,使得輸出的向量得以去除數據的相關性,因此可以將它應用在影像的壓縮上。

 

⑦小波壓縮(有損壓縮)【9】:Wavelet-Based Coding,主要的方式是利用離散小波轉換來處理。示例如下:

      

       上圖是二維離散小波轉換的結構圖,輸入信號{\displaystyle {\mathit {x[m,n]}}} ,由結構圖順序可以得到:1)先沿n方向做離散小波轉換得{\displaystyle {\mathit{v_{1,L}[m,n]}}=\sum _{k=0}^{K-1}x[m,2n-k]g[k]}

       2)再沿m方向得{\displaystyle {\mathit{x_{1,L}[m,n]}}=\sum _{k=0}^{K-1}v_{1,L}[2m-k,n]g[k]} {\displaystyle{\mathit {x_{1,H_{1}}[m,n]}}=\sum _{k=0}^{K-1}v_{1,L}[2m-k,n]h[k]}

       上述過程即做完一階二維的離散小波轉換。下圖是將視頻經過處理的結果,可以看出在高頻部分,左下角爲水平方向的邊緣;右上方爲垂直方向的邊緣;右下方爲圖形的角落。而左上角爲低頻,可繼續做小波轉換,分出更粗略、接近原視頻的縮略圖,來達到壓縮效果。

      

 

 

 

三、無損壓縮Huffman編碼和有損壓縮JPEG原理及步驟:

1)無損壓縮Huffman編碼:

       在第二節③中已經詳細介紹過Huffman編碼的原理和步驟,接下來再舉個例子:設已得到文件中每個符號(對應每個字節)的出現概率統計情況爲


      將出現次數多的符號結點作爲左子樹,出現次數少的符號結點作爲右子樹,則可得到如下構造Huffman樹的過程,以及最終每個符號對應編碼的表。 

 

2)有損壓縮JPEG:

       JPEG壓縮是有損壓縮,它利用了人的視角系統的特性,使用量化和無損壓縮編碼相結合來去掉視角的冗餘信息和數據本身的冗餘信息。其編碼過程如下【10】:

      ①色彩空間轉換:首先,視頻由RGB(紅綠藍)轉換爲一種稱爲YUV的不同色彩空間。(Y成分表示一個像素的亮度,U和V成分一起表示色調與飽和度。)這種編碼系統非常有用,因爲人類的眼睛對於亮度差異的敏感度高於色彩變化。使用這種知識,編碼器(encoder)可以被設計得更有效率地壓縮視頻。

      ②縮減取樣(Downsampling):上面所作的轉換使下一步驟變爲可能,也就是減少U和V的成分(也可稱爲"色度抽樣"(chroma subsampling)。在JPEG上這種縮減取樣的比例可以是4:4:4(無縮減取樣),4:2:2(在水平方向2的倍數中取一個),以及最普遍的4:2:0(在水平和垂直方向2的倍數中取一個)。對於壓縮過程的剩餘部分,Y、U、和V都是以非常類似的方式來個別地處理。

      ③離散餘弦變換(Discrete cosine transform):下一步,將視頻中的每個成分(Y, U, V)生成三個區域,每一個區域再劃分成如瓷磚般排列的一個個的8×8子區域,每一子區域使用二維的離散餘弦變換(DCT)轉換到頻率空間。(具體步驟爲將8x8子區域裏的每個值減128,使其範圍變爲-128~127,然後使用離散餘弦變換和舍位取整得到結果。在結果的8x8子區域裏左上角之相當大的數值稱爲DC係數;其他63個值稱爲AC係數。然後對所有8×8表格中的DC係數使用差分編碼,對AC係數使用進程編碼。)

      ④量化(Quantization):人類眼睛在一個相對大範圍區域,容易辨別亮度上細微差異,但是卻難以分辨高頻率亮度變動的確切強度。這讓我們能在高頻率成分上極佳地降低信息的數量。簡單地把頻率領域上每個成分,除以一個對於該成分的常量即可,然後舍位取最接近的整數,這是整個過程中的主要有損運算。以這個結果而言,經常會把很多更高頻率的成分舍位成爲接近0,而剩下很多會變成小的正或負數。一個普遍的量化矩陣是: ,然後使用這個量化矩陣與前面所得到的DCT係數矩陣逐項相除得到結果。

      ⑤熵編碼技術(entropy coding):熵編碼是無損數據壓縮的一個特別形式。它牽涉到將視頻成分以Z字體(zigzag)排列,把相似頻率組羣在一起(矩陣中往左上方向是越低頻率之係數,往右下較方向是較高頻率之係數),插入長度編碼的零,且接着對剩下的使用霍夫曼編碼。 JPEG標準也允許(但是並不要求)在數學上優於霍夫曼編碼的算術編碼之使用。然而,這個特色幾乎很少被使用,因爲它被專利所涵蓋,且它相較於霍夫曼編碼在編碼和解碼上會更慢。使用算術編碼一般會讓文件更小約5%。當剩下的所有係數都是零,對於過早結束的序列,JPEG有一個特別的霍夫曼編碼用詞,EOB。

      至此JPEG壓縮編碼完成,而要使用到壓縮的圖像時則需要進行解碼,即包含反向以上所有操作。

四、實現Huffman編碼和JPEG壓縮編碼(關鍵代碼):

1)Huffman編碼【11】:

       ①首先定義需要建立的Huffman樹結點結構,如下所示:

// 二叉樹結點

struct TreeNode {

        // 頻率

        unsigned long frequency;

        // 深度,編碼長度

        int length;

        // 父結點

        TreeNode * parent;

        // 左結點

        TreeNode * left;

        // 右結點

        TreeNode * right;

        // 字節編碼

        char * code;

        TreeNode() :length(0), frequency(0),code(0), parent(0), left(0), right(0){}

};

 

      ②讀入bmp圖像,使用定義好的緩衝區和其他變量存儲圖像文件頭、長度以及圖像內容,並將圖像內容(即圖像各個像素值)存入各像素值編碼的數組中,每次讀取一個像素值,對應的像素值結點frequency++。

      ③讀取完畢後,首先根據各像素值frequency大小從小到大排序;然後構建樹,從小到大不斷兩兩依次合併,要注意合併之後還需根據frequency插入適當的位置中;最後生成編碼。關鍵代碼如下:

// 編碼

void HuffmanCode::CreateCode()

{

        // 將所有非0次值按指定順序加入臨時隊列

        for (int i = 0; i < 256; i++)

                  if (code[i].frequency > 0)

                           // 將結點按順序加入臨時隊列

                           AddToList(&code[i]);

 

        // 構建樹:取出前兩個合併爲一個結點,並再加入到隊列,直到只有一個結點爲止

        while (NULL != CodeHead&& CodeHead->parent != NULL)

        {

                  // 取出前兩個結點

                 

                  // 合併結點

                 

                  // 增加結點兒子深度

                  AddChildLen(p);

                  // 將結點按順序加入臨時隊列

                  AddToList(p);

        }

        // 構建樹完成,生成編碼

        for (int i = 0; i < 256; i++)

                  if (code[i].frequency > 0)

                           CreateCode(&code[i]);

}

// 生成結點編碼

void HuffmanCode::CreateCode(TreeNode* pNode)

{

        // 沒有,則不需要空間

        …

        // 只有一個節點的情況。即只有一個字符出現過

        if (pNode->length == 0)

        {

                 

        }

        else

        {

                  // 左兒子爲1

                 

                  // 右兒子爲0

                  …

        }

}

      ④在輸出文件中寫入之前存儲好的文件頭、文件長度以及上一步得到的以0、1字符串形式表示的編碼表,即可得到根據huffman編碼完成的無損壓縮圖像。

      ⑤當需要顯示壓縮圖像時,將上述流程逆操作即可。

 

2)JPEG編碼【12】:

      ①首先分離RGB顏色分量,並將其轉換到YUV顏色空間。

      ②將圖像分爲8*8的block,並對每個block進行DCT變換:

%產生一個 8*8 DCT 變換矩陣

T=dctmtx(8);

%進行 DCT 變換 BY BU BV double 類型

BY=blkproc(Y,[88],'P1*x*P2',T,T');

BU=blkproc(U,[88],'P1*x*P2',T,T');

BV=blkproc(V,[88],'P1*x*P2',T,T');  

 

   ③分別聲明低頻分量量化表a和高頻分量量化表b,並使用a對Y分量進行量化,使用b對U、V分量進行量化:

    BY2=blkproc(BY,[8 8],'round(x./P1)',a);

  BU2=blkproc(BU,[8 8],'round(x./P1)',b);

  BV2=blkproc(BV,[8 8],'round(x./P1)',b);

 

      ④分別對三個分量數組使用Z字型順序重新排列,將數組最後一個非0元素捨棄,並加上結束標記,壓縮完成:

%8x8 的塊轉化爲列

y =im2col(x, [8 8], 'distinct'); 

%分塊數

xb =size(y, 2);                  

%按照order的順序排列數據

y =y(order, :);                 

%設置塊尾結束標誌

eob =max(y(:)) + 1;              

r =zeros(numel(y) + size(y, 2), 1);

%計數

count= 0;

%每次處理一個塊,找到最後一個非零元素,加入塊結束標誌

for j =1:xb                      

    …    

end

%刪除r中不需要的元素

r((count+ 1):end) = [];

          

      ⑤當需要顯示壓縮圖像時,將上述流程逆操作即可。

 

五、實驗結果:

1)Huffman編碼:

     

圖1 文件大小

      如圖1所示,t1.bmp爲輸入圖像,t2.bmp爲輸出圖像,可以看到大小都是1,407KB,證明Huffman編碼確實是無損編碼;hfm文件是輸入圖像壓縮後的文件,大小爲1,288KB。因此可以計算得到,Huffman編碼壓縮了大概100KB,壓縮率爲91.5%,沒有失真。

      輸入圖像與輸出圖像如圖2,3所示,因爲是無損壓縮,所以恢復原圖後視覺上也不會有任何變化。

圖2 輸入圖像

圖3 輸出圖像

2)JPEG編碼:

圖4 文件大小

      如圖4所示,t1.bmp是輸入圖像,t2.jpg是輸出圖像,大小分別是1,407KB和49KB,可見JPEG有損壓縮具有非常高的壓縮比,其壓縮比爲3.5%,根據公式 得,失真率SNR爲29.6。

圖5 輸入圖像

圖6 輸出圖像

      輸入圖像與輸出圖像如圖5,6所示,儘管壓縮了將近97%的大小,但視覺上變化並不大,但如果仔細觀察還是可以發現輸出圖像比輸入圖像要淡一些,這也正是在YUV顏色空間上減弱了UV分量所導致的。

 

六、總結:

      從第五節的實驗結果可以看到,JPEG編碼壓縮和解壓縮之後得到的圖像,與原圖差距不大,然而在壓縮率上遠遠超過了Huffman編碼。針對這次實驗結果,我認爲有如下原因:

      1)Huffman編碼屬於無損壓縮,JPEG編碼屬於有損壓縮,在不能失去任何信息的前提下,Huffman編碼只能做到減少冗餘表示這一層,極大地限制了可以被壓縮的空間。而JPEG編碼不受無損的限制,可以在像素顏色分量層面忽略許多高頻細節信息,使得這些細枝末節的部分全部省略,大大地壓縮了表示空間。

      2)在用C++實現Huffman編碼進行圖像壓縮時,沒有在真正意義上將每一個像素值以一個位的形式存儲起來,而是用char(實際是1個字節8位)存儲,這樣就佔據了很多空間。

      3)在用Matlab實現JPEG編碼進行圖像壓縮時,使用了imwrite函數以.jpg形式存儲輸出圖像,可能會使得輸出圖像在通過jpeg編碼後再次用matlab自帶的編碼重新壓縮一遍,於是我在不進行任何處理下直接使用imwrite函數對原圖以.jpg形式儲存,如圖4所示中的directimg.jpg,可以看到只有59KB,這也證明了imwrite函數在對不同格式的圖像文件操作時確實具有壓縮功能。

      儘管有如上原因或多或少干擾了本次實驗結果,但對於無損壓縮和有損壓縮各自的優點和缺點在這次實驗也能稍顯區別:如果想要圖像經過壓縮和解壓縮後能完全保持原圖的所有細節信息,應該選擇無損壓縮,例如在朋友圈曬照片、將圖像作爲壁紙等情況;如果不追求圖像經過壓縮和解壓縮後顯示的質量,而在於存儲空間時,則應該選擇有損壓縮,例如傳輸大量文件、快速顯示當前頁面所有圖像等情況。

 


 

參考文獻

【1】維基百科:圖像壓縮、有損數據壓縮、無損數據壓縮

【2】Li Z N, Drew M S, LiuJ. Fundamentals of multimedia. 2nd ed[J]. Technology Literacy Applications inLearning Environments, 2004.

【3~10】維基百科:遊程編碼、香農-範諾編碼、哈夫曼編碼、算術編碼、離散餘弦變換、K-L 轉換、小波壓縮、JPEG

【11】http://bbs.csdn.net/topics/300165376

【12】http://blog.csdn.net/geekzph/article/details/53105858

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章