使用互信息进行图像配准

熵是描述一个事件的不确定性的,在英文中的描述是disorder,也就是无序,杂乱。写一个事件的熵可以表示为:

E ( A ) = − ∑ A p i log ⁡ 2 p i E(A)= -\sum_{A} p_i \log_2 p_i E(A)=Apilog2pi

p i = 0 p_i=0 pi=0的时候, p i log ⁡ p i = 0 p_i\log p_i=0 pilogpi=0。也就是事件所有可能情况的概率与其对应对数的乘积就和的相反数。假设一个事件 A ( X ) A(X) A(X)存在 { x = 1 , x = 2 , x = 3 , x = 4 } \{x=1,x=2,x=3,x=4\} {x=1,x=2,x=3,x=4}这几种情况,并且经过多次实验,发现这几种情况出现的频数分别为:

x1234
频数8372

那么可以计算出, p 1 = 8 20 = 0.4 , p 2 = 3 20 = 0.15 , p 3 = 7 20 = 0.35 , p 4 = 2 20 = 0.1 p_1=\frac{8}{20}=0.4,p_2=\frac{3}{20}=0.15,p_3=\frac{7}{20}=0.35,p_4=\frac{2}{20}=0.1 p1=208=0.4,p2=203=0.15,p3=207=0.35,p4=202=0.1。则事件A的熵为:

E ( A ) = − ( p 1 log ⁡ 2 p 1 + p 2 log ⁡ 2 p 2 + p 3 log ⁡ 2 p 3 + p 4 log ⁡ 2 p 4 ) = 1.8016 E(A)=-(p_1\log_2 p_1+p_2\log_2 p_2+p_3\log_2 p_3+p_4\log_2 p_4)=1.8016 E(A)=(p1log2p1+p2log2p2+p3log2p3+p4log2p4)=1.8016

在熵的理论中,认为在自然条件下,所有的事情都会慢慢变得杂乱无序,而熵是描述杂乱无序的程度,所以熵都是会变大的。而熵的最大和最小值分别是多少?

还是事件 A A A,重复做了20次实验,发现所有的结果都是 x = 1 x=1 x=1的情况,这个时候事件是非常确定的,是最不无序的状态,这个时候熵是最小的。

x1234
频数20000

E m i n ( A ) = − p 1 log ⁡ 2 p 1 = 0 E_{min}(A)=-p_1 \log_2 p_1=0 Emin(A)=p1log2p1=0

而当做了20次实验,发现所有情况出现的概率是均等的,也就是各出现了5次的话,这个时候是最杂乱无序的,熵是最大的。

x1234
频数5555

E m a x ( A ) = − 4 × 1 4 log ⁡ 2 ( 1 4 ) = 2 E_{max}(A)=-4\times \frac{1}{4}\log_2(\frac{1}{4})=2 Emax(A)=4×41log2(41)=2

