摘 要: JPEG2000是新一代的静态图像压缩标准,而Kakadu是JPEG2000官方建议的实现代码之一,也是目前效率最高的JPEG2000开源代码,然而Kakadu代码移植的高难度性制约了JPEG2000的代码实现。为了实现Kakadu代码在不同平台的移植,针对Kakadu代码结构的高复杂性,分析了Kakadu的系统结构以及参考程序的参数,重点剖析了其代码的具体结构,并以Kakadu_V2.2.3移植到TI TMS320DM642为例,对代码进行移植性分析与实现,为代码在不同平台的实现进行了有益的探索。
关键词
: JPEG2000 Kakadu 代码分析 DM642
中图法分类号: TP311.11
文献标识码:A 文章编号:1006-8961(年) -
Analysis of Kakadu's structure and portability based on
JPEG2000
Abstract JPEG2000 is the next generation of static image compression standard. Kakadu is one of the JPEG2000 implemental codes of which the official proposed, and is also the most efficient codes around the world. However the difficulty of Kakadu's portability restricts code's realization of JPE2000. In order to implement Kakadu's portability on different platforms, this paper aims at the highly complexity of JPEG2000 and its implemental codes-Kakadu, and gives a detailed analysis on Kakadu's architecture, referential appreciation's parameter and the code's specific structure. In the end, this paper gives an analysis on portability and implementation of Kakadu_V2.2.3, so as to give an beneficial research on different platforms. All the analysis and implementation of portability based on TI TMS320DM642.
Key words: JPEG2000, Kakadu, Code Analysis, DM642
1 引言
JPEG2000与传统的JPEG标准有很大的不同,由于其卓越的图像压缩性能和很高的灵活性,被认为在各个领域有着广阔的应用前景。JPEG2000的这些特性主要是来源于小波变换、比特平面编码和算术编码技术。由于这些技术的引入,JPEG2000的算法复杂度也相应提高,且在一定程度上限制了JPEG2000的应用[1]。
Kakadu作为JPEG2000开源代码的开山鼻祖,其作者David Taubman正是JPEG2000核心编码EBCOT[2]的发明者,可见Kakadu在JPEG2000实现代码中的地位。但由于Kakadu是使用标准C++编写的程序,并加入了一些JPEG2000标准所没有的特性,因此其代码复杂性非常高。而且标准C++并不像C语言那样受到各嵌入式系统的广泛支持。以上种种原因表明,Kakadu代码的入门和移植都非常困难。因此对Kakadu代码和移植性的分析就显得非常必要。
2 JPEG2000标准的概述
JPEG2000,即ISO/ITU15444,是由国际标准化组织ISO和国际电信标准化联盟ITU-T于2000年底联合颁布的新一代图像压缩国际标准。JPEG2000的编解码流程[3]如图1所示。
图1 JPEG2000的编解码流程
在JPEG2000编码中,图像的预处理为颜 {MOD}空间的转换和对图像分切片处理。小波变换采用的小波基在JPEG2000标准中统一为Le Gall5/3小波和Daubechies 9/7小波。JPEG2000的量化和JPEG的量化过程基本一致。熵编码是整个JPEG2000编码过程的核心,在JPEG2000采用了David Taubman教授的EBCOT算法。JPEG2000的解码过程仅仅是编码过程的逆过程而已。
3 Kakadu的系统结构
本文所用到的Kakadu版本为Kakadu_V2.2.3。Kakadu核心代码由三个子系统组成:编码参数子系统、压缩数据和结构子系统、处理引擎子系统[4],Kakadu系统结构如图2所示。
其中编码参数子系统处理用于编解码时需要的各种参数。应用程序把编解码参数传送到压缩数据和结构子系统,形成切片、分量切片、分辨率、编码块等JPEG2000的数据块,再传送到处理引擎子系统。处理引擎子系统处理完输入数据后,可选择把数据流保存在内存中或者输出保存到文件里。
图2 Kakadu系统结构图
如图2所示,这三个子系统的通讯是通过一系列的类完成的,如kdu_params、kdu_codestream、kdu_tile等。在Kakadu里,这些相互通讯的类都是接口类,即它仅仅是三个子系统内部对象的接口。因此这些接口类并没有标准的析构函数--销毁接口对于内部对象没有任何意义。这种处理方式可以使开发人员随意调用这些接口类对象,而又不会使内部数据结构受到破坏性影响,有利于整个编解码系统的稳定性。
4 Kakadu代码分析
下面以kakadu标准的编码工程kdu_compress来对Kakadu代码进行剖析。
4.1数据的读入和存放
① 在栈(stack)内建立存放警告和错误信息的缓存空间,并定义当警告和错误发生时,程序的回调函数。它是通过kdu_customize_warnings和kdu_customize_errors这两个函数来定义的。在默认情况下,当警告发生时,程序会记录警告信息并继续运行;当错误发生时,程序记录错误信息并停止运行。
② 程序实例化一个叫kdu_args类的对象来保存命令行参数,并通过kdu_args的成员函数kdu_args::get_first、kdu_args::find、kdu_args::advance来分析该命令行参数,获取输入文件路径、输出文件路径和一些基本的编解码参数,这些基本参数包括质量层数量、质量层码率、切片大小等。
③ 程序将利用上面得到的输出文件路径,实例化两个空的输出文件类的对象:kdu_simple_file_target和jp2_target。而最后的代码流将保存到哪个对象,取决于输出文件设定为原始代码流格式还是标准的jp2格式文件。
④ 程序继续实例化kdu_image_in类的对象,并通过kdu_image_in对象的构造函数kdu_image_in::kdu_image_in()把输入文件保存在系统堆(heap)中,并通过kdu_image_in对象内部私有的in指针返回原始数据地址。
⑤ 系统在分析完命令行参数和读取输入文件的文件头信息后,把这些编解码参数放到siz_params的对象所连接的栈中。由于JPEG2000的参数众多,还有一些默认的参数没有设定,此时程序需要调用siz_params:: finalize()完成整个编码参数子系统的构造。
4.2数据的压缩编码
① 程序首先实例化kdu_codestream类的对象。kdu_codestream的对象是整个程序的核心对象。如前所述,kdu_codestream类是一个接口类,所有的压缩编解码和中间数据都由对象连接。而且由于kdu_codestream仅仅是接口类,在初始化时不能调用其对象的构造函数,此时程序必须调用成员函数kdu_codestream::create完成初始化工作。在初始化时,程序已经把siz_params指向的数据自动保存到kdu_codestream指向的内存中。因此,此时可以销毁siz_params内部对象以节省内存空间。
② 计算各质量层输出的字节数大小。程序根据给定的质量层码率
rate[i](由命令行参数给定)、原始图像每像素所占用的位数
b、原始图像的像素总数
num进行计算。计算公式为:
byte[
i]=
rate[
i]*
num*
b/64
其中
i为质量层的索引号。若没有给定码率,即在无损压缩的情况下,质量层的字节数为0。
③ 判断是否需要率预测。率预测并不是JPEG2000中原有的功能,而是Kakadu中为了加速编解码速度而引入的机制。这是个非常实用强大的功能,其原理是把压缩后率失真优化算法(PCRD-opt)中丢弃数据流的操作提前到位平面编码中进行,既降低了内存,又加快了速度。只有当率预测标记
allow_rate_prediction和最高质量层字节数
byte[max]同时大于零,程序才会调用kdu_codestream::set_max_bytes使能率预测。
④ 对kdu_codestream对象的进行预处理。预处理工作包含两个部分:首先是判断图像是否需要翻转。翻转因子包括:transpose、vflip和hflip,在上述的命令行分析过程中获得。程序通过调用kdu_codestream::change_appearance(transpose,vflip,hflip)来对图像进行翻转。若transpose、vflip、hflip均为零,图像不翻转。其次是根据命令行参数生成感兴趣区域(roi)信息。它通过实例化类create_roi_source和kdu_roi_image的对象完成。注意,这两步预处理工作仅仅是对坐标的表示方式做了改变。
⑤ 创建数据处理引擎。它是通过实例化kdc_flow_control类的对象和调用kdc_flow_control的构造函数来实现的。程序在调用kdc_flow_control::kdc_flow_control()时会自动调用上述的kdu_codestream对象的信息和保存在内存中原始图像数据信息,以判断小波变化的层数、位平面编码的精度等,以创建最小最合适的处理引擎。由于处理引擎的数据量很大,因此程序必须用new操作符在系统堆里面创建。
⑥ 数据编码。由于图像大多是彩 {MOD}图像,既包含了多个图像分量,又包含了多切片。在进行数据编码时,各个切片分量需要独立进行。切片之间是相互独立的,因此有多少个切片,就必须创建多少个处理引擎kdu_flow_control。在分量间切换时,必须调用各个处理引擎的成员函数kdu_flow_control::advance_components()。最后的数据处理是调用kdu_flow_control:: process_components()进行的。这个成员函数kdu_flow_control:: process_components()包括了数据的颜 {MOD}空间转换、小波变换、量化、位平面编码、MQ,即JPEG2000里面的tier1过程。由此可见,Kakadu把大部分的编码操作都封装起来,这对于从事实际产品开发的研发人员来说非常方便。
⑦ tier2的操作。在上述的⑥中,应用程序已经把数据编码成码流,并保存到kdu_codestream对象所连接的内部缓存中。tier2过程,即把这些码流通过压缩后率失真优化算法截断,形成最终的压缩码流。应用程序通过调用kdu_codestream::flush来实现这个非常复杂的算法。对于工程人员来说,只需调用成员函数kdu_codestream::flush,而不用去理会其内部机制。
⑧ 最后应用程序把代码流输出到文件中,并清理使用过的各种缓存、内部对象。如前所述,在Kakadu里面提供给用户的类都是接口类,即没有给出明确的析构函数。因此需要调用kdu_codestream::destroy()来销毁内部对象。而kdu_codestream对象在内部连接到其他各级对象中,因此kdu_codestream::destroy()将销毁所有在栈中建立的对象。但对于质量层字节数等在堆中创建的数组或对象,必须通过delete[]删除。
纵上分析,Kakadu应用程序的压缩编码操作并不像其他编码程序通过把数据顺序地递交到不用的子系统中进行编码。Kakadu创造性地构造了"处理引擎",把数据压缩到这个引擎。这个"处理引擎"是可以按需求裁剪的,因此可以最大限度地降低运行时内存使用率。而Kakadu创造性的率预测机制,大大减少了编码时间。
5 移植性分析
原始的Kakadu代码是基于PC平台的,而越来越多的应用需要把代码移植到嵌入式设备中。因此,本文以Kakadu_V2.2.3移植到TI TMS320DM642为例,对代码进行移植性分析。TMS320DM642是TI公司的第二代高性能超长指令字DSP,其自带的编译器支持标准C语言的全部特性,但并不支持C++的所有特性。如不支持异常处理(exection handling)、不支持模板参数、不支持标准的输入输出流对象(如ostream、istream等)。而在Kakadu中,恰恰大量使用了异常处理、ostream和istream类。DM642编译器支持的C++头文件与VC编译环境的头文件也不一致,也需要用户去手动修改。具体的修改如下:
在原工程的头文件,string.h、assert.h、stdio.h和math.h要分别用CSTRING、CASSERT、CSTDIO、CMATH来代替。而fstream、iostream这一类型的C++头文件,由于istream、ostream已经被改写了,可以直接去掉。
在Kakadu中,对基本输入输出流的使用主要在原始数据的读入和错误、警告信息的输出。对原始数据的读入,是使用了子函数ifstream::read。对于这一部分的ifstream对象,可用FILE类型的文件指针来改写。而在读入图像数据操作时,则直接用std::fopen和std::fseek进行改写。而对于由ostream的对象所保存的错误、警告信息,最简单的方法是把相关的ostream类的代码屏蔽,这虽然不影响程序的运行,但不利于调试。处理这个问题最有效的方法是使用TI公司的实时操作系统DSP/BIOS[5]中的LOG模块带代替ostream对象的相关成员函数。LOG模块是基于实时操作系统的,运行起来更有效率。
对于C++标准的异常处理函数,如try、catch,也可以使用屏蔽的方法处理,其前提是需要用户仔细检查代码,确保程序在运行期间不发生任何意外。此外,最高效的方法是使用DSP/BIOS系统带的软件中断模块。CCS里面使用软件中断非常方便,在C++的代码里面设置了中断入口函数,在DSP/BIOS设置工具中只需设置其中断优先级。
可见,尽管DM642的编码器对C++的支持度不是很好(这也是目前嵌入式编译器的通病),但只要按照具体的硬件对Kakadu做相应修改,仍然能很好地将其移植到非PC平台上。
6 结束语
目前,JPEG2000因其独特的优势受到了业界的关注。JPEG2000在医疗图像、网络传输、保密性方面对JPEG有无可比拟的优势,几乎可以确定会取代JPEG。由于独立开发编解码器所需人力、物力很大,开发周期也很长,大部分公司都选择了代码移植来缩短研发周期,降低成本。而目前可选的JPEG2000开源代码不多,Kakadu几乎是唯一的选择。
现阶段虽然对JPEG2000的研究很多,但大多停留在理论水平,没有涉及到真正的代码实现。即使涉及到代码实现,也没有涉及到最有效率的Kakadu代码。本文给出了Kakadu 标准C++代码的分析流程和在DM642上的移植性分析,对希望从事Kakadu图像处理平台开发的研发人员有指导意义。
[参考文献]
[1] 沈兰荪, 卓力. 小波编码与网络视频传输[M]. 北京:科学出版社, 2005. 121~143.
[2] David Taubman. High Performance Scalable Image Compression With EBCOT[J]. IEEE Transactions on Image Processing, 2000, 19(7): 1158~1170.
[3] A Skodras, C Christopoulos, T Ebrahimi. The JPEG 2000 still image compression standard[J]. IEEE Signal Processing Magazine, 2001. 18(5): 36~58.
[4] David Taubman. Kakadu Survey Documentation. 2001[R]: 3~5.
[5] 李进. 实时操作系统DSP/BIOS在DSP开发中的应用[J]. 微电子技术, 2003, (04): 63~65.
http://blog.csdn.net/kxy_tech/archive/2009/02/24/3931627.aspx