现在的位置: 首页 > 综合 > 正文

LZ77算法

2013年02月02日 ⁄ 综合 ⁄ 共 6555字 ⁄ 字号 评论关闭

随着计算机和网络的高速发展,计算机机群化,网络广域化,人们已经不再局限在一台计算机,局限在一个地点办公,而是多台计算机、多地点同时办公,这样就要采用远程控制和远程机群管理。现在有一些软件实现的远程控制,但是只能控制一台机器,而且受到操作系统的限制。本文中提出了实现远程控制的硬件方法。  在实现过程中,最为主要的部分就是视频信号的传输问题。若要实现远程控制,就要把视频信号数据经过网络传输到控制端主机,因此要把视频模拟信号数字化,也就是模数变换。我们必须保证图像清晰度和分辨率,这样模数变换后的数据量就会很大,我们就不得不考虑网络传输能力的问题。当然局域网内部传输速度很快,但是我们要做到可以在Internet上传输,就面临着数据的压缩问题。

  1 视频信号数据压缩算法原理介绍

  对数据压缩有好多方法,按照能否完全恢复到原来数据的情形来分有2种,即有损压缩和无损压缩。有损压缩比如JEPG压缩,一张位图被压缩成JPEG格式的过程中会丢掉一些数据,这样在解码的时候就不能恢复到原来的那张位图了。而无损压缩,以LZ,LZ77,LZW为代表,压缩前后的数据没有损失。

  1.1 LZ77压缩算法

  LZ77算法是无损压缩算法中的一种,采用词典编码思想,在词典中查找最大匹配字符串来实现压缩,具有快速解压缩和内存消耗小的特点而被广泛采用。现在用得很多的Gzip也是采用LZ77方案的。

  LZ77数据编解码原理的算法如图1和图2所示。

 

 

 

  1.2 JPEG压缩的算法核心DCT〔1〕

  JPEG压缩过程如图3所示。

 

  在JPEG压缩算法中通过离散余弦变换DCT(Discrete Cosine Transformation)去除数据冗余,来达到压缩数据的目的。JPEG采用8×8子块的二维离散余弦变换算法:

 

  DCT变换后矩阵内的某个数值,u,v代表DCT变换后矩阵内某个数值的坐标位置。Syx代表图像数据矩阵内的数个数值,y,x代表图像数据矩阵内某个数值的坐标位置。

  2 实验环境

  实验中采用的是德州仪器(TI)公司的DSP产品TMS320C6000中64系列。从64系列DSP的特点来看适合于高速图像压缩。这种定点DSP内核电压为1.2 V左右,工作频率可达500/600/720 MHz,这样每秒可以执行4000/4800/5760条指令(MIPS)。这种芯片支持那些既要求高性能、高可编程性,又要求低功耗、低价位应用的快速开发。除了高速内核还有一个64信道增强型直接存储器存取Enhanced Direct-Memory-Access(EDMA)能够实现高效输入/输出(I/O);1个16 b和1个64 b外部存储器接口(EMIF)用于高带宽存储器存取;3个多通道缓冲串口McBSP;2个32 b计时器能够记录外部事件;1个HPI(16/32 b)主机接口;1个16 b通用输入/输出(GPIO)引脚,经编程可生成不同CPU中断和EDMA事件。另外6416还有Turbo Decoder Coprocessor(TCP),ViterbiDecoder Coprocessor,UniversalTestand Operations PHYInterface for ATM(UTOPIA)。

  我们可以利用VLIW超长指令集结构。VLIW是一种非常适合图像压缩处理等多媒体应用的结构,他支持指令级并行性,这就使得采用他的DSP可以在单时钟周期内执行多项操作。TI公司提供了可变长度解码和离散余弦变换等图像、视频编解码中固有的算法的汇编语言函数库,从而加快算法的运行,缩短数据压缩时间。

  FPGA也可以作JPGE压缩,但是JPEG压缩属于分割及区域特征提取等不同层次、不同种类的处理。其中有的运算本身结构比较简单,但是数据量大、计算速度要求高。在实时信号处理系统中,低层的信号预处理算法处理的数据量大,对处理速度的要求高,但原理框图如图4所示(参考TI的第三DSP方开发公司Ateme公司的IEK(Imaging Evaluation Kit))。

 

  FPGA与DSP,DSP与ARM之间用的是FIFO,逐次地发送和提取。

  第一步:AD采样,FPGA控制什么时候采样和采样频率等,执行时间T1。

  第二步:FPGA向FIFO1发数据,(此时AD在采样,直到一张图像)数据满了,同时DSP提取数据(DSP会启动DMA把数据送到SDRAM中,直到一张图像)执行时间T2。

  第三步:DSP进行JPEG压缩,压缩后的数据也在同一SDRAM中,执行时间T3。

  第四步:DSP向FIFO2发数据,FIFO满了之后ARM从中提取数据,并发送执行时间T4。

  3 实验中的关键问题

  3.1 JPEG代码优化

  JPEG代码使用了第6版,由Thomas G.Lane.编写JPEG组织提供。如1.2所述,JPEG压缩的核心部分是DCT,他占用了大部分压缩时间,所以要对他进行优化处理来提高效率。这里TI(Texas Instruments)公司提供了图像处理的函数库,在本文中采用62x系列的IMGLIB函数库来仿真,调用了其中的离散余弦变换函数IMG_fdct_8x8()〔2〕。IMG_fdct_8x8()函数采用TI公司DSP汇编语言来实现的,程序代码大小为1 216 B,运行一个8×8DCT只需208个cycles。

 

  比较结果如表1所示(769kB位图)

  3.2 CCS(Code Composer Studio)仿真的内存问题

  图像处理的时候需要比较大的内存空间,这里包括程序空间,数据空间都要很大,一张位图就近1 M左右,所以在仿真的时候就有内存分配问题。程序里面要有专门管理内存的函数,其他函数要通过他来申请内存,使用之后还要释放内存。对于DSP片上和片外的实际内存分配来说,TI公司的汇编器和链接器创建的目标文件采用一种COFF(通用目标文件格式),该目标文件格式更利于模块化编程,为管理代码段和目标系统存储器提供了强有力和灵活的编程方法。我们可以通过编写链接命令文件(.cmd文件)将链接信息放在一个文件中,以便在多次使用同样的链接信息时调用。在命令文件中使用2个十分有用的伪指令Memory和Sections,来指定实际应用中的存储器结构和进行地址的映射。Memory用来指定目标存储器结构,Sections用来控制段的构成与地址分配。同时我们要注意,Build Option菜单里面的Heap和Stacksize的规定,一般来说要规定的相对大一些,防止出现内存溢出问题。

 

  4 试验结果

  4.1 DSPLZ压缩测试

  压缩85.1 kB的BMP图像(再大的图像需要更长的模拟时间,10 min以上或更长)DSPCPU时钟周期是40 ns,25 MHz。压缩比较结果如表2所示。

 

  4.2 DSP和PIIICPU的JPEG数据压缩比较

  TI(Texas Instruments)公司提供了DSPTMS320C6x系列的开发仿真软件CCS2.10(CodeComposer Studio 2.10)。CCS有良好的C语言开发环境,C语言编译器和优化器,还有profile控件可以测出程序中每个函数的执行时间。在VC环境下JPEG代码的运行时间可以通过以下2个函数确定QueryPerformance Counter(&t),Query PerformanceFrequency(&f),这2个函数是VC提供的仅供Windows使用的高精度时间函数,并要求计算机从硬件上支持64位高分辨率性能计数器。

  结果如表3所示(压缩对象:图像大小769 kB,1024×768)。

 

  注明:在CCS环境下,使用代码优化之后速度可以提高2倍左右。

  5 结 语

 

  对于TI公司的TMS320C6000系列的DSP,JPEG的核心算法离散余弦变换有固定的函数库可以有效实现,所以JPEG的压缩速度很快,这样在远程控制端的图像更新速度很容易就可以满足要求。而LZ77压缩算法相比较而言,就不适合于用DSP来压缩,因为他没有用到DSP所具有的特性,因此压缩速度很慢,这样从刷新速度的角度来看,尽管他的压缩图像不失真,但不适合应用于远程控制的应用。从而确定了一种比较好的视频信号压缩问题的解决方案。

 

 

