小言_互联网的博客

图像配准系列之基于FFD形变与梯度下降法的图像配准

529人阅读  评论(0)

前面我们讲到梯度下降法时,就有提到:人们经常要求解一个问题的最优解,通常做法是对该问题进行数学建模,转换成一个目标函数,然后通过一定的算法寻求该函数的最小值,最终寻求到最小值时的输入参数就是问题的最优解。

当我们有两张图像A和B,图像A与图像B形状相似,但具有位置偏移:平移、旋转、缩放、局部扭曲形变等。如果以图像A为基准图像,以图像B为浮动图像,对图像B进行配准,使其与图像A位置对齐,那么我们可以使用FFD自由形变作为坐标变换模型,对图像B进行形变,并计算图像A与形变之后的图像B的相似度,通过求解FFD形变的最优控制参数,使得两者相似度达到最大,从而使用最优控制参数对图像B进行形变,实现其配准

我们把图像A与FFD形变之后图像B的相似度作为目标函数,然后使用优化算法来求解这个目标函数的最优解,本文我们使用的优化算法是梯度下降法。

目标函数的示意图所下图所示:

梯度下降法求最优控制参数的示意图如下图所示:

下面我们分点讲解所有的步骤,然后再上C++代码。

1. 梯度下降法原理。

其原理我们在之前的文章已经详细讲解,此处不再重复,读者可参考博主的以下博文:

梯度下降法详解

2. FFD自由变换原理。

FFD的原理我们在之前的博文中也讲解过:

图像配准系列之基于B样条的FFD自由变换原理与C++实现

3. 图像的相似度衡量与目标函数。

常见的图像相似度衡量指标有峰值信噪比(PSNR)、结构相似度(SSIM)、归一化互相关(NCC)、归一化互信息(NMI)、均方误差(MSE)等。由于归一化互相关系数计算相对简单,且具有的良好凹凸特性利于求解最优参数,因此经过综合考虑,本文选择归一化互相关系数作为相似度的衡量指标。假设图像A与图像B都是m行n列的图像,那么归一化互相关系数可以按照下式计算:

上式中,归一化互相关NCC越大,说明图像A与图像B越相似,反之则两者差异越大。由于梯度下降法为求解目标函数的最小值,所以我们需要把以上函数取个倒数,使得NCC越小,图像的相似度越高:

需要注意,计算NCC的图像B是经过FFD形变的,因此目标函数的数学表达式如下,其中X为FFD形变模型的所有控制参数。

很多时候,图像之间存在很多局部的形变差异,所以通过分块来求解的NCC'更能表现两图的相似度。因此我们在以上基础上,在对图像A与图像B进行相同的分块,计算两图中对应位置块的NCC',最后再取所有块的NCC'的平均值作为整张图像的NCC'。假设把图像的高平均分为r块,宽平均分为c块,那么最终的目标函数F的表达式如下:

4. C++代码实现。

(1) 分块计算归一化互相关代码


   
  1. double cal_cc_block(Mat S1, Mat Si, int row, int col)
  2. {
  3. int ksize_row = S1.rows/row;
  4.    int ksize_col = S1.cols/col;
  5. Mat tmp1, tmpi;
  6. double sum = 0.0;
  7. for( int i = 0; i < row; i++)
  8. {
  9. int i_begin = i*ksize_row;
  10. for( int j = 0; j < col; j++)
  11. {
  12. double sum1 = 0.0;
  13. double sum2 = 0.0;
  14. double sum3 = 0.0;
  15. int j_begin = j*ksize_col;
  16. for ( int t1 = i_begin; t1 < i_begin+ksize_row; t1++)
  17. {
  18. uchar *S1_data = S1.ptr<uchar>(t1);
  19. uchar *Si_data = Si.ptr<uchar>(t1);
  20. for ( int t2 = j_begin; t2 < j_begin+ksize_col; t2++)
  21. {
  22. sum1 += S1_data[t2]*Si_data[t2];
  23. sum2 += S1_data[t2]*S1_data[t2];
  24. sum3 += Si_data[t2]*Si_data[t2];
  25. }
  26. }
  27. sum += sqrt(sum2*sum3)/(sum1+ 0.0000001);
  28. }
  29. }
  30. sum /= (row*col);
  31. return sum;
  32. }

