当前位置:   article > 正文

【OpenCV】SURF图像拼接和Stitcher拼接

surf图像拼接

介绍两种图像拼接的方法,一种是SURF算法的图像拼接,另一种是Stitcher硬拼接

首先先从简单的讲起

一、Stitcher直接拼接

可以实现多张图片一起拼接,只要两行代码就可以实现拼接;

1.首先准备多张图片,放入向量容器中

  1. Mat img1 =imread("a.png");
  2. Mat img2 =imread("b.png");
  3. Mat img3 =imread("c.png");
  4. Mat img4 =imread("d.png");
  5. //图片放入容器中
  6. vector<Mat>images;
  7. images.push_back(img1);
  8. images.push_back(img2);
  9. images.push_back(img3);
  10. images.push_back(img4);

2. 创建Stitcher对象,调用拼接算法

第一行false是表示不使用gpu加速;

  1. //保存最终拼接图
  2. Mat result;
  3. Stitcher sti=Stitcher::createDefault(false);
  4. //将vector容器中所有的图片按顺序进行拼接,输出result
  5. Stitcher::Status sta=sti.stitch(images,result);
  6. if(sta!=Stitcher::OK)
  7. {
  8. cout<<"拼接失败"<<endl;
  9. }
  10. imshow("result",result);//显示

注:createDefault的方法在opencv3可用,opencv4改变了调用方式,为Stitcher::create

来看看这种拼接的效果吧

原图:

 4张图

 第一次输出效果:

 第二次输出效果:

可以看到,虽然能够拼接,但是有时候可能会丢失一部分,导致最右边没有拼接上;

虽然使用起来很简单,但是不能每次都达到想要的效果;

这边介绍第二种拼接方法:

二、SURF算法

SURF拼接一次只能拼接两张图片,其大致步骤就是匹配两幅图像中的特征点,找到最优匹配特征点;

根据配对的特征点计算坐标映射矩阵,求出右图的透视转换坐标;然后将右图透视转换后生成的图与左图进行整合,使用copyto将两图拼接

1.查找特征点

  1. Mat left=imread("left.png");
  2. Mat right=imread("right.png");
  3. imshow("left",left);
  4. imshow("right",right);
  5. //创建SURF算法对象
  6. Ptr<SURF> surf;
  7. //create 函数参数 海森矩阵阀值 800特征点以内
  8. surf =SURF::create(800);
  9. //创建一个暴力匹配器 用于特征点匹配
  10. BFMatcher matcher;
  11. //特征点容器 存放特征点KeyPoint 两张图准备两个
  12. vector<KeyPoint>key1,key2;
  13. //保存特征点
  14. Mat c,d;
  15. //1.查找特征点
  16. //左图 右图 识别特征点 是Mat对象 用c d保存
  17. surf->detectAndCompute(left,Mat(),key2,d);
  18. surf->detectAndCompute(right,Mat(),key1,c);
  19. //特征点对比
  20. vector<DMatch> matches;
  21. matcher.match(d,c,matches);//特征点匹配过后存入matchers容器
  22. //将匹配过后的特征点排序 从小到大,找到特征点连线
  23. sort(matches.begin(),matches.end());

2.保存最优匹配的特征点对象,进行划线

  1. vector<DMatch>best_matches;
  2. int prtpoint=std::min(50,(int)(matches.size()*0.15));
  3. for(int i=0;i<prtpoint;i++)
  4. {
  5. best_matches.push_back(matches[i]);
  6. }
  7. //2.1进行划线,连接两个最优特征点对象
  8. //NOT_DRAW_SINGLE_POINTS不画单个的点
  9. Mat outimg;
  10. drawMatches(left,key2,right,key1,best_matches,outimg,Scalar::all(-1),Scalar::all(-1),
  11. vector<char>(),DrawMatchesFlags::NOT_DRAW_SINGLE_POINTS);

3.特征点匹配

查找所有最优匹配特征点中,右图需要通过透视转换变形,而左图查找基准线

  1. vector<Point2f>imagepoint1,imagepoint2;
  2. for (int i= 0 ;i < best_matches.size();i++)
  3. {
  4. //查找特征点可连接处 右图需要通过透视转换变形
  5. imagepoint1.push_back(key1[best_matches[i].trainIdx].pt);
  6. //查找特征点可连接处 左图查找基准线
  7. imagepoint2.push_back(key2[best_matches[i].queryIdx].pt);
  8. }