1、从当前压缩位置开始,考察未编码的数据,并试图在滑动窗口中找出最长的匹配字符串,如果找到,则进行步骤 2,否则进行步骤 3。

2、输出三元符号组 ( off, len, c )。其中 off 为窗口中匹配字符串相对窗口边界的偏移,len 为可匹配的长度,c 为下一个字符。然后将窗口向后滑动 len + 1 个字符,继续步骤 1。

3、输出三元符号组 ( 0, 0, c )。其中 c 为下一个字符。然后将窗口向后滑动 len + 1 个字符,继续步骤 1。

我们结合实例来说明。假设窗口的大小为 10 个字符,我们刚编码过的 10 个字符是:abcdbbccaa,即将编码的字符为:abaeaaabaee

我们首先发现,可以和要编码字符匹配的最长串为 ab ( off = 0, len = 2 ), ab 的下一个字符为 a,我们输出三元组:( 0, 2, a )

现在窗口向后滑动 3 个字符,窗口中的内容为:dbbccaaaba

下一个字符 e 在窗口中没有匹配,我们输出三元组:( 0, 0, e )

窗口向后滑动 1 个字符,其中内容变为:bbccaaabae

我们马上发现,要编码的 aaabae 在窗口中存在( off = 4, len = 6 ),其后的字符为 e,我们可以输出:( 4, 6, e )

