严格地将 2D 图像注册到 3D 体积,并为仿射变换提供良好的初始猜测

Rigidly register a 2D image to a 3D volume with good initial guess for affine transformation

我有一个 3D 体积和一个 2D 图像以及两者之间的近似映射(没有倾斜的仿射变换,已知缩放,旋转和平移近似已知并且需要拟合)。因为这个映射有错误,我想进一步将 2D 图像注册到 3D 体积。我以前没有为注册目的编写代码,但是因为我找不到任何程序或代码来解决这个问题,所以我想尝试这样做。我相信注册的标准是优化mutual information。我认为这也适用于此,因为两幅图像之间的强度不相等。所以我想我应该做一个转换函数,一个互信息函数和一个优化函数。

我确实在 mathworks thread from two years ago, based on an article. The OP reports that she managed to get the code to work, but I'm not getting how she did that exactly. Also in the IP package for matlab there is an implementation, but I dont have that package and there does not seem to be an equivalent for octave. SPM is a program that uses matlab and has registration implemented, but does not cope with 2d to 3d registration. On the file exchange there is a brute force method 上找到了一些使用互信息配准两个 2D 图像的 Matlab 代码。

她所做的是将多平面重建函数和 similarity/error 函数传递给最小化算法。但是细节我不太明白。也许重新开始会更好:

load mri; volume = squeeze(D);

phi = 3; theta = 2; psi = 5; %some small angles
tx = 1; ty = 1; tz = 1; % some small translation
dx = 0.25, dy = 0.25, dz = 2; %different scales
t = [tx; ty; tz];
r = [phi, theta, psi]; r = r*(pi/180);
dims = size(volume);
p0 = [round(dims(1)/2);round(dims(2)/2);round(dims(3)/2)]; %image center
S = eye(4); S(1,1) = dx; S(2,2) = dy; S(3,3) = dz;

Rx=[1 0 0 0;
    0 cos(r(1)) sin(r(1)) 0;
    0 -sin(r(1)) cos(r(1)) 0;
    0 0 0 1];
Ry=[cos(r(2)) 0 -sin(r(2)) 0;
    0 1 0 0;
    sin(r(2)) 0 cos(r(2)) 0;
    0 0 0 1];
Rz=[cos(r(3)) sin(r(3)) 0 0;
    -sin(r(3)) cos(r(3)) 0 0;
    0 0 1 0;
    0 0 0 1];
R = S*Rz*Ry*Rx;
%make affine matrix to rotate about center of image
T1 = ( eye(3)-R(1:3,1:3) ) * p0(1:3);
T = T1 + t; %add translation
A = R;
A(1:3,4) = T;
Rold2new = A;
Rnew2old = inv(Rold2new);

%the transformation
[xx yy zz] = meshgrid(1:dims(1),1:dims(2),1:1);
coordinates_axes_new = [xx(:)';yy(:)';zz(:)'; ones(size(zz(:)))'];
coordinates_axes_old = Rnew2old*coordinates_axes_new;
Xcoordinates = reshape(coordinates_axes_old(1,:), dims(1), dims(2), dims(3));
Ycoordinates = reshape(coordinates_axes_old(2,:), dims(1), dims(2), dims(3));
Zcoordinates = reshape(coordinates_axes_old(3,:), dims(1), dims(2), dims(3));

%interpolation/reslicing
method = 'cubic'; 
slice= interp3(volume, Xcoordinates, Ycoordinates, Zcoordinates, method);
%so now I have my slice for which I would like to find the correct position

% first guess for A
A0 = eye(4); A0(1:3,4) = T1; A0(1,1) = dx; A0(2,2) = dy; A0(3,3) = dz; 
% this is pretty close to A
% now how would I fit the slice to the volume by changing A0 and examining some similarity measure?
% probably maximize mutual information?
% http://www.mathworks.com/matlabcentral/fileexchange/14888-mutual-information-computation/content//mi/mutualinfo.m

