数字图像基础之IPP 图像处理编程基础

2.5 IPP图像处理编程基础

在图像处理方面,Inte IPP库提供了较为全面的支持,因此,熟悉IPP中关于图像处理中的函数的功能是调用IPP进行图像处理的基础。因此,我们特别用一节来具体介绍Intel® IPP中的图像处理函数的一些特定模式,比如,这些函数的参数的用法,意义是什么。举个特殊而且在图像处理中常会出现的例子,在IPP图像处理中,图像数据在内存中的分布会是什么样子的?这些函数应该知道图像数据的每一行的开始地址在什么地方,每个像素占几个字节,以及图像的宽度,高度等等信息。理解这些参数的含义是调用Intel® IPP图像处理的基础。我们在本节的以下内容进行详细介绍。



2.5.1 内存中图像数据的定义

在Intel® IPP对图像的定义中,除了需要图像数据的内存起始地址外,还需要知道图像的大小(行、列各占多少像素)、每一行的跨度(stride,图像每一行占用多少字节的内存空间)等。我们以图像加法为例来进行说明,下面是一段图像加法的代码示例:

在上述函数的参数列表中,每一个图像(两个源图像,一个目标图像)数据都定义了一个数据起始地址(指针),以及内存跨度(stride,即step变量,内存跨度的定义见稍后的介绍)。但是我们看见,却没有对每一个图像定义宽度和高度,而是定义了一个roiSize的变量,该变量描述了处理源图像数据和目标图像数据的范围,我们把这个区域成为焦点区域(ROI,Rectangule Of Interest)。

焦点区域(ROI,rectangule of interest或热区) 基本上大多数的Inte IPP图像处理函数,包括上述的加法实例,都必须要ROI参数来定于图像处理的“区域”,这也是函数名 ippiAdd _32f _C3R 中最后字一个字母“R”的意义。ROI就像是一个模板,只有在该模板范围内的数据才被读写,在很多情况下,ROI即定义了图像的大小(有时也可能按需要只需处理图像中规定的部分区域),在这种情况下,每一个图像都至少需要提供ROI大小的内存数据以进行操作(在上述的例子中,如果某一个源图像或目标图像的内存块大小比roiSize规定的要小,就会引起内存读写错误)。下图所示为一段使用了ROI作为参数的代码:

上述图像赋值后图像的数据如下所示。为了更好的理解使用ROI的原因,我们需要对“行跨度”参数有很好的理解。

行跨度(stride,step) 行跨度是一个非常重要的参数,也是调用IPP函数库进行图像处理必须熟练理解、掌握、运用的一个参数。它定义了一个图像数据每相邻两行之间的跨度,也即相邻两行占用的内存宽度(以字节为单位)。行跨度是每个图像数据不可缺少的一个重要属性。从上面的函数我们可以看出,图像数据的大小有可能比roiSize规定的要大,这个时候,我们就可以从跨度这个属性了解到图像数据的真实宽度是多少,从而保证函数操作的正确性(如果一个图像数据的行跨度的属性错误,在很多情况下进行的操作将都会是不可预料的结果)。

行跨度和图像的宽度通常大小不同(这也是行跨度参数的重要性的原因),其不同的原因在于数据处理效率上的考虑。我们在此做一点说明:由于CPU中缓存(cache)的机制和基于一些内存读写上的考虑,很多基于英特尔体系结构的处理器在读写特殊的内存边界的时候,会有更好的性能,处理速度会更快,比如图像数据的每行起始在内存地址中8(16,或32,取决于特定的处理器)的整数倍的时候,读写的速度会更快,这样就会使得很多的运算能够以更高效率运行。因此,Intel® IPP函数在分配图像内存数据时,如果图像的宽度不是在这些特殊数字的整数倍时,会补充一些位数(padding,补位操作),使得下一行的数据从整数倍边界开始(关于分配的细节,我们在下一节阐述)。这样,在很多情况下,行跨度会大于图像数据的真实宽度,这也是ROI参数在大多数情况下(定义图像真实大小)的意义。

如果我们想处理的不是整幅图像,我们可以对两个变量的具体值进行定义,第一个参数就是要处理部分的起始地址,如图2.18所示,该地址为pData,不是整幅图像的起始地址,而是有一个偏移量,偏移量的计算公式为:

如果图像数据是多字节格式,如(Ipp32f),最后一项还需要除以每个像素占有的空间(以字节为单位)。

图2.18 IPP库中图像数据空间




2.5.2 图像数据的内存分配、赋值与拷贝

Intel® IPP库对图像数据的分配,赋值和拷贝提供了全面的支持。这些数据的操作是熟练理解、掌握Intel® IPP库的基础,因此需要引起足够的重视。

内存分配函数 Intel® IPP提供了针对图像数据的内存分配,我们可以调用ippiMalloc函数实现分配功能。函数形式如下所示(mod的形式请参考手册):

从函数的形式我们可以看出,该函数以图像的行、列数作为参数,同时用pStepBytes作为输出参数,它即为上述定义的行跨度。为了验证该函数的功能,我们做了一些实验,分别输入不同的行,列数,分析其行跨度,在内存边界定义为32的倍数的处理器上,行跨度返回的是:

图2.19 说明了内存分配中的补位操作机制。为了让大家熟悉该内存分配函数的运行机制,我们在实验部分让大家进一步通过实践去理解和掌握。