这样,我们将可以匹配的字符串都变成了指向窗口内的指针,并由此完成了对上述数据的压缩。

解压缩的过程十分简单,只要我们向压缩时那样维护好滑动的窗口,随着三元组的不断输入,我们在窗口中找到相应的匹配串,缀上后继字符 c 输出(如果 off 和 len 都为 0 则只输出后继字符 c )即可还原出原始数据。

当然,真正实现 LZ77 算法时还有许多复杂的问题需要解决,下面我们就来对可能碰到的问题逐一加以探讨。

编码方法

我们必须精心设计三元组中每个分量的表示方法,才能达到较好的压缩效果。一般来讲,编码的设计要根据待编码的数值的分布情况而定。对于三元组的第一个分量——窗口内的偏移,通常的经验是,偏移接近窗口尾部的情况要多于接近窗口头部的情况,这是因为字符串在与其接近的位置较容易找到匹配串,但对于普通的窗口大小(例如 4096 字节)来说,偏移值基本还是均匀分布的,我们完全可以用固定的位数来表示它。

编码 off 需要的位数 bitnum = upper_bound( log2( MAX_WND_SIZE ))

由此,如果窗口大小为 4096,用 12 位就可以对偏移编码。如果窗口大小为 2048,用 11 位就可以了。复杂一点的程序考虑到在压缩开始时,窗口大小并没有达到 MAX_WND_SIZE,而是随着压缩的进行增长,因此可以根据窗口的当前大小动态计算所需要的位数,这样可以略微节省一点空间。

对于第二个分量——字符串长度,我们必须考虑到,它在大多数时候不会太大,少数情况下才会发生大字符串的匹配。显然可以使用一种变长的编码方式来表示该长度值。在前面我们已经知道,要输出变长的编码,该编码必须满足前缀编码的条件。其实 Huffman 编码也可以在此处使用,但却不是最好的选择。适用于此处的好的编码方案很多,我在这里介绍其中两种应用非常广泛的编码。

第一种叫 Golomb 编码。假设对正整数 x 进行 Golomb 编码,选择参数 m,令