好吧,我希望别人的方法,这可能会比我的更好,因为我以前从未做过任何优化或注册。所以我等待 Knedlsepps 赏金几乎完成。但是我现在确实有一些代码可以正常工作。它会找到局部最优值,因此初始猜测一定是好的。我自己编写了一些函数,按原样从文件交换中提取了一些函数,并从文件交换中广泛编辑了一些其他函数。现在我将所有代码放在一起作为示例在这里工作,轮换已关闭,将尝试并更正它。我不确定示例代码和我的原始代码之间的代码差异在哪里,一定是在替换一些变量和数据加载方案时输入错误。

我所做的是将起始仿射变换矩阵分解为正交矩阵和上三角矩阵。然后我假设正交矩阵是我的旋转矩阵,所以我从中计算欧拉角。我直接从仿射矩阵中获取翻译,并且如问题中所述,我假设我知道缩放矩阵并且没有剪切。因此,我拥有仿射变换的所有自由度,我的优化函数更改并从中构造一个新的仿射矩阵,将其应用于体积并计算互信息。 matlab 优化函数 patternsearch 然后最小化 1-MI/MI_max.

当我在我的多模态大脑图像真实数据上使用它时,我注意到它在大脑提取图像上效果更好,因此去除了头骨和头骨外部的组织。

%data
load mri; volume = double(squeeze(D));

%transformation parameters
phi = 3; theta = 1; psi = 5; %some small angles
tx = 1; ty = 1; tz = 3; % some small translation
dx = 0.25; dy = 0.25; dz = 2; %different scales
t = [tx; ty; tz];
r = [phi, theta, psi]; r = r*(pi/180);
%image center and size
dims = size(volume);
p0 = [round(dims(1)/2);round(dims(2)/2);round(dims(3)/2)]; 
%slice coordinate ranges
range_x = 1:dims(1)/dx;
range_y = 1:dims(2)/dy;
range_z = 1;

%rotation 
R = dofaffine([0;0;0], r, [1,1,1]);
T1 = ( eye(3)-R(1:3,1:3) ) * p0(1:3); %rotate about p0
%scaling 
S = eye(4); S(1,1) = dx; S(2,2) = dy; S(3,3) = dz;
%translation
T = [[eye(3), T1 + t]; [0 0 0 1]];
%affine 
A = T*R*S;

