ITK配准需要的成分有:图像(包括参考图像和待配准图像)、转换函数、度量、校对机和优化器。上述这些成分在配准过程中都需要被实例化。
第一、成分类型定义
1、对于图像类型定义:
const unsigned int Dimension = 2;
typedef float PixelType;
输入数据的类型通过下面几行表达:
typedef itk::Image< PixelType, Dimension > FixedImageType;
typedef itk::Image< PixelType, Dimension > MovingImageType;
2、把参考图像空间映射到待配准图像空间的转换如下:
typedef itk::TranslationTransform< double, Dimension > TransformType;
3、衡量标准为比较两幅图像的搭配质量。衡量标准通常已经参数化,例如说下面对图像类型的声明:
typedef itk::MeanSquare sImageToImageMetric<FixedImageType, MovingImageType > MetricType;
4、选择校对机的类型,校对机就会对待配准图像在非网格位置的程度进行评估(即为所谓的插值类型):
typedef itk:: LinearInterpolateImageFunction< MovingImageType, double > InterpolatorType;
到此,准备工作已经做好。接着还需要定义配准方法的类型:
配准方法的类型是通过参考和待配准图像的类型来表示的。到目前为止,这类方法在连接我们所有已经描述的成分时是可靠的。
typedef itk::ImageRegistrationMethod< FixedImageType, MovingImageType > RegistrationType;
第二、已经定义的进行相关类型的实例化:
每一个配准要素都是通过它的New( ) 创建的,并且通过各自的itk::SmartPointer 赋值。
度量的实例化: MetricType::Pointer metric = MetricType::New( ); //
转换函数的实例化:TransformType::Pointer transform = TransformType::New( );
优化器Type::Pointer 优化器 = 优化器Type::New( );
插值类型的实例化:InterpolatorType::Pointer interpolator = InterpolatorType::New( );
配准类型实例化:RegistrationType::Pointer registration = RegistrationType::New( );
每一个要素被连接到配准方法的程序中:
registration->SetMetric( metric ); //设置配准方法的衡量标准
registration->SetOptimitor( optimitor); //设置配准方法的优化器
registration->SetTransform( transform ); //设置配准方法的转换函数
registration->SetInterpolator( interpolator ); //设置配准方法的插值类型
第三、图像(参考图像和待配准图像)输入:
在这个例子里,参考和待配准图像从文件里读取。这就需要itk::ImageRegistrationMethod
从readers 的输出中获得它的输入数据。
registration->SetFixedImage( fixedImageReader->GetOutput( ) );
registration->SetMovingImage( movingImageReader->GetOutput( ) );
第四、设置参考图像的配准区域:
如果仅仅考虑一些输入的参考图像的特别区域能够达到要求,配准可能要受到一定的限制。这些区域通过SetFixedImageRegion( ) 方法定义。你可以用这些模型减少配准的计算时间,或者避免不想要的对象出现在图像中。这样的区域通过参考图像的BufferedRegion定义。需要切记的是:在这个区域首先要调用它的Update( ) 方法:
fixedImageReader->Update( );
registration->SetFixedImageRegion( fixedImageReader->GetOutput( )->GetBufferedRegion( ) );
第五、初始化配准类型的参数:
变换参数通过将它们在队列里传递来初始化,这能用来调整开始时引入的变化。在这种情况下,平移变换用于配准。用于变换的参数队列由沿着每一维的方向的平移值构成。设置参数值到零以便将变换初始成恒等变换。注意:队列的结构要求作为一个argument 来传递元素的数量。
typedef RegistrationType::ParametersType ParametersType;
ParametersType initialParameters( transform->GetNumberOfParameters( ) );
initialParameters[0] = 0.0; // Initial offset in mm along X
initialParameters[1] = 0.0; // Initial offset in mm along Y
registration->SetInitialTransformParameters( initialParameters );
第六、准备执行配准方法:
优化器用来驱动配准的执行。ImageRegistrationMethod类协调整体以确保在传递给优化器之前一切都到位了。通常要对优化器的参数进行微调。每一个优化器都有特别的参数,参数要在执行的优化策略里进行解释。这个例子里的优化器一个梯度下降的变量,可以使振幅不要太大。每一次迭代,优化器就会沿着itk::ImageToImageMetric 派生的方向产生一个振幅。振幅的初始长度由用户定义。每次派生方向随机变化,优化器假设局部极值已经被传递并且已经将振幅减为一半。在振幅减半若干次之后,优化器也许将会在参数空间的有限区域内活动。用户可以定义振幅减小到多少可以看成收敛。这就相当于对最后的变换定义了一个精度。
初始振幅的长度用SetMaximumStepLength( ) 定义,收敛域的公差用SetMinimumStepLength( ) 定义:
optimizer->SetMaximumStepLength( 4.00 );
optimizer->SetMinimumStepLength( 0.01 );
这种情况下优化器不会得到我们想要的公差,建立迭代的次数需要谨慎。最大数用
SetNumberOfIterations( )定义:
optimizer->SetNumberO fIterations( 200 );
通过调用Update( ) 函数触发配准程序的执行。如果在初始化或执行配准时出现错误,会出现异常提醒。因此我们放Update( ) 函数在一个try/catch 模块里:
try
{
registration->Update( );
}
catch( itk::ExceptionObject & err )
{
std::cerr << "ExceptionObj ect caught !" << std::endl;
std::cerr << err << std::endl;
return - 1;
}
第七、得到配准结果:
配准程序的结果是一系列定义空间变换的参数序列。最终结果由GetLastTransformParameters( )获得:
ParametersType finalParameters = registration->GetLastTransformParameters( );
在itk::TranslationTransform 里,有一个直接的参数的解释。队列的每一个元素对应着沿着一个空间维度的平移:
const double TranslationAlongX = finalParameters[0];
const double TranslationAlongY = finalParameters[1];
优化器能够询问抵达收敛的迭代的实际次数。GetCurrentIteration( ) 会返回这个值。迭代的次数太多说明也许最大振幅长度太小了,这样不太合适,因为它导致太长的计算时间太长。
const unsigned int numberOfIterations = optimizer->GetCurrentIteration( );
相应地,最后参数集合的图像量规值能用优化器的GetValue( ) 得到:
const double bestValue = optimizer->GetValue( );