摘要:最近做双目匹配,需要用到OpenCV的特征识别匹配,但是对于低反射率物体即使投影了随机散斑之后出来的效果依旧很差,于是乎看看特征匹配的源码,看看能不能从原理上有所发现(用的knnMatch并且已经极线对准,可是效果在有的图上比较凉凉)。废话不多说,这篇博文讲的是看源码学习OpenCV,仿佛没找到比较好的文章,于是,自己看,写一个。后续有发现的话在后面补充。环境:OpenCV3.2源码,VS2013(为什么不用新的?公司要求啊。。。要求统一环境)
//其实很早就该写了,但是考虑到科研可能要用这个改匹配算法,就先没写,后来才用了别的方案。这篇博文讲的是这个算法的组织,以及可以根据学习结果改一改匹配函数用来针对性提升效果。所以默认读者是会用的。
先看看OpenCV的官方文档
这里用的OpenCV3.3的文档,为什么不用3.2的文档?因为我之前用3.3后来被迫换3.2,于是乎懒得下3.2文档,毕竟基本都一样,发现不对的话再去查,这里懒一下,其实这么做是不对的。(反正是实习,随便玩哈哈哈*2333)看文档有助于看懂源码,尤其是理清类之间的关系。
基类的方法,这里主要要看的是箭头标记的那俩,因为这俩是我要用的。发现这个DescriptorMatcher是个抽象的基类,后面有俩子类,实际上我们在用的时候也是用的后面俩,分别是BFMatch和FlannBasedMatcher。
上面是match()的文档,看不清可以去文档官网一大把doc,各种版本都有哈,我习惯下下来看,比较方便,一样的。
再看看knnMatch()的文档,嗯。好了不解释了,很简单的英文。match()匹配点对,knnMatch()返回每个点的k个匹配点,所以感觉knnMatch给的选择更多一点,而且给出候选点更可能包含真正的匹配点(事实就是这样的,后面说)
在源码里找到match()的实现:
发现match()实际上调用就是knnMatch()把返回k个的匹配包了一层皮,设置返回一个最匹配的点。所以重点就一个knnMatch()就完了。我们转到定义,去看看knnMatch()发现:
使用时候用的API:BFMatch和FlannBasedMatcher
看一眼源码发现knnMatch()第一个调用的是第二个重载的knnMatch()(猜也猜得到)然后有调用的DescriptorMatcher: :knnMatchImpl()在两个基类的实现,于是乎vs go to define时候有:BF还是FLANN,也就是说上面说过的两个子类要分别实现父类:DescriptorMatcher的方法了(顺便看一下大佬写C++,学学经验)我goto到了BFMatcher::knnMatchImpl()。这个方法分别在两个子类里面实现了。这个基类是没有实现的。
忽略掉一堆为了安全机制和指令集还有工程上的那堆杂七杂八的成熟的考虑,比如一堆宏和条件编译。看重点,判断两个向量的匹配度还不是要算距离函数,什么欧氏距离、余弦距离、汉明距离…
找到了计算距离函数的地方,这里默认欧氏距离了(看默认参数)。然后去找找文档。文档里没有给匹配函数指定距离函数的参数,其实在构造器构造函数里:
也是默认构造用的欧氏距离,读者有兴趣可以试试别的距离函数,
enum NormTypes { NORM_INF = 1,
NORM_L1 = 2,
NORM_L2 = 4,
NORM_L2SQR = 5,
NORM_HAMMING = 6,
NORM_HAMMING2 = 7,
NORM_TYPE_MASK = 7,
NORM_RELATIVE = 8, //!< flag
NORM_MINMAX = 32 //!< flag
};
文档里有对NormTypes的公式定义,很清晰,宏分别对应什么距离函数一清二楚。但是这是有问题的,一会看下面。
在core/stat.cpp里实现的距离计算。(字体突然变了?换成了VS2017,2013版的go to def找不到,17版这些方面还有实时看资源占用是真的方便。。想用17。。。)可以看到源码对于CV_8U还是很友好的,实现了不少,对于CV_32F只实现了三种,说好的那么多NormType。当然这里我们看到的可能不一样,我不记得我是不是改过源码,印象中至少其他部分我改过,改完应该会有我的标记的,毕竟过去接近一年了,坏习惯。红色剪头是crosscheck的实现,一个自我调用,参数取false就完成了,这个操作天秀啊(对于当时我这个没见过世面的菜鸡来说。现在习惯了)。
如果想要修改匹配规则,按照源码的布局,在你的工程里复制大部分BFMatch的源码,照葫芦画瓢改就可以了,对于CV_32F,我尝试了不同的距离函数,也可以自定义距离运算。这里贴出来:
这样就可以自己随心所欲地修改匹配规则了。详细的就不贴了,最后。我放弃了OpenCV的匹配规则(尝试实现了源码没有实现的方法,发现还是NORM_L2稳定可靠,尴尬,因为浮点数乘除太容易出现问题了),而是采用纯数学计算和图像处理的方法。如果文章录的话,我把文章再贴这。不过文章也没有描述的很详细,领导不让写详细,摊手。照葫芦画瓢,Flann也能自己看懂了。不过建议先找文章和文档看,不然直接上手源码还是有压力的,看完文章和文档可能还有不清楚的,这时候源码描述的一清二楚。先写到这里,我写博客真随意。