% first guess for A
r00 = [1,1,1]*pi/180;
R00 = dofaffine([0;0;0], r00, [1 1 1]);
t00 = T1 + t + ( eye(3) - R00(1:3,1:3) ) * p0;
A0 = dofaffine( t00, r00, [dx, dy, dz] );
[ t0, r0, s0 ] = dofaffine( A0 );
x0 = [ t0.', r0, s0 ];

%the transformation
slice = affine3d(volume, A, range_x, range_y, range_z, 'cubic');
guess = affine3d(volume, A0, range_x, range_y, range_z, 'cubic');

%initialisation
Dt = [1; 1; 1]; 
Dr = [2 2 2].*pi/180;
Ds = [0 0 0];
Dx = [Dt', Dr, Ds];
%limits
LB = x0-Dx;
UB = x0+Dx;
%other inputs
ref_levels = length(unique(slice));
Qref = imquantize(slice, ref_levels);
MI_max = MI_GG(Qref, Qref);
%patternsearch options
options = psoptimset('InitialMeshSize',0.03,'MaxIter',20,'TolCon',1e-5,'TolMesh',5e-5,'TolX',1e-6,'PlotFcns',{@psplotbestf,@psplotbestx});

%optimise
[x2, MI_norm_neg, exitflag_len] = patternsearch(@(x) AffRegOptFunc(x, slice, volume, MI_max, x0), x0,[],[],[],[],LB(:),UB(:),options);

%check
p0 = [round(size(volume)/2).'];
R0 = dofaffine([0;0;0], x2(4:6)-x0(4:6), [1 1 1]);
t1 = ( eye(3) - R0(1:3,1:3) ) * p0;
A2 = dofaffine( x2(1:3).'+t1, x2(4:6), x2(7:9) ) ;
fitted = affine3d(volume, A2, range_x, range_y, range_z, 'cubic');
overlay1 = imfuse(slice, guess);
overlay2 = imfuse(slice, fitted);
figure(101); 
ax(1) = subplot(1,2,1); imshow(overlay1, []); title('pre-reg')
ax(2) = subplot(1,2,2); imshow(overlay2, []); title('post-reg');
linkaxes(ax);

function normed_score = AffRegOptFunc( x, ref_im, reg_im, MI_max, x0 )
    t = x(1:3).';
    r = x(4:6);
    s = x(7:9);

    rangx = 1:size(ref_im,1);
    rangy = 1:size(ref_im,2);
    rangz = 1:size(ref_im,3);

    ref_levels =  length(unique(ref_im));
    reg_levels =  length(unique(reg_im));

    t0 = x0(1:3).';
    r0 = x0(4:6);
    s0 = x0(7:9);
    p0 = [round(size(reg_im)/2).'];
    R = dofaffine([0;0;0], r-r0, [1 1 1]);
    t1 = ( eye(3) - R(1:3,1:3) ) * p0;
    t = t + t1;
    Ap = dofaffine( t, r, s );

    reg_im_t = affine3d(reg_im, A, rangx, rangy, rangz, 'cubic');

    Qref = imquantize(ref_im, ref_levels);
    Qreg = imquantize(reg_im_t, reg_levels);

    MI = MI_GG(Qref, Qreg);

    normed_score = 1-MI/MI_max;
end

function [ varargout ] = dofaffine( varargin )
% [ t, r, s ] = dofaffine( A )
% [ A ] = dofaffine( t, r, s )
if nargin == 1
    %affine to degrees of freedom (no shear)
    A = varargin{1};

    [T, R, S] = decompaffine(A);
    r = GetEulerAngles(R(1:3,1:3));
    s = [S(1,1), S(2,2), S(3,3)];
    t = T(1:3,4);

    varargout{1} = t;
    varargout{2} = r;
    varargout{3} = s;
elseif nargin == 3
    %degrees of freedom to affine (no shear)
    t = varargin{1};
    r = varargin{2};
    s = varargin{3};

    R = GetEulerAngles(r); R(4,4) = 1;
    S(1,1) = s(1); S(2,2) = s(2); S(3,3) = s(3); S(4,4) = 1;
    T = eye(4); T(1,4) = t(1); T(2,4) = t(2); T(3,4) = t(3);
    A = T*R*S;

    varargout{1} = A;
else
    error('incorrect number of input arguments');
end
end

function [ T, R, S ] = decompaffine( A )
    %I assume A = T * R * S
    T = eye(4);
    R = eye(4);
    S = eye(4);
    %decompose in orthogonal matrix q and upper triangular matrix r
    %I assume q is a rotation matrix and r is a scale and shear matrix
    %matlab 2014 can force real solution
    [q r] = qr(A(1:3,1:3));
    R(1:3,1:3) = q;
    S(1:3,1:3) = r;

    % A*S^-1*R^-1 = T*R*S*S^-1*R^-1 = T*R*I*R^-1 = T*R*R^-1 = T*I = T
    T = A*inv(S)*inv(R);
    t = T(1:3,4);
    T = [eye(4) + [[0 0 0;0 0 0;0 0 0;0 0 0],[t;0]]];
end

function [varargout]= GetEulerAngles(R)

assert(length(R)==3)

dims = size(R);

    if min(dims)==1
        rx = R(1); ry = R(2); rz = R(3);
        R = [[                           cos(ry)*cos(rz),                          -cos(ry)*sin(rz),          sin(ry)];...
             [ cos(rx)*sin(rz) + cos(rz)*sin(rx)*sin(ry), cos(rx)*cos(rz) - sin(rx)*sin(ry)*sin(rz), -cos(ry)*sin(rx)];...
             [ sin(rx)*sin(rz) - cos(rx)*cos(rz)*sin(ry), cos(rz)*sin(rx) + cos(rx)*sin(ry)*sin(rz),  cos(rx)*cos(ry)]];
        varargout{1} = R;
    else
        ry=asin(R(1,3));
        rz=acos(R(1,1)/cos(ry));
        rx=acos(R(3,3)/cos(ry));

        if nargout > 1 && nargout < 4
            varargout{1} = rx;
            varargout{2} = ry;
            varargout{3} = rz;
        elseif nargout == 1
            varargout{1} = [rx ry rz];
        else
            error('wrong number of output arguments');
        end
    end
end