你所能用到的無損壓縮編碼(一)


      這個系列將結合C/C++介紹無損壓縮編碼的實現,正如Charles Petzold在<CODE:Hidden Language of Computer Hardware and Software>里所表達出來的意思一樣,計算機最本質的能力就是將各種信息通過電路的開合轉換成為一系列的數字,然后對其按照一定的規則進行編碼,利用這些編碼記錄一些動作或者數據,完成人們想要的功能。計算機的指令是一種編碼,數據也是一種編碼,正如人類用各自民族特有的符號組成自己的語言一樣,計算機也是依靠着編碼形成了自己的語言。計算機的需要存儲大量的數據,雖然現在的硬盤已經容量越來越大,但是如何存儲更多的內容永遠是計算機科學家不斷追求的一個方向,壓縮編碼就像語言中的簡稱一樣,使用盡量少的空間來存儲和表達完整或者重要的信息。比如在日常生活中你會把電子計算機簡稱為計算機,本來五個字的內容現在只要三個字表達,但是完全沒有改變其所表達的意思,這也是一種壓縮。

     壓縮編碼是一種在計算機中常用的技術,在現代的電腦中基本無處不在,特別是在現在電腦中存儲有大量的圖片,視頻的情況下,壓縮編碼幾乎運用於所有格式的多媒體信息中,使得電腦可以更多的存儲大量豐富的多媒體信息。人們根據從壓縮編碼中是否能完整無誤的恢復出原始信息,又將壓縮編碼分成無損壓縮和有損壓縮兩種,無損壓縮就是可以通過壓縮之后內容完整無誤的恢復出原始信息,而有損壓縮不是說不能恢復出原始信息,而是能夠部分的恢復出原始信息,而這恢復的信息一般都是重要的信息,所謂損失是損失的次要信息,廣泛使用的JPEG格式圖片所采用的算法就是一種有損壓縮的方法,它利用圖像本身相關性很強的性質將原始圖像數據進行有損壓縮之后,然后解壓縮呈現給用戶的還是能夠看到比較完整的原始圖像。截止到現在,已經有很多有損壓縮和無損壓縮編碼技術,對於壓縮編碼的研究也一直是多媒體的一個研究熱點。

     既然是介紹無損壓縮編碼,那么就從最簡單的RLE開始,RLE 全稱Run-Length Encoding,一個接受的比較光的是行程編碼,首先,讓我們看一下RLE在Wiki中的定義:

     Run-length encoding (RLE) is a very simple form of data compression in which runs of data (that is, sequences in which the same data value occurs in many consecutive data elements) are stored as a single data value and count, rather than as the original run. This is most useful on data that contains many such runs: for example, simple graphic images suchas icons, line drawings, and animations. It is not useful with files that don't have many runs as it could greatly increase the file size.

     從定義中可以看出來,所謂的行程編碼就是記錄連續數據的行程(runs)長短和數值本身(data),壓縮之后的編碼分成兩組,value值和count值,value值就是連續出現的data值,count是其連續出現的次數。定義中也說了RLE對重復數據比較集中出現的數據壓縮效果比較好,也就是行程長的數據,所以RLE主要應用於二值圖像之中,如果相同數據連續出現的比較少,那么RLE壓縮的效果將會很不理想,大部分情況下會越壓縮越大。

     下面舉個例子,比如000001111100000,使用RLE進行無損壓縮之后是5 0 5 1 5 0, 可以看到本來使用15個數字的現在使用6個數字就能保存,大大減少了需要存儲的數字的空間,這樣也就是達到了壓縮的目的。但是,反過來,如果數據是0101010101,那么使用RLE壓縮后的結果就是1 0 1 1 1 0 1 1 1 0 1 1 1 0 1 1 1 0 1 1,本來只需要10個空間存儲的數據,現在壓縮之后變成了需要20個空間來存儲,如果是1M的文件的話,那么壓縮之后就變成2M。這也部分解釋了為什么有時候使用壓縮軟件壓縮文件,壓縮之后的文件大小比原始文件大小還要大,所以壓縮算法也不是完全就能實現壓縮功能的,不光是對於RLE,對於大部分壓縮算法,都有出現越壓縮越大的可能。

     上面所敘述的都是壓縮算法的原理,按照這些原理,就能編寫出RLE壓縮算法的程序,但是愛因斯坦說過: “ 從理論上說理論和實踐是一回事,但是從實踐上說他們不是一回事 ” ,所以在使用RLE壓縮的時候,還要處理一個問題,先給大家展示下我的函數:

  

 1 //RLE壓縮算法函數
 2 //輸入:原始數據,原始信息長度,壓縮數據
 3 //輸出:壓縮率
 4 double RLE(int input[],long inputLenth,int output[])
 5 {
 6     int count=1;//統計行程
 7     int j=0;
 8     for(int i=1;i<inputLenth;i++)
 9     {
10         //如果兩個相鄰的數據的值不同,記錄下行程和數值,並重新初始化行程。
11         if(input[i]!=input[i-1])
12         {
13             output[j]=count;
14             output[j+1]=input[i-1];
15             count=1;
16             j+=2;
17         }
18         //如果兩個相鄰的數據的值相同,增加行程長度。
19         else
20         {
21             count++;
22         }
23         //對最后一個輸入數據進行一些特殊處理
24         if(i==inputLenth-1)
25         {
26             output[j]=count;
27             output[j+1]=input[i];
28             j+=2;
29             output[j]=0;
30             
31         }
32         
33     }
34     return (double)j/(double)inputLenth;
35 }

       對於函數的說明見函數的注釋,這里我需要特別說明的主要是兩個方面:

       第一、正常情況下,用戶是無法得知壓縮之后的數據是多長的,所以你也無法預先設定輸出數組的長度,這樣會導致一個問題,在使用壓縮數據時,也無法知道什么時候是壓縮數據的結尾,如果這個問題不得到解決,那么將會導致在解壓縮的時候得到不正確的結果,也就無法達到無損壓縮的目的了,所以設計者在使用RLE時規定如果數據全部壓縮結束,那么在結尾放上一個0,也就是采用一個0行程,因為正確數據壓縮時,行程最小也是1,所以用0表示結束完美的解決了這個問題。

      第二,在處理原始數據最后一個數據的時候,可能會出現兩種情況,倒數第一個數據和倒數第二個數據相同或者不相同,如果相同,那么行程就會加1,這樣就導致了最后一組數據丟失,所以需要對最后一個數進行特殊處理,如果不同,那么會丟失最后一個數據,那么也會丟失最后一個數據,這也就是我的函數中最后一個if的含義。

      在此,我要說明的是,我的這個壓縮函數只是一個粗略的版本,寫出來之后也不願意多細想,加上本人水平也有限,歡迎各位高手對我的函數提出改進意見。

      對於RLE壓縮算法有了了解之后,解壓縮其實很簡單,將壓縮之后的數據每一組每一組的讀出來,第一個是數量,第二個是數值,然后按照規則進行恢復就可以了。下面是我的解壓縮函數:

   

 1 //RLE解壓縮算法函數
 2 //輸入:壓縮數據,壓縮信息長度,原始數據
 3 void RLERecovery(int input[],int inputLenth,int output[])
 4 {
 5     int i=0;
 6     int count=0;
 7     while(i<inputLenth)
 8     {
 9        for(int j=0;j<input[i];j++)
10        {
11            output[j+count]=input[i+1];
12        }
13        //加上行程,保證輸出數組的結果是正確的
14        count+=input[i];
15        i+=2; 
16     }
17  
18 
19 }

       由於RLE主要用於二值圖像,所以在我們的函數中使用二值圖像來測試我們的壓縮算法和解壓縮算法,測試圖像如下:

      

       由於用的是二值圖像,所以一個字節里有含有8個像素點,具體關於bmp圖像的讀寫,請看我的上一篇和上上一篇日志,這里就不再詳述,測試用的代碼如下:

     

