请看赵春江https://me.csdn.net/zhaocj的主页,他已经对Opencv图像拼接流程中的代码做了很详细的解释。前人栽树,后人乘凉。
一.本文所做的事
1.重构了Opencv图像拼接的源代码,整个代码是面向过程的;
2.在赵春江源码分析基础上,对一些细节部分进行说明。
代码链接:https://github.com/mhhai/ImageStitch
二.特征点检测
一切起源于这段代码
Ptr
finder =newOrbFeaturesFinder(); 这段代码生成了一个OrbFeaturesFinder对象,其构造函数为:
OrbFeaturesFinder
可以看出这里面创建了一个ORB类。关于这一点,看OrbFeaturesFinder类的继承结构
![61108131313c80d44b98f8cba606a372.png](https://img-blog.csdnimg.cn/img_convert/61108131313c80d44b98f8cba606a372.png)
真正实现ORB算法是在orb.cpp中的ORB_Impl类,其他的都是一些函数调用过程,这也是opencv难以看懂的部分。OrbFeaturesFinder类声明(在matchers.hpp)如下:
class
可以看出OrbFeaturesFinder是没有重载()的,那么它为什么可以使用
(
这段代码呢,因为在其父类FeaturesFinder中实现了()的重载,派生类直接使用父类的公有方法。父类FeaturesFinder重载()代码如下:
void
接下来,在函数operator()中,调用了find函数,由于OrbFeaturesFinder类实现了自己的find函数版本,所以,接下来执行OrbFeaturesFinder中的find函数,该函数有点长,但还是拿来说明一下:
void
之所以把这段这么长的代码贴出来,是因为这段代码从全局的角度讲述了怎样进行特征点检测。继续看
finder = new OrbFeaturesFinder();
这段代码,由OrbFeaturesFinder类的构造函数可知,使用默认参数时是检测出1500个特征点,建立5层高斯金字塔图像,那么第一个参数Size(3,1)参数作何解释?Size(3,1)是说把图像分成1 * 3的网格,对每个网格进行特征点检测,每个网格检测出的数目是500,那么总共检测出的特征点数目就为1500,可以看下面的示意图:
![6a1c6569ffd4b76dc169a41ce5f5cad5.png](https://img-blog.csdnimg.cn/img_convert/6a1c6569ffd4b76dc169a41ce5f5cad5.png)
不是检测出1500个特征点吗,在哪里设置500这个参数了?其实在你第一次写
finder = new OrbFeaturesFinder();
这段代码时,构造函数中的这段代码
orb = ORB::create(n_features * (99 + grid_size.area())/100/grid_size.area(), scaleFactor, nlevels);
create函数第一个参数的值被设置成了510,也就是每个区域检测出的特征点数目不是刚好500,而是多一点。通过设置find的函数中的r、c参数,你可以对想要的部分进行特征点检测。另外,从这里可以看出,Opencv实现的ORB特征点检测并不是对整幅图像建立高斯金字塔,而是对每个网格建立高斯金字塔。另外每层金字塔检测出来的数目是根据尺度实现分配好的,这方面可以参看赵春江的书或者博客。
三.特征点匹配
这一部分类与类之间的关系比较复杂,具体可以看下图:
![ce755fc26267e7a6c9d30663ece291db.png](https://img-blog.csdnimg.cn/img_convert/ce755fc26267e7a6c9d30663ece291db.png)
特征匹配使用的是最近邻匹配(BestOf2NearestMatcher),实现最近邻匹配可以使用暴力匹配法和K-D树最近邻搜索。不是说暴力匹配法一定不好。对于3000个特征点对以下的特征匹配,暴力匹配法是优于K-D树搜寻的,因为K-D树的构建本身就需要耗费时间。
当你使用下面代码时
BestOf2NearestMatcher
其构造函数为:
BestOf2NearestMatcher
该类内部维护的一个FeaturesMatcher对象(智能指针)就被实例化为CpuMatcher,所以特征匹配调用的关系虽然很复杂,但是没有必要去研究这些,只需要着重于CpuMatcher类中的match函数,该函数实现了两个特征点集之间的匹配。BestOf2NearestMatcher类也有一个match函数,该match函数属于更上层,它调用了CpuMatcher类中的match()函数以及findHomography()函数,其中findHomography()实现了使用RANSAC算法去除误匹配。关于findHomography()重构的代码放在下一小节中,findHomography()能够看到RANSAC算法的精细之处。另外,knnMatch()代码没有重构出来,个人感觉这部分代码重构难度大,而且不重要,因此还是重构findHomography()更具有意义。
四.计算单应性矩阵
这部分代码是在特征匹配的基础上继续深入的,目的是为了更好地看出计算单应性矩阵的过程。包括通过4个点计算单应性矩阵、使用重映射误差剔除外点,更新RANSAC算法的迭代阈值等。
五.恢复相机内外参
这部分代码只重构的相机内参的部分。相机外参涉及到光束平差法、雅可比矩阵和波形校正等等。。。这部分我也很晕。所以,计算外参,尤其是多幅图像拼接之间的外参计算是很麻烦的。愿有后来人能将这部分代码讲清楚。。。
六.投影变换
如果你的项目镜头是固定的,也就是说你的内外参是固定的,上面的结果就可以看作是预训练的过程。因此你就可以不需要太去了解光束平差法怎么实现之类的细节问题。这一部分我会再细讲一下。
七.动态规划法寻找最佳缝合线
这部分很早之前就重构出来了,因此很多细节都我忘了。还是可以看一看的。
八.图像融合
这部分是我针对项目中特殊镜头成像写的,因此不具普遍性。这部分代码主要包括计算能量函数、寻找最佳缝合线,渐入渐出法融合。因为只寻找了小部分的缝合线,所以PC机图像融合部分耗是0.3s(两张图片)。当然还是很慢。由于Opencv羽化融合不适合我的图像,所以前面代码中的pano结果中间会有细微的黑影。
总结:
基本重构出了Opencv图像拼接整个流程的代码,由于一些工作是早期做的,所以很多东西没有记录下来,只有重构好的代码保留了下来。所以大家可以当作这是一份参考资料。