图2.19 IPP库中图像内存空间分配机制

内存赋值函数 内存赋值函数如下式定义:

从该函数可以看出,我们可以对图像数据的任何部分进行赋值操作。

内存拷贝函数 内存拷贝函数如下式定义:

我们可以看出,拷贝函数可以拷贝不同的跨度的操作,这为特定的需要提供了方便,我们举一个经常用到的实例。

在位图数据结构中,每一行数据也采取了对齐策略。但是其对齐的地址边界是4的倍数,而通常情况下,IPP对齐的倍数是32位的,因此,为了显示IPP函数处理的位图数据,通常需要将IPP处理的位图数据转换成支持位图的格式。举一个300*300大小的RGB位图的例子,在IPP函数中,通常的行跨度为(补了28个字节):

而符合位图格式要求的图像数据的行跨度应为(刚好为4的倍数,补了0字节):

因此,我们需要进行两者之间的转化,可以通过分别将源图像和目的图像的行跨度定义为928和900,IPP图像拷贝函数自动将源图像中每行中对其所补充的28位去掉。将源图像数据拷贝到符合位图格式要求的目标图像数据,以便进一步通过程序来显示图像。



2.5.3 IPP图像处理中的算术、逻辑、阈值和比较运算

在IPP库中,对图像之间的运算提供了较为全面的支持,这些运算有算术运算(Arithmetic operations)、逻辑运算(logic operations)、阈值运算(threshold operations)和比较运算(compare operations)。

在这些运算中,有些运算的法则比较直接,如两个图像间的加法、减法等算术运算,这些运算都是基于两个图像像素之间的加减等基本算术运算,比较容易理解。

而逻辑运算是基于两个图像像素值的位运算(bitwise operations),这些运算包括逻辑与(AND)、非(NOT)、或(OR,inclusive OR)、异或(XOR,eXclusive OR)和移位操作。逻辑运算都是基于整数的运算,而算术运算支持浮点数。

阈值运算也是图像处理中经常会用到的一种运算,它将图像像素的值(单个通道或多个通道)与某一个值(多个通道时可以分别定义每个通道的值)进行比较运算,比较运算通常有:“大于(GT,greater than”)”、“less than(LT)”等,如果源图像中的像素值(input pixel value)满足比较操作的条件,则将输出图像中对应的像素值赋给某一个特定的值,如下面的代码段所示,当像素中的值大于6的时候,我们将其对应的像素值定义为6。

因此,上述程序代码的输出如下所示:

比较运算是图像像素间值的比较(或图像每一个像素与某一个特定值的比较),比较运算和阈值运算中的比较运算一致。在此,需要注意的是该函数的输出是一副单通道的8u数据类型的图像,如果比较的结果为真(true),则输出为一个非0值0xFF(IPP_MAX_8U),否则输出0 。 如下图所示代码,我们将图像值与7进行大于比较。结果如图2.20所示:

图2.20 图:IPP库函数实现图像比较(与7进行比较)生成掩码

关于图像之间的算术、逻辑、阈值及比较运算,在第五章实验一中有一个综合的应用可以参考。IPP中这些运算详细说明请参考ipp使用手册。



2.5.4 一个简单的图像处理编程模板工程

在图像处理的各种应用中,通常需要评价不同处理所带来的效果,需要对处理前后的图像进行显示,同时,为了方便编程,我们提供了一个VS的模板工程(见目录“experiments\IppDemo模板”),该工程是一个Windows的多文档应用程序,可以方便交互与显示。该工程中关于图像的类提供了方便图像数据处理的诸多功能的类图如图2.21所示。

该工程中还有几个实现了特地功能的类,其功能简述如表2.9所示。在基于该模板的基础上,在图像处理实验程序的编写中,除添加菜单和对菜单功能函数进行映射的较少的工作外,可以将注意力和重点集中在对图像的数据进行处理的细节上。后续的很多章节(第二、四、五、六、八章)的实验都是基于此模板工程的基础上进行的,因此,希望读者对该模板比较熟悉。

基于该模板工程,典型的图像处理代码编写的步骤是

  1. 根据必要信息生成目标图像(见代码示例);
  2. 根据源图像和目标图像的信息作为参数调用IPP处理函数;
  3. 显示处理后的目标图像效果(只需调用MakeVisible()函数即可)。

图2.21 图像处理模板工程关键类结构图

表2.9 模板工程关键类及其函数介绍

类名

实现的功能

关键属性或函数

CIppiImage

对图像数据进行了封装

根据图像信息(宽度、高度、通道等)分配图像内存;获取图像的信息。

CIppiImageStore

对文件的读写进行了封装,能读写RAW和BMP文件,实现了两种格式之间的相互转化

实现了两种图像文件格式读写函数

CIppiImageDC

实现了根据图像数据创建Windows内存位图图像,以便进行显示。

Create函数实现了在内存中创建位图,参考其它Windows位图编程技术。

CSampleView

视图类,负责显示功能

OnDraw函数,实现刷新

CSampleDoc

文档内,多继承自类CIppiImage

参考Windows多文档编程。



其中,行跨度的计算代码如下:

内存分配代码如下:

生成图像数据的代码如下:

For more complete information about compiler optimizations, see our Optimization Notice.