飞道的博客

图像配准系列之“Sift特征点+薄板样条变换”配准方法

607人阅读  评论(0)

在前文中,我们已经有好几篇讲图像配准的文章:

1. 图像配准系列之Sift特征点检测与匹配(1)

2. 图像配准系列之Sift特征点检测与匹配(2)

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

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

5. 图像配准系列之基于FFD形变与LM算法的图像配准

6. 图像配准系列之基于FFD形变与粒子群算法的图像配准

7. 图像配准系列之层次FFD形变配准

8. TPS薄板样条变换计算原理及C++实现

在以上第1、第2篇文章中,我们讲了图像配准的概念、Sift特征点的基本原理,以及基于“Sift特征点+仿射变换”的图像配准原理和C++实现。我们知道,仿射变换模型具有整体的特性,对局部形变的矫正效果并不理想。相比来说FFD变换与TPS薄板样条变换,则对局部形变具有更强的矫正能力。基于FFD变换的配准方法我们在前文已经讲过,本文我们主要讲基于“Sift特征点+TPS薄板样条变换”的配准方法,并使用C++与Opencv来将之实现。

1. Sift算法原理

在上述第1、2篇文章中已讲,此处不再重复。

2. TPS薄板样条变换原理

在上述第8篇文章中已讲,此处也不再重复。

3. 配准步骤

配准步骤的大致步骤如下图所示:

其中特征向量的匹配与剔除,我们在上方超链接的第2篇文章已讲过。

像素重采样时,由于坐标是浮点型的,会使用到插值算法,我们在之前的文章也详细说明:

常见图像插值算法的原理与C++实现

双三次插值算法的C++实现与SSE指令优化

4. 代码实现

好了,原理我们基本都讲过,下面直接上代码。

(1) TPS的代码

可参考开头超链接的第8篇文章,不过这里需要修正一下矩阵W的计算,调用Opencv的solve函数解矩阵方程LW=Y时,如果参数是DECOMP_LU时会出错,但是如果改为DECOMP_EIG就没问题,这里应该涉及到矩阵可逆以及求解算法的问题,有空再继续深究。


   
  1. //W = {w0, w1, w2, ..., a1, ax, ay}
  2. void cal_W(vector<Point2f> points1, vector<Point2f> points2, Mat &W)
  3. {
  4.    int N = points1.size();
  5. Mat L;
  6.   cal_L(points1, L);
  7. Mat Y = Mat::zeros(Size( 2, N+ 3), CV_32FC1); //row:N+3, col:2
  8. for( int i = 0; i < N; i++)
  9. {
  10. Y.ptr< float>(i)[ 0] = points2[i].x;
  11. Y.ptr< float>(i)[ 1] = points2[i].y;
  12. }
  13. //solve(L, Y, W, DECOMP_LU); //LW = Y,求W
  14. solve(L, Y, W, DECOMP_EIG);
  15.    //W = L.inv()*Y;
  16. }

(2) 检测特征点代码

这里有个问题需要说明一下,如果直接对整张图像进行Sift特征点检测,得到的特征点分布不均匀,然而点分布越均匀,TPS变换效果越好,因此这样直接检测整张图的特征点,最后配准效果并不理想。所以我们将图像分为多个块,分别检测各个块的特征点,然后拼接成整张图像的特征点,这样一来,特征点的分布就比较均匀了

代码如下:


   
  1. vector<KeyPoint> detect_sift_block(Mat img, int num, int b_row, int b_col)
  2. {
  3. const int block_rsize = img.rows / b_row;
  4. const int block_csize = img.cols / b_col;
  5. vector<KeyPoint> kp;
  6. vector<KeyPoint> kp_tmp;
  7. Ptr<SiftFeatureDetector> siftdtc = SiftFeatureDetector::create(num, 3, 0.04, 10.0, 1.6);
  8.   
  9. for ( int i = 0; i < b_row; i++) //分块检测,确保均匀分布
  10. {
  11. for ( int j = 0; j < b_col; j++)
  12. {
  13. int xx = j*block_csize;
  14. int yy = i*block_rsize;
  15. Mat tmp;
  16. img(Rect(xx, yy, block_csize, block_rsize)).copyTo(tmp);
  17. siftdtc->detect(tmp, kp_tmp);
  18.        //将小块的点坐标转换成整张图像的点坐标
  19. for ( int k = 0; k < kp_tmp.size(); k++)
  20. {
  21. kp_tmp[k].pt.x += xx;
  22. kp_tmp[k].pt.y += yy;
  23. }
  24.        //将多个小块的特征点拼接成整张图像的特征点
  25. kp.insert(kp.end(), kp_tmp.begin(), kp_tmp.end());
  26. }
  27. }
  28.    return kp;
  29. }

(3) 配准代码