4.透视转换,图形融合

根据配对的特征点计算坐标映射矩阵,求出透视转换坐标;将右图透视转换后生成的图与左图进行整合,使用copyto将两图拼接

  1. Mat homo = findHomography(imagepoint1,imagepoint2,CV_RANSAC);
  2. //根据透视转换矩阵进行计算 右图的四个坐标
  3. CalcCorners(homo,right);
  4. //接收透视转换结果
  5. Mat imageTransForm;
  6. //透视转换
  7. warpPerspective(right,imageTransForm,homo,Size(MAX(corners.right_top.x,corners.right_bottom.x),left.rows));
  8. imshow("imageTransForm",imageTransForm);
  9. //将左图和右转换图进行整合
  10. int dst_width = imageTransForm.cols;//右转换图的宽
  11. int dst_height = left.rows;//左图的高
  12. Mat dst(dst_height,dst_width,CV_8UC3);//最终结果图
  13. dst.setTo(0);
  14. imageTransForm.copyTo(dst(Rect(0,0,imageTransForm.cols,imageTransForm.rows)));
  15. left.copyTo(dst(Rect(0,0,left.cols,left.rows)));

 5.拼接后图片可能存在拼接处的裂缝,还有扭曲,可以进行一些优化

  1. //图像融合的去裂缝处理操作
  2. void OptimizeSeam(Mat& img1, Mat& trans, Mat& dst)
  3. {
  4. int start = MIN(corners.left_top.x, corners.left_bottom.x);//开始位置,即重叠区域的左边界
  5. double processWidth = img1.cols - start;//重叠区域的宽度
  6. int rows = dst.rows;
  7. int cols = img1.cols; //注意,是列数*通道数
  8. double alpha = 1;//img1中像素的权重
  9. for (int i = 0; i < rows; i++)
  10. {
  11. uchar* p = img1.ptr<uchar>(i); //获取第i行的首地址
  12. uchar* t = trans.ptr<uchar>(i);
  13. uchar* d = dst.ptr<uchar>(i);
  14. for (int j = start; j < cols; j++)
  15. {
  16. //如果遇到图像trans中无像素的黑点,则完全拷贝img1中的数据
  17. if (t[j * 3] == 0 && t[j * 3 + 1] == 0 && t[j * 3 + 2] == 0)
  18. {
  19. alpha = 1;
  20. }
  21. else
  22. {
  23. //img1中像素的权重,与当前处理点距重叠区域左边界的距离成正比,实验证明,这种方法确实好
  24. alpha = (processWidth - (j - start)) / processWidth;
  25. }
  26. d[j * 3] = p[j * 3] * alpha + t[j * 3] * (1 - alpha);
  27. d[j * 3 + 1] = p[j * 3 + 1] * alpha + t[j * 3 + 1] * (1 - alpha);
  28. d[j * 3 + 2] = p[j * 3 + 2] * alpha + t[j * 3 + 2] * (1 - alpha);
  29. }
  30. }
  31. }