(2) 初始化控制参数代码


   
  1. #define randf(a, b) (((rand()%10000+rand()%10000*10000)/100000000.0)*((b)-(a))+(a))
  2. void init_bpline_para(Mat src, int row_block_num, int col_block_num, Mat &grid_points, float min, float max)
  3. {
  4. int grid_rows = row_block_num + BPLINE_BOARD_SIZE;
  5. int grid_cols = col_block_num + BPLINE_BOARD_SIZE;
  6. int grid_size = grid_rows*grid_cols;
  7. grid_points.create(Size( 2*grid_size, 1), CV_32FC1);
  8. float *grid_points_data = grid_points.ptr< float>( 0);
  9. srand(( unsigned int)time( NULL));
  10. for ( int i = 0; i < grid_size; i++)
  11. {
  12. grid_points_data[i] = randf(min, max); //x
  13. int cnt = 100000000;
  14. while(cnt--);
  15. grid_points_data[i+grid_size] = randf(min, max); //y
  16. cnt = 100000000;
  17. while(cnt--);
  18. }
  19. }

(3) 基于C++与CUDA实现的FFD变换代码


   
  1. __global__ void Bspline_Ffd_kernel(uchar *srcimg, uchar *dstimg, int row_block_num, int col_block_num, float *grid_points, int row, int col)
  2. {
  3. int x = threadIdx.x + blockDim.x * blockIdx.x; //col
  4. int y = threadIdx.y + blockDim.y * blockIdx.y; //row
  5. if(x < col && y < row)
  6. {
  7. int grid_rows = row_block_num + BPLINE_BOARD_SIZE;
  8. int grid_cols = col_block_num + BPLINE_BOARD_SIZE;
  9. int grid_size = grid_rows*grid_cols;
  10. float delta_x = col* 1.0/col_block_num;
  11. float delta_y = row* 1.0/row_block_num;
  12. float x_block = x / delta_x;
  13. float y_block = y / delta_y;
  14. int j = floor(x_block);
  15. int i = floor(y_block);
  16. float u = x_block - j;
  17. float v = y_block - i;
  18. float pX[ 4], pY[ 4];
  19. pX[ 0] = ( 1 - u*u*u + 3*u*u - 3*u) / 6.0;
  20. pX[ 1] = ( 4 + 3*u*u*u - 6*u*u) / 6.0;
  21. pX[ 2] = ( 1 - 3*u*u*u + 3*u*u + 3*u) / 6.0;
  22. pX[ 3] = u*u*u / 6.0;
  23. pY[ 0] = ( 1 - v*v*v + 3*v*v - 3*v) / 6.0;
  24. pY[ 1] = ( 4 + 3*v*v*v - 6*v*v) / 6.0;
  25. pY[ 2] = ( 1 - 3*v*v*v + 3*v*v + 3*v) / 6.0;
  26. pY[ 3] = v*v*v / 6.0;
  27. float Tx = 0;
  28. float Ty = 0;
  29. for ( int m = 0; m < 4; m++) //行
  30. {
  31. for ( int n = 0; n < 4; n++) //列
  32. {
  33. int control_point_x = j + n;
  34. int control_point_y = i + m;
  35. float temp = pY[m] * pX[n];
  36. Tx += temp*grid_points[control_point_y*grid_cols+control_point_x]; //x
  37. Ty += temp*grid_points[control_point_y*grid_cols+control_point_x+grid_size]; //y
  38. }
  39. }
  40. float src_x = x + Tx;
  41. float src_y = y + Ty;
  42. int x1 = floor(src_x);
  43. int y1 = floor(src_y);
  44. if (x1 < 1 || x1 >= col -1 || y1 < 1 || y1 >= row -1) //越界
  45. {
  46. dstimg[y*col+x] = 0;
  47. }
  48. else
  49. {
  50. //dstimg[y*col+x] = srcimg[y1*col+x1]; //最邻近插值
  51. int x2 = x1 + 1; //双线性插值
  52. int y2 = y1 + 1;
  53. uchar pointa = srcimg[y1*col+x1];
  54. uchar pointb = srcimg[y1*col+x2];
  55. uchar pointc = srcimg[y2*col+x1];
  56. uchar pointd = srcimg[y2*col+x2];
  57. uchar gray = (uchar)((x2 - src_x)*(y2 - src_y)*pointa - (x1 - src_x)*(y2 - src_y)*pointb - (x2 - src_x)*(y1 - src_y)*pointc + (x1 - src_x)*(y1 - src_y)*pointd);
  58. dstimg[y*col+x] = gray;
  59. }
  60. }
  61. }
  62. void Bspline_Ffd_cuda(Mat srcimg, Mat &dstimg, int row_block_num, int col_block_num, Mat grid_points)
  63. {
  64. dim3 Bpline_Block(16, 16); //每个线程块有16*16个线程
  65. int M = (srcimg.cols+Bpline_Block.x -1)/Bpline_Block.x;
  66. int N = (srcimg.rows+Bpline_Block.y -1)/Bpline_Block.y;
  67. dim3 Bpline_Grid(M, N);
  68. int grid_rows = row_block_num + BPLINE_BOARD_SIZE;
  69. int grid_cols = col_block_num + BPLINE_BOARD_SIZE;
  70. int grid_size = grid_rows*grid_cols;
  71. int img_size = srcimg.cols*srcimg.rows;
  72. uchar *srcimg_cuda;
  73. uchar *dstimg_cuda;
  74. float *grid_points_cuda;
  75. cudaMalloc(( void**)&srcimg_cuda, img_size);
  76. cudaMalloc(( void**)&dstimg_cuda, img_size);
  77. cudaMalloc(( void**)&grid_points_cuda, 2*grid_size* sizeof( float));
  78. cudaMemcpy(srcimg_cuda, srcimg.data, img_size, cudaMemcpyHostToDevice);
  79. cudaMemcpy(grid_points_cuda, grid_points.data, 2*grid_size* sizeof( float), cudaMemcpyHostToDevice);
  80. Bspline_Ffd_kernel<< <Bpline_Grid, Bpline_Block >> >(srcimg_cuda, dstimg_cuda, row_block_num, col_block_num, grid_points_cuda, srcimg.rows, srcimg.cols);
  81. Mat tmp(srcimg.size(), CV_8UC1);
  82. cudaMemcpy(tmp.data, dstimg_cuda, img_size, cudaMemcpyDeviceToHost);
  83. tmp.copyTo(dstimg);
  84. cudaFree(srcimg_cuda);
  85. cudaFree(dstimg_cuda);
  86. cudaFree(grid_points_cuda);
  87. }