int _tmain(int argc, _TCHAR* argv[])
{
     
     //使用C里面的函數獲得文件長度
     string fileName="inputtest.bmp";
     long length=GetFileLength(fileName);
 
     
     ofstream fout("output.txt",ios::binary);
     ifstream fin("inputtest.bmp",ios::binary);
     
     int inputValue;
      
 
     string str;
     int count=0;

     int *input=new int[length*8];
     //暫時將壓縮后的數據長度設置和輸入的一樣,實際上有很多的浪費,由於不知道壓縮后數據的長度
     int *output=new int[length*8];
     //讀入圖像數據
     while(getline(fin,str))
     {
         for(int i=0;i<str.length();i++)
         {
             for(int j=0;j<8;j++)
             {
                input[i*8+j+count]=(((unsigned char)str[i]>>j)&0x01);
             }
         }
         count+=str.length();
     
     }
     
     //輸出壓縮率並且進行壓縮
     cout<<RLE(input,length*8,output)<<endl;
     
     //通過行程0判斷已經壓縮結束
     int j=0;
     while(j%2!=0||output[j]!=0)
     { 
         fout<<output[j]<<" ";
         j++;
     }
     //同時得到j就是壓縮之后數據的長度
 
     
     fin.close();
     fout.close();
     
     /////////////////////////////////////////////////////////////////////////////////////////
     //解壓縮過程
     ofstream newFout("outputtest.bmp",ios::binary);
     ifstream newFin("output.txt");
 
     //為了簡便,就使用j作為壓縮數組的長度
     int *newInput=new int[j];
     int *newOutput=new int[length*8];
     int k=0;
     while(!newFin.eof())
     {
        newFin>>newInput[k];
        k++;
     }
     
     //解壓縮
     RLERecovery(newInput,j,newOutput);
     
     //將像素拼接,恢復原始數據恢復原始數據
     for(k=0;k<length*8;k+=8)
     {
          
         newFout<<(unsigned char)(newOutput[k+7]*128+newOutput[k+6]*64+newOutput[k+5]*32+newOutput[k+4]*16+newOutput[k+3]*8+newOutput[k+2]*4+newOutput[k+1]*2+newOutput[k]);
     } 
     cout<<endl;


    int i;
    cin>>i;
    
    return 0;
}

      輸出后的壓縮文件為一個10kb的文本文件,相比原始圖像的375kb要小很多,而且你仍然可以再采用其余的編碼和程序設計技巧使得這個數據大小更小,

         你也可以通過輸出壓縮率的方式來看算法對某個文件的壓縮效率,在解壓縮方面,通過Ultra-edit,我們也可以看到,解壓縮后的文件和原始文件一致的。

       

         在解壓縮還原原始圖像的時候,需要注意的就是“大端”和“小端”的問題,這個詳細且有興趣的話的可以見我前兩篇文章。

         歡迎各位高手對代碼以及文章提出意見。

  

 

 

     


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM