數據壓縮的起源要比計算機的起源早得多,數據壓縮技術在計算機技術的萌芽時期就已經被提上了議事日程,軍事科學家、數學家、電子學家一直在研究有關信息如何被高效存儲和傳遞的問題。隨著信息論的產生和發展,數據壓縮也由熱門話題演變成了真正的技術。

數據壓縮可分成兩種類型,一種叫做無損壓縮,另一種叫做有損壓縮。
無 損壓縮是指使用壓縮后的數據進行重構(或者叫做還原,解壓縮),重構后的數據與原來的數據完全相同;無損壓縮用于要求重構的信號與原始信號完全一致的場 合。磁盤文件的壓縮就是一個很常見的例子。根據目前的技術水平,無損壓縮算法一般可以把普通文件的數據壓縮到原來的1/2~1/4。
有損壓縮是指使用壓縮后的數據進行重構,重構后的數據與原來的數據有所不同,但不會讓人對原始資料
表 達的信息造成誤解。有損壓縮適用于重構信號不一定非要和原始信號完全相同的場合。例如,圖像和聲音的壓縮就可以采用有損壓縮,因為其中包含的數據往往多于 我們的視覺系統和聽覺系統所能接收的信息,丟掉一些數據而不至于對聲音或者圖像所表達的意思產生誤解,但可大大提高壓縮比。

壓縮技術大致可以按照以下的方法分類:
?????????????????????????????????????????????????????????????????????????????????????? 壓縮技術
?????????????????????????????????????????????????????????????????????????????????????????????? |
?????????????????????????????????????????????????????????????????????????/------------------------------\
????????????????????????????????????????????????????????通用無損數據壓縮 ????????????多媒體數據壓縮(大多為有損壓縮)
?????????????????????????????????????????????????????????????????????? | ????????????????????????????????????????????????????????|
??????????????????????????????????????????????????????????/----------------\? ?????????????????/------------------------------------\
??????????????????????????????????????????????????基于統計????????? 基于字典???音頻壓縮???????? 圖像壓縮?????????????? 視頻壓縮
?????????????????????????????????????????????????? 模型的壓 ????????模型的壓 ????????| ????????????????????????| ????????????????????????????????|
??????????????????????????????????????????????????縮技術 ????????????縮技術 ????????MP3等 ????/-------------------\????????????AVI
????????????????????????????????????????????????????????| ????????????????????????|???????????????????????????????二值 灰度 彩色 矢量?????? MPEG2等
????????????????????????????????????????????????????/------\ ????????/-------------\????????????????????圖像 圖像 圖像 圖像
????????????????????????????????????????????Huffman 算術 LZ77 LZ78 LZW???????????????????? ?| ????????|???????? ?|????????\?
????????????????????????????????????????????編碼 ????編碼 ????\-------------/????????????????傳真機 FELICS GIF ????????PostScript
????????????????????????????????????????????????| ????????????|???????????????????? | ????????????????????????標準 ????JPEG等 JPEG等 Windows WMF等
???????????????????????????????????UNIX下 ????????? 接近無損???????PKZIP、LHarc、ARJ、
???????????????????????????????????的COMPACT? 壓縮極限????????UNIX下的COMPRESS
???????????????????????????????????程序等???????????? 的高級應用????程序等


通用無損數據壓縮的歷史
科學家在研究中發現,大多數信息的表達都存在著一定的冗余度,通過采用一定的模型和編碼方法,可以
降低這種冗余度。貝爾實驗室的 Claude Shannon 和 MIT 的 R.M.Fano 幾乎同時提出了最早的對符號進行有
效編碼從而實現數據壓縮的 Shannon-Fano 編碼方法。

D.A.Huffman 于1952年第一次發表了他的論文“最小冗余代碼的構造方法”(A Method for the Construction of Minimum Redundancy Codes)。從此,數據壓縮開始在商業程序中實現并被應用在許多技術領域。UNIX 系統上一個壓縮程序 COMPACT 就是 Huffman 0 階自適應編碼的具體實現。80 年代初,Huffman編碼又在CP/M 和DOS 系統中實現,其代表程序叫 SQ。在數據壓縮領域,Huffman 的這一論文事實上開創了數據壓縮技術一個值得回憶的時代,60 年代、70 年代乃至 80 年代的早期,數據壓縮領域幾乎一直被 Huffman 編碼及其分支所壟斷。如果不是后面將要提到的那兩個以色列人,也許我們今天還要在 Huffman編碼的 0 和 1 的組合中流連忘返。

80年代,數學家們不滿足于 Huffman 編碼中的某些致命弱點,他們從新的角度入手,遵循 Huffman 編碼的主導思想,設計出另一種更為精確,更能接近信息論中“熵”極限的編碼方法——算術編碼。憑借算術編碼的精妙設計和卓越表現,人們終于可以向著數據壓 縮的極限前進了。可以證明,算術編碼得到的壓縮效果可以最大地減小信息的冗余度,用最少量的符號精確表達原始信息內容。當然,算術編碼同時也給程序員和計 算機帶來了新的挑戰:要實現和運行算術編碼,需要更為艱苦的編程勞動和更加快速的計算機系統。也就是,在同樣的計算機系統上,算術編碼雖然可以得到最好的 壓縮效果,但卻要消耗也許幾十倍的計算時。這就是為什么算術編碼不能在我們日常使用的壓縮工具中實現的主要原因。

那么,能不能既在壓縮效 果上超越 Huffman,又不增加程序對系統資源和時間的需求呢?我們必須感謝下面將要介紹的兩個以色列人。直到 1977 年,數據壓縮的研究工作主要集中于熵、字符和單詞頻率以及統計模型等方面,研究者們一直在絞盡腦汁為使用Huffman編碼的程序找出更快、更好的改進方 法。1977 年以后,一切都改變了。