(4) 目标函数代码


   
  1. float F_fun_bpline(Mat S1, Mat Si, int row_block_num, int col_block_num, Mat grid_points)
  2. {
  3. double result;
  4.   Mat Si_tmp;
  5.   
  6. Bspline_Ffd_cuda(Si, Si_tmp, row_block_num, col_block_num, grid_points);
  7.   result = cal_cc_block(S1, Si_tmp,  55);    //默认分为5*5块计算互相关
  8. return result;
  9. }

(5) 求取梯度代码


   
  1. void cal_gradient(Mat S1, Mat Si, int row_block_num, int col_block_num, Mat grid_points, Mat &gradient)
  2. {
  3. float EPS = 1; //1e-4f;
  4. gradient.create(grid_points.size(), CV_32FC1);
  5. float a1 = F_fun_bpline(S1, Si, row_block_num, col_block_num, grid_points);
  6. Mat grid_p = grid_points.clone();
  7. for( int i = 0; i < grid_points.cols; i++)
  8. {
  9. grid_p.at< float>( 0, i) += EPS;
  10. float a2 = F_fun_bpline(S1, Si, row_block_num, col_block_num, grid_p);
  11. grid_p.at< float>( 0, i) -= EPS;
  12.     gradient.at< float>( 0, i) = (a2 - a1) / EPS;   
  13. }
  14. }

