多媒体技术相关压缩算法

来源:互联网 发布:网络电影有效怎么分账 编辑:程序博客网 时间:2024/05/29 08:39

1、香农编码


这个例子展示了一组字母的香浓编码结构(如图a所示)这五个可被编码的字母有如下出现次数:

Symbol

A

B

C

D

E

Count

15

7

6

6

5

Probabilities

0.38461538

0.17948718

0.15384615

0.15384615

0.12820513

从左到右,所有的符号以它们出现的次数划分。在字母B与C之间划定分割线,得到了左右两组,总次数分别为22,17。 这样就把两组的差别降到最小。通过这样的分割, A与B同时拥有了一个以0为开头的码字, C,D,E的码子则为1,如图b所示。 随后, 在树的左半边,于A,B间建立新的分割线,这样A就成为了码字为00的叶子节点,B的码子01。经过四次分割, 得到了一个树形编码。 如下表所示,在最终得到的树中, 拥有最大频率的符号被两位编码, 其他两个频率较低的符号被三位编码。

符号

A

B

C

D

E

编码

00

01

10

110

111


 

2、哈夫曼编码

 

 

 

 

3、算术编码(理解即可)

算术编码的基本原理是将编码的消息表示成实数0和1之间的一个间隔(Interval),消息越长,编码表示它的间隔就越小,表示这一间隔所需的二进制位就越多。

算术编码用到两个基本的参数:符号的概率和它的编码间隔。信源符号的概率决定压缩编码的效率,也决定编码过程中信源符号的间隔,而这些间隔包含在0到1之间。编码过程中的间隔决定了符号压缩后的输出。

给定事件序列的算术编码步骤如下:

(1)编码器在开始时将“当前间隔” [ L, H) 设置为[0,1)。

(2)对每一事件,编码器按步骤(a)和(b)进行处理

  (a)编码器将“当前间隔”分为子间隔,每一个事件一个。

  (b)一个子间隔的大小与下一个将出现的事件的概率成比例,编码器选择子间隔对应于下一个确切发生的事件相对应,并使它成为新的“当前间隔”。

(3)最后输出的“当前间隔”的下边界就是该给定事件序列的算术编码。