1977 年,以色列人 Jacob Ziv 和 Abraham Lempel 發表了論文“順序數據壓縮的一個通用算法”(A Universal Alogrithem for Sequential Data Compression)。1978 年,他們發表了該論文的續篇“通過可變比率編碼的獨立序列的壓縮”(Compression of Individual Sequences via Variable-Rate Coding)。在這兩篇論文中提出的壓縮技術分別被稱為 LZ77 和 LZ78 (不知為什么,作者名字的首字母被倒置了)。簡單地說,這兩種壓縮方法的思路完全不同于從 Shannon 到 Huffman 到算術壓縮的傳統思路,人們將基于這一思路的編碼方法稱作“字典”式編碼。字典式編碼不但在壓縮效果上大大超過了 Huffman,而且,對于好的實現,其壓縮和解壓縮的速度也異常驚人。

1984 年,Terry Welch 發表了名為“高性能數據壓縮技術”(A Technique for High-Performance Data Compression)的論文,描述了他在 Sperry Research Center(現在是Unisys的一部分)的研究成果。他實現了LZ78 算法的一個變種 —— LZW。LZW 繼承了 LZ77 和 LZ78 壓縮效果好、速度快的優點,而且在算法描述上更容易被人們接受(有的研究者認為是由于 Welch 的論文比 Ziv 和 Lempel 的更容易理解),實現也比較簡單。不久,UNIX 上出現了使用 LZW 算法的 Compress 程序,該程序性能優良,并有高水平的文檔,很快成為了 UNIX 世界的壓縮程序標準。緊隨其后的是 MS-DOS 環境下的ARC程序( System Enhancement Associates, 1985 ),還有象 PKWare、PKARC 等仿制品。LZ78和LZW一時間統治了UNIX和DOS兩大平臺。

80 年代中期以后,人們對 LZ77 進行了改進,隨之誕生了一批我們今天還在大量使用的壓縮程序。Haruyasu Yoshizaki(Yoshi)的LHarc和Robert Jung的ARJ是其中兩個著名的例子。LZ77得以和LZ78、LZW一起壟斷當今的通用數據壓縮領域。

目前,基于字典方式的壓縮已經有了一個被廣泛認可的標準,從古老的PKZip到現在的WinZip,特別是隨
著Internet上文件傳輸的流行,ZIP 格式成為了事實上的標準,沒有哪一種通用的文件壓縮、歸檔系統不支
持 ZIP 格式。本章主要介紹目前用得最多和技術最成熟的無損壓縮編碼技術,包括包含霍夫曼(Huffman)編碼、算術編碼、RLE編碼和詞典編碼 。注意有一部分壓縮算法受到美國專利法的保護(例如 LZW 算法的某些部分和高階算術壓縮算法的某些細節等)。