(6) 使用梯度来更新控制参数的代码


   
  1. void update_grid_points(Mat &grid_points, Mat gradient, float alpha)
  2. {
  3. for( int i = 0; i < grid_points.cols; i++)
  4. {
  5. grid_points.at< float>( 0, i) = grid_points.at< float>( 0, i) - gradient.at< float>( 0, i)*alpha;
  6. }
  7. }

(7) 梯度下降法代码


   
  1. int bpline_match(Mat S1, Mat Si, Mat &M, int row_block_num, int col_block_num, Mat &grid_points)
  2. {
  3. int max_iter = 5000; //最多迭代次数
  4. Mat gradient, pre_gradient;
  5. Mat pre_grid_points;
  6. double e = 0.000005; //定义迭代精度
  7. float ret1 = 0.0;
  8. float ret2 = 0.0;
  9. int cnt = 0;
  10. float alpha = 8000000;
  11.    //求梯度
  12.   cal_gradient(S1, Si, row_block_num, col_block_num, grid_points, gradient);    //求梯度
  13. int out_cnt = 0;
  14. while (cnt < max_iter)
  15. {
  16. pre_grid_points = grid_points.clone();
  17.     update_grid_points(grid_points, gradient, alpha);   //更新输入参数
  18. ret1 = F_fun_bpline(S1, Si, row_block_num, col_block_num, pre_grid_points); //F_fun(S1, Si, S1_entropy, delta_x, delta_y, pre_grid_points);
  19. ret2 = F_fun_bpline(S1, Si, row_block_num, col_block_num, grid_points); //F_fun(S1, Si, S1_entropy, delta_x, delta_y, grid_points);
  20. if (ret2 > ret1) //如果当前轮迭代的目标函数值大于上一轮的函数值,则减小步长并重新计算梯度、重新更新参数
  21. {
  22. alpha *= 0.8;
  23. grid_points = pre_grid_points.clone();
  24. continue;
  25. }
  26.      cout<< "f="<<ret2<< ", alpha="<<alpha<< endl;
  27. if ( abs(ret2 - ret1) < e)
  28. {
  29. out_cnt++;
  30. if(out_cnt >= 4) //如果连续4次目标函数值不改变,则认为求到了最优解,停止迭代
  31.       {
  32.         Bspline_Ffd_cuda(Si, M, row_block_num, col_block_num, grid_points);
  33. return 0;
  34. }
  35. }
  36. else
  37. {
  38. out_cnt = 0;
  39.     }
  40. gradient.copyTo(pre_gradient);
  41. //求梯度
  42.     cal_gradient(S1, Si, row_block_num, col_block_num, grid_points, gradient);   //求梯度
  43.      //如果梯度相比上一次迭代有所下降,则增大步长
  44. if(norm(gradient, NORM_L2) <= norm(pre_gradient, NORM_L2))
  45.        alpha *=  2;
  46. cnt++;
  47. }
  48. return -1;
  49. }

(8) 测试代码


   
  1. void ffd_match_test(void)
  2. {
  3. Mat img1 = imread( "lena.jpg", CV_LOAD_IMAGE_GRAYSCALE);
  4.   Mat img2 = imread( "lena_out.jpg", CV_LOAD_IMAGE_GRAYSCALE);
  5. int row_block_num = 30;
  6. int col_block_num = 30;
  7. Mat grid_points;
  8. init_bpline_para(img1, row_block_num, col_block_num, grid_points, -0.001, 0.001);
  9. Mat out;
  10. bpline_match(img1, img2, out, row_block_num, col_block_num, grid_points);
  11. imshow( "img1", img1);
  12. imshow( "img2", img2);
  13. imshow( "out", out);
  14. imshow( "img1-img2", abs(img1-img2));
  15. imshow( "img1-out", abs(img1- out));
  16. waitKey();
  17. }

运行上述代码,对扭曲的Lena图像进行配准,结果如下图所示。可以看到,配准之后,配准图像没有那么扭曲了,也即与基准图像的位置更加对齐了。

基准图像

浮动图像

配准图像

基准图像与浮动图像的差值图

基准图像与配准图像的差值图

目标函数值的下降过程

微信公众号如下,欢迎关注:


转载:https://blog.csdn.net/shandianfengfan/article/details/113750401
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场