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)