在前面的文章中,我们讲到使用FFD形变作为坐标变换模型,使用梯度下降法作为优化算法来寻求FFD的最优控制参数:
LM算法可以看作是梯度下降法与高斯-牛顿法的结合算法,它既具有梯度下降法的稳健性,又具有高斯-牛顿法的快速收敛性,而且相比来说更不容易陷入局部极值。有关LM算法的数学公式推导,我们也在前文中有详细说明:
本文我们将使用LM算法作为优化算法来寻求FFD变换的最优控制参数。并与梯度下降法比较配准结果。
1. LM算法的计算过程
(1) 差分法求控制参数的梯度。
假设FFD变换模型有r+3行c+3列的控制点,每个控制点有x方向、y方向的两个控制参数,因此总共有N=2*(r+3)*(c+3)个控制参数需要优化,所有控制参数组成一个1行N列的向量X:
对于每个控制参数,其梯度的计算如下式,其中F为目标函数,EPS为一个较小的数,一般取1或者0.5即可。
所有控制点的梯度组成一个1行N列的梯度向量:
(2) 计算当前控制参数的目标函数值f0、矩阵gk、海塞矩阵H。
(3) 使用海塞矩阵计算矩阵G、矩阵h。
上式中,u为LM算法的控制步长,通常u取一个较小的初始值(比如0.001),在迭代优化过程中u的值根据情况而改变,详细如何改变在后续步骤说明。矩阵I为N*N的单位矩阵:
(4) 判断是否满足以下条件,如果满足则认为找到最优控制参数,因此停止循环迭代:
上式中,norm函数为L2范数,e通常取一个很小的值,比如10-12。比如X的L2范数可按下式计算:
(5) 更新X得到X',然后计算X'的目标函数值f1,并计算步长因子ρ。
(6) 判断ρ是否大于0。
I. 如果ρ大于0则减小u的值:
则把X'赋值给X,并改变u的值。接着判断迭代次数是否达到设定的最大次数,如果达到则停止迭代,否则跳转到第(1)步执行。
II. 如果ρ小于等于0则增大u和v:
接着判断迭代次数是否达到设定的最大次数,如果达到则停止迭代,否则跳转到第(3)步执行。
上式中,在迭代开始之前通常把v初始化值为2。
2. C++实现代码
cal_gradient、F_fun_bpline、Bspline_Ffd_cuda、init_bpline_para在上篇文章中已经讲过,此处不再重复贴出来:
求海塞矩阵代码:
-
Mat
cal_hessian_matrix(Mat gradient)
-
{
-
Mat
gradient_trans;
-
Mat hessian;
-
transpose(gradient, gradient_trans); //转置
-
hessian =
gradient_trans*gradient;
-
return
hessian;
-
}
LM算法代码:
-
void bpline_match_LM(Mat S1, Mat Si, Mat &M, int row_block_num, int col_block_num, Mat &grid_points)
-
{
-
int iter =
150;
//迭代次数
-
double e =
1e-12;
// 误差限
-
double u =
1e-3;
-
double v =
2.0;
-
-
Mat H;
-
Mat G;
-
Mat new_grid_points;
-
Mat I = cv::Mat::eye(grid_points.cols, grid_points.cols, CV_32FC1);
//单位矩阵
-
Mat h, h_T;
-
Mat gradient, gradient_T;
-
float f0, f1;
-
Mat gk;
-
double low =
1.0;
-
-
cal_gradient(S1, Si, row_block_num, col_block_num, grid_points, gradient);
//求梯度
-
f0 = F_fun_bpline(S1, Si, row_block_num, col_block_num, grid_points);
-
transpose(gradient, gradient_T);
-
gk = gradient_T*f0;
-
H = cal_hessian_matrix(gradient);
//由雅克比矩阵计算海塞矩阵
-
-
-
for(
int i =
0; i < iter; i++)
-
{
-
G = H + u*I;
-
-
Mat G_T;
-
invert(G, G_T, DECOMP_SVD);
-
h = -G_T*gk;
-
-
if(norm(h, NORM_L2) < e*(norm(grid_points, NORM_L2)+e))
-
break;
-
-
-
transpose(h, h_T);
-
new_grid_points = grid_points + h_T;
-
f1 = F_fun_bpline(S1, Si, row_block_num, col_block_num, new_grid_points);
-
Mat L_0_h =
0.5*h_T*(u*h-gk);
//1_N*N_1 = 1*1
-
low = (f0 - f1)/L_0_h.at<
float>(
0,
0);
-
-
-
if(low >
0)
-
{
-
grid_points = new_grid_points.clone();
-
double tmp =
2*low
-1;
//5*low-1;
-
tmp =
1 - tmp*tmp*tmp;
-
-
double t =
0.333333;
-
u = u*(t > tmp ? t : tmp);
-
v =
2;
-
cal_gradient(S1, Si, row_block_num, col_block_num, grid_points, gradient);
//求梯度
-
f0 = F_fun_bpline(S1, Si, row_block_num, col_block_num, grid_points);
-
transpose(gradient, gradient_T);
-
gk = gradient_T*f0;
-
H = cal_hessian_matrix(gradient);
//由雅克比矩阵计算海塞矩阵
-
}
-
else
-
{
-
u *= v;
-
v *=
2;
-
}
-
-
-
cout<<
"f1="<<f1<<
", low="<<low<<
", u="<<u<<
endl;
-
}
-
-
-
Bspline_Ffd_cuda(Si, M, row_block_num, col_block_num, grid_points);
-
}
测试代码:
-
void ffd_match_LM_test(void)
-
{
-
Mat img1 = imread(
"lena.jpg", CV_LOAD_IMAGE_GRAYSCALE);
-
Mat img2 = imread(
"lena_out.jpg", CV_LOAD_IMAGE_GRAYSCALE);
-
-
-
int row_block_num =
30;
-
int col_block_num =
30;
-
Mat grid_points;
-
init_bpline_para(img1, row_block_num, col_block_num, grid_points,
-0.001,
0.001);
-
Mat
out;
-
bpline_match_LM(img1, img2,
out, row_block_num, col_block_num, grid_points);
-
-
-
imshow(
"img1", img1);
-
imshow(
"img2", img2);
-
imshow(
"out",
out);
-
imshow(
"img1-img2", abs(img1-img2));
-
imshow(
"img1-out", abs(img1-
out));
-
waitKey();
-
}
运行上述代码,对扭曲的Lena图像进行配准,结果如下图所示。
基准图像
浮动图像
配准图像
基准图像与浮动图像的差值图
基准图像与配准图像的差值图
目标函数值的下降过程
由上图可知,LM算法的优化结果比梯度下降法更理想,其收敛速度更快,且优化结果更接近最优解。
本人微信公众号如下,会不定时更新更精彩的内容,欢迎扫码关注:
转载:https://blog.csdn.net/shandianfengfan/article/details/113778069