用于图像处理的 2D 快速小波变换库






4.87/5 (38投票s)
本文演示了 2D 快速小波变换在图像处理中的应用

引言
自从我读博士期间就开始研究小波分析,并在多年间开发了各种 C++ 小波变换库。这个库涉及快速小波变换(FWT)的二维实现。二维 FWT 用于图像处理任务,如图像压缩、去噪和快速缩放。我的目的是设计支持自定义滤波器并且易于使用的二维 FWT 实现。我已为其配备了已知的滤波器系列:Daubechies、Coiflets 和 Biorthogonal 小波。用于压缩任务的小波是 Bior53 和 Bior97。使用 Daub1 滤波器,您可以实现快速二进位图像缩放算法。您还可以尝试其他滤波器进行压缩和去噪,并比较结果,您还可以添加自己的自定义小波。该库的一个优点是重构 FWT 滤波器经过重新排列,使得您只需在频谱上进行卷积运算,这对于您想要 MMX 优化来说非常重要。
背景
您需要对小波变换有基本了解才能熟悉讨论的主题。您可以通过互联网上提供的众多资源,或者从 Matlab Wavelet Toolbox 文档中学习,或者查看这个网站。如果您计划通过派生自己的实现来扩展代码,并且针对特定滤波器进行数值优化,那么具备一些 C++ 抽象基类的经验也是可取的。
Using the Code
该库的 GUI 由我使用 Windows Forms 和 .NET 2.0 Framework 设计,因此任何人都可以立即开始使用它,无需太多麻烦。它提供图像可视化、RGB 颜色空间中的二维FWT
变换和合成。只需解压缩包含\filters目录的二进制文件并运行它。使用“文件”菜单加载图像,然后使用“变换”菜单进行变换。单击FWT2D
RGB 变换后,您将看到一个半透明对话框,您可以在其中选择滤波器(位于\filters目录中)、选择尺度数(通常为 3 用于图像变换)以及去噪阈值(范围从 0 到 128),低于此阈值的所有高频带像素都将被置零。在FWT
库中,此阈值每个尺度会减小 4 倍,这样在重构图像时就不会出现过多的失真。图像帧的标题会显示FWT
频谱中非零系数的百分比。重构后,您还会看到与原始未经变换图像相比的误差(以 dB 为单位)。
C++ 库本身由几个类组成
BaseFWT2D
- 变换的抽象基类FWT2D
- 二维 FWT 的实现,它是BaseFWT2D
的派生类vec1D
- 滤波器的 1D 向量包装器
二维 FWT 变换和合成函数在BaseFWT2D
(trans()
和synth()
)中定义,它们使用私有的虚函数来变换和合成图像的行和列,这些函数在FWT2D
类中实现。我设计的这种实现方式使得任何人都可以为我在背景部分提到的特定滤波器派生出自己的优化版 FWT 变换。我已编写了用于图像压缩的 Bior53、Bior97 的 MMX 优化版本,以及用于快速图像缩放的 Daub1 的实现,并计划将来在此文章之外发布它们。
FWT 库快速入门
要开始使用二维 FWT 库,您只需使用滤波器名称初始化FWT2D
对象,提供其位置的完整路径,在构造函数调用后检查状态,初始化所需单通道图像的width
和height
(您需要安排单独的通道,从 [RGB] 流中复制 R 通道到图像Width
xHeight
缓冲区,同样也复制 G 和 B 通道,您也可以将其转换为 YUV 空间等,但那样您还需要 Y、U 和 V 通道的单独缓冲区),然后继续进行变换和合成。这样,每个类对象都针对特定的分波器和图像通道(您可以为不同通道使用不同的分波器)。
/*
unsigned char* pRchannel = new unsigned char[width * height];
for (unsigned int i = 0; i < width * height; i++) {
//Taking R channels from RGB buffer
pRchannel[i] = pRGB[3*i + 0];
}
*/
unsigned char* pRchannel; //R channel buffer unsigned char 0...255
unsigned int width; //R channel width
unsigned int height; //R channel height
const wchar_t* perr; //error text message returned after status() function
unsigned int status; //numerical error value
unsigned int scales = 3;
unsigned int TH = 20;
FWT2D r_fwt(L"path\\bior97.flt");
r_fwt.init(width, height);
perr = r_fwt.status(status);
if (perr != 0) {
//Output text representation of the error pointed by perr
return;
}
//After initialization you can do multiple trans() and synth() calls
//with upcoming image data for analysis or FWT spectrum for synthesis
r_fwt.trans(pRchannel, scales, TH);
r_fwt.synth(pRchannel);
//and so on ...
//clean up before destroying the object
r_fwt.close();
如果您想在调用trans()
后访问 FWT 频谱进行可视化或熵编码,您可以使用BaseFWT2D
函数getspec()
或getspec2d()
。第一个函数提供一个 1Dchar
指针到频谱,第二个函数提供一个 2Dchar
指针,因此您可以将频谱作为二维数组访问。请注意,原始颜色通道通过减去 128 转换为char
,FWT 频谱也在char
区间内,即 -128...127。
char** pspec = r_fwt.getspec2d();
for (unsigned int j = 0; j < height; j++ ) {
for (unsigned int i = 0; i < width; i++ ) {
char val = pspec[j][i];
}
}
FWT 库详细参考
精美的 1D 向量包装器在vec1D
类中实现。它允许定义数组第一个元素的起始索引。默认情况下,它为 0,就像普通的 C 数组一样,但您也可以定义一个正数,例如 1,以获得类似 Matlab 的数组,或者一个负数,就像小波滤波器那样。如果您定义起始索引为 -3,长度为 6(总元素数),您将使用以下索引访问数组元素:-3、-2、-1、0、1、2。这样,您的数组就围绕 0 对称,这与特定小波滤波器的中心点例如一致。数组数据本身是 16 位对齐的,以便执行 SSE 优化的浮点运算。
//plain C array of 6 elements with indices from 0 to 5 initialized to 0.0f
unsigned int size = 6;
vec1D vec1(size);
vec1(0) = 1.0f; //Set the first element to 1.0
//Initialize the array data with external buffer
unsigned int offset = -3;
float data[] = {-3.0f, -2.0f, -1.0f, 0.0f, 1.0f, 2.0f};
vec1D vec2(size, offset, data);
//Print out the array contents
for (int i = vec2.first(); i <= vec2.last(); i++ ) {
wprintf(L"%i %f\n", i, vec2(i));
}
抽象基类BaseFWT2D
提供了变换和重构图像、获取频谱、转储加载的小波滤波器、检索频谱中尺度数等所有必要功能。首先创建从BaseFWT2D
公有派生的FWT2D
类对象。
-
FWT2D::FWT2D(const w_char* filter_name);
-
FWT2D::FWT2D(const wchar_t* fname, const float* tH, unsigned int thL, int thZ, const float* tG, unsigned int tgL, int tgZ, const float* H, unsigned int hL, int hZ, const float* G, unsigned int gL, int gZ);
使用第一个构造函数,您可以提供外部文件中的滤波器,并指定其完整路径。使用第二个构造函数,您可以从内存缓冲区提供滤波器。tH
和tG
是图像变换的低通和高通滤波器,thL
和tgL
是它们的长度,thZ
和tgZ
是第一个索引值(例如 -3)。这同样适用于H
和G
滤波器,但它们用于从频谱合成图像。
-
const wchar_t* BaseFWT2D::status(int& status);
构造函数后检查状态。status
= 0 表示成功,函数返回NULL
指针,否则检查返回的错误消息。
然后,您需要使用以下函数之一初始化类对象
-
void BaseFWT2D::init(unsigned int width, unsigned int height);
-
void BaseFWT2D::init(char* data, char* tdata, unsigned int width, unsigned int height);
最后一个函数提供了将外部缓冲区提供给类对象data
和tdata
的机会,两者的大小均为width
xheight
。这样,您可以从外部源填充data
缓冲区并继续进行
-
int BaseFWT2D::trans(unsigned int scales, unsigned int th = 0);
如果函数成功返回NULL
,图像将被 FWT 频谱替换在data
缓冲区中,或者如果您尚未初始化库,则返回-1
。
或者使用init(unsigned int width, unsigned int height)
函数进行初始化,并使用两个函数对从外部data
缓冲区提供的图像进行变换
-
int BaseFWT2D::trans(const char* data, unsigned int scales, unsigned int th = 0);
-
int BaseFWT2D::trans(const unsigned char* data, unsigned int scales, unsigned int th = 0);
第一个函数变换char
类型数据缓冲区中的图像,即您的数据已经进行了 DC 移位并且在 -128?127 的范围内。最后一个函数接受无符号char
缓冲区,并使用 MMX 优化的private
函数从中减去 128。如果函数成功,则返回NULL
,否则,如果您尚未初始化库,则返回-1
。请注意,图像数据在变换后保持不变。scales
定义了 FWT 变换的层数,th
是去噪阈值(0...128)。
您可以使用以下函数访问 FWT 频谱
-
char* BaseFWT2D::getspec();
-
char** BaseFWT2D::getspec2d();
使用char*
指针,您将获得一个直接的width
xheight
数组;使用char**
指针,您可以像使用二维数组指针一样,在 (列, 行) 位置访问单个系数。
要重构图像,请使用以下函数
-
int BaseFWT2D::synth();
-
int BaseFWT2D::synth(char* data);
-
int BaseFWT2D::synth(unsigned char* data);
第一个函数仅将图像从频谱重构到类自己的内部缓冲区(可以使用相应的init()
函数从外部提供)。第二个和第三个函数重构图像,并将其复制到data
缓冲区,格式为 char -128...127 或 unsignedchar
0...255。
一旦您创建了类对象并用图像width
和height
对其进行了初始化,您就可以进行任意次数的图像变换和合成。完成类对象的使用后,调用close()
方法并销毁对象。
-
void BaseFWT2D::close();
您可以使用以下函数来获取和设置FWT
的尺度数
-
unsigned int BaseFWT2D::getJ();
-
void BaseFWT2D::setJ(unsigned int j);
最后一个函数提供了在合成之前更改尺度数的可能性,因此如果您有一个具有 3 个尺度的FWT
频谱,您可以将其更改为例如 1,并仅执行一个级别的FWT
合成。
如果您计划自己扩展代码,您需要从BaseFWT2D
类派生自己的类,并为transrows()
、transcols()
、synthrows()
和synthcols()
提供覆盖。您需要精通二维 FWT 分析。如果您想为特定长度的滤波器实现 MMX、SSE 优化版本,这将很有用。如前所述,我计划将来发布 Bior53 和 Bior97 小波滤波器的 SSE 优化以及 Daub1 滤波器的 MMX 优化版本。
关注点
编程中比较繁琐的部分是编写BaseFWT2D::makeHGsynth()
函数,该函数重新排列了合成滤波器系数,使其成为奇偶系数。有了这些,您就可以用简单的卷积运算替换重构过程中选择合成滤波器中的偶数和奇数系数的 2*m 和 2*m+1 操作。