2020-12.24 用SURF实现简易的两张图像拼接

第九天

今日写了一天代码,故打算细写一篇记录。

为什么写拼接

   尽管OPENCV已经已经提供了方便强大且全面的stiching类,但是作为初学者~~(尤其是opencv正儿八经的工程都没写过一个的)~~ ,有必要体验一下整个缝合的过程。加之之前书看了一堆,到底怎么用,还是得自己动手才有感觉。
   环境:opencv3.4.5+vscode2017+ubuntu18.04,注意opencv要有contrib库,别跟我个憨憨一样再回头装。如果你真的没有。

原理

  整体流程有四+1步(主要参考别人博客)

  1. 从两幅图片中提取特征点,可以用SURF、SIFT、ORB、FLANN等花里胡哨的,我用的是SURF。
  2. 匹配两幅图像中的特征点,这一步是核心,匹配效果的好坏将严重影响效果。
  3. 根据配对的特征点求出旋转矩阵,这里只用了仿射变换,但opencv的实现里好像还有相机参数估计和透视变换。
  4. 将其中一张图片旋转到另一个的坐标系下,并将重合的部分拼接到一起
  5. 边缘优化。

这里贴出两个主要参考的博客:
原始大哥的博客
超级大哥的博客,参考了上面,但内容更全

1.提取特征点

    我这里用的是SURF,因为它虽然精度差一点,但速度快上几倍,稳定性也更好。
当然我不会讲SURF的原理,因为我也就只是看过一遍,想看的话网上一大把;更重要的是,你其实没必要知道它的详细数学过程,只要知道大概方法和怎么用就行了。

///*------------------------------------------------------SURF特征点检测

      int minHEssian = 2000;//hessian阈值,越大筛选越严格,匹配的特征点越少
      //初始化SURF类和特征点向量
      cv::Ptr<SURF>detector  = SURF::create(minHEssian);
      std::vector<KeyPoint>    keyPoint1,keyPoint2;
      //检测特征点,保存在vector中
      detector->detect(srcImage1,keyPoint1);
      detector->detect(srcImage2,keyPoint2);

      //计算特征向量描述符
      cv::Ptr<SURF>extractor  = SURF::create();
      Mat descriptors1,descriptors2;
      std::vector< DMatch > matches;
      extractor->compute ( srcImage1, keyPoint1, descriptors1);
      extractor->compute ( srcImage2, keyPoint2, descriptors2);


    吐槽一下:opencv都出到4了,由于版权大家只能用3,书上讲的却都是2,函数名字一个都不对。

2.匹配特征点

    上面我们用SURF求得了两幅图片的特征点,现在就该匹配他们了,上代码。

      //使用匹配器匹配
      cv::Ptr<DescriptorMatcher>matcher  = DescriptorMatcher::create("BruteForce");//匹配方法
      matcher->match(descriptors2,descriptors1,matches);   //前者称为query集,后者成为train集
      sort(matches.begin(),matches.end());//排序,误差距离短的在前面
      matches.erase(matches.begin()+GOODPOINTNUM,matches.end());//只用前GOODPOINTNUM个匹配点

      //绘制匹配点
      Mat imgMatches;
      cv::drawMatches(srcImage2,keyPoint2,srcImage1,keyPoint1,matches,imgMatches); 

      cv::imshow("特征点匹配",imgMatches);

    这里用了一个noob优化,排序,然后取最相近的前几个点,虽然有用,但效果有限,主要是为了去除明显错误的匹配。效果如下:

在这里插入图片描述网上有很短匹配的优化算法,这里我只是DD尝试版,就不列出了。

3.求出变换矩阵

基础知识:
在这里插入图片描述

   首先说一下基本思想,就是为什么我们知道对应特征点之后就能把图像匹配过去了
首先我们得明确匹配在一起的特征点的物理意义是什么:它们是我们在不同的图片中发现的“相同物体”——至少算法认为它们是相同的。也就是说,相当于现实里物体的一点,你站了两个不同的角度去拍摄它,尽管在两幅图上的位置和样子不同,但它是同一个点!而仿射变换处理的是什么呢?是一个二维图形的旋转、平移、缩放、翻转(严格的来说仿射只是二维的,但我们可以认为拍摄两个照片的位置非常接近,相机只进行了上述运动)。
   但是!!!运动是相对的,对一个图像进行向左的平移,不就等于你的摄像头向右平移吗?因此,既然两幅图是在不同的位置(位姿不同)对统一个东西(匹配的特征点)投影生成的二维图像,那么这两个相机的位置就一定对应了一个仿射变换矩阵,而我们只需要有足够的数据,即特征点(3点就能确定一个平面),就能将其解出。
   当然,我们没必要自己去考虑如何用过饱和的数据求出尽可能精确的解,但是有必要知道基本的思路。

代码很简单~~(毕竟不用懂都能写)~~ :