例1:假设信源符号为{A, B, C, D},这些符号的概率分别为{ 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。上面的信息可综合在表03-04-1中。

表03-04-1 信源符号,概率和初始编码间隔

符号

A

B

C

概率

0.1

0.4

0.2

0.3 

初始编码间隔

[0, 0.1)

[0.1, 0.5)

[0.5, 0.7)

[0.7, 1] 

如果二进制消息序列的输入为:C A D A C D B。编码时首先输入的符号是C,找到它的编码范围是[0.5,0.7]。由于消息中第二个符号A的编码范围是[0, 0.1],因此它的间隔就取[0.5, 0.7]的第一个十分之一作为新间隔[0.5,0.52]。依此类推,编码第3个符号D时取新间隔为[0.514, 0.52],编码第4个符号A时,取新间隔为[0.514, 0.5146],…。消息的编码输出可以是最后一个间隔中的任意数。整个编码过程如图03-04-1所示。

 

图03-04-1 算术编码过程举例

 

这个例子的编码和译码的全过程分别表示在表03-04-2和表03-04-3中。 

表03-04-2 编码过程

步骤 

输入符号

编码间隔 

编码判决

1

C

[0.5, 0.7]

符号的间隔范围[0.5, 0.7] 

2

A

[0.5, 0.52]

[0.5, 0.7]间隔的第一个1/10

3

D

[0.514, 0.52]

[0.5, 0.52]间隔的最后一个1/10

4

A

[0.514, 0.5146]

[0.514, 0.52]间隔的第一个1/10

5

C

[0.5143,0.51442]

[0.514, 0.5146]间隔的第五个1/10开始,二个1/10

6

D

[0.514384,0.51442]

[0.5143, 0.51442]间隔的最后3个1/10

7

B

[0.5143836,0.514402]

[0.514384,0.51442]间隔的4个1/10,从第1个1/10开始

8

从[0.5143876, 0.514402]中选择一个数作为输出:0.5143876

表03-04-3 译码过程

步骤 

间隔

译码符号 

译码判决 

1

[0.5, 0.7]

C

0.51439在间隔 [0.5, 0.7)

2

[0.5,0.52]

A

0.51439在间隔 [0.5, 0.7)的第1个1/10

3

[0.514,0.52]

D

0.51439在间隔[0.5, 0.52)的第7个1/10

4

[0.514,0.5146]

A

0.51439在间隔[0.514, 0.52]的第1个1/10

5

[0.5143,0.51442]

C

0.51439在间隔[0.514, 0.5146]的第5个1/10

6

[0.514384,0.51442]

D

0.51439在间隔[0.5143, 0.51442]的第7个1/10

7

[0.51439,0.5143948]

B

0.51439在间隔[0.51439,0.5143948]的第1个1/10

8

译码的消息:C A D A C D B

 

4、游程编码

行程编码的基本原理是:用一个符号值或串长代替具有相同值的连续符号(连续符号构成了一段连续的“行程”。行程编码因此而得名),使符号长度少于原始数据的长度。只在各行或者各列数据的代码发生变化时,一次记录该代码及相同代码重复的个数,从而实现数据的压缩。

常见的游程编码格式包括TGA,Packbits,PCX以及ILBM。

例如:5555557777733322221111111

行程编码为:(5,6)(7,5)(3,3)(2,4)(1,7)。可见,行程编码的位数远远少于原始字符串的位数。

并不是所有的行程编码都远远少于原始字符串的位数,但行程编码也成为了一种压缩工具。

5、LZ77

算法核心是查找从前向缓冲存储器开始的最长匹配串

编码算法的具体执行步骤如下:

把编码位置设置到输入数据流的开始位置

查找窗口中最长匹配串

以(Pointer, Length,Characters)的格式输出,其中Pointer是指向窗口中匹配串的指针,Length表示匹配字符的长度,Characters是前向缓冲存储器中的不匹配的第1个字符,如果没有匹配,输出形式为( 0, 0, new symbol )

如果前向缓冲存储器不是空的,则把编码位置和窗口向前移(Length+1)个字符,然后返回到步骤2

 

 

6、LZ78

LZ78算法的基本思路与LZ77算法类似,也是利用已经处理过的编码信息,但它发生匹配时,不是保存一个三元组,而是一个二元组:匹配位置和不匹配的第一个字符。同时,还要将这个字符串保存到内存中,为此,它需要一个不断增长的编码字串表(字典)。

与LZ77相比,LZ78的最大优点是在每个编码步骤中减少了字符串比较的数目,而压缩率与LZ77类似。

如对符号串“ababcbabaaaaaaa”编码,需要的字典:

 

编码步骤

1 在开始时,词典和当前前缀P都是空的

2 当前字符C:=字符流中的下一个字符

3 判断P+C是否在词典中:

  a.是:用C扩展P,P:= P+C

  b.否:① 输出与P相对应的码字和当前字符C

        ② 把P+C 添加到词典中

        ③ 令P:=空值

4.判断字符流中是否还有字符需要编码

  a.是:返回到步骤2

  b.否:若P非空,输出相应码字,结束

LZ78编码示例

ABBCBCABABCAABCAAB

 

(0,A)(0,B)(2,C)(3,A)(2,A)(4,A)(6,B)

7、LZW

LZW算法-编码原理

Welch modification (Welch 1984)

为了解决在发送新字符时的低效问题

与LZ78的区别

LZW只输出代表词典中的缀-符串(String)的码字(code word),因此开始时词典被初始化为包含可能在字符流出现中的所有单字符

由于所有可能出现的单个字符都事先包含在词典中,每个编码步骤开始时都使用单字符前缀(one-character prefix),因此在词典中搜索的第1个缀-符串有两个字符

LZW算法-编码步骤

1:开始时的词典包含所有单字符,当前前缀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算法-编码示例

 

LZW算法-解码步骤

 


1:在开始译码时词典包含所有单字符

2:cW:=码字流中的第一个码字,输出String.cW到码字流。

3:先前码字pW:=当前码字cW,

4:当前码字cW:=码字流中的下一个码字。

5:判断String.cW是否在词典中

   (1) 是,则:

       ① String.cW输出到字符流

       ② P:=String.pW,C:=Sting.cW的第一个字符。

       ③ 把P+C添加到词典。

   (2) 否,则:

       ① P:=String.pW,C:=String.pW的第一个字符。

       ② 输出P+C到字符流,然后把它添加到词典中。

6: 判断码字流中是否还有码字要译

    (1) 是,就返回到步骤3。

    (2) 否, 结束。


 

1 0
原创粉丝点击