b = 2m

q = INT((x - 1)/b)

r = x - qb - 1

则 x 可以被编码为两部分,第一部分是由 q 个 1 加 1 个 0 组成,第二部分为 m 位二进制数,其值为 r。我们将 m = 0, 1, 2, 3 时的 Golomb 编码表列出:

     值 x          m = 0         m = 1         m = 2         m = 3
-------------------------------------------------------------
      1               0           0 0          0 00          0 000
      2              10           0 1          0 01          0 001
      3             110          10 0          0 10          0 010
      4            1110          10 1          0 11          0 011
      5           11110         110 0         10 00          0 100
      6          111110         110 1         10 01          0 101
      7         1111110        1110 0         10 10          0 110
      8        11111110        1110 1         10 11          0 111
      9       111111110       11110 0        110 00         10 000
从表中我们可以看出,Golomb 编码不但符合前缀编码的规律,而且可以用较少的位表示较小的 x 值,而用较长的位表示较大的 x 值。这样,如果 x 的取值倾向于比较小的数值时,Golomb 编码就可以有效地节省空间。当然,根据 x 的分布规律不同,我们可以选取不同的 m 值以达到最好的压缩效果。

对我们上面讨论的三元组 len 值,我们可以采用 Golomb 方式编码。上面的讨论中 len 可能取 0,我们只需用 len + 1 的 Golomb 编码即可。至于参数 m 的选择,一般经验是取 3 或 4 即可。

可以考虑的另一种变长前缀编码叫做 γ 编码。它也分作前后两个部分,假设对 x 编码,令 q = int( log2x ),则编码的前一部分是 q 个 1 加一个 0,后一部分是 q 位长的二进制数,其值等于 x - 2q 。γ编码表如下:

     值 x      γ编码
---------------------
      1         0
      2        10 0
      3        10 1
      4       110 00
      5       110 01
      6       110 10
      7       110 11
      8      1110 000
      9      1110 001
其实,如果对 off 值考虑其倾向于窗口后部的规律,我们也可以采用变长的编码方法。但这种方式对窗口较小的情况改善并不明显,有时压缩效果还不如固定长编码。

对三元组的最后一个分量——字符 c,因为其分布并无规律可循,我们只能老老实实地用 8 个二进制位对其编码。

根据上面的叙述,相信你一定也能写出高效的编码和解码程序了。

另一种输出方式

LZ77 的原始算法采用三元组输出每一个匹配串及其后续字符,即使没有匹配,我们仍然需要输出一个 len = 0 的三元组来表示单个字符。试验表明,这种方式对于某些特殊情况(例如同一字符不断重复的情形)有着较好的适应能力。但对于一般数据,我们还可以设计出另外一种更为有效的输出方式:将匹配串和不能匹配的单个字符分别编码、分别输出,输出匹配串时不同时输出后续字符。

我们将每一个输出分成匹配串和单个字符两种类型,并首先输出一个二进制位对其加以区分。例如,输出 0 表示下面是一个匹配串,输出 1 表示下面是一个单个字符。

之后,如果要输出的是单个字符,我们直接输出该字符的字节值,这要用 8 个二进制位。也就是说,我们输出一个单个的字符共需要 9 个二进制位。

如果要输出的是匹配串,我们按照前面的方法依次输出 off 和 len。对 off,我们可以输出定长编码,也可以输出变长前缀码,对 len 我们输出变长前缀码。有时候我们可以对匹配长度加以限制,例如,我们可以限制最少匹配 3 个字符。因为,对于 2 个字符的匹配串,我们使用匹配串的方式输出并不一定比我们直接输出 2 个单个字符(需要 18 位)节省空间(是否节省取决于我们采用何种编码输出 off 和 len)。

这种输出方式的优点是输出单个字符的时候比较节省空间。另外,因为不强求每次都外带一个后续字符,可以适应一些较长匹配的情况。

抱歉!评论已关闭.