itk::Image 是遵循范型编程思想的类,其类型是由算法行为的类演化而来。
ITK
支持任何像素类型和空间维的图像。
1.创建图像
这部分源代码在 Examples/DataRepresentation/Image/Image.cxx
中。
这个例子阐述了如何人为地创建一个 itk::Image 类,下面是对图像类进行实例化、声明和创建的最简单程序代码。
//首先,必须包含图像类的头文件:
#include “itkImage.h”
//然后必须决定像素的类型和图像的维,用这两个参数对图像类进行实例化。下面是创建一个三维、像素是无符号短字符数据类型的图像的程序:
typedef itk::Image< unsigned short, 3 > ImageType;
//然后就可以调用 New( )操作创建图像并将结果分配到 itk::SmartPointer。
ImageType::Pointer image = ImageType::New( );
在 ITK 中,图像以一个或多个区域组合的形式存在。一个区域是图像的一个子集,并有可能是系统中其他类所占有图像的一部分。一个比较普遍的区域是 LargePossibleRegion是一个完全定义的图像。其他重要的区域还有:BufferedRegion 是内存中图像的一部分;RequestedRegion 是在对图像进行操作时被滤波器或其他类要求的一部分。
如上所述,在 ITK 中人为地创建图像需要对图像进行实例化,并将描述图像的区域与图像结合起来。
一个区域是由两个类来定义的:itk::Index 和 itk::Size 类。与图像结合的图像中的 原始区域 是由 Index 来定义的。区域的延伸或大小是由 Size 来定义的。Index 是由一个 n 维数列来表示的,在拓扑图像结构中这些表示图像最初的像素的数列的元素都是整数。当人为创建图像时,用户就需要定义图像的大小和图像的起始位置。有了这两个参数,就可以选择处理的区域。
图像的起始点是由一个 Index 类定义的,这个类中存放了一个 n 维数列,数列中的元素都是整数,表示图像中各维上最初的像素值。
如上所述,在 ITK 中人为地创建图像需要对图像进行实例化,并将描述图像的区域与图像结合起来。
一个区域是由两个类来定义的:itk::Index 和 itk::Size 类。与图像结合的图像中的 原始区域 是由 Index 来定义的。区域的延伸或大小是由 Size 来定义的。Index 是由一个 n 维数列来表示的,在拓扑图像结构中这些表示图像最初的像素的数列的元素都是整数。当人为创建图像时,用户就需要定义图像的大小和图像的起始位置。有了这两个参数,就可以选择处理的区域。
图像的起始点是由一个 Index 类定义的,这个类中存放了一个 n 维数列,数列中的元素都是整数,表示图像中各维上最初的像素值。
ImageType::IndexType start;//起点位置
start[0] = 0; // first index on X
start[1] = 0; // first index on Y
start[2] = 0; // first index on Z
//区域大小是用一个相同大小的图像数列来表示的(使用 Size 类),数列中的元素是无符号
整数,表示图像像素在各个方向上的延伸。
ImageType::SizeType size;//各方向尺寸
size[0] = 200; // size along X
size[1] = 200; // size along Y
size[2] = 200; // size along Z
//定义了起始地址和图像大小这两个参数就可以创建一个 ImageRegion 对象,这个区域是
用图像的起始地址和大小来初始化的。
ImageType::RegionType region;
region.SetSize( size );
region.SetIndex( start );
最后,这个区域传递给图像对象来定义其延伸和初始地址。SetRegion 方法同时设定了LargePossibleRegion、BufferedRegion 和 RequestedRegion。注意:并未执行任何操作来给图像像素数据分配内存,调用 Allocate( )来进行分配内存,直到给区域分配了足够的内存来存放信息分配都不需要任何指令。
image->SetRegions( region );
image->Allocate( );
实际上,很少直接给图像分配内存和对图像进行初始化,图像通常都是从一个源文件直接读取的,比如从一个文件或从硬件获取数据。
2.从文件中读取图像
ImageType::Pointer image = reader->GetOutput( );
在 Reader
运行之前任何访问图像数据的企图都将产生一个无像素数据的图像,正如未对图像初始化必将导致程序崩溃一样。
3.访问像素数据
本小节的源代码在文件 Examples/DataRepresentation/Image/Image3.cxx
中。
这个例子阐述了 SetPixel( )
和
GetPixel( )方法的用法。这两个方法可以直接访问图像中包含的像素数据。考虑到这两种方法相对缓慢,在高性能的访问需求中不宜使用。图像迭代器是有效访问图像像素数据的合适机制(有关图像迭代器的信息参见第十一章
)
。
图像中每个像素的位置是由一个特定的 index
来区分的。一个
index
是一个整数数列,它定义了像素在图像中位置的坐标值。index
类型可以由图像自动定义并可以被像
itk::Index这样的操作访问。数列的长度必须与对应图像的大小相匹配。
下面的代码是对一个 index
变量的声明并对其成员值进行分配。注意:
index
并不是用智能指针进行访问的,这是因为 index
是不对任何对象共享的轻度对象。这些小对象产生出多样的版本,比使用智能指针机制分享它们更加有效。
下面的代码是对 index
类型实例的声明并进行初始化,以便与其在图像中的像素位置进行联系:
ImageType::IndexType pixelIndex;
pixelIndex[0] = 27; // x position
pixelIndex[1] = 29; // y position
pixelIndex[2] = 37; // z position
用 index
定义了像素位置后,就可以访问图像中像素的内容。
GetPixel( )
方法将可得到像素值。
ImageType::PixelType pixelValue = image->GetPixel( pixelIndex );
SetPixel( )方法可设定像素值。
image->SetPixel( pixelIndex, pixelValue+1 );
请注意 GetPixel( )
使用拷贝来返回像素值,所以该方法不能用来更改图像数据的值。
记住 SetPixel( )
和
GetPixel( )
这两种方法都是低效率的,只能用来调试或支持交互,例如点击鼠标查询像素值。
4.定义原点和间距
本小节的源代码在文件 Examples/DataRepresentation/Image/Image4.cxx
中。
尽管 ITK
可以执行一般图像处理的任务,但是这个平台的基本目的是处理医学图像数据,因此必须强制增加图像额外的信息。特别地,在一些坐标系中同像素和图像之间的物理空间位置相关联的信息极其重要。
图像原点和间距是很多应用的基础。例如配准就是在物理坐标中执行的。在这样的处理中没有定义合适的原点和间距必将导致不一致的结果。没有空间信息的医学图像不能被用于医学诊断、图像分析、特征提取、放射辅助治疗和图像指导手术。换句话说,缺乏空间信息的医学图像不仅仅是无用的,而且是很危险的。
![](https://i-blog.csdnimg.cn/blog_migrate/7ff0b929e8e6a5b64f11e3f471625017.png)
图 4-1
阐述了与
itk::image
相关的几个重要几何概念。在这个图表中,圆卷表示像素的中心。像素值假定为像素中心的单位脉冲函数。像素间距是像素中心之间的距离,在各个坐标方向上可以是不同的。图像原点是图像中第一个像素的坐标。一个像素就是含有数据值的像素中心周围的矩形区域,如图 4-1
中右图所示,它可以被认为是图像网格中的一个单元。图像值的线性内插法就是在以这些像素中心为拐点的 Delaunay 区域中执行的。
图像间距是在一个大小与图像坐标相匹配的 FixedArray
中表示的。为了人为地设定图像的间距,必须创建一个和图像数据类型相一致的数列。然后将这个数列的元素用相邻像素中心的间距来进行初始化。
下面的程序代码阐述了图像类中处理原点和间距的方法:
ImageType::SpacingType spacing;
// Note: measurement units (e.g., mm, inches, etc.) are defined by the application.
spacing[0] = 0.33; // spacing along X
spacing[1] = 0.33; // spacing along Y
spacing[2] = 1.20; // spacing along Z
使用 SetSpacing( )
方法来指向数列。
image->SetSpacing( spacing );
使用GetSpacing( )
方法可以从图像中得到间距信息。这种方法返回一个
FixedArray
的值。
返回对象可以用来读取数列的内容。注:关键字 const
表示数列是不可修改的。
const ImageType::SpacingType& sp = image->GetSpacing( );
std::cout << "Spacing = ";
std::cout << sp[0] << ", " << sp[1] << ", " << sp[2] << std::endl;
图像原点的处理方式和间距是相似的。必须首先分配好合适的尺寸。任何一个组成部分都可以指定为原点坐标。这些坐标和图像中第一个像素的位置相一致,图像可以用物理空间中的任意参考系统,用户必须确保同一个应用下的所有图像都是使用相同的参考系统,这在图像配准应用中极其重要。
下面的代码阐述了初始化图像原点的变量的创建和分配:
ImageType::PointType origin;
origin[0] = 0.0; // coordinates of the first pixel in N-D
origin[1] = 0.0;
origin[2] = 0.0;
image->SetOrigin( origin );
使用 GetOrigin( )
方法也可以从图像中得到原点。这将产生一个返回值,这个返回值可以用来读取数列中的内容。再次提醒注意:关键字 const
表示数列是不可修改的。
const ImageType::PointType& orgn = image->GetOrigin( );
std::cout << "Origin = ";
std::cout << orgn[0] << ", " << orgn[1] << ", " << orgn[2] << std::endl;
图像原点和间距一经初始化,就会以物理空间坐标来正确映射到图像像素。下面的代码阐述了如何将物理空间映射到读取最近像素内容的图像 index
中。
首先,必须声明一个 itk::Point
类型。这个
Point
类型在用来表示坐标的类型和空间大小之上模块化。在这种特殊的情况下,Point
的大小必须和图像的大小相匹配。
typedef itk::Point< double, ImageType::ImageDimension > PointType;
像itk::Index
一样,Point
类是一个相对较小、较简单的对象。由于这个原因,它不用像
ITK中大的数据对象那样引用计数,从而它也不能同itk::SmartPointers
一起操作使用。
Point
对象像其他C++
类实例一样简单进行声明。
Point
一经声明,就可以使用传统的数列符号来访问它的成员。特别地,允许使用[ ]
操作。出于效率原因,在使用
index
访问一个特殊的
Point
成员时并不用执行任何校验限制。用户必须确认index
在有效范围
{0
,
−
1}
内。
PointType point;
point[0] = 1.45; // x coordinate
point[1] = 7.21; // y coordinate
point[2] = 9.28; // z coordinate
图像将使用当前的原点和间距把 point
映射到 index
。必须提供一个
index
对象来接收映射结果。可以用图像类型中定义的 IndexType
来对
index
对象进行实例化:
ImageType::IndexType pixelIndex;
bool isInside = image->TransformPhysicalPointToIndex( point, pixelIndex );
if ( isInside )
{
ImageType::PixelType pixelValue = image->GetPixel( pixelIndex );
pixelValue += 5;
image->SetPixel( pixelIndex, pixelValue );
}
GetPixel( )和
SetPixel( )
方法都是非常低效的访问像素数据的方法,当需要大量访问像素数据时应该使用图像迭代器。