更一般地来讲,当一个事件有n个可能出现的情况,那么最大和最小的熵分别是:
{ E m a x ( A ) = − log ⁡ 2 ( 1 n ) = log ⁡ 2 ( n ) ( 当所有情况等概率出现 ) E m i n ( A ) = 0 ( 当只有一种情况出现 ) \begin{cases} E_{max}(A)=-\log_2(\frac{1}{n})=\log_2(n) &(当所有情况等概率出现) \\ E_{min}(A)=0 &(当只有一种情况出现) \end{cases} {Emax(A)=log2(n1)=log2(n)Emin(A)=0(当所有情况等概率出现)(当只有一种情况出现)

互信息

互信息(Mutual Information)是熵理论中的概念,描述了当一个事件的引入,对原来事件不确定性的减少量。计算互信息需要使用到,熵,联合熵,条件熵等概率,方便理解,可以使用到韦恩图的方式来理解。

  • 熵:事件的不确定度,韦恩图中蓝色和红色圆圈分别表示事件 X , Y X,Y X,Y的熵。
  • 联合熵:两个事件共同的不确定度,当两个事件完全独立的时候,联合熵等于两个事件熵的和,不完全独立的时候,等于两个事件韦恩图的并集。并且有 H ( X , Y ) ≤ H ( X ) + H ( Y ) H(X,Y)≤H(X)+H(Y) H(X,Y)H(X)+H(Y) H ( X , Y ) = − ∑ x ∑ y p ( x , y ) log ⁡ 2 p ( x , y ) H(X,Y)=-\sum_x\sum_yp(x,y) \log_2 p(x,y) H(X,Y)=xyp(x,y)log2p(x,y)
  • 条件熵:事件X在事件Y引入的情况下,剩余的不确定度,在图中表示为两个圆去除中间共同的部分。
  • 互信息:事件X在引入事件Y后不确定度的减少量,即为中间公共的部分,计算公式为: I ( X , Y ) = ∑ x ∑ y p ( x , y ) log ⁡ p ( x , y ) p ( x ) p ( y ) I(X,Y)=\sum_x\sum_y p(x,y)\log\frac{p(x,y)}{p(x)p(y)} I(X,Y)=xyp(x,y)logp(x)p(y)p(x,y),但是一般地我们更喜欢使用下面的公式来进行互信息的计算。

I ( X , Y ) = H ( X ) + H ( Y ) − H ( X , Y ) (互信息计算公式) I(X,Y)=H(X)+H(Y)-H(X,Y) \tag{互信息计算公式} I(X,Y)=H(X)+H(Y)H(X,Y)(互信息计算公式)

显然,当互信息越大,表示两个事件的"重合"越高,"相关性"越大

使用互信息的图像配准

这部分代码来自 ITK Examples/RegistrationITKv4/ImageRegistration17.cxx。数据来自 ITK Testing Data。由于ITK给的代码读取部分有问题,添加了部分修改。

  • 在include部分添加了 #include <itkPNGImageIO.h>的包含头文件。

  • 在读取文件之前添加ImageIO。

    ......
    fixedImageReader->SetFileName(argv[1]);
    fixedImageReader->SetImageIO(itk::PNGImageIO::New());
    movingImageReader->SetFileName(argv[2]);
    movingImageReader->SetImageIO(itk::PNGImageIO::New());
    ......
    
  • 并且,不需要保存信息,将Print out results以后的结果都注释掉了。

输入参数分别为:

C:/depolytools/InsightToolkit-5.2.0/source/Examples/Data/BrainProtonDensitySliceBorder20.png C:/depolytools/InsightToolkit-5.2.0/source/Examples/Data/BrainProtonDensitySliceShifted13x17y.png 0 0

需要替换以下图片的路径即可。


#include "itkImageRegistrationMethod.h"

#include "itkTranslationTransform.h"
#include "itkMutualInformationHistogramImageToImageMetric.h"
#include "itkAmoebaOptimizer.h"

#include "itkImageFileReader.h"
#include "itkImageFileWriter.h"
#include <itkPNGImageIO.h>
#include "itkResampleImageFilter.h"
#include "itkCastImageFilter.h"
#include "itkCommand.h"
class CommandIterationUpdate : public itk::Command
{
public:
    using Self = CommandIterationUpdate;
    using Superclass = itk::Command;
    using Pointer = itk::SmartPointer<Self>;
    itkNewMacro(Self);

protected:
    CommandIterationUpdate() { m_IterationNumber = 0; }

public:
    using OptimizerType = itk::AmoebaOptimizer;
    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 (!itk::IterationEvent().CheckEvent(&event))
        {
            return;
        }
        std::cout << m_IterationNumber++ << "   ";
        std::cout << optimizer->GetCachedValue() << "   ";
        std::cout << optimizer->GetCachedCurrentPosition() << std::endl;
    }

private:
    unsigned long m_IterationNumber;
};


// C:/depolytools/InsightToolkit-5.2.0/source/Examples/Data/BrainProtonDensitySliceBorder20.png C:/depolytools/InsightToolkit-5.2.0/source/Examples/Data/BrainProtonDensitySliceShifted13x17y.png 0 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 ";
        std::cerr << " outputImagefile ";
        std::cerr << " [initialTx] [initialTy]" << std::endl;
        return EXIT_FAILURE;
    }

    constexpr unsigned int Dimension = 2;
    using PixelType = unsigned char;

    using FixedImageType = itk::Image<PixelType, Dimension>;
    using MovingImageType = itk::Image<PixelType, Dimension>;

    using TransformType = itk::TranslationTransform<double, Dimension>;

    using OptimizerType = itk::AmoebaOptimizer;
    using InterpolatorType =
        itk::LinearInterpolateImageFunction<MovingImageType, double>;
    using RegistrationType =
        itk::ImageRegistrationMethod<FixedImageType, MovingImageType>;


    using MetricType =
        itk::MutualInformationHistogramImageToImageMetric<FixedImageType,
        MovingImageType>;


    auto transform = TransformType::New();
    auto optimizer = OptimizerType::New();
    auto interpolator = InterpolatorType::New();
    auto registration = RegistrationType::New();

    registration->SetOptimizer(optimizer);
    registration->SetTransform(transform);
    registration->SetInterpolator(interpolator);


    auto metric = MetricType::New();
    registration->SetMetric(metric);

    using HistogramSizeType = MetricType::HistogramSizeType;

    HistogramSizeType histogramSize;

    histogramSize.SetSize(2);

    histogramSize[0] = 256;
    histogramSize[1] = 256;

    metric->SetHistogramSize(histogramSize);
    metric->ComputeGradientOff();


    const unsigned int numberOfParameters = transform->GetNumberOfParameters();


    using FixedImageReaderType = itk::ImageFileReader<FixedImageType>;
    using MovingImageReaderType = itk::ImageFileReader<MovingImageType>;

    auto fixedImageReader = FixedImageReaderType::New();
    auto movingImageReader = MovingImageReaderType::New();

    fixedImageReader->SetFileName(argv[1]);
    fixedImageReader->SetImageIO(itk::PNGImageIO::New());
    movingImageReader->SetFileName(argv[2]);
    movingImageReader->SetImageIO(itk::PNGImageIO::New());

    registration->SetFixedImage(fixedImageReader->GetOutput());
    registration->SetMovingImage(movingImageReader->GetOutput());

    fixedImageReader->Update();
    movingImageReader->Update();

    FixedImageType::ConstPointer fixedImage = fixedImageReader->GetOutput();

    registration->SetFixedImageRegion(fixedImage->GetBufferedRegion());


    transform->SetIdentity();

    using ParametersType = RegistrationType::ParametersType;

    ParametersType initialParameters = transform->GetParameters();

    initialParameters[0] = 0.0;
    initialParameters[1] = 0.0;

    if (argc > 5)
    {
        initialParameters[0] = std::stod(argv[4]);
        initialParameters[1] = std::stod(argv[5]);
    }

    registration->SetInitialTransformParameters(initialParameters);

    std::cout << "Initial transform parameters = ";
    std::cout << initialParameters << std::endl;

    OptimizerType::ParametersType simplexDelta(numberOfParameters);
    simplexDelta.Fill(5.0);

    optimizer->AutomaticInitialSimplexOff();
    optimizer->SetInitialSimplexDelta(simplexDelta);
    optimizer->MaximizeOn();
    optimizer->SetParametersConvergenceTolerance(0.1); // 1/10th pixel
    optimizer->SetFunctionConvergenceTolerance(0.001); // 0.001 bits
    optimizer->SetMaximumNumberOfIterations(200);

    // Create the Command observer and register it with the optimizer.
    //
    auto observer = CommandIterationUpdate::New();
    optimizer->AddObserver(itk::IterationEvent(), observer);


    try
    {
        registration->Update();
        std::cout << "Optimizer stop condition: "
            << registration->GetOptimizer()->GetStopConditionDescription()
            << std::endl;
    }
    catch (const itk::ExceptionObject& err)
    {
        std::cout << "ExceptionObject caught !" << std::endl;
        std::cout << err << std::endl;
        return EXIT_FAILURE;
    }

    ParametersType finalParameters = registration->GetLastTransformParameters();

    const double finalTranslationX = finalParameters[0];
    const double finalTranslationY = finalParameters[1];

    double bestValue = optimizer->GetValue();
    
    // Print out results
    //
    std::cout << "Result = " << std::endl;
    std::cout << " Translation X = " << finalTranslationX << std::endl;
    std::cout << " Translation Y = " << finalTranslationY << std::endl;
    std::cout << " Metric value  = " << bestValue << std::endl;

    return EXIT_SUCCESS;
}

这里使用到得优化器是Amoeba,不需要梯度信息,所以在metric里可以设置metric->ComputeGradientOff();,而配准结果越好,互信息越大,所以需要在优化器设置optimizer->MaximizeOn();。直接运行代码,可以很快得到比较好的输出。

Result =
 Translation X = 13.011
 Translation Y = 17.0249
 Metric value  = -2.49226

理论上来讲,这里的Metric value的值应该是一个正数,并且不断变大,但是实际上输出却是一个不断变小的负数,这是由于优化器本身是做最小化优化的,为了直接用于最大化优化,在Metric的值上添加一个负号就变成了最小化优化的结果。

  • 1
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值