PnP问题的DLT解法
欢迎关注知乎@司南牧
已知:上一帧相机座标系下的点的三维齐次座标Q,和,那n个点在当前帧中的二维齐次座标q, 和相机内参矩阵K。
待求解变量 :当前帧相对上一帧的位姿变换矩阵[R∣t] 。
约束方程:
K[R∣t]Q=λq
我们记
[R∣t]=⎣⎡a11a21a31a12a22a32a13a23a33a14a24a34⎦⎤
DLT的思路就是把aij代入式(1),然后求出aij就可以求出[R∣t]。可以看到]现在我们有12个未知数一对点只能提供两个方程,所以需要6对点。
目前已知相机内参矩阵
K=⎣⎡fx000fy0cxcy1⎦⎤
还已知在上一帧相机座标系下的三维齐次座标
Q=⎣⎢⎢⎡xyz1⎦⎥⎥⎤
还已知对应点在当前帧的二维座标
q=⎣⎡uv1⎦⎤
将式子(2),(3),(4),(5)代入到式(1)中得到:
K[R∣t]Q=⎣⎡fx000fy0cxcy1⎦⎤⎣⎡a11a21a31a12a22a32a13a23a33a14a24a34⎦⎤⎣⎢⎢⎡xyz1⎦⎥⎥⎤=λ⎣⎡uv1⎦⎤
进行矩阵乘法得到:
⎣⎡fxa11+cxa31fya21+cya31a31fxa12+cxa32fya22+cya32a32fxa13+cxa33fya23+cya33a33fxa14+cxa34fya24+cya34a34⎦⎤⎣⎢⎢⎡xyz1⎦⎥⎥⎤=⎣⎡x(fxa11+cxa31)+y(fxa12+cxa32)+z(fxa13+cxa33)+(fxa14+cxa34)x(fya21+cya31)+y(fya22+cya32)+z(fya23+cya33)+(fya24+cya34)xa31+ya32+za33+a34⎦⎤=λ⎣⎡uv1⎦⎤
根据式子(7)我们就得到了三个方程。我们将最后一行代入前两行消除λ可以得到:
[x(fxa11+cxa31)+y(fxa12+cxa32)+z(fxa13+cxa33)+(fxa14+cxa34)x(fya21+cya31)+y(fya22+cya32)+z(fya23+cya33)+(fya24+cya34)]=[uxa31+uya32+uza33+ua34vxa31+vya32+vza33+va34]
整理得到:
xfxa11+yfxa12+zfxa13+fxa14+x(cx−u)a31+y(cx−u)a32+z(cx−u)a33+(cx−u)a34=0xfya21+yfya22+zfya23+fya24+x(cy−v)a31+y(cy−v)a32+z(cy−v)a33+(cy−v)a34=0
所以一对点对应两个方程。需要6对点才能解该方程。
式子(9)写成矩阵的形式得到:
[xfx0yfx0zfx0fx00xfy0yfy0zfy0fyx(cx−u)x(cy−v)y(cx−u)y(cy−v)z(cx−u)z(cy−v)(cx−u)(cy−v)]⎣⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎢⎡a11a12a13a14a21a22a23a24a31a32a33a34⎦⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎥⎤=0
这就变成了求解Ax=0问题。在线性代数里面有很多方式可以求解这个方程。
在SLAM中常用的方法是这样看Ax=0x,所以只用求矩阵A特征值为0所对应的特征向量。用SVD对矩阵A分解得到A=UDV,其中V的最后一列就是Ax=0的解。
根据此时求出的x可以算出[R′∣t′],但是Ax=0它也可以看做是Acx=c0,所以此时求出的x它是真实的[R|t]乘上一个常数后得到的结果。我们需要计算出那个常数。而且求出的R′并不是一个正交矩阵。而我们的约束条件要求R′是一个正交矩阵。为了将它变成一个正交矩阵需要对求得的R′进行SVD分解让让其变成正交矩阵
U′D′V′=svd(R′)
其中U′,V′都是正交矩阵,D′是对角矩阵,为了让R′变成正交矩阵我们需要让D′对角线元素全部相等。
E′=dia(3tr(D′))R′=U′E′V′
而且旋转矩阵R还有一个性质就是行列式为1.所以放缩因子就是c=±3tr(D′)。到底取正还是负有两种方式:
- 代入式子(12)计算R′的行列式看是否大于0
- λ它是点在相机座标系下的z轴方向的座标值,而点肯定是在相机的前方,所以λ>0。我们计算下面这个式子看是否大于0.(推荐使用这个,因为计算量相对较小)
c(xa31+ya32+za33+a34)=λ>0
编程实践
Python代码
import numpy as np
fx = 1
fy = 1
cx = 0
cy= 0
K = np.array([
[fx,0,cx],
[0,fy,cy],
[0,0,1]
])
Rt_groundtruth = np.array([
[1,0,0,3],
[0,1,0,3],
[0,0,1,3]
])
Qn = np.random.rand(4,6)
qn = K @ Rt_groundtruth @ Qn
qn = qn/qn[-1]
zeros_nx4 = np.zeros((Qn.shape[1],4))
A_up = np.column_stack((fx*Qn.T,zeros_nx4, Qn.T * np.expand_dims((cx-qn[0]).T,1)))
A_below = np.column_stack((zeros_nx4,fy*Qn.T, Qn.T * np.expand_dims((cy-qn[1]).T,1)))
A = np.row_stack((A_up,A_below))
U,D,V=np.linalg.svd(A)
x = V[-1]
Rt = x.reshape((3,4))
R = Rt[:3,:3]
t = Rt[:3,-1]
U,D,V = np.linalg.svd(R)
c=1/(np.sum(D)/3)
Q_verify = np.random.rand(4,1)
q_verify = K @ Rt_groundtruth @ Q_verify
if np.sum(c*(Q_verify.T*Rt[-1]))<0:
c = -c
D = np.diag(D*c)
R = U @ D @ V
print(R)
print(t*c)