使用具有局部形变的Lena图像进行配准测试,分别使用TPS变换与仿射变换作为坐标变换模型,并比较结果。


   
  1. void Sift_Tps_test(void)
  2. {
  3. Mat img1 = imread( "lena.jpg", CV_LOAD_IMAGE_GRAYSCALE);
  4.   Mat img2 = imread( "lena_ffd.jpg", CV_LOAD_IMAGE_GRAYSCALE);
  5.   
  6. imshow( "image before", img1);
  7. imshow( "image2 before", img2);
  8.    // SIFT - 检测关键点并在原图中绘制
  9. vector<KeyPoint> kp1, kp2;
  10. kp1 = detect_sift_block(img1, 50, 5, 5);
  11. kp2 = detect_sift_block(img2, 50, 5, 5);
  12. Mat outimg1, outimg2;
  13. drawKeypoints(img1, kp1, outimg1);
  14. drawKeypoints(img2, kp2, outimg2);
  15. imshow( "image1 keypoints", outimg1);
  16.   imshow( "image2 keypoints", outimg2);
  17. // SIFT - 特征向量提取
  18. Ptr<SiftDescriptorExtractor> extractor = SiftDescriptorExtractor::create();
  19. Mat descriptor1, descriptor2;
  20. extractor->compute(img1, kp1, descriptor1);
  21. extractor->compute(img2, kp2, descriptor2);
  22. // 两张图像的特征匹配
  23. Ptr<DescriptorMatcher> matcher = DescriptorMatcher::create( "BruteForce");
  24. vector<DMatch> matches;
  25. Mat img_matches;
  26.   matcher->match(descriptor1, descriptor2, matches);
  27. //计算匹配结果中距离最大和距离最小值
  28. double min_dist = matches[ 0].distance, max_dist = matches[ 0].distance;
  29. for ( int m = 0; m < matches.size(); m++)
  30. {
  31. if (matches[m].distance < min_dist)
  32. {
  33. min_dist = matches[m].distance;
  34. }
  35. if (matches[m].distance > max_dist)
  36. {
  37. max_dist = matches[m].distance;
  38. }
  39. }
  40. cout << "min dist=" << min_dist << endl;
  41. cout << "max dist=" << max_dist << endl;
  42. vector<DMatch> goodMatches;
  43. for ( int i = 0; i < matches.size(); i++) //筛选出较好的匹配点
  44. {
  45. if (matches[i].distance < 0.35*max_dist)
  46. {
  47. goodMatches.push_back(matches[i]);
  48. }
  49. }
  50. cout << "The number of good matches:" << goodMatches.size() << endl;
  51. //坐标转换为float类型
  52. vector <KeyPoint> good_kp1, good_kp2;
  53. for ( int i = 0; i < goodMatches.size(); i++)
  54. {
  55. good_kp1.push_back(kp1[goodMatches[i].queryIdx]);
  56. good_kp2.push_back(kp2[goodMatches[i].trainIdx]);
  57. }
  58. //坐标变换
  59. vector <Point2f> p01, p02;
  60. for ( int i = 0; i < goodMatches.size(); i++)
  61. {
  62. p01.push_back(good_kp1[i].pt);
  63. p02.push_back(good_kp2[i].pt);
  64. }
  65. vector<uchar> RANSACStatus; //用以标记每一个匹配点的状态,等于0则为外点,等于1则为内点。
  66. findFundamentalMat(p01, p02, RANSACStatus, CV_FM_RANSAC, 4.5); //p1 p2必须为float型
  67. vector<Point2f> f1_features_ok;
  68. vector<Point2f> f2_features_ok;
  69. for ( int i = 0; i < p01.size(); i++) //剔除跟踪失败点
  70. {
  71. if (RANSACStatus[i])
  72. {
  73. f1_features_ok.push_back(p01[i]); //基准图特征点
  74. f2_features_ok.push_back(p02[i]); //流动图特征点
  75. }
  76. }
  77. #if 1   //这里使用TPS变换
  78. Mat Tx, Ty;
  79. Tps_TxTy(f1_features_ok, f2_features_ok, img2.rows, img2.cols, Tx, Ty);
  80. Mat tps_out;
  81. remap(img2, tps_out, Tx, Ty, INTER_CUBIC);
  82. imshow( "img2-img1", abs(img2-img1));
  83. imshow( "tps_out-img1", abs(tps_out - img1));
  84. imshow( "tps_out", tps_out);
  85. #else //这里使用仿射变换
  86. Mat T = estimateRigidTransform(f2_features_ok, f1_features_ok, true); //false表示刚性变换
  87. Mat affine_out;
  88. warpAffine(img2, affine_out, T, img2.size(), INTER_CUBIC);
  89. imshow( "img2-img1", abs(img2 - img1));
  90. imshow( "affine_out-img1", abs(affine_out - img1));
  91. imshow( "affine_out", affine_out);
  92. #endif
  93. cv::waitKey( 0);
  94. }

5. 配准结果

(1) 首先是使用“Sift+仿射变换”的结果:

参考图像


浮动图像

参考图像的Sift特征点

浮动图像的Sift特征点

配准图像

参考图像与浮动图像的差值图

参考图像与配准图像的差值图

(2) 其次是使用“Sift+TPS变换”的结果:

配准图像

参考图像与浮动图像的差值图

参考图像与配准图像的差值图

由以上结果可知,仿射变换模型并不能很好的矫正局部形变,相比来说,TPS变换却可以较好地矫正局部形变。不过TPS计算更复杂更耗时,因此在实际操作中,我们需要根据图像地特点来选择合适的坐标变换模型,使配准效果与配准耗时都满足应用场景要求。

欢迎扫码关注以下微信公众号,接下来会不定时更新更加精彩的内容噢~


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