///*----------------------------------------------------------坐标系转换
    std::vector<cv::Point2f> imagePoints1, imagePoints2;//findHomography需要Point2f类型
     for(int i=0;i<matches.size();i++)
     {
   
        imagePoints2.push_back(keyPoint2[matches[i].queryIdx].pt);
        imagePoints1.push_back(keyPoint1[matches[i].trainIdx].pt);
     }
    //获得透视矩阵并进行投影 
     //图像1到2的映射,3*3转换阵
   Mat transMat = cv::findHomography(imagePoints1,imagePoints2,CV_RANSAC);
   Mat adjustMat1=(cv::Mat_<double>(3,3)<<1.0,0,srcImage1.cols/2,0,1.0,0,0,0,1.0);  //平移变换矩阵,将矩阵沿着长的方向平移一个图像
   transMat=adjustMat1*transMat;
   cout<<"透视变换矩阵为"<<transMat<<endl;

我中间加了个平移矩阵,是因为,有的点仿射变换后会跑到外边去,这样我能把他移回来(主要是横向)。
在这里插入图片描述

仿射变换:
https://www.zhihu.com/question/20666664
https://www.cnblogs.com/happystudyeveryday/p/10547316.html 可以看一下这个里面的平移、旋转等单独矩阵,单拿出来处理图像用也很不错,我就从这里面取的。
http://www.opencv.org.cn/opencvdoc/2.3.2/html/doc/tutorials/imgproc/imgtrans/warp_affine/warp_affine.html
同时也顺便了解一下透视变换(三维):
https://zhuanlan.zhihu.com/p/36082864
https://zhuanlan.zhihu.com/p/36191127
https://www.cnblogs.com/liekkas0626/p/5262942.html

变换后(有平移,绿色是最佳匹配点):
在这里插入图片描述

4.图像配准与拼接

    我这里是按照大哥的方法找到最佳匹配点然后将两幅图像在此纵向对齐,你也可以粗暴的直接覆盖到右边,当然不管怎么样都得先扩大一下图像面积,然后将两张图精准的放进去,剩下的就主要数字游戏了。

cv::Point2f PointTrans(const Point2f srcPoint,  const Mat& transMat)//对点仿射变换
{
   
  Mat oriPos,tarPos;
  oriPos=(cv::Mat_<double>(3,1)<<srcPoint.x,srcPoint
  • 3
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
您可以使用Matlab中的FFT函数进行谐波分析,基于提供的数据,可以按照以下步骤编写程序: 1. 定义给定数据的采样频率 `fs`,根据数据中的时间间隔计算得出,即 `fs = 1 / (时间间隔)`; 2. 对输入数据进行FFT变换,使用fft函数,得到频域数据 `Y`; 3. 计算基波幅值 `fundamental`,即第一个频率分量的幅值,即 `abs(Y(1))`; 4. 计算2到11次谐波幅值 `harmonics`,即第2到11个频率分量的幅值,即 `abs(Y(2:11))`; 5. 输出结果。 下面是一个可能的Matlab程序: ``` % 定义时间间隔和数据 t = [0 0.000208333 0.000416667 0.000625 0.000833333 0.001041667 0.00125 0.001458333 0.001666667 0.001875 0.002083333 0.002291667 0.0025 0.002708333 0.002916667 0.003125 0.003333333 0.003541667 0.00375 0.003958333 0.004166667 0.004375 0.004583333 0.004791667 0.005 0.005208333 0.005416667 0.005625 0.005833333 0.006041667 0.00625 0.006458333 0.006666667 0.006875 0.007083333 0.007291667 0.0075 0.007708333 0.007916667 0.008125 0.008333333 0.008541667 0.00875 0.008958333 0.009166667 0.009375 0.009583333 0.009791667 0.01 0.010208333 0.010416667 0.010625 0.010833333 0.011041667 0.01125 0.011458333 0.011666667 0.011875 0.012083333 0.012291667 0.0125]; y = [15.92 15.68 15.52 15.04 14.6 14.08 12.96 12.24 10.96 9.76 8.28 6.8 5.56 3.24 1.72 -0.76 -3 -5.24 -6.76 -8.4 -10.24 -11.32 -12.72 -14.4 -15.2 -16.28 -16.6 -17.28 -17.44 -17.24 -17.32 -17.04 -16.84 -16.04 -15.88 -15.16 -14.08 -13.32 -12.04 -10.88 -10.16 -7.96 -6.4 -4.72 -2.36 -0.48 2.08 3.8 5.92 7.32 8.88 10.36 11.64 12.88 13.72 14.84 15.44 15.76 15.96 16.04 15.52]; % 计算采样频率 fs = 1 / (t(2) - t(1)); % 计算FFT变换 Y = fft(y); % 计算基波幅值 fundamental = abs(Y(1)); % 计算2到11次谐波幅值 harmonics = abs(Y(2:11)); % 输出结果 fprintf('基波幅值: %.2f\n', fundamental); fprintf('2到11次谐波幅值: %.2f ', harmonics); fprintf('\n'); ``` 注意:这个程序假设您提供的数据是一个完整的周期数据,如果不是一个完整的周期数据需要进行预处理,例如使用窗函数。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值