阅读DLS、EPNP、UPNP三篇论文,对三种算法进行推导与对比。
PnP是通过n个点来估计位姿,比如P3P算法,如果n越多,那么就可以认为估计的位姿越来越准确,但是如果算法的复杂度随n的上升而快速上升,就会导致更多的点求解位姿时速度变得很慢,因此如果一个PnP算法的复杂度是O(n)的,那么理论上它就具有极高的精度和速度。所以我们这次来看一些具有O(n)复杂度的求解PnP的算法,先看下EPNP,然后是DLS和UPNP(Unified PnP)。
EPNP
这篇论文的核心思想就是用4个控制点来表示出所有的点,这样无论估计多少个点,都会最终回归到使用4个控制点来估计位姿,而在计算控制点的复杂度是O(n),这样就可以保证算法的复杂度是O(n)的。
控制点
假设空间下的点为
理论上来说空间点是三维的,可以使用3个控制点就可以完全表示,但是为了更好的同时表示旋转和平移,空间都是使用齐次坐标,这样相当于就是4维的,所以需要4个控制点。
通过上式第二行可以看出,还应该有下式成立
只要上式中
对于如何计算出控制点,作者在文中特意提到了使用中心和特征值的方法。
我个人对这里的理解是:如果用控制点作为新的基底,然后使用归一化的坐标来原有空间点的齐次坐标进行表示,可以将原有的齐次坐标映射成另一个坐标,但是此时由于控制点任意,那么映射后的坐标是不定的,仅仅这样随意选取控制点去映射不能使得控制点综合所有空间点的信息,而如果实现给映射后的坐标值一个约束,即满足
上面是世界坐标系中空间点与控制点的关系,下面我们来看一下相机坐标系中的空间点与控制点的关系。
这里可以看到,由于这条
因此,我们只需要求解出控制点在相机坐标系下的坐标,就可以求解出所有空间点在相机坐标系下的坐标。
求解相机坐标系下的控制点
在求解之前,我们先来结合一下相机模型来看一下每个相机坐标系下的空间点的成像,并且把每个空间点用控制点来表示
显然,每一行都能得到一个方程,而利用第三行能消去
由于需要求解的是相机坐标系下的控制点,也就是
其中
上式可以看成是
其中
求解
上述解中除了右奇异向量外,还有一项非常重要的
考虑一下特征值与特征向量
因此,有几个特征值趋近于0,那么就说明解空间的基底个数有几个。
结合上面的分析,就可以知道总共会有N=1、2、3、4这四种情况,原文中对每一种情况都进行了讨论与计算,主要计算的方式就是两条原则
- 相机坐标系下控制点之间的距离 与 世界坐标系下控制点之间的距离是一样的
- 相机坐标系下的控制点是
,而 可以用 和 表示
通过上述方法就可以求解出
但是同样,如果把两个坐标系下控制点间距的差作为优化目标,也同样可以通过优化的方式求解,作者在原文中使用优化来对上述闭式解进行进一步的计算,从而得出一个更加精准的解,也就是优化下式。相当于使用上述闭式解来求解出一个非常好的初始值。
虽然这里其实是引入了迭代,但首先这其实是一个可选的步骤,之前的闭式解的精度也相当高了;其次,由于闭式解已经相当接近最优解了,所以迭代的次数不会很多,甚至可以设置成一个固定的迭代次数。因此不影响本算法的复杂度仍然为O(n)
在得到
ORB_SLAM2中的PnP求解器
ORB_SLAM2中的PnP求解器就是基于EPnP的,不过他在EPnP的外面包了一层RANSAC,它的核心代码基本跟上述步骤一致,此处先抛开RANSAC不谈,毕竟RANSAC的思想也不是很难。
但是ORB-SLAM2中一开始并不知道N的值,所以默认都是当作N=4来计算的,只不过由于N=4时未知量个数超过了方程个数,所以此处的做法是先提取出原方程的一部分来构成新的方程,使用SVD求解出这个方程后用优化的方法来进一步求解出
1 | double PnPsolver::compute_pose(double R[3][3], double t[3]) |
DLS
DLS也是一种不错的PnP求解方法,下面也来阐述一下,不过这里的符号将不再和原文保持一致,因为后面一篇UPnP也存在类似的步骤。
问题建模
还是以最最常见的P3P来看一下,问题的建模非常简单,而我们的目标就是优化一个损失函数,让重投影误差最小。
上式中的
理论上来说直接进行优化也不是不行,但是这会受到初始值得影响,不一定稳定;且运行速度上也可能会存在一定的问题。
DLS是这样处理的,下式中的
也就是下式(这里应该都是矩阵/向量,只不过我最近生成latex公式的工具用的不太熟,改生成出来的latex源码实在是不方便,就懒得弄了)
然后我们求解
注意这里
这里相当于是求解出来
注意,之前是有n个
接下来使用Cayley-Gibbs-Rodriguez (CGR) 参数化,目的是把
在说CGR参数化之前,先来说说为什么需要这么做。
首先,正常情况下需要表示一个旋转,往往只需要3个参数即可,而
因为这两个约束的存在,会对直接求解旋转矩阵造成很多的麻烦,有时候人们会不管约束,直接求解旋转矩阵,再投影到一个最接近的欧式正交群的流形上来求解。而CGR就是把旋转矩阵分解成三个不相关的参数,这样就可以不用考虑约束的问题了。
CGR参数化只有简单的两个公式:
这里的
感觉网上也没有很具体阐述这个CGR参数化,好像大家都是直接拿来用的,也不知道是怎么推导出来的,好像只有在这篇文章有很少的一些解释
这种参数化最大的作用就是把旋转矩阵转化成无约束的形式,然后方便后续求解,可以看一下这里
在我们使用CGR参数化后,由于
重写损失函数
因为通过CGR参数化,旋转矩阵已经不在具有约束,因此就可以转换成一种无约束的优化。
其中,
接下来的优化目标就是最小化这个噪声,这样相当于就能求解出旋转了,只要求解出旋转,那么就能回代到之前的式子中求解出平移
Macaulay matrix来直接求解
这部分可能理解存在偏差,如果有人知道这是怎么一回事欢迎交流下
文中又针对性的提出了一些更进一步的直接的解法,作者使用Bezout
bound用于确定由损失函数
如果已经求出了最优解,那么每个偏导都将是等于0
但是关于
这里的
然后用
然后通过
由于
这里把
接下来通过矩阵分块可以得到一个特征值、特征向量的形式,因此只需要特征值分解就可以求出解了。
其中
当然DLS也可以用LM去优化之前那个式子,说实话,我感觉用优化或许更好理解一点...
UPNP
这个UPnP指的是Unified PnP,论文地址,从名字来看,这篇也是提出一个复杂度为O(n)的解法,可以在一定程度上看成是DLS的拓展,因为之前的EPnP和DLS都是针对光心位于相机坐标系原点的情况来考虑的,而这篇UPnP则考虑了光心不在相机坐标系原点下的更一般的情况的建模。
这个是作者关于各个算法之间的比较,可以看到列出的所有算法都是O(n)的,而仅有少数算法可以处理非中心光心的情况。此外,之前我们看到DLS是将旋转矩阵参数化成三个参数,因此会出现旋转奇点情况,而本文是参数化成了四元数,则不会有这种情况。
模型建立
下面是非中心光心下的模型示意图。
可以看到这种情况下出来的光线不会最终经过光心,因此这种情况下的模型会有所不一样。
相当于对之前的光心增加了偏移向量而已,然后接下来的处理也与DLS非常相似,也是先把
接下来也是非常熟悉的分块矩阵变换
要注意,这里面的
这里的
同样也能得到
同样可以导出优化目标,这里的
四元数参数化
然后使用四元数对旋转矩阵进行参数化(之前DLS使用CGR进行参数化,但是仅有3个维度会导致奇点问题),这样做的目的是推导出一个O(n)复杂度的误差矩阵。
先放一个结论在这里,之前的
首先,先把之前的
其中
接下来就需要对
其实这里就是把一个四元数转化成旋转矩阵,但是略有差别,因为这里是单位四元数,也就是
考虑一下,对于点
这样子,我们就可以把
接下来再定义
第二个等号这里我没看明白是怎么把
这里可以看到,我们最终把
但我们最终需要的不是误差项,而是误差项的平方,而平方又可以写成转置与自身的相乘,因此有
这里的
上面是对于单个点的误差,如果对所有点的误差进行求和就有
这个矩阵
Gröbner basis求解方程组
上述可以使用优化法来进行求解旋转矩阵,但是与DLS一样,使用非迭代的方法来求解位姿的闭式解。之前DLS使用一个奇怪的Macaulay matrix来求解,而这里则是使用Gröbner basis来求解,明显UPnP的这个方法更加常见一点。
因为最优解一定是极值点,所以有导数等于0,这样就得到了4个方程
这里得到了四个方程,但是其实还有一个潜在的方程,因为这些方程都是二次齐次项,所以对于一个解来说,如果乘以一个常数,那么这个解也是成立的(简单看成
这四个方程包含四个未知数,但是这是一个非线性方程,也就是四元二次方程组,之前DLS也遇到了这种高次方程组,而DLS是把所有的高次项看作是一个新的变量从而转化成线性方程并使用Macauly matrix来求解的。UPnP使用四元数参数化得到的高次方程组性质要好一点,因为每个高次项都是有且仅有二次,作者这里使用了Gröbner basis来求解。
关于Gröbner basis的具体内容,可以参考这篇和这篇,我个人对这里的理解也不是特别的充分,且原文这里说的感觉也是一笔略过的样子,就不班门弄斧了。
然后作者还探讨了一下使用带有单位矩阵约束的拉格朗日乘数法与Gröbner
basis方法之间的差距,简单来说因为拉格朗日乘数法中
二次对称性与二次最优
二次对称(2-fold symmetry)指的是q和-q对应的是同一个旋转矩阵,因为对旋转矩阵做四元数参数化时,所有的项要么是常数要么是二次项,负号全部对消。
而我们这里需要求解的多项式中虽然也都是平方或常数项,但是二次对称性会导致更多的解,这里的目的是在求解的过程中就直接避免这种情况的发生,因此在求解的过程中,我们需要引入一个额外的约束,这个约束是
openGV
openGV库实现了UPnP算法,当然还有一些其他的算法。编译这个库只需要安装Eigen3,我们要看的两个程序在test/test_absolute_pose.cpp
和test/test_absolute_pose_sac.cpp
中,前者是直接求解,后者是在RANSAC框架下进行求解。
不过这个库里的RANSAC没有UPnP的实现,需要自己稍微改一下程序添加进去看下效果。这里就简单跑2400个sample、用16个点估计位姿也就是P16P,看下准确率和平均时间
类型 | 无噪声无外点 | 2pixel噪声无外点 | 2pixel噪声10%外点 | 5pixel噪声10%外点 | 5pixel噪声20%外点 |
---|---|---|---|---|---|
UPnP准确率 | 100% | 100% | 85.3% | 82.4% | 69.4% |
UPnP每个sample耗时 | 0.68ms | 0.69ms | 0.68ms | 0.69ms | 0.68ms |
UPnP-RANSAC准确率 | 100% | 100% | 100% | 100% | 100% |
UPnP-RANSAC每个sample耗时 | 2.54ms | 122ms | 123ms | 126ms | 127ms |
这里其实把生成数据的时间也统计进去了,如果只考虑UPnP本身的耗时的话,还会更低,这个算法效率真的挺高的。