完整源码:

  1. #include <iostream>
  2. #include <opencv2/opencv.hpp>
  3. #include <opencv2/highgui.hpp>//图像融合
  4. #include <opencv2/xfeatures2d.hpp>//拼接算法
  5. #include <opencv2/calib3d.hpp>
  6. #include <opencv2/imgproc.hpp>
  7. using namespace std;
  8. using namespace cv;
  9. using namespace cv::xfeatures2d;
  10. typedef struct
  11. {
  12. Point2f left_top;
  13. Point2f left_bottom;
  14. Point2f right_top;
  15. Point2f right_bottom;
  16. }four_corners_t;
  17. four_corners_t corners;
  18. void CalcCorners(const Mat& H, const Mat& src)
  19. {
  20. double v2[] = { 0, 0, 1 };//左上角
  21. double v1[3];//变换后的坐标值
  22. Mat V2 = Mat(3, 1, CV_64FC1, v2); //列向量
  23. Mat V1 = Mat(3, 1, CV_64FC1, v1); //列向量
  24. V1 = H * V2;
  25. //左上角(0,0,1)
  26. cout << "V2: " << V2 << endl;
  27. cout << "V1: " << V1 << endl;
  28. corners.left_top.x = v1[0] / v1[2];
  29. corners.left_top.y = v1[1] / v1[2];
  30. //左下角(0,src.rows,1)
  31. v2[0] = 0;
  32. v2[1] = src.rows;
  33. v2[2] = 1;
  34. V2 = Mat(3, 1, CV_64FC1, v2); //列向量
  35. V1 = Mat(3, 1, CV_64FC1, v1); //列向量
  36. V1 = H * V2;
  37. corners.left_bottom.x = v1[0] / v1[2];
  38. corners.left_bottom.y = v1[1] / v1[2];
  39. //右上角(src.cols,0,1)
  40. v2[0] = src.cols;
  41. v2[1] = 0;
  42. v2[2] = 1;
  43. V2 = Mat(3, 1, CV_64FC1, v2); //列向量
  44. V1 = Mat(3, 1, CV_64FC1, v1); //列向量
  45. V1 = H * V2;
  46. corners.right_top.x = v1[0] / v1[2];
  47. corners.right_top.y = v1[1] / v1[2];
  48. //右下角(src.cols,src.rows,1)
  49. v2[0] = src.cols;
  50. v2[1] = src.rows;
  51. v2[2] = 1;
  52. V2 = Mat(3, 1, CV_64FC1, v2); //列向量
  53. V1 = Mat(3, 1, CV_64FC1, v1); //列向量
  54. V1 = H * V2;
  55. corners.right_bottom.x = v1[0] / v1[2];
  56. corners.right_bottom.y = v1[1] / v1[2];
  57. }
  58. //图像融合的去裂缝处理操作
  59. void OptimizeSeam(Mat& img1, Mat& trans, Mat& dst)
  60. {
  61. int start = MIN(corners.left_top.x, corners.left_bottom.x);//开始位置,即重叠区域的左边界
  62. double processWidth = img1.cols - start;//重叠区域的宽度
  63. int rows = dst.rows;
  64. int cols = img1.cols; //注意,是列数*通道数
  65. double alpha = 1;//img1中像素的权重
  66. for (int i = 0; i < rows; i++)
  67. {
  68. uchar* p = img1.ptr<uchar>(i); //获取第i行的首地址
  69. uchar* t = trans.ptr<uchar>(i);
  70. uchar* d = dst.ptr<uchar>(i);
  71. for (int j = start; j < cols; j++)
  72. {
  73. //如果遇到图像trans中无像素的黑点,则完全拷贝img1中的数据
  74. if (t[j * 3] == 0 && t[j * 3 + 1] == 0 && t[j * 3 + 2] == 0)
  75. {
  76. alpha = 1;
  77. }
  78. else
  79. {
  80. //img1中像素的权重,与当前处理点距重叠区域左边界的距离成正比,实验证明,这种方法确实好
  81. alpha = (processWidth - (j - start)) / processWidth;
  82. }
  83. d[j * 3] = p[j * 3] * alpha + t[j * 3] * (1 - alpha);
  84. d[j * 3 + 1] = p[j * 3 + 1] * alpha + t[j * 3 + 1] * (1 - alpha);
  85. d[j * 3 + 2] = p[j * 3 + 2] * alpha + t[j * 3 + 2] * (1 - alpha);
  86. }
  87. }
  88. }
  89. void directly()
  90. {
  91. Mat img1 =imread("a.png");
  92. Mat img2 =imread("b.png");
  93. Mat img3 =imread("c.png");
  94. Mat img4 =imread("d.png");
  95. // imshow("a",img1);
  96. // imshow("b",img2);
  97. // imshow("c",img3);
  98. // imshow("d",img4);
  99. //图片放入容器中
  100. vector<Mat>images;
  101. images.push_back(img1);
  102. images.push_back(img2);
  103. images.push_back(img3);
  104. images.push_back(img4);
  105. //保存最终拼接图
  106. Mat result;
  107. Stitcher sti=Stitcher::createDefault(false);
  108. //将vector容器中所有的图片按顺序进行拼接,输出result
  109. Stitcher::Status sta=sti.stitch(images,result);
  110. if(sta!=Stitcher::OK)
  111. {
  112. cout<<"拼接失败"<<endl;
  113. }
  114. imshow("result",result);
  115. }
  116. int main()
  117. {
  118. Mat left=imread("left.png");
  119. Mat right=imread("right.png");
  120. imshow("left",left);
  121. imshow("right",right);
  122. //创建SURF算法对象
  123. Ptr<SURF> surf;
  124. //create 函数参数 海森矩阵阀值 800特征点以内
  125. surf =SURF::create(800);
  126. //创建一个暴力匹配器 用于特征点匹配
  127. BFMatcher matcher;
  128. //特征点容器 存放特征点KeyPoint 两张图准备两个
  129. vector<KeyPoint>key1,key2;
  130. //保存特征点
  131. Mat c,d;
  132. //1.查找特征点
  133. //左图 右图 识别特征点 是Mat对象 用c d保存
  134. surf->detectAndCompute(left,Mat(),key2,d);
  135. surf->detectAndCompute(right,Mat(),key1,c);
  136. //特征点对比
  137. vector<DMatch> matches;
  138. matcher.match(d,c,matches);//特征点匹配过后存入matchers容器
  139. //将匹配过后的特征点排序 从小到大,找到特征点连线
  140. sort(matches.begin(),matches.end());
  141. //2.保存最优匹配的特征点对象
  142. vector<DMatch>best_matches;
  143. int prtpoint=std::min(50,(int)(matches.size()*0.15));
  144. for(int i=0;i<prtpoint;i++)
  145. {
  146. best_matches.push_back(matches[i]);
  147. }
  148. //2.1进行划线,连接两个最优特征点对象
  149. //NOT_DRAW_SINGLE_POINTS不画单个的点
  150. Mat outimg;
  151. drawMatches(left,key2,right,key1,best_matches,outimg,Scalar::all(-1),Scalar::all(-1),
  152. vector<char>(),DrawMatchesFlags::NOT_DRAW_SINGLE_POINTS);
  153. //imshow("outimg",outimg);//划线图
  154. //3.特征点匹配
  155. vector<Point2f>imagepoint1,imagepoint2;
  156. for (int i= 0 ;i < best_matches.size();i++)
  157. {
  158. //查找特征点可连接处 右图需要通过透视转换变形
  159. imagepoint1.push_back(key1[best_matches[i].trainIdx].pt);
  160. //查找特征点可连接处 左图查找基准线
  161. imagepoint2.push_back(key2[best_matches[i].queryIdx].pt);
  162. }
  163. //4、透视转换 图形融合
  164. //先进行计算坐标映射矩阵
  165. Mat homo = findHomography(imagepoint1,imagepoint2,CV_RANSAC);
  166. //根据透视转换矩阵进行计算 右图的四个坐标
  167. CalcCorners(homo,right);
  168. //接收透视转换结果
  169. Mat imageTransForm;
  170. //透视转换
  171. warpPerspective(right,imageTransForm,homo,Size(MAX(corners.right_top.x,corners.right_bottom.x),left.rows));
  172. imshow("imageTransForm",imageTransForm);
  173. //将左图和右转换图进行整合
  174. int dst_width = imageTransForm.cols;//右转换图的宽
  175. int dst_height = left.rows;//左图的高
  176. Mat dst(dst_height,dst_width,CV_8UC3);//最终结果图
  177. dst.setTo(0);
  178. imageTransForm.copyTo(dst(Rect(0,0,imageTransForm.cols,imageTransForm.rows)));
  179. left.copyTo(dst(Rect(0,0,left.cols,left.rows)));
  180. //5、优化图像,中间缝合处理
  181. OptimizeSeam(left,imageTransForm,dst);
  182. //输出拼接图像
  183. imshow("dst",dst);
  184. //directly();//直接拼接
  185. waitKey(0);
  186. return 0;
  187. }

感谢观看!!!!

以上就是全部内容,如果对您有帮助,欢迎点赞评论,或者发现有哪里写错的,欢迎指正!

本文内容由网友自发贡献,转载请注明出处:【wpsshop博客】
推荐阅读
  

闽ICP备14008679号