4.1 仙農-范諾與霍夫曼編碼
4.1.1 仙農-范諾(Shannon-Fano)編碼
仙農-范諾編碼算法需要用到下面兩個基本概念:
1. Entropy(熵)
(1) 熵是信息量的度量方法,表示一條信息中真正需要編碼的信息量。事件發生的可能性越小(數學上就
是概率越?。硎灸骋皇录霈F的消息越多。
(2) 某個事件的信息量用Ii=-log2 pi表示(有時稱為surprise), 其中pi為第i個事件的概率,0 pi 1對數以2為底時,熵的單位是"bits"。
2. 信源S的熵
按照仙農(Shannon)的理論,信源S的熵定義為
其中pi是符號si在S中出現的概率;log2(1/ pi)表示包含在si中的信息量,也就是編碼si所需要的位數。
例如,一幅用256級灰度表示的圖像,如果每一個象素點灰度的概率均為pi=1/256,編碼每一個象素點就需
要8位。(最大熵分布)
最小熵分布: 除了一個符號外其余符號的概率全為0,H=0bits.(定義0log20=0)

例如,對下面這條只出現了 a b c 三個字符的字符串:aabbaccbaa,字符串長度為 10,字符 a b c 分
別出現了 5 3 2 次,則 a b c 在信息中出現的概率分別為 0.5 0.3 0.2,他們的熵分別為:
Ea = -log2(0.5) = 1
Eb = -log2(0.3) = 1.737
Ec = -log2(0.2) = 2.322
整條信息的熵也即表達整個字符串需要的位數為:
Ea * 5 + Eb * 3 + Ec * 2 = 14.855 位
如果用計算機中常用的 ASCII 編碼,表示上面的字符串需要整整80位!信息為什么能被壓縮而不丟失原
有的信息內容呢?簡單地講,用較少的位數表示較頻繁出現的符號,這就是數據壓縮的基本準則。(怎樣用 0
1 這樣的二進制數碼表示零點幾個二進制位呢?確實很困難,但不是沒有辦法。一旦找到了準確表示零點幾個
二進制位的方法,就接近無損壓縮的極限了。)
[例4.1] 有一幅40個象素組成的灰度圖像,灰度共有5級,分別用符號A、B、C、D和E表示,40個象素中出
現灰度A的象素數有15個,出現灰度B的象素數有7個,出現灰度C的象素數有7個等等,如表4-01所示。
如果用3個位表示這5個等級的灰度值,也就是每個象素用3位表示(等長編碼),編碼這幅圖像總共需要120
位。
表4-01 符號在圖像中出現的數目
按照仙農理論,這幅圖像的熵為 H(S)=(15/40)×log2(40/15) + (7/40)×log2(40/7) +… + (5/40)
×log2(40/5) = 2.196
這就是說每個符號用2.196位表示,40個象素需用87.84位。
最早闡述和實現這種編碼的是Shannon(1948年)和Fano(1949年),因此被稱為仙農-范諾(Shannon-Fano)算
法。這種方法采用從上到下的方法進行編碼。
首先按照符號出現的頻度或概率排序,例如,A,B,C,D和E,如表4-02所示。
然后使用遞歸方法分成兩個部分,每一部分具有近似相同的次數,如圖4-01所示。
按照這種方法進行編碼得到的總位數為91,實際的壓縮比約為1.3 : 1。
表4-02 Shannon-Fano算法舉例表
符 號 A B C D E
出現的次數 15 7 7 6 5
符號 出現的次數(pi) log2(1/pi) 分配的代碼 需要的位數
A 15 (0.375) 1.4150 00 30
B 7 (0.175) 2.5145 01 14
C 7 (0.175) 2.5145 10 14
D 6 (0.150) 2.7369 110 18
E 5 (0.125) 3.0000 111 15
第 4 頁
4
圖4-01 仙農-范諾算法編碼舉例
4.1.2 霍夫曼(Huffman)編碼
霍夫曼在1952年提出了另一種編碼方法,即從下到上的編碼方法。現以一個具體的例子說明它的編碼步
驟:
(1) 初始化,根據符號概率的大小按由大到小順序對符號進行排序,如表4-03和圖4-02所示。
(2) 把概率最小的兩個符號組成一個節點,如圖4-02中的D和E組成節點P1。
(3) 重復步驟2,得到節點P2、P3和P4,形成一棵“樹”,其中的P4稱為根節點。
(4) 從根節點P4開始到相應于每個符號的“樹葉”,從上到下標上“0”(上枝)或者“1”(下枝),至于哪
個為“1”哪個為“0”則無關緊要,最后的結果僅僅是分配的代碼不同,而代碼的平均長度是相同的。
(5) 從根節點P4開始順著樹枝到每個葉子分別寫出每個符號的代碼,如表4-03所示。
(6) 按照仙農理論,這幅圖像的熵為
H(S)=(15/39)×log2(39/15) + (7/39)×log2(39/7) + … + (5/39)×log2(39/5) = 2.1859
壓縮比1.37:1。
表4-03 霍夫曼編碼舉例
圖4-02 霍夫曼編碼方法
霍夫曼碼的碼長雖然是可變的,但卻不需要另外附加同步代碼(前綴代碼)。例如,碼串中的第1位為0,
那末肯定是符號A,因為表示其他符號的代碼沒有一個是以0開始的,因此下一位就表示下一個符號代碼的第1
符號 出現的次數(pi) log2(1/pi) 分配的代碼 需要的位數
A 15(0.3846) 1.38 0 15
B 7(0.1795) 2.48 100 21
C 6(0.1538) 2.70 101 18
D 6(0.1538) 2.70 110 18
E 5(0.1282) 2.96 111 15
第 5 頁
4
位。同樣,如果出現“110”,那么它就代表符號D。如果事先編寫出一本解釋各種代碼意義的“詞典”,即碼
簿,那么就可以根據碼簿一個碼一個碼地依次進行譯碼。
與仙農-范諾編碼相同,這兩種方法都自含同步碼,在編碼之后的碼串中不需要另外添加標記符號(即在
譯碼時分割符號的特殊代碼)。
采用霍夫曼編碼時有兩個問題值得注意:
①霍夫曼碼沒有錯誤保護功能,在譯碼時,如果碼串中沒有錯誤,那么就能一個接一個地正確譯出代碼。
但如果碼串中有錯誤,哪怕僅僅是1位出現錯誤,不但這個碼本身譯錯,更糟糕的是一錯一大串,全亂了套,
這種現象稱為錯誤傳播(error propagation)。計算機對這種錯誤也無能為力,說不出錯在哪里,更談不上去
糾正它。
②霍夫曼碼是可變長度碼,因此很難隨意查找或調用壓縮文件中間的內容,然后再譯碼,這就需要在存儲
代碼之前加以考慮。
盡管如此,霍夫曼碼還是得到廣泛應用。 霍夫曼編碼方法的編碼效率比仙農-范諾編碼效率高一些。
4.2 算術編碼
算術編碼在圖像數據壓縮標準(如JPEG,JBIG)中扮演了重要的角色。在算術編碼中,消息用0到1之間的實
數進行編碼,算術編碼用到兩個基本的參數:符號的概率和編碼間隔。信源符號的概率決定壓縮編碼的效率,
也決定編碼過程中信源符號的間隔,而這些間隔包含在0到1之間。編碼過程中的間隔決定了符號壓縮后的輸
出。算術編碼器的編碼過程可用下面的例子加以解釋。
[例4.2] 假設信源符號為{00, 01, 10, 11},這些符號的概率分別為{ 0.1, 0.4, 0.2, 0.3 },根據這些
概率可把間隔[0, 1)分成4個子間隔:[0, 0.1), [0.1, 0.5), [0.5, 0.7), [0.7, 1),其中[x,y)表示半開放
間隔,即包含x不包含y。上面的信息可綜合在表4-04中。
表4-04 信源符號,概率和初始編碼間隔
如果二進制消息序列的輸入為:10 00 11 00 10 11 01。編碼時首先輸入的符號是10,找到它的編碼范圍
是[0.5, 0.7)。消息中第二個符號00的編碼范圍是[0, 0.1),因此就取[0.5, 0.7)的第一個十分之一作為新間
隔[0.5, 0.52)。依此類推,編碼第3個符號11時取新間隔為[0.514, 0.52),編碼第4個符號00時,取新間隔為
[0.514, 0.5146),… 。消息的編碼輸出可以是最后一個間隔中的任意數。整個編碼過程如圖4-03所示。
符號 00 01 10 11
概率 0.1 0.4 0.2 0.3
初始編碼間隔 [0, 0.1) [0.1, 0.5) [0.5, 0.7) [0.7, 1)
第 6 頁
4
圖4-03 算術編碼過程舉例
這個例子的編碼和譯碼的全過程分別表示在表4-05和表4-06中。
根據上面所舉的例子,可把計算過程總結如下。
考慮一個有M個符號i=(1,2,…,M)的字符表集,假設概率p( i)=pi,而
。輸入符號用xn表示,第n個子間隔的范圍用
表示。其中l0=0,d0=1和p0=0,ln表示間隔左邊界的值,rn 表示間
隔右邊界的值,dn=rn-ln表示間隔長度。編碼步驟如下:
步驟1:首先在1和0之間給每個符號分配一個初始子間隔,子間隔的長度等于它的概率,初始子間隔的范
圍用I1=[l1,r1)=[ , )表示。令d1=r1-l1,L=l1和R=r1。
步驟2:L和R的二進制表達式分別表示為:

其中ui 和vi 等于“1”或者“0”。
①如果u1
≠v1 ,不發送任何數據,轉到步驟3;
②如果u1=v1,就發送二進制符號u1。
比較u2
和v2:如果u2≠v2 ,不發送任何數據,轉到步驟3;
如果u2=v2,就發送二進制符號u2。

這種比較一直進行到兩個符號不相同為止,然后進入步驟3。
步驟3:n加1,讀下一個符號。假設第n個輸入符號為xn= i,按照以前的步驟把這個間隔分成如下所示的
子間隔:
第 7 頁
4
令L=ln,R=rn 和 dn=rn-ln,然后轉到步驟2。
表4-05 編碼過程
表4-06 譯碼過程
[例4.3] 假設有4個符號的信源,它們的概率如表4-07所示:
表4-07 符號概率
輸入序列為xn: 2, 1, 3,…。它的編碼過程如圖4-04所示,現說明如下。
輸入第1個符號是x1= 2,可知i=2,定義初始間隔=[0.5, 0.75),由此可知
d1=0.25,左右邊界的二進制數分別表示為:L=0.5=0.1(B),R=0.7=0.11… (B) 。按照步驟2,u1=v1,發
送1。因u2≠v2,因此轉到步驟3。
輸入第2個字符x2= 1,i=1,它的子間隔=[0.5, 0.625),由此可
得d2=0.125。左右邊界的二進制數分別表示為:L=0.5=0.100 … (B),R=0.101… (B)。按照步驟2,
u2=v2=0,發送0,而u3和v3不相同,因此在發送0之后就轉到步驟3。
輸入第3個字符,x3= 3,i=3,它的子間隔=[0.59375, 0.609375)
,由此可得d3=0.015625。左右邊界的二進制數分別表示為:L=0.59375=0.10011 (B),R=
步驟 輸入符號 編碼間隔 編碼判決
1 10 [0.5, 0.7) 符號的間隔范圍[0.5, 0.7)
2 00 [0.5, 0.52) [0.5, 0.7)間隔的第一個1/10
3 11 [0.514, 0.52) [0.5, 0.52)間隔的最后三個1/10
4 00 [0.514, 0.5146) [0.514, 0.52)間隔的第一個1/10
5 10 [0.5143, 0.51442) [0.514, 0.5146)間隔的第五個1/10開始,二個1/10
6 11 [0.514384, 0.51442 [0.5143, 0.51442)間隔的最后3個1/10
7 01 [0.5143836, 0.514402) [0.514384, 0.51442)間隔的4個1/10,從第1個1/10開始
8 從[0.5143876, 0.514402)中選擇一個數作為輸出:0.5143876
步驟 間隔 譯碼符號譯碼判決
1 [0.5, 0.7) 10 0.51439在間隔 [0.5, 0.7)
2 [0.5, 0.52) 00 0.51439在間隔 [0.5, 0.7)的第1個1/10
3 [0.514, 0.52) 11 0.51439在間隔[0.5, 0.52)的第7個1/10
4 [0.514, 0.5146) 00 0.51439在間隔[0.514, 0.52)的第1個1/10
5 [0.5143, 0.51442) 10 0.51439在間隔[0.514, 0.5146)的第5個1/10
6 [0.514384, 0.51442) 11 0.51439在間隔[0.5143, 0.51442)的第7個1/10
7 [0.51439, 0.5143948) 01 0.51439在間隔[0.51439, 0.5143948)的第1個1/10
8 譯碼的消息:10 00 11 00 10 11 01
信源符號ai 1 2 3 4
概率pi p1=0.5 p2=0.25 p3=0.125 p4=0.125
初始編碼間隔 [0, 0.5) [0.5, 0.75) [0.75, 0.875) [0.875, 1)
第 8 頁
4
0.609375=0.100111 (B)。按照步驟2,u3=v3=0,u4=v4=1,u5=v5=1,但u6和v6不相同,因此在發送011之后轉到
步驟3。

發送的符號是:10011…。被編碼的最后的符號是結束符號。
圖4-04 算術編碼概念
就這個例子而言,算術編碼器接受的第1位是“1”,它的間隔范圍就限制在[0.5, 1),但在這個范圍里有
3種可能的碼符2, 3和4,因此第1位沒有包含足夠的譯碼信息。在接受第2位之后就變成“10”,它落在
[0.5, 0.75)的間隔里,由于這兩位表示的符號都指向2開始的間隔,因此就可斷定第一個符號是2。在接受
每位信息之后的譯碼情況如下表4-08所示。
表4-08 譯碼過程表
在上面的例子中,我們假定編碼器和譯碼器都知道消息的長度,因此譯碼器的譯碼過程不會無限制地運行
下去。實際上在譯碼器中需要添加一個專門的終止符,當譯碼器看到終止符時就停止譯碼。
在算術編碼中需要注意的幾個問題:
(1) 由于實際的計算機的精度不可能無限長,運算中出現溢出是一個明顯的問題,但多數機器都有16位、
32位或者64位的精度,因此這個問題可使用比例縮放方法解決。
(2) 算術編碼器對整個消息只產生一個碼字,這個碼字是在間隔[0, 1)中的一個實數,因此譯碼器在接受
到表示這個實數的所有位之前不能進行譯碼。
(3) 算術編碼也是一種對錯誤很敏感的編碼方法,如果有一位發生錯誤就會導致整個消息譯錯。
算術編碼可以是靜態的或者自適應的。在靜態算術編碼中,信源符號的概率是固定的。在自適應算術編碼
中,信源符號的概率根據編碼時符號出現的頻繁程度動態地進行修改,在編碼期間估算信源符號概率的過程叫
做建模。需要開發動態算術編碼的原因是因為事先知道精確的信源概率是很難的,而且是不切實際的。當壓縮
消息時,我們不能期待一個算術編碼器獲得最大的效率,所能做的最有效的方法是在編碼過程中估算概率。因
此動態建模就成為確定編碼器壓縮效率的關鍵。
接受的數字 間隔 譯碼輸出
1 [0.5, 1) -
0 [0.5, 0.75) 2
0 [0.5, 0.609375) 1
1 [0.5625, 0.609375) -
1 [0.59375, 0.609375) 3
… … …
第 9 頁
4
4.3 RLE編碼
在一幅圖像中經常包含有許多顏色相同的圖塊。在這些圖塊中,許多行上都具有相同的顏色,或者在一行
上有許多連續的像素都具有相同的顏色值。在這種情況下就不需要存儲每一個像素的顏色值,而僅僅存儲一個
像素的顏色值,以及具有相同顏色的像素數目就可以,或者存儲一個像素的顏色值,以及具有相同顏色值的行
數。這種壓縮編碼稱為行程編碼(run length encoding,RLE),具有相同顏色并且是連續的像素數目稱為行程
長度。
假定有一幅灰度圖像,第n行的像素值如圖4-05所示:
圖4-05 RLE編碼的概念
用RLE編碼方法得到的代碼為:80315084180。代碼中用黑體表示的數字是行程長度,黑體字后面的數字代
表像素的顏色值。例如黑體字50代表有連續50個像素具有相同的顏色值,它的顏色值是8。
對比RLE編碼前后的代碼數可以發現,在編碼前要用73個代碼表示這一行的數據,而編碼后只要用11個代
碼表示代表原來的73個代碼,壓縮前后的數據量之比約為7:1,即壓縮比為7:1。這說明RLE確實是一種壓縮技
術,而且這種編碼技術相當直觀,也非常經濟。
譯碼時按照與編碼時采用的相同規則進行,還原后得到的數據與壓縮前的數據完全相同。
RLE所能獲得的壓縮比有多大,這主要是取決于圖像本身的特點。如果圖像中具有相同顏色的圖像塊越
大,圖像塊數目越少,獲得的壓縮比就越高。反之,壓縮比就越小。
RLE壓縮編碼尤其適用于計算機生成的圖像,對減少圖像文件的存儲空間非常有效。然而,RLE對顏色豐富
的自然圖像就顯得力不從心,在同一行上具有相同顏色的連續像素往往很少,而連續幾行都具有相同顏色值的
連續行數就更少。如果仍然使用RLE編碼方法,不僅不能壓縮圖像數據,反而可能使原來的圖像數據變得更
大。請注意,這并不是說RLE編碼方法不適用于自然圖像的壓縮,相反,在自然圖像的壓縮中還真少不了RLE,
只不過是不能單純使用RLE一種編碼方法,需要和其他的壓縮編碼技術聯合應用。
4.4 詞典編碼
有許多場合,開始時不知道要編碼數據的統計特性,也不一定允許你事先知道它們的統計特性。因此,人
們提出了許許多多的數據壓縮方法,盡可能獲得最大的壓縮比。這些技術統稱為通用編碼技術。詞典編碼
(Dictionary Encoding)技術就屬于這一類。
4.4.1 詞典編碼的思想
詞典編碼(dictionary encoding)的根據是數據本身包含有重復代碼這個特性。例如文本文件和光柵圖像
就具有這種特性。詞典編碼法的種類很多,歸納起來大致有兩類。
第一類詞典算法是企圖查找正在壓縮的字符序列是否在以前輸入的數據中出現過,然后輸出僅僅是指向早
期出現過的字符串的“指針”。這種編碼概念如圖4-06所示。
第 10 頁
4
圖4-06 第一類詞典法編碼概念
這里所指的“詞典”是指用以前處理過的數據來表示編碼過程中遇到的重復部分。這類編碼算法都是以
Abraham Lempel和Jakob Ziv在1977年開發和發表的稱為LZ77算法為基礎的,例如1982年由Storer和Szymanski
改進的稱為LZSS算法 。
第二類詞典算法是企圖從輸入的數據中創建一個“短語詞典(dictionary of the phrases)”,這種短語
不一定是具有具體含義的短語,可以是任意字符的組合。編碼過程中遇到已經在詞典中出現的“短語”時,編
碼器就輸出這個詞典中的短語的“索引號”,而不是短語本身。這個概念如圖4-07所示。
圖4-07 第二類詞典法編碼概念
J.Ziv和A.Lempel在1978年首次發表了介紹這種編碼方法的文章。在他們的研究基礎上,Terry A.Weltch
在1984年發表了改進這種編碼算法的文章,因此把這種編碼方法稱為LZW(Lempel-Ziv Walch)壓縮編碼,在高
速硬盤控制器上 首先應用了這種算法。
4.4.2 LZ77算法
為了更好地說明LZ77算法的原理,首先介紹算法中用到的幾個術語:
(1) 輸入數據流(input stream):要被壓縮的字符序列。
(2) 字符(character):輸入數據流中的基本單元。
(3) 編碼位置(coding position):輸入數據流中當前要編碼的字符位置,指前向緩沖存儲器中的開始字
符。
(4) 前向緩沖存儲器(Lookahead buffer):存放從編碼位置到輸入數據流結束的字符序列的存儲器。
(5) 窗口(window):指包含W個字符的窗口,字符是從編碼位置開始向后數,也就是最后處理的W個字
符 。(滑動窗口)
(6) 指針(pointer):指向窗口中的匹配串的開始位置且含長度的指針。
LZ77編碼算法的核心是查找從前向緩沖存儲器開始的與窗口中最長的匹配串。編碼算法的具體執行步驟如
下:
第 11 頁
4
(1) 把編碼位置設置到輸入數據流的開始位置。
(2) 查找窗口中最長的匹配串。
(3) 以“(Pointer, Length) Character”三元組的格式輸出,其中Pointer是指向窗口中匹配串的指針,
Length表示匹配字符的長度,Characters是前向緩沖存儲器中的不匹配的第1個字符。沒有匹配的字符串時,
輸出“(0, 0) Character”
(4) 如果前向緩沖存儲器不是空的,則把編碼位置和窗口向前移(Length+1)個字符,然后返回到步驟2。
[例4.4] 待編碼的數據流如表4-09所示,編碼過程如表4-10所示?,F作如下說明:
(1) “步驟”欄表示編碼步驟。
(2) “位置”欄表示編碼位置,輸入數據流中的第1個字符為編碼位置1。
(3) “匹配串”欄表示窗口中找到的最長的匹配串。
(4) “字符”欄表示匹配之后在前向緩沖存儲器中的第1個字符。
(5) “輸出”欄以“(Back_chars, Chars_length) Explicit_character”格式輸出。其中,
(Back_chars, Chars_length)是指向匹配串的指針,告訴譯碼器“在這個窗口中向后退Back_chars個字符然后
拷貝Chars_length個字符到輸出”,Explicit_character是真實字符。例如,表4-10中的輸出“(5,2) C”告
訴譯碼器回退5個字符,然后拷貝2個字符“AB”
表4-09待編碼的數據流
表4-10 編碼過程
4.4.3 LZSS算法
LZ77通過輸出真實字符解決了在窗口中出現沒有匹配串的問題,但這個解決方案包含有冗余信息。冗余信
息表現在兩個方面,一是空指針,二是編碼器輸出的字符可能包含在下一個匹配串中的字符。
LZSS算法以比較有效的方法解決這個問題,思想是如果匹配串的長度比指針本身的長度 (最小匹配串長
度)長就輸出指針,否則就輸出真實字符。由于輸出的壓縮數據流中包含有指針和字符本身,為了區分它們就
需要有額外的標志位,即ID位。
LZSS編碼算法的具體執行步驟如下:
(1) 把編碼位置置于輸入數據流的開始位置。
(2) 在前向緩沖存儲器中查找與窗口中最長的匹配串
① Pointer :=匹配串指針。
② Length :=匹配串長度。
(3) 判斷匹配串長度Length是否大于等于最小匹配串長度(Length≥MIN_LENGTH),
如果“是”:輸出指針,然后把編碼位置向前移動Length個字符。
位置 1 2 3 4 5 6 7 8 9
字符 A A B C B B A B C
步驟 位置 匹配串 字符 輸出
1 1 -- A (0,0) A
2 2 A B (1,1) B
3 4 -- C (0,0) C
4 5 B B (2,1) B
5 7 A B C (5,2) C
第 12 頁
4
如果“否”:輸出前向緩沖存儲器中的第1個字符,然后把編碼位置向前移動一個字符。
(4) 如果前向緩沖存儲器不是空的,就返回到步驟2。
[例4.5] 編碼字符串如表4-11所示,編碼過程如表4-12所示。現說明如下:
(1) “步驟”欄表示編碼步驟。
(2) “位置”欄表示編碼位置,輸入數據流中的第1個字符為編碼位置1。
(3) “匹配”欄表示窗口中找到的最長的匹配串。
(4) “字符”欄表示匹配之后在前向緩沖存儲器中的第1個字符。
(5) “輸出”欄的輸出為:
① 如果匹配串本身的長度Length≥MIN_LENGTH,輸出指向匹配串的指針,格式為(Back_chars,
Chars_length)。該指針告訴譯碼器“在這個窗口中向后退Back_chars個字符然后拷貝Chars_length個字符到
輸出”。
② 如果匹配串本身的長度Length≤MIN_LENGTH,則輸出真實的匹配串。
表4-11 輸入數據流
表4-12 編碼過程(MIN_LENGTH = 2)
在相同的計算環境下,LZSS算法比LZ77可獲得比較高的壓縮比,而譯碼同樣簡單。這也就是為什么這種算
法成為開發新算法的基礎,許多后來開發的文檔壓縮程序都使用了LZSS的思想。例如,PKZip, ARJ, LHArc和
ZOO等等,其差別僅僅是指針的長短和窗口的大小等有所不同。
LZSS同樣可以和熵編碼聯合使用,例如ARJ就與霍夫曼編碼聯用,而PKZip則與Shannon-Fano聯用,它的后
續版本也采用霍夫曼編碼。
4.4.4 LZ78算法
在介紹LZ78算法之前,首先說明在算法中用到的幾個術語和符號:
(1) 字符流(Charstream):要被編碼的數據序列。
(2) 字符(Character):字符流中的基本數據單元。
(3) 前綴(Prefix): 在一個字符之前的字符序列。
(4) 綴-符串(String):前綴+字符。
(5) 碼字(Code word):碼字流中的基本數據單元,代表詞典中的一串字符。
(6) 碼字流(Codestream): 碼字和字符組成的序列,是編碼器的輸出。
(7) 詞典(Dictionary): 綴-符串表。按照詞典中的索引號對每條綴-符串(String)指定一個碼字(Code
位置 1 2 3 4 5 6 7 8 9 10 11
字符 A A B B C B B A A B C
步驟 位置 匹配串 輸出
1 1 -- A
2 2 A A
3 3 -- B
4 4 B B
5 5 -- C
6 6 B B (3,2)
7 8 A A B (7,3)
8 11 C C
第 13 頁
4
word)。
(8) 當前前綴(Current prefix):在編碼算法中使用,指當前正在處理的前綴,用符號P表示。
(9) 當前字符(Current character):在編碼算法中使用,指當前前綴之后的字符,用符號C表示。
(10) 當前碼字(Current code word): 在譯碼算法中使用,指當前處理的碼字,用W表示當前碼字,
String.W表示當前碼字的綴-符串。
1. 編碼算法
LZ78的編碼思想是不斷地從字符流中提取新的綴-符串(String),通俗地理解為新“詞條”,然后用“代
號”也就是碼字(Code word)表示這個“詞條”。這樣一來,對字符流的編碼就變成了用碼字(Code word)去替
換字符流(Charstream),生成碼字流(Codestream),從而達到壓縮數據的目的。
在編碼開始時詞典是空的,不包含任何綴-符串(string)。在這種情況下編碼器就輸出一個表示空字符串
的特殊碼字(例如“0”)和字符流中(Charstream)的第一個字符C,并把這個字符C添加到詞典中作為一個由一
個字符組成的綴-符串(string)。在編碼過程中,如果出現類似的情況,也照此辦理。
在詞典中已經包含某些綴-符串(String)之后,如果“當前前綴P +當前字符C”已經在詞典中,就用字符C
來擴展這個前綴,這樣的擴展操作一直重復到獲得一個在詞典中沒有的綴-符串(String)為止。此時就輸出表
示當前前綴P的碼字(Code word)和字符C,并把P+C添加到詞典中,然后開始處理字符流(Charstream)中的下一
個前綴。
LZ78編碼器的輸出是碼字-字符(W,C)對,每次輸出一對到碼字流中,并用字符C擴展與碼字W相對應的綴-
符串(String),生成新的綴-符串(String),然后添加到詞典中。
LZ78編碼的具體算法如下:
步驟1: 在開始時,詞典和當前前綴P都是空的。
步驟2: 當前字符C := 字符流中的下一個字符。
步驟3: 判斷P+C是否在詞典中:
(1) 如果“是”:用C擴展P,讓P := P+C ;
(2) 如果“否”:
① 輸出與當前前綴P相對應的碼字和當前字符C;
② 把字符串P+C 添加到詞典中。
③ 令P := 空值。
(3) 判斷字符流中是否還有字符需要編碼
① 如果“是”:返回到步驟2。
② 如果“否”:若當前前綴P不是空的,輸出相應于當前前綴P的碼字,然后結束編碼。
2. 譯碼算法
在譯碼開始時譯碼詞典是空的,它將在譯碼過程中從碼字流中重構。每當從碼字流中讀入一對碼字-字符
(W,C)對時,碼字就參考已經在詞典中的綴-符串,然后把當前碼字的綴-符串string.W 和字符C輸出到字符流
(Charstream),而把當前綴-符串(string.W+C)添加到詞典中。在譯碼結束之后,重構的詞典與編碼時生成的
詞典完全相同。
LZ78譯碼的具體算法如下:
步驟1: 在開始時詞典是空的。
步驟2: 當前碼字W := 碼字流中的下一個碼字。
步驟3: 當前字符C := 緊隨碼字之后的字符。
步驟4: 把當前碼字的綴-符串(string.W)輸出到字符流(Charstream),然后輸出字符C。
步驟5: 把string.W+C添加到詞典中。
步驟6: 判斷碼字流中是否還有碼字要譯
第 14 頁
4
(1) 如果“是”,就返回到步驟2。
(2) 如果“否”,則結束。
[例4.6] 編碼字符串如表4-13所示,編碼過程如表4-14所示?,F說明如下:
(1) “步驟”欄表示編碼步驟。
(2) “位置”欄表示在輸入數據中的當前位置。
(3) “詞典”欄表示添加到詞典中的綴-符串,綴-符串的索引等于“步驟”序號。
(4) “輸出”欄以(當前碼字W, 當前字符C)簡化為(W, C)的形式輸出。
表4-13 編碼字符串
表4-14 編碼過程
與LZ77相比,LZ78的最大優點是在每個編碼步驟中減少了綴-符串(String)比較的數目,而壓縮率與LZ77
類似。
4.4.5 LZW算法
在LZW算法中使用的術語與LZ78使用的相同,僅增加了一個術語—前綴根(Root),它是由單個字符組成的
綴-符串(String)。在編碼原理上,LZW與LZ78相比有如下差別:
① LZW只輸出代表詞典中的綴-符串(String)的碼字(code word)。這就意味在開始時詞典不能是空的,它
必須包含可能在字符流出現中的所有單個字符,即前綴根(Root)。
② 由于所有可能出現的單個字符都事先包含在詞典中,每次編碼開始時都使用一個字符前綴(onecharacter
prefix),因此在詞典中增加的第1個綴-符串有兩個字符。
現將LZW編碼算法和譯碼算法介紹如下。
1. 編碼算法
LZW編碼是圍繞稱為詞典的轉換表來完成的。這張轉換表存放稱為前綴(Prefix)的字符序列,并為每個表
項分配一個碼字(Code word),或者叫做序號,如表4-15所示。這張轉換表實際上是把8位ASCII字符集進行擴
充,增加的符號用來表示在文本或圖像中出現的可變長度ASCII字符串。擴充后的代碼可用9位、10位、11位、
12位甚至更多的位來表示。Welch的論文中用了12位,12位可以有4096個不同的12位代碼,這就是說,轉換表
有4096個表項,其中256個表項用來存放已定義的字符,剩下3840個表項用來存放前綴(Prefix)。
表4-15 詞典
位置 1 2 3 4 5 6 7 8 9
字符 A B B C B C A B A
步驟 位置 詞典 輸出
1 1 A (0,A)
2 2 B (0,B)
3 3 B C (2,C)
4 5 B C A (3,A)
5 8 B A (2,A)
碼字(Code word) 前綴(Prefix)
1
第 15 頁
4
LZW編碼器(軟件編碼器或硬件編碼器)就是通過管理這個詞典完成輸入與輸出之間的轉換。LZW編碼器的輸
入是字符流(Charstream),字符流可以是用8位ASCII字符組成的字符串,而輸出是用n位(例如12位)表示的碼
字流(Codestream),碼字代表單個字符或多個字符組成的字符串。
LZW編碼器使用了一種很實用的分析(parsing)算法,稱為貪婪分析算法(greedy parsing algorithm)。在
貪婪分析算法中,每一次分析都要串行地檢查來自字符流(Charstream)的字符串,從中分解出已經識別的最長
的字符串,也就是已經在詞典中出現的最長的前綴(Prefix)。用已知的前綴(Prefix)加上下一個輸入字符C也
就是當前字符(Current character)作為該前綴的擴展字符,形成新的擴展字符串——綴-符串(String):
Prefix+C。這個新的綴-符串(String)是否要加到詞典中,還要看詞典中是否存有和它相同的綴-符串String。
如果有,那么這個綴-符串(String)就變成前綴(Prefix),繼續輸入新的字符,否則就把這個綴-符串(String)
寫到詞典中生成一個新的前綴(Prefix),并分配給一個代碼。
LZW編碼算法的具體執行步驟如下:
步驟1: 開始時的詞典包含所有可能的根(Root),而當前前綴P是空的;
步驟2: 當前字符(C) :=字符流中的下一個字符;
步驟3: 判斷綴-符串P+C是否在詞典中
(1) 如果“是”:P := P+C // (用C擴展P) ;
(2) 如果“否”
① 把代表當前前綴P的碼字輸出到碼字流;
② 把綴-符串P+C添加到詞典;
③ 令P := C //(現在的P僅包含一個字符C);
步驟4: 判斷碼字流中是否還有碼字要譯
(1) 如果“是”,就返回到步驟2;
(2) 如果“否”
① 把代表當前前綴P的碼字輸出到碼字流;
② 結束。
LZW編碼算法可用偽碼表示。開始時假設編碼詞典包含若干個已經定義的單個碼字。例如,256個字符的碼
字,用偽碼可以表示成:
… …
193 A
194 B
… …
255
… …
1305 abcdefxyF01234
… …
Dictionary[j] ← all n single-character, j=1, 2, …,n
j ← n+1
Prefix ← read first Character in Charstream
while((C ← next Character)!=NULL)
第 16 頁
4
2. 譯碼算法
LZW譯碼算法中還用到另外兩個術語:
① 當前碼字(Current code word):指當前正在處理的碼字,用cW表示,用string.cW表示當前綴-符串;
② 先前碼字(Previous code word):指先于當前碼字的碼字,用pW表示,用string.pW表示先前綴-符
串。
LZW譯碼算法開始時,譯碼詞典與編碼詞典相同,它包含所有可能的前綴根(roots)。LZW算法在譯碼過程
中會記住先前碼字(pW),從碼字流中讀當前碼字(cW)之后輸出當前綴-符串string.cW,然后把用string.cW的
第一個字符擴展的先前綴-符串string.pW添加到詞典中。
LZW譯碼算法的具體執行步驟如下:
步驟1: 在開始譯碼時詞典包含所有可能的前綴根(Root)。
步驟2: cW := 碼字流中的第一個碼字。
步驟3: 輸出當前綴-符串string.cW到碼字流。
步驟4: 先前碼字pW := 當前碼字cW。
步驟5: 當前碼字cW := 碼字流中的下一個碼字。
步驟6: 判斷當前綴-符串string.cW是否在詞典中
(1) 如果“是”,則:
① 把當前綴-符串string.cW輸出到字符流。
② 把先前綴-符串string.pW + 當前前綴-符串string.cW的第一個字符C添加到詞典。
(2) 如果“否”,則:
① 輸出先前綴-符串string.pW + 先前綴-符串string.pW的第一個字符到字符流,
② 把它添加到詞典中。
步驟7: 判斷碼字流中是否還有碼字要譯
(1) 如果“是”,就返回到步驟4。
(2) 如果“否”, 結束。
LZW譯碼算法可用偽碼表示如下:
Codestream ← cW for Prefix
Dictionary[j] ← all n single-character, j=1, 2, …,n
j ← n+1
cW ← first code from Codestream
Charstream ← Dictionary[cW]
pW ← cW
While((cW ← next Code word)!=NULL)
第 17 頁
4
[例4.7] 編碼字符串如表4-16所示,編碼過程如表4-17所示?,F說明如下:
(1) “步驟”欄表示編碼步驟;
(2) “位置”欄表示在輸入數據中的當前位置;
(3) “詞典”欄表示添加到詞典中的綴-符串,它的索引在括號中;
(4) “輸出”欄表示碼字輸出。
表4-16 被編碼的字符串
表4-17 LZW的編碼過程
表4-18解釋了譯碼過程。每個譯碼步驟譯碼器讀一個碼字,輸出相應的綴-符串,并把它添加到詞典中。
例如,在步驟4中,先前碼字(2)存儲在先前碼字(pW)中,當前碼字(cW)是(4),當前綴-符串string.cW是輸出
(“A B”),先前綴-符串string.pW ("B")是用當前綴-符串string.cW ("A")的第一個字符,其結果("B A")
添加到詞典中,它的索引號是(6)
表4-18 LZW的譯碼過程
LZW算法得到普遍采用,它的速度比使用LZ77算法的速度快,因為它不需要執行那么多的綴-符串比較操
位置 1 2 3 4 5 6 7 8 9
字符 A B B A B A B A C
步驟 位置 詞典 輸出
(1) A
(2) B
(3) C
1 1 (4) A B (1)
2 2 (5) B B (2)
3 3 (6) B A (2)
4 4 (7) A B A (4)
5 6 (8) A B A C (7)
6 -- -- -- (3)
步驟 代碼 詞典 輸出
(1) A
(2) B
(3) C
1 (1) -- -- A
2 (2) (4) A B B
3 (2) (5) B B B
4 (4) (6) B A A B
5 (7) (7) A B A A B A
6 (3) (8) A B A C C
第 18 頁
4
作。對LZW算法進一步的改進是增加可變的碼字長度,以及在詞典中刪除老的綴-符串。在GIF圖像格式和UNIX
的壓縮程序中已經采用了這些改進措施之后的LZW算法。
LZW算法取得了專利,專利權的所有者是美國的一個大型計算機公司—Unisys(優利系統公司),除了商業
軟件生產公司之外,可以免費使用LZW算法。