介紹如何在 ITK 的多階段(multi-stage)影像對準架構下,組合平移對準(translation registration)與仿射對準(affine registration)。
2D 多階段線性影像對準
以下是使用多階段影像對準架構,將平移轉換(translation transform)與仿射轉換(affine transform)串接之後,進行影像對準的 C++ 範例。
#include <itkImageRegistrationMethodv4.h> #include <itkMattesMutualInformationImageToImageMetricv4.h> #include <itkRegularStepGradientDescentOptimizerv4.h> #include <itkConjugateGradientLineSearchOptimizerv4.h> #include <itkTranslationTransform.h> #include <itkAffineTransform.h> #include <itkCompositeTransform.h> #include <itkImageFileReader.h> #include <itkImageFileWriter.h> #include <itkImageMomentsCalculator.h> #include <itkResampleImageFilter.h> #include <itkCastImageFilter.h> #include <itkCheckerBoardImageFilter.h> #include <itkCommand.h> // 觀察階段或層級改變的 Observer template <typename TRegistration> class RegistrationInterfaceCommand : public itk::Command { public: using Self = RegistrationInterfaceCommand; using Superclass = itk::Command; using Pointer = itk::SmartPointer<Self>; itkNewMacro(Self); protected: RegistrationInterfaceCommand() = default; public: using RegistrationType = TRegistration; // 可接受 const 物件的包裝函數 void Execute(itk::Object * object, const itk::EventObject & event) override { Execute((const itk::Object *)object, event); } void Execute(const itk::Object * object, const itk::EventObject & event) override { if (!(itk::MultiResolutionIterationEvent().CheckEvent(&event))) { return; } std::cout << "\nObserving from class " << object->GetNameOfClass(); if (!object->GetObjectName().empty()) { std::cout << " \"" << object->GetObjectName() << "\"" << std::endl; } const auto * registration = static_cast<const RegistrationType *>(object); if (registration == nullptr) { itkExceptionMacro(<< "Dynamic cast failed, object of type " << object->GetNameOfClass()); } unsigned int currentLevel = registration->GetCurrentLevel(); typename RegistrationType::ShrinkFactorsPerDimensionContainerType shrinkFactors = registration->GetShrinkFactorsPerDimension(currentLevel); typename RegistrationType::SmoothingSigmasArrayType smoothingSigmas = registration->GetSmoothingSigmasPerLevel(); std::cout << "-------------------------------------" << std::endl; std::cout << " Current multi-resolution level = " << currentLevel << std::endl; std::cout << " shrink factor = " << shrinkFactors << std::endl; std::cout << " smoothing sigma = " << smoothingSigmas[currentLevel] << std::endl; std::cout << std::endl; } }; // 觀察影像對準疊代過程的 Observer class CommandIterationUpdate : public itk::Command { public: using Self = CommandIterationUpdate; using Superclass = itk::Command; using Pointer = itk::SmartPointer<Self>; itkNewMacro(Self); protected: CommandIterationUpdate() = default; public: using OptimizerType = itk::GradientDescentOptimizerv4Template<double>; using OptimizerPointer = const OptimizerType *; void Execute(itk::Object * caller, const itk::EventObject & event) override { Execute((const itk::Object *)caller, event); } void Execute(const itk::Object * object, const itk::EventObject & event) override { auto optimizer = static_cast<OptimizerPointer>(object); if (optimizer == nullptr) { return; // Optimizer 類型錯誤,不做任何處理 } if (!(itk::IterationEvent().CheckEvent(&event))) { return; } std::cout << optimizer->GetCurrentIteration() << " "; std::cout << optimizer->GetValue() << " "; std::cout << optimizer->GetCurrentPosition() << " " << m_CumulativeIterationIndex++ << std::endl; } private: unsigned int m_CumulativeIterationIndex{ 0 }; }; int main(int argc, char * argv[]) { if (argc < 4) { std::cerr << "Missing Parameters " << std::endl; std::cerr << "Usage: " << argv[0]; std::cerr << " fixedImageFile movingImageFile outputImagefile"; std::cerr << " [checkerboardbefore] [CheckerBoardAfter]" << std::endl; return EXIT_FAILURE; } constexpr unsigned int Dimension = 2; using PixelType = float; using FixedImageType = itk::Image<PixelType, Dimension>; using MovingImageType = itk::Image<PixelType, Dimension>; // 第一階段影像對準:Translation 轉換 using TTransformType = itk::TranslationTransform<double, Dimension>; using TOptimizerType = itk::RegularStepGradientDescentOptimizerv4<double>; using MetricType = itk::MattesMutualInformationImageToImageMetricv4<FixedImageType, MovingImageType>; // 這裡的 ImageRegistrationMethodv4 並沒有定義轉換的類型, // 預設會使用 Transform 作為轉換的輸出類型 using TRegistrationType = itk::ImageRegistrationMethodv4<FixedImageType, MovingImageType>; TOptimizerType::Pointer transOptimizer = TOptimizerType::New(); MetricType::Pointer transMetric = MetricType::New(); TRegistrationType::Pointer transRegistration = TRegistrationType::New(); transRegistration->SetOptimizer(transOptimizer); transRegistration->SetMetric(transMetric); // 設定轉換物件 TTransformType::Pointer translationTx = TTransformType::New(); transRegistration->SetInitialTransform(translationTx); // 將輸入的轉換物件直接用於輸出的轉換物件 transRegistration->InPlaceOn(); // 讀取影像 using FixedImageReaderType = itk::ImageFileReader<FixedImageType>; using MovingImageReaderType = itk::ImageFileReader<MovingImageType>; FixedImageReaderType::Pointer fixedImageReader = FixedImageReaderType::New(); MovingImageReaderType::Pointer movingImageReader = MovingImageReaderType::New(); fixedImageReader->SetFileName(argv[1]); movingImageReader->SetFileName(argv[2]); // 設定輸入影像 transRegistration->SetFixedImage(fixedImageReader->GetOutput()); transRegistration->SetMovingImage(movingImageReader->GetOutput()); // 設定 Registration 名稱 transRegistration->SetObjectName("TranslationRegistration"); // 僅使用單一層級、低解析度之模糊影像進行影像對準 constexpr unsigned int numberOfLevels1 = 1; TRegistrationType::ShrinkFactorsArrayType shrinkFactorsPerLevel1; shrinkFactorsPerLevel1.SetSize(numberOfLevels1); shrinkFactorsPerLevel1[0] = 3; TRegistrationType::SmoothingSigmasArrayType smoothingSigmasPerLevel1; smoothingSigmasPerLevel1.SetSize(numberOfLevels1); smoothingSigmasPerLevel1[0] = 2; transRegistration->SetNumberOfLevels(numberOfLevels1); transRegistration->SetShrinkFactorsPerLevel(shrinkFactorsPerLevel1); transRegistration->SetSmoothingSigmasPerLevel(smoothingSigmasPerLevel1); transMetric->SetNumberOfHistogramBins(24); // RegularStepGradientDescentOptimizerv4 相關參數 transOptimizer->SetNumberOfIterations(200); transOptimizer->SetRelaxationFactor(0.5); transOptimizer->SetLearningRate(16); transOptimizer->SetMinimumStepLength(1.5); // 觀察影像對準疊代過程的 Observer CommandIterationUpdate::Pointer observer1 = CommandIterationUpdate::New(); transOptimizer->AddObserver(itk::IterationEvent(), observer1); // 觀察階段或層級改變的 Observer using TranslationCommandType = RegistrationInterfaceCommand<TRegistrationType>; TranslationCommandType::Pointer command1 = TranslationCommandType::New(); transRegistration->AddObserver(itk::MultiResolutionIterationEvent(), command1); // 第二階段影像對準:Affine 轉換 // 定義第二階段用的相關組件類型 using ATransformType = itk::AffineTransform<double, Dimension>; using AOptimizerType = itk::ConjugateGradientLineSearchOptimizerv4Template<double>; // 預設使用 Transform 作為轉換的輸出類型, // 避免不同類型轉換在串接上形成型別錯誤 using ARegistrationType = itk::ImageRegistrationMethodv4<FixedImageType, MovingImageType>; AOptimizerType::Pointer affineOptimizer = AOptimizerType::New(); MetricType::Pointer affineMetric = MetricType::New(); ARegistrationType::Pointer affineRegistration = ARegistrationType::New(); affineRegistration->SetOptimizer(affineOptimizer); affineRegistration->SetMetric(affineMetric); affineMetric->SetNumberOfHistogramBins(24); fixedImageReader->Update(); FixedImageType::Pointer fixedImage = fixedImageReader->GetOutput(); // 計算固定影像的重心,作為旋轉中心點 using FixedImageCalculatorType = itk::ImageMomentsCalculator<FixedImageType>; FixedImageCalculatorType::Pointer fixedCalculator = FixedImageCalculatorType::New(); fixedCalculator->SetImage(fixedImage); fixedCalculator->Compute(); FixedImageCalculatorType::VectorType fixedCenter = fixedCalculator->GetCenterOfGravity(); // 建立 Affine 轉換 ATransformType::Pointer affineTx = ATransformType::New(); // 設定旋轉中心點 const unsigned int numberOfFixedParameters = affineTx->GetFixedParameters().Size(); ATransformType::ParametersType fixedParameters(numberOfFixedParameters); for (unsigned int i = 0; i < numberOfFixedParameters; ++i) { fixedParameters[i] = fixedCenter[i]; } affineTx->SetFixedParameters(fixedParameters); // 設定轉換物件 affineRegistration->SetInitialTransform(affineTx); // 將輸入的轉換物件直接用於輸出的轉換物件 affineRegistration->InPlaceOn(); // 設定輸入影像 affineRegistration->SetFixedImage(fixedImageReader->GetOutput()); affineRegistration->SetMovingImage(movingImageReader->GetOutput()); // 設定 Registration 名稱 affineRegistration->SetObjectName("AffineRegistration"); // 前一個階段的輸出會以 DataObjectDecorator 包裝, // 此處以 SetMovingInitialTransformInput 設定為調動影像初始轉換 affineRegistration->SetMovingInitialTransformInput( transRegistration->GetTransformOutput()); // 自動估計參數尺度 using ScalesEstimatorType = itk::RegistrationParameterScalesFromPhysicalShift<MetricType>; ScalesEstimatorType::Pointer scalesEstimator = ScalesEstimatorType::New(); scalesEstimator->SetMetric(affineMetric); scalesEstimator->SetTransformForward(true); // GradientDescentLineSearchOptimizerv4 相關參數 affineOptimizer->SetScalesEstimator(scalesEstimator); affineOptimizer->SetDoEstimateLearningRateOnce(true); affineOptimizer->SetDoEstimateLearningRateAtEachIteration(false); affineOptimizer->SetLowerLimit(0); affineOptimizer->SetUpperLimit(2); affineOptimizer->SetEpsilon(0.2); affineOptimizer->SetNumberOfIterations(200); affineOptimizer->SetMinimumConvergenceValue(1e-6); affineOptimizer->SetConvergenceWindowSize(10); // 觀察影像對準疊代過程的 Observer CommandIterationUpdate::Pointer observer2 = CommandIterationUpdate::New(); affineOptimizer->AddObserver(itk::IterationEvent(), observer2); // 使用兩個層級進行影像對準 constexpr unsigned int numberOfLevels2 = 2; ARegistrationType::ShrinkFactorsArrayType shrinkFactorsPerLevel2; shrinkFactorsPerLevel2.SetSize(numberOfLevels2); shrinkFactorsPerLevel2[0] = 2; shrinkFactorsPerLevel2[1] = 1; ARegistrationType::SmoothingSigmasArrayType smoothingSigmasPerLevel2; smoothingSigmasPerLevel2.SetSize(numberOfLevels2); smoothingSigmasPerLevel2[0] = 1; smoothingSigmasPerLevel2[1] = 0; affineRegistration->SetNumberOfLevels(numberOfLevels2); affineRegistration->SetShrinkFactorsPerLevel(shrinkFactorsPerLevel2); affineRegistration->SetSmoothingSigmasPerLevel(smoothingSigmasPerLevel2); // 觀察階段或層級改變的 Observer using AffineCommandType = RegistrationInterfaceCommand<ARegistrationType>; AffineCommandType::Pointer command2 = AffineCommandType::New(); affineRegistration->AddObserver(itk::MultiResolutionIterationEvent(), command2); // 實際進行兩階段的影像對準 try { affineRegistration->Update(); std::cout << "Optimizer stop condition: " << affineRegistration->GetOptimizer()->GetStopConditionDescription() << std::endl; } catch (itk::ExceptionObject & err) { std::cout << "ExceptionObject caught !" << std::endl; std::cout << err << std::endl; return EXIT_FAILURE; } // 輸出個階段影像對準結果 std::cout << " Translation transform parameters after registration: " << std::endl << transOptimizer->GetCurrentPosition() << std::endl << " Last LearningRate: " << transOptimizer->GetCurrentStepLength() << std::endl; std::cout << " Affine transform parameters after registration: " << std::endl << affineOptimizer->GetCurrentPosition() << std::endl << " Last LearningRate: " << affineOptimizer->GetLearningRate() << std::endl; // 以 CompositeTransform 組合各階段影像對準結果 using CompositeTransformType = itk::CompositeTransform<double, Dimension>; CompositeTransformType::Pointer compositeTransform = CompositeTransformType::New(); compositeTransform->AddTransform(translationTx); compositeTransform->AddTransform(affineTx); // 建立 ResampleFilter 套用影像轉換 using ResampleFilterType = itk::ResampleImageFilter<MovingImageType, FixedImageType>; ResampleFilterType::Pointer resample = ResampleFilterType::New(); // 設定影像轉換 resample->SetTransform(compositeTransform); // 設定輸入影像 resample->SetInput(movingImageReader->GetOutput()); // 預設背景像素值 PixelType backgroundGrayLevel = 100; // 設定輸出影像資訊 resample->SetSize(fixedImage->GetLargestPossibleRegion().GetSize()); resample->SetOutputOrigin(fixedImage->GetOrigin()); resample->SetOutputSpacing(fixedImage->GetSpacing()); resample->SetOutputDirection(fixedImage->GetDirection()); resample->SetDefaultPixelValue(backgroundGrayLevel); // 將輸出影像轉型為指定類型 using OutputPixelType = unsigned char; using OutputImageType = itk::Image<OutputPixelType, Dimension>; using CastFilterType = itk::CastImageFilter<FixedImageType, OutputImageType>; using WriterType = itk::ImageFileWriter<OutputImageType>; WriterType::Pointer writer = WriterType::New(); CastFilterType::Pointer caster = CastFilterType::New(); writer->SetFileName(argv[3]); caster->SetInput(resample->GetOutput()); writer->SetInput(caster->GetOutput()); writer->Update(); // 產生棋盤式影像比較圖 using CheckerBoardFilterType = itk::CheckerBoardImageFilter<FixedImageType>; CheckerBoardFilterType::Pointer checker = CheckerBoardFilterType::New(); checker->SetInput1(fixedImage); checker->SetInput2(resample->GetOutput()); caster->SetInput(checker->GetOutput()); writer->SetInput(caster->GetOutput()); // 預設像素值 resample->SetDefaultPixelValue(0); // 輸出影像對準前的棋盤式影像比較圖 using TransformType = itk::IdentityTransform<double, Dimension>; TransformType::Pointer identityTransform; try { identityTransform = TransformType::New(); } catch (const itk::ExceptionObject & err) { err.Print(std::cerr); return EXIT_FAILURE; } identityTransform->SetIdentity(); resample->SetTransform(identityTransform); if (argc > 4) { writer->SetFileName(argv[4]); writer->Update(); } // 輸出影像對準後的棋盤式影像比較圖 resample->SetTransform(compositeTransform); if (argc > 5) { writer->SetFileName(argv[5]); writer->Update(); } return EXIT_SUCCESS; }
將這個 C++ 程式儲存為 MultiStageReg.cxx
之後,搭配以下 CMakeLists.txt
設定檔以 CMake 編譯。
cmake_minimum_required(VERSION 3.10.2) # 設定專案名稱 project(MultiStageReg) # 尋找並引入 ITK 函式庫 find_package(ITK REQUIRED) include(${ITK_USE_FILE}) # 增加一個執行檔 add_executable(MultiStageReg MultiStageReg.cxx) # 定義執行檔連結方式 target_link_libraries(MultiStageReg ${ITK_LIBRARIES})
編譯與執行的指令如下:
# 編譯程式 mkdir build cd build cmake .. # 執行程式 ./MultiResReg BrainT1SliceBorder20.png \ BrainProtonDensitySliceShifted13x17y.png \ output.png checkerboard1.png checkerboard2.png
這裡的 BrainT1SliceBorder20.png
與 BrainProtonDensitySliceShifted13x17y.png
圖檔可以從 ITK 的 GitHub 網站取得,執行後除了可以產生對準後的圖形之外,也可以產生影像對準前後的棋盤式影像比較圖。
參考資料:MultiStageImageRegistration1.cxx、MultiStageImageRegistration2.cxx