赞
踩
(1)定义
对于两个点云source和target,求source经位姿变换后得到的点云与target尽可能重合的旋转
R
R
R与平移
t
t
t。由于实际中source和target两个点云中的点不一定完全对应,因此取二者能够相互关联的那些点(公共点)来参与位姿计算:
X
=
{
x
1
,
x
2
,
⋯
,
x
N
x
}
Y
=
{
y
1
,
y
2
,
⋯
,
y
N
y
}
N
x
=
N
y
X = \{ x_1,x_2,\cdots,x_{N_x} \} \ \ \ \ \ \ \ \ \ \ Y = \{ y_1, y_2, \cdots, y_{N_y} \} \ \ \ \ \ \ N_x=N_y
X={x1,x2,⋯,xNx} Y={y1,y2,⋯,yNy} Nx=Ny
目标:通过最小化损失函数计算旋转矩阵
R
R
R和平移向量
t
t
t
m
i
n
E
(
R
,
t
)
=
m
i
n
1
N
y
∑
i
=
1
N
y
∣
∣
x
i
−
R
y
i
−
t
∣
∣
2
min E(R,t) = min \frac{1}{N_y}\sum_{i=1}^{N_y}|| x_i-Ry_i-t||^2
minE(R,t)=minNy1i=1∑Ny∣∣xi−Ryi−t∣∣2
流程
获取两个点云之间的关联点一般是以二者距离最近的两个点为相互关联点,但若两初始点云相距较远,此时通过距离最近找到的关联点不一定是真正的关联点。在具有较好的初始位姿前提下,可通过迭代的方式,将当前迭代中变换后的点作为下一迭代的起始点,直到两关联点云达到预期的尽可能重合状态(较好的初始位姿是为了使经过迭代后两点云相互靠近,否则即使迭代多次后两点云仍不会达到预期重合状态)。其流程如下所示:
(2)ICP公式推导
根据定义可知,目标函数如下:
E
(
R
,
t
)
=
1
N
y
∑
i
=
1
N
y
∣
∣
x
i
−
R
y
i
−
t
−
u
x
+
R
u
y
+
u
x
−
R
u
y
∣
∣
2
=
1
N
y
∑
i
=
1
N
y
(
∣
∣
x
i
−
u
x
−
R
(
y
i
−
u
y
)
+
(
u
x
−
R
u
y
−
t
)
∣
∣
2
)
=
1
N
y
∑
i
=
1
N
y
(
∣
∣
x
i
−
u
x
−
R
(
y
i
−
u
y
)
∣
∣
2
+
∣
∣
u
x
−
R
u
y
−
t
∣
∣
2
+
2
(
x
i
−
u
x
−
R
(
y
i
−
u
y
)
)
T
(
u
x
−
R
u
y
−
t
)
)
=
1
N
y
∑
i
=
1
N
y
(
∣
∣
x
i
−
u
x
−
R
(
y
i
−
u
y
)
∣
∣
2
+
∣
∣
u
x
−
R
u
y
−
t
∣
∣
2
)
E(R,t)=\frac{1}{N_y}\sum_{i=1}^{N_y}||x_i-Ry_i-t-u_x+Ru_y+u_x-Ru_y||^2 \\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ =\frac{1}{N_y}\sum_{i=1}^{N_y}(||x_i-u_x-R(y_i-u_y)+(u_x-Ru_y-t)||^2) \\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ =\frac{1}{N_y}\sum_{i=1}^{N_y}(||x_i-u_x-R(y_i-u_y)||^2+||u_x-Ru_y-t||^2 \\ \ \ \ \ \ \ \ \ \ \ \ +2(x_i-u_x-R(y_i-u_y))^T(u_x-Ru_y-t)) \\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ =\frac{1}{N_y}\sum_{i=1}^{N_y}(||x_i-u_x-R(y_i-u_y)||^2+||u_x-Ru_y-t||^2)
E(R,t)=Ny1i=1∑Ny∣∣xi−Ryi−t−ux+Ruy+ux−Ruy∣∣2 =Ny1i=1∑Ny(∣∣xi−ux−R(yi−uy)+(ux−Ruy−t)∣∣2) =Ny1i=1∑Ny(∣∣xi−ux−R(yi−uy)∣∣2+∣∣ux−Ruy−t∣∣2 +2(xi−ux−R(yi−uy))T(ux−Ruy−t)) =Ny1i=1∑Ny(∣∣xi−ux−R(yi−uy)∣∣2+∣∣ux−Ruy−t∣∣2)
其中,
u
x
u_x
ux和
u
y
u_y
uy分别是点云X和Y的质心,即:
u
x
=
1
N
x
∑
i
=
1
N
x
x
i
u
y
=
1
N
y
∑
i
=
1
N
y
y
i
u_x=\frac{1}{N_x}\sum_{i=1}^{N_x}x_i \ \ \ \ \ \ \ \ \ \ u_y=\frac{1}{N_y}\sum_{i=1}^{N_y}y_i
ux=Nx1i=1∑Nxxi uy=Ny1i=1∑Nyyi
上述推导中去掉两点云的中心,其目的是: 中心到中心之间的距离就是两点云之间的平移,去中心之后两点云之间的约束只剩下旋转,这样可将平移和旋转解耦开。
令 E 1 ( R , t ) = 1 N y ∑ i = 1 N y ( ∣ ∣ x i − u x − R ( y i − u y ) ∣ ∣ 2 E_1(R,t)=\frac{1}{N_y}\sum_{i=1}^{N_y}(||x_i-u_x-R(y_i-u_y)||^2 E1(R,t)=Ny1i=1∑Ny(∣∣xi−ux−R(yi−uy)∣∣2 E 2 ( R , t ) = 1 N y ∑ i = 1 N y ∣ ∣ u x − R u y − t ∣ ∣ 2 E_2(R,t)=\frac{1}{N_y}\sum_{i=1}^{N_y}||u_x-Ru_y-t||^2 E2(R,t)=Ny1i=1∑Ny∣∣ux−Ruy−t∣∣2
那么,
E
(
R
,
t
)
=
E
1
(
R
,
t
)
+
E
2
(
R
,
t
)
E(R,t)=E_1(R,t)+E_2(R,t)
E(R,t)=E1(R,t)+E2(R,t)。
在求解
m
i
n
E
(
R
,
t
)
minE(R,t)
minE(R,t)的过程中,由于第一项
E
1
(
R
,
t
)
E_1(R,t)
E1(R,t)只和
R
R
R有关,因此只要根据
m
i
n
E
1
(
R
,
t
)
minE_1(R,t)
minE1(R,t)求出
R
R
R,令第二项
E
2
(
R
,
t
)
E_2(R,t)
E2(R,t)取最小值0,即可求出平移
t
t
t。
求解旋转矩阵 R R R
记两点云的去质心点云分别为:
x
i
,
=
x
i
−
u
x
x_i^,=x_i-u_x
xi,=xi−ux
y
i
,
=
y
i
−
u
y
y_i^,=y_i-u_y
yi,=yi−uy则
E
1
(
R
,
t
)
=
1
N
y
∑
i
=
1
N
y
∣
∣
x
i
−
u
x
−
R
(
y
i
−
u
y
)
∣
∣
2
=
1
N
y
∑
i
=
1
N
y
∣
∣
x
i
,
−
R
y
i
,
∣
∣
2
=
1
N
y
∑
i
=
1
N
y
(
x
i
,
T
x
i
,
+
y
i
,
T
R
T
R
y
i
,
−
2
x
i
,
T
R
y
i
,
)
=
1
N
y
∑
i
=
1
N
y
(
x
i
,
T
x
i
,
+
y
i
,
T
y
i
,
−
2
x
i
,
T
R
y
i
,
)
E_1(R,t)=\frac{1}{N_y}\sum_{i=1}^{N_y}||x_i-u_x-R(y_i-u_y)||^2=\frac{1}{N_y}\sum_{i=1}^{N_y}||x_i^,-Ry_i^,||^2 \\ \ \ \ \ =\frac{1}{N_y}\sum_{i=1}^{N_y}(x_i^{,T}x_i^,+y_i^{,T}R^TRy_i^,-2x_i^{,T}Ry_i^,) =\frac{1}{N_y}\sum_{i=1}^{N_y}(x_i^{,T}x_i^,+y_i^{,T}y_i^,-2x_i^{,T}Ry_i^,)
E1(R,t)=Ny1i=1∑Ny∣∣xi−ux−R(yi−uy)∣∣2=Ny1i=1∑Ny∣∣xi,−Ryi,∣∣2 =Ny1i=1∑Ny(xi,Txi,+yi,TRTRyi,−2xi,TRyi,)=Ny1i=1∑Ny(xi,Txi,+yi,Tyi,−2xi,TRyi,)
其中,只有第三项与
R
R
R有关。
令
E
1
,
(
R
,
t
)
=
∑
i
=
1
N
y
x
i
,
T
R
y
i
,
E_1^,(R,t)=\sum_{i=1}^{N_y}x_i^{,T}Ry_i^,
E1,(R,t)=∑i=1Nyxi,TRyi,则
m
i
n
E
1
(
R
,
t
)
minE_1(R,t)
minE1(R,t)即
m
a
x
E
1
,
(
R
,
t
)
maxE_1^,(R,t)
maxE1,(R,t)
E 1 , ( R , t ) = ∑ i = 1 N y x i , T R y i , = ∑ i = 1 N y T r a c e ( x i , T R y i , ) = ∑ i = 1 N y T r a c e ( R y i , x i , T ) = T r a c e ( R H ) E_1^,(R,t)=\sum_{i=1}^{N_y}x_i^{,T}Ry_i^,=\sum_{i=1}^{N_y}Trace(x_i^{,T}Ry_i^,)=\sum_{i=1}^{N_y}Trace(Ry_i^,x_i^{,T})=Trace(RH) E1,(R,t)=i=1∑Nyxi,TRyi,=i=1∑NyTrace(xi,TRyi,)=i=1∑NyTrace(Ryi,xi,T)=Trace(RH)
因此,问题转换为寻找合适的 R R R,使得 T r a c e ( R H ) Trace(RH) Trace(RH)取得最大值
定理:若有正定矩阵 A A T AA^T AAT,则对于任意正交矩阵 B B B,有 T r a c e ( A A T ) ≥ T r a c e ( B A A T ) Trace(AA^T) \geq Trace(BAA^T) Trace(AAT)≥Trace(BAAT)
意义:若能找到
R
R
R,把
T
r
a
c
e
(
R
H
)
Trace(RH)
Trace(RH)转换成
T
r
a
c
e
(
A
A
T
)
Trace(AA^T)
Trace(AAT)的形式,按照上述定理有:
T
r
a
c
e
(
R
H
)
=
T
r
a
c
e
(
A
A
T
)
≥
T
r
a
c
e
(
B
A
A
T
)
Trace(RH) = Trace(AA^T) \geq Trace(BAA^T)
Trace(RH)=Trace(AAT)≥Trace(BAAT)
此时
T
r
a
c
e
(
R
H
)
Trace(RH)
Trace(RH)取得最大值,故该
R
R
R就是我们要找的旋转矩阵
证明:
t
r
(
B
A
A
T
)
=
t
r
(
A
T
B
A
)
=
∑
i
a
i
T
(
B
a
i
)
其中
,
a
i
为
A
的列向量
tr(BAA^T)=tr(A^TBA)=\sum_ia_i^T(Ba_i) \ \ \ \ \ \ 其中,a_i为A的列向量
tr(BAAT)=tr(ATBA)=i∑aiT(Bai) 其中,ai为A的列向量
根据柯西-施瓦茨不等式(两向量乘积 小于等于 两向量模的乘积)
a
i
T
(
B
a
i
)
≤
(
a
i
T
a
i
)
(
a
i
T
B
T
B
a
i
)
=
a
i
T
a
i
a_i^T(Ba_i) \leq \sqrt{(a_i^Ta_i)(a_i^TB^TBa_i)}=a_i^Ta_i
aiT(Bai)≤(aiTai)(aiTBTBai)
=aiTai
因此,
t
r
(
B
A
A
T
)
=
∑
i
a
i
T
(
B
a
i
)
≤
∑
i
a
i
T
a
i
=
t
r
(
A
A
T
)
tr(BAA^T)=\sum_ia_i^T(Ba_i) \leq \sum_ia_i^Ta_i = tr(AA^T)
tr(BAAT)=i∑aiT(Bai)≤i∑aiTai=tr(AAT)
问题转换:要寻找合适的 R R R,使得 T r a c e ( R H ) Trace(RH) Trace(RH)取得最大值。即要寻找合适的 R R R,可将 T r a c e ( R H ) Trace(RH) Trace(RH)转换成 T r a c e ( A A T ) Trace(AA^T) Trace(AAT)的形式
具体求解:
对
H
H
H进行SVD分解,有
H
=
U
Σ
V
T
H=U\Sigma V^T
H=UΣVT
。令
。令
。令
R
=
V
U
T
R=VU^T
R=VUT$,则有:
R
H
=
V
U
T
U
Σ
V
T
=
V
Σ
V
T
=
V
Σ
1
2
Σ
1
2
V
T
=
V
Σ
1
2
(
V
Σ
1
2
)
T
RH=VU^TU\Sigma V^T=V\Sigma V^T=V\Sigma^{\frac{1}{2}}\Sigma^{\frac{1}{2}}V^T=V\Sigma^{\frac{1}{2}}(V\Sigma^{\frac{1}{2}})^T
RH=VUTUΣVT=VΣVT=VΣ21Σ21VT=VΣ21(VΣ21)T
在确定旋转矩阵
R
R
R后,将其代入目标函数第二项
E
2
,
(
R
,
t
)
E_2^,(R,t)
E2,(R,t)即可求出平移:
t
=
u
x
−
R
u
y
t=u_x-Ru_y
t=ux−Ruy
思考1: E ( R , t ) E(R,t) E(R,t)公式的第一步中对点云去质心的目的
两点云之间的位姿变换包括旋转+平移,而平移就是两点云之间的质心距离。因此,对点云进行去质心操作后,剩下的就是旋转。也就是说,通过去质心的方式将旋转和平移解耦开了。
思考2:在求解 R R R时,为什么要引入 T r a c e Trace Trace
主要是为了应用柯西-施瓦茨不等式,使得目标函数第一项能取得最小值,此时的 R R R便为所要求的旋转矩阵。
(3)ICP的改进
数据点选取
数据关联
剔除外点
损失函数
(1)定义
对于两个点云source和target,将点云进行栅格划分。若已知target中栅格内点云的分布(
μ
,
Σ
\mu,\Sigma
μ,Σ),求两个点云之间的变换矩阵,使得将source中对应栅格内的点进行变换后的点的分布与target中栅格内点的分布近似。同ICP一样,初始的source与target可能相距较远,需要通过多次迭代才能实现转换后点的分布一致。
(2)求解过程
2D模型下,需要求解沿x, y方向的平移和绕z方向的旋转,即
p
=
p
3
=
[
t
x
t
y
ϕ
z
]
T
p=p_3=\left[
3D模型下,需要求解沿x, y, z方向的平移和绕x, y, z方向的旋转,即
p
=
p
6
=
[
t
x
t
y
t
z
ϕ
x
ϕ
y
ϕ
z
]
T
p=p_6=\left[
均值: μ = 1 N x ∑ i = 1 N x x i \mu=\frac{1}{N_x}\sum_{i=1}^{N_x}x_i μ=Nx1i=1∑Nxxi协方差: Σ = 1 N x − 1 ∑ i = 1 N x ( x i − μ ) ( x i − μ ) T \Sigma=\frac{1}{N_x-1}\sum_{i=1}^{N_x}(x_i-\mu)(x_i-\mu)^T Σ=Nx−11i=1∑Nx(xi−μ)(xi−μ)T
根据预测的位姿,对点进行旋转和平移: y i , = T ( p , y i ) = R y i + t y_i^,=T(p,y_i)=Ry_i+t yi,=T(p,yi)=Ryi+t
计算二者的概率分布相似性
旋转和平移后的点与目标点集中的点在同一坐标系下,此时**将变换后的点代入到目标点的分布中,求出它有多大程度满足目标点的分布。**若求出的概率越大,则表示二者越接近
f
(
X
,
y
i
,
)
=
1
2
π
∣
Σ
∣
e
x
p
(
(
y
i
,
−
μ
)
T
Σ
−
1
(
y
i
,
−
μ
)
2
)
f(X,y_i^,)=\frac{1}{\sqrt{2\pi}|\Sigma|}exp(\frac{(y_i^,-\mu)^T\Sigma^{-1}(y_i^,-\mu)}{2})
f(X,yi,)=2π
∣Σ∣1exp(2(yi,−μ)TΣ−1(yi,−μ))
比较变换后的点与目标点之间的接近程度,另一种方法是:求出变换后的点的分布,然后在分布上比较二者之间的相似性。但这种方法的缺点是:每次迭代都需要求出变换后的点的分布,导致计算量较大。上面方法是直接将变换后的点代入目标点的分布中,以计算出变换后的点有多大程度符合目标点的分布。
对于所有点而言,将每个点代入目标点分布中进行相乘,得到对应的联合概率为:
Ψ
=
∏
i
=
1
N
y
f
(
X
,
T
(
p
,
y
i
)
)
=
∏
i
=
1
N
y
1
2
π
∣
Σ
∣
e
x
p
(
−
(
y
i
,
−
μ
)
T
Σ
−
1
(
y
i
,
−
μ
)
2
)
⇒
l
n
Ψ
=
∑
i
=
1
N
y
(
−
(
y
i
,
−
μ
)
T
Σ
−
1
(
y
i
,
−
μ
)
2
+
l
n
1
2
π
∣
Σ
∣
)
\Psi=\prod_{i=1}^{N_y}f(X,T(p,y_i))=\prod_{i=1}^{N_y}\frac{1}{\sqrt{2\pi}|\Sigma|}exp(-\frac{(y_i^,-\mu)^T\Sigma^{-1}(y_i^,-\mu)}{2}) \\ \Rightarrow \\ ln \ \Psi=\sum_{i=1}^{N_y}(-\frac{(y_i^,-\mu)^T\Sigma^{-1}(y_i^,-\mu)}{2}+ln\frac{1}{\sqrt{2\pi}|\Sigma|}) \ \ \ \ \ \
Ψ=i=1∏Nyf(X,T(p,yi))=i=1∏Ny2π
∣Σ∣1exp(−2(yi,−μ)TΣ−1(yi,−μ))⇒ln Ψ=i=1∑Ny(−2(yi,−μ)TΣ−1(yi,−μ)+ln2π
∣Σ∣1)
令:
e
i
(
p
)
=
y
i
,
−
μ
e_i(p)=y_i^,-\mu
ei(p)=yi,−μ
F
i
(
p
)
=
e
i
T
(
p
)
Σ
−
1
e
i
(
p
)
=
(
y
i
,
−
μ
)
T
Σ
−
1
(
y
i
,
−
μ
)
F_i(p)=e_i^T(p)\Sigma^{-1}e_i(p)=(y_i^,-\mu)^T\Sigma^{-1}(y_i^,-\mu)
Fi(p)=eiT(p)Σ−1ei(p)=(yi,−μ)TΣ−1(yi,−μ)则有:
m
i
n
∑
i
=
1
N
y
(
y
i
,
−
μ
)
T
Σ
−
1
(
y
i
,
−
μ
)
=
m
i
n
∑
i
=
1
N
y
F
i
(
p
)
min\sum_{i=1}^{N_y}(y_i^,-\mu)^T\Sigma^{-1}(y_i^,-\mu)=min\sum_{i=1}^{N_y}F_i(p)
mini=1∑Ny(yi,−μ)TΣ−1(yi,−μ)=mini=1∑NyFi(p)
迭代优化过程为:在迭代中通过不断调整
p
→
p
+
Δ
p
p \rightarrow p+\Delta p
p→p+Δp找到
Δ
p
\Delta p
Δp使得目标函数达到最小值
m
i
n
∑
i
=
1
N
y
F
i
(
p
)
=
∑
i
=
1
N
y
e
i
T
(
p
+
Δ
p
)
Σ
−
1
e
i
(
p
+
Δ
p
)
min\sum_{i=1}^{N_y}F_i(p) = \sum_{i=1}^{N_y}e_i^T(p+\Delta p)\Sigma^{-1}e_i(p+\Delta p)
mini=1∑NyFi(p)=i=1∑NyeiT(p+Δp)Σ−1ei(p+Δp)
泰勒展开:
e
i
(
p
+
Δ
p
)
≈
e
i
(
p
)
+
d
e
i
d
p
Δ
p
=
e
i
+
J
i
Δ
p
F
i
(
p
+
Δ
p
)
=
e
i
T
(
p
+
Δ
p
)
Σ
−
1
e
i
(
p
+
Δ
p
)
≈
(
e
i
+
J
i
Δ
p
)
T
Σ
−
1
(
e
i
+
J
i
Δ
p
)
=
e
i
T
Σ
−
1
e
i
+
2
e
i
T
Σ
−
1
J
i
Δ
p
+
Δ
p
T
J
i
T
Σ
−
1
J
i
Δ
p
=
F
i
(
p
)
+
2
b
i
T
Δ
p
+
Δ
p
T
H
i
Δ
p
其中
,
b
i
T
=
e
i
T
Σ
−
1
J
i
H
i
=
J
i
T
Σ
−
1
J
i
e_i(p+\Delta p) \approx e_i(p)+\frac{de_i}{dp}\Delta p=e_i+J_i\Delta p \\ F_i(p+\Delta p)=e_i^T(p+\Delta p)\Sigma^{-1}e_i(p+\Delta p) \approx (e_i+J_i\Delta p)^T\Sigma^{-1}(e_i+J_i\Delta p) \\ =e_i^T\Sigma^{-1}e_i+2e_i^T\Sigma^{-1}J_i\Delta p+\Delta p^TJ_i^T\Sigma^{-1}J_i\Delta p = F_i(p)+2b_i^T\Delta p+\Delta p^TH_i\Delta p \\ 其中,b_i^T=e_i^T\Sigma^{-1}J_i\ \ \ H_i=J_i^T\Sigma^{-1}J_i
ei(p+Δp)≈ei(p)+dpdeiΔp=ei+JiΔpFi(p+Δp)=eiT(p+Δp)Σ−1ei(p+Δp)≈(ei+JiΔp)TΣ−1(ei+JiΔp)=eiTΣ−1ei+2eiTΣ−1JiΔp+ΔpTJiTΣ−1JiΔp=Fi(p)+2biTΔp+ΔpTHiΔp其中,biT=eiTΣ−1Ji Hi=JiTΣ−1Ji
目标函数随自变量的变化为:
Δ
F
i
(
p
)
=
F
i
(
p
+
Δ
p
)
−
F
i
(
p
)
=
2
b
i
T
Δ
p
+
Δ
p
T
Δ
H
i
Δ
p
\Delta F_i(p)=F_i(p+\Delta p)-F_i(p)=2b_i^T\Delta p+\Delta p^T\Delta H_i \Delta p
ΔFi(p)=Fi(p+Δp)−Fi(p)=2biTΔp+ΔpTΔHiΔp
由此,上述优化问题可转化为:找到
Δ
p
\Delta p
Δp使得
Δ
F
i
(
p
)
\Delta F_i(p)
ΔFi(p)取得极小值。令其导数为零,则有:
d
Δ
F
i
(
p
)
d
Δ
p
=
2
b
i
+
2
H
i
Δ
p
=
0
=
=
>
H
i
Δ
p
=
−
b
i
\frac{d\Delta F_i(p)}{d \Delta p}=2b_i+2H_i\Delta p=0 \ \ \ ==> \ \ \ H_i\Delta p=-b_i
dΔpdΔFi(p)=2bi+2HiΔp=0 ==> HiΔp=−bi
根据定义:
b
i
T
=
e
i
T
Σ
−
1
J
i
b_i^T=e_i^T\Sigma^{-1}J_i
biT=eiTΣ−1Ji,
H
i
=
J
i
T
Σ
−
1
J
i
H_i=J_i^T\Sigma^{-1}J_i
Hi=JiTΣ−1Ji。因此,只需得到
J
i
=
d
e
i
d
p
J_i=\frac{de_i}{dp}
Ji=dpdei,即可求出
Δ
p
\Delta p
Δp
雅可比矩阵求解
令
p
=
[
t
x
t
y
ϕ
z
]
T
p=\left[
y
i
,
=
T
(
p
,
y
i
)
=
[
c
o
s
ϕ
z
−
s
i
n
ϕ
z
s
i
n
ϕ
z
c
o
s
ϕ
z
]
y
i
+
[
t
x
t
y
]
=
[
c
o
s
ϕ
z
−
s
i
n
ϕ
z
s
i
n
ϕ
z
c
o
s
ϕ
z
]
[
y
i
1
y
i
2
]
+
[
t
x
t
y
]
=
[
c
o
s
ϕ
z
y
i
1
−
s
i
n
ϕ
z
y
i
2
+
t
x
s
i
n
ϕ
z
y
i
1
+
c
o
s
ϕ
z
y
i
2
+
t
y
]
e
i
=
y
i
,
−
μ
=
[
c
o
s
ϕ
z
y
i
1
−
s
i
n
ϕ
z
y
i
2
+
t
x
−
μ
x
s
i
n
ϕ
z
y
i
1
+
c
o
s
ϕ
z
y
i
2
+
t
y
−
μ
y
]
J
i
=
[
∂
e
i
1
∂
t
x
∂
e
i
1
∂
t
y
∂
e
i
1
∂
ϕ
z
∂
e
i
2
∂
t
x
∂
e
i
2
∂
t
y
∂
e
i
2
∂
ϕ
z
]
=
[
1
0
−
y
i
1
s
i
n
ϕ
z
−
y
i
2
c
o
s
ϕ
z
0
1
y
i
1
c
o
s
ϕ
z
−
y
i
2
s
i
n
ϕ
z
]
y_i^,=T(p,y_i)=\left[
p
=
[
t
x
t
y
t
z
ϕ
x
ϕ
y
ϕ
z
]
T
p=\left[
y
i
,
=
T
(
p
,
y
i
)
=
R
x
R
y
R
z
y
i
+
t
=
[
c
y
c
z
−
c
y
s
z
s
y
c
x
s
z
+
s
x
s
y
c
z
c
x
c
z
−
s
x
s
y
s
z
−
s
x
c
y
s
x
s
z
−
c
x
s
y
c
z
c
x
s
y
s
z
+
s
x
c
z
c
x
c
y
]
y
i
+
[
t
x
t
y
t
z
]
y_i^,=T(p,y_i)= R_xR_yR_zy_i+t= \left[
雅可比矩阵:
J
i
=
[
1
0
0
0
c
f
0
1
0
a
d
g
0
0
1
b
e
h
]
J_i=\left[
a
=
y
i
x
(
−
s
x
s
z
+
c
x
s
y
c
z
)
+
y
i
y
(
−
s
x
c
z
−
c
x
s
y
s
z
)
+
y
i
z
(
−
c
x
c
y
)
b
=
y
i
x
(
c
x
s
z
+
s
x
s
y
c
z
)
+
y
i
y
(
−
s
x
s
y
c
z
)
+
y
i
y
(
−
s
x
s
y
s
z
+
c
x
c
z
)
+
y
i
z
(
−
s
x
c
y
)
c
=
y
i
x
(
−
s
y
c
z
)
+
y
i
y
(
s
y
s
z
)
+
y
i
z
(
c
y
)
d
=
y
i
x
(
s
x
c
y
c
z
)
+
y
i
y
(
−
s
x
c
y
s
z
)
+
y
i
z
(
s
x
s
y
)
e
=
y
i
x
(
−
c
x
c
y
c
z
)
+
y
i
y
(
c
x
c
y
s
z
)
+
y
i
z
(
−
c
x
s
y
)
f
=
y
i
x
(
−
c
y
s
z
)
+
y
i
y
(
−
c
y
c
z
)
g
=
y
i
x
(
c
x
c
z
−
s
x
s
y
s
z
)
+
y
i
y
(
−
c
x
s
z
−
s
x
s
y
c
z
)
h
=
y
i
x
(
s
x
c
z
+
c
x
s
y
s
z
)
+
y
i
y
(
c
x
s
y
s
z
−
s
x
s
z
)
a=y_{ix}(-s_xs_z+c_xs_yc_z)+y_{iy}(-s_xc_z-c_xs_ys_z)+y_{iz}(-c_xc_y) \\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ b = y_{ix}(c_xs_z+s_xs_yc_z)+y_{iy}(-s_xs_yc_z)+y_{iy}(-s_xs_ys_z+c_xc_z)+y_{iz}(-s_xc_y) \\ c=y_{ix}(-s_yc_z)+y_{iy}(s_ys_z)+y_{iz}(c_y) \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \\ d=y_{ix}(s_xc_yc_z)+y_{iy}(-s_xc_ys_z)+y_{iz}(s_xs_y) \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \\ e=y_{ix}(-c_xc_yc_z)+y_{iy}(c_xc_ys_z)+y_{iz}(-c_xs_y) \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \\ f=y_{ix}(-c_ys_z)+y_{iy}(-c_yc_z) \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \\ g=y_{ix}(c_xc_z-s_xs_ys_z)+y_{iy}(-c_xs_z-s_xs_yc_z) \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \\ h=y_{ix}(s_xc_z+c_xs_ys_z)+y_{iy}(c_xs_ys_z-s_xs_z) \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \
a=yix(−sxsz+cxsycz)+yiy(−sxcz−cxsysz)+yiz(−cxcy) b=yix(cxsz+sxsycz)+yiy(−sxsycz)+yiy(−sxsysz+cxcz)+yiz(−sxcy)c=yix(−sycz)+yiy(sysz)+yiz(cy) d=yix(sxcycz)+yiy(−sxcysz)+yiz(sxsy) e=yix(−cxcycz)+yiy(cxcysz)+yiz(−cxsy) f=yix(−cysz)+yiy(−cycz) g=yix(cxcz−sxsysz)+yiy(−cxsz−sxsycz) h=yix(sxcz+cxsysz)+yiy(cxsysz−sxsz)
(3)NDT与ICP的比较
ICP是要求两点云经转换后尽可能接近,NDT是要求两点云经转换后点的分布尽可能接近,终止条件相较于ICP而言没那么严格,因此在一定程度上NDT的鲁棒性要好过于ICP。另外,NDT的效率高于ICP,主要体现在:NDT中只用计算一次target点云的分布(均值和方差),后续迭代中会不断复用这一分布,以此作为衡量两点云匹配的吻合程度;而ICP需要在每一次迭代中使用上一位姿更新点云位置,因此效率低于NDT。
(1)整体思路
(2)实现步骤
(3)代码实现
import os import argparse from tkinter.messagebox import NO import progressbar import collections import numpy as np import open3d as o3d import copy import concurrent.futures from scipy.spatial.distance import pdist from scipy.spatial.transform import Rotation import matplotlib.pyplot as plt # from ICP import icp_exact_match # 3对点即可求解 def solve_procrustes_transf(P,Q): # 求解平移旋转矩阵 solve_procrustes_transformation up = P.mean(axis=0) uq = Q.mean(axis=0) # move to center: P_centered = P - up Q_centered = Q - uq U, s, V = np.linalg.svd(np.dot(Q_centered.T, P_centered), full_matrices=True, compute_uv=True) R = np.dot(U, V) t = uq - np.dot(R, up) # format as transform: T = np.zeros((4, 4)) T[0:3, 0:3] = R T[0:3, 3] = t T[3, 3] = 1.0 return T def get_init_matches(feature_source, feature_target): ##对 feature target fpfh 建立 kd—tree feature_source = feature_source.transpose() feature_target = feature_target.transpose() fpfh_search_tree = o3d.geometry.KDTreeFlann(feature_target) ##建立 pairs _, N = feature_source.shape matches = [] # 对于每一个source keypoints中的点,在target keypoints中找到fpfh descriptors空间上找到与之最近的点索引idx_nn_target,二者构成一对pair for i in range(N): query = feature_source[:, i] _, idx_nn_target, _ = fpfh_search_tree.search_knn_vector_xd(query, 1) # source -> target matches.append([i,idx_nn_target[0]]) # 通过knn 寻找唯一 的 nearest points 一一配对 构建pair matches = np.asarray(matches) return matches def iter_match(keypoints_source, keypoints_target, sample_matches,): # 获取sample点的索引 sample_source_idx, sample_target_idx = sample_matches[:, 0], sample_matches[:, 1] # 获取sample点 sample_source_points = np.array(keypoints_source)[sample_source_idx] sample_target_points = np.array(keypoints_target)[sample_target_idx] # 对sample点,通过procrustes_transf计算其变换矩阵T T = solve_procrustes_transf(sample_source_points, sample_target_points) return T def ransac_match(keypoints_source, keypoints_target, fpfh_source, fpfh_target,iterators): # 在fpfh空间上,建立source与target之间的初始匹配 matches = get_init_matches(fpfh_source, fpfh_target) N, _ = matches.shape idx_matches = np.arange(N) # 对每对 pair 打标签 poses = [] corresponces = [] keypoints_source_matches_idx, keypoints_target_matches_idx = matches[:, 0], matches[:, 1] keypoints_source_matches_points = np.array(keypoints_source)[keypoints_source_matches_idx] keypoints_target_matches_points = np.array(keypoints_target)[keypoints_target_matches_idx] for iter in range(iterators): # 每次迭代随机选取3对pair sample_matches = matches[np.random.choice(idx_matches, 3, replace=False)] # 通过随机选取的3对pair,获取其对应的变换矩阵T T = iter_match(keypoints_source, keypoints_target, sample_matches) R, t = T[0:3, 0:3], T[0:3, 3] poses.append(T) keypoints_source_matches_points = np.dot(keypoints_source_matches_points, R.T) + t # 获取3对pair得到的变换矩阵产生的corresponce corresponce = np.linalg.norm(keypoints_target_matches_points - keypoints_source_matches_points,axis = 1) corresponces.append(np.sum(corresponce)) min_idx = np.argmin(corresponces) # 将corresponce最小的变换矩阵作为RANSAC提取到的最佳位姿pose return poses[min_idx]
import open3d as o3d import os import numpy as np from pyntcloud import PyntCloud import matplotlib.pyplot as plt from sklearn.neighbors import KDTree # KDTree 进行搜索 import random from pandas import DataFrame import pandas as pd from scipy import spatial import heapq # 获取提取到的特征点及其对应于原点集中的索引 def iss(data, gamma12=0.7, gamma23=0.7, leaf_size=4, iss_radius=0.15, max_nms_points=1000, nms_radius=0.15): n, _ = data.shape tree = KDTree(data, leaf_size) # 以leafsize为最小节点,对源点集data构建kdtree nearest_idx = tree.query_radius(data, iss_radius) # 通过kdtree的rnn搜索,快速查找所有点的iss_radius邻域点,查找到的索引存储在nearest_idx中 iss_points_before_nms = [] min_values = [] for i in range(45, n): # print(i) current_point_neighbor_idx = nearest_idx[i] # 当前点i的邻域点 # 若当前点没有iss_radius邻域点,则对下一点进行处理 if len(current_point_neighbor_idx) == 0: continue i_idx = np.argwhere(current_point_neighbor_idx == i) # 查找current_point_neighbor_idx数组中值为当前点i的索引 current_point_neighbor_idx = np.delete(current_point_neighbor_idx, i_idx) # 去掉当前点i # current_point_neighbor_idx在删掉当前点索引i后,有可能为空 if len(current_point_neighbor_idx) == 0: continue Wi = np.linalg.norm(data[i] - data[current_point_neighbor_idx], axis=1) # 按照公式,计算当前点i到其所有邻域点之间的距离pi-pj Wi = 1.0 / Wi cov_tmp = np.zeros((3, 3)) for j in range(len(current_point_neighbor_idx)): pij = (data[i] - data[current_point_neighbor_idx[j]])[:, np.newaxis] # shape:(3, ) ==> (3, 1) 3行1列 cov_tmp += Wi[j] * (pij.dot(pij.transpose())) w_sum = np.sum(Wi) cov_pi = cov_tmp / np.sum(Wi) # 当前点pi的协方差矩阵 sigma = np.linalg.svd(cov_pi, compute_uv=False) # svd分解求协方差矩阵的特征值 if (sigma[1] / sigma[0] < gamma12) and (sigma[2] / sigma[1] < gamma23): iss_points_before_nms.append(data[i]) min_values.append(sigma[2]) iss_points_after_NMS = [] iss_points_after_NMS_idx = [] iss_points_before_nms_idx = [i for i in range(len(iss_points_before_nms))] nms_kdtree = spatial.KDTree(iss_points_before_nms, leaf_size) # 以iss_points_before_nms为源点集,构建kdtree for k in range(max_nms_points): max_index = min_values.index(max(min_values)) # 获取最小特征值里面当前迭代的最大值 max_point = iss_points_before_nms[max_index] delete_idx = nms_kdtree.query_ball_point(max_point, nms_radius) # 处于当前最大点4邻域内的所有点的索引 for idx in delete_idx: # 如果当前点max_point的nms_radius邻域点iss后的点集中,则将这些邻域点删除掉 if idx in iss_points_before_nms_idx: del min_values[iss_points_before_nms_idx.index(idx)] del iss_points_before_nms[iss_points_before_nms_idx.index(idx)] del iss_points_before_nms_idx[iss_points_before_nms_idx.index(idx)] iss_points_after_NMS.append(max_point) # 获取NMS后的点 idx = np.where((data == max_point).all(axis=1)) iss_points_after_NMS_idx.append(idx) # 获取NMS后的点在源点云data中的索引 # nms后的最大点数可能超过iss后的点数 if len(min_values) == 0: break return iss_points_after_NMS, iss_points_after_NMS_idx
from ctypes import pointer import imp import os import matplotlib.pyplot as plt import numpy as np import open3d as o3d import math from pyntcloud import PyntCloud from pandas import DataFrame from scipy import spatial from sklearn.neighbors import KDTree from mpl_toolkits.mplot3d import Axes3D # 功能: 计算当前点的spfh描述 # 输入: # raw_points: 原始点云的所有三维点 Nx3 (N个点的x、y、z坐标) # raw_points_normal: 原始点云的所有三维点对应的法向量 Nx3 (N个点各自法向量的3个方向的分量) # near_points_idx: 原始点云中所有点的指定邻域大小的邻域点索引 NxM (N个点,每个点有M个邻域点,每个点的M不一定相等) # B: 每个描述子直方图中bin的个数 # 输出: # signature: [alpha, phi, theta]三元组的直方图组合,用以表示当前点的spfh描述 def SPFH(raw_points, raw_points_normal, key_point_idx, near_points_idx, B): key_near_idx = near_points_idx[key_point_idx] # 当前key_point的邻域点索引(包含自身) key_near_idx_list = list(key_near_idx) key_near_idx_list.remove(key_point_idx) # 当前key_point的邻域点索引中,去掉自身索引 n1 = raw_points_normal[key_point_idx] n2 = raw_points_normal[key_near_idx_list] # 坐标系 u = n1 v = np.cross(u, (raw_points[key_near_idx_list] - raw_points[key_point_idx]) / np.linalg.norm(raw_points[key_near_idx_list] - raw_points[key_point_idx])) w = np.cross(u, v) # 三元组[alpha, phi, theta] alpha = np.sum(v * n2, axis=1) phi = np.sum(u*(raw_points[key_near_idx_list] - raw_points[key_point_idx]) / np.linalg.norm(raw_points[key_near_idx_list] - raw_points[key_point_idx]), axis=1) theta2 = np.arctan(w*n2, u*n2) theta = np.sum(np.arctan(w*n2, u*n2), axis=1) # histogram alpha_histogram = np.histogram(alpha, B, range=(-1.0, 1.0))[0] sum_alpha_histogram = np.sum(alpha_histogram) alpha_histogram = alpha_histogram / sum_alpha_histogram phi_histogram = np.histogram(phi, B, range=(-1.0, 1.0))[0] sum_phi_histogram = np.sum(phi_histogram) phi_histogram = phi_histogram / sum_phi_histogram theta_histogram = np.histogram(theta, B, range=(-np.pi, np.pi))[0] sum_theta_histogram = np.sum(theta_histogram) theta_histogram = theta_histogram / np.sum(sum_theta_histogram) signature = np.hstack((alpha_histogram, phi_histogram, theta_histogram)) return signature # 功能: 计算当前点的fpfh描述 当前点的spsh描述 + 当前点的邻域点的spfh描述 # 输入: # raw_points: 原始点云的所有三维点 Nx3 (N个点的x、y、z坐标) # raw_points_normal: 原始点云的所有三维点对应的法向量 Nx3 (N个点各自法向量的3个方向的分量) # near_points_idx: 原始点云中所有点的指定邻域大小的邻域点索引 NxM (N个点,每个点有M个邻域点,每个点的M不一定相等) # B: 每个描述子直方图中bin的个数 # 输出: # fpfh: 当前点与其邻域点的spfh直方图描述的组合,用以表示当前点的fpfh描述 def fpfh(raw_points, raw_points_normal, key_point_idx, near_points_idx, B=5): # 当前key_point的spfh 3B spfh_pq = SPFH(raw_points, raw_points_normal, key_point_idx, near_points_idx, B) # 当前key_point邻域点的spfh key_near_idx = near_points_idx[key_point_idx] # 当前key_point的邻域点索引(包含自身) key_near_idx_list = list(key_near_idx) key_near_idx_list.remove(key_point_idx) # 当前key_point的邻域点索引中,去掉自身索引 # p1_near_num W = 1.0 / np.linalg.norm((raw_points[key_point_idx] - raw_points[key_near_idx_list]), axis=1) # [p1_near_num, 3B] spfh_pk = np.asarray([SPFH(raw_points, raw_points_normal, point_k_idx, near_points_idx, B) for point_k_idx in key_near_idx_list]) # np.dot() 矩阵相乘里面包含了np.sum() spfh_pk = np.dot(W, spfh_pk) / len(W) fpfh = spfh_pq + spfh_pk # 归一化 fpfh = fpfh / np.linalg.norm(fpfh) return fpfh
import open3d as o3d import os import numpy as np from pyntcloud import PyntCloud import matplotlib.pyplot as plt from sklearn.neighbors import KDTree # KDTree 进行搜索 import random from pandas import DataFrame import pandas as pd from scipy import spatial import heapq def icp(pcd_source, pcd_target, init_pose, iterators, threshold): source_points = np.array(pcd_source.points) # (20000, 3) 三维空间点3个方向的坐标值 target_points = np.array(pcd_target.points) # (20000, 3) 三维空间点3个方向的坐标值 init_R = init_pose[0:3, 0:3] init_t = init_pose[0:3, 3] source_points = np.dot(source_points, init_R.T) + init_t for iter in range(iterators): # 计算source与target的中心点 center_source_point = np.mean(source_points, axis=0) center_target_point = np.mean(target_points, axis=0) # 计算source与target的各自去中心点 del_center_source_points = source_points - center_source_point del_center_target_points = target_points - center_target_point # 计算矩阵W W = np.zeros((3, 3)) for i in range(len(del_center_source_points)): W += del_center_source_points[i] * del_center_target_points[i].T # 对矩阵W进行SVD分解 U, Sigma, VT = np.linalg.svd(W) R = U * VT t = center_target_point - np.dot(center_source_point, R.T) # 计算变换后,source与target之间的误差 error = 0 for i in range(len(source_points)): error += np.sum(np.abs(source_points[i] - target_points[i])) # 根据error判断是否可提前终止icp if error < threshold: break # 更新source_points source_points = np.dot(source_points, R.T) + t return R, t
from cmath import nan import os import argparse import progressbar import collections import numpy as np import open3d as o3d from ISS import * from FPFH import * from filter import * from RANSAC import * from ICP import * from data_process import * from sklearn.neighbors import KDTree import matplotlib.pyplot as plt if __name__ == '__main__': radius = 0.5 #step1 读取数据 input_dir = "registration_dataset" df_output = { 'idx1': [], 'idx2': [], 't_x': [], 't_y': [], 't_z': [], 'q_w': [], 'q_x': [], 'q_y': [], 'q_z': [] } registration_results = read_registration_results(os.path.join(input_dir,'reg_result.txt')) for i,r in (list(registration_results.iterrows())): idx_target = int(r['idx1']) idx_source = int(r['idx2']) # idx_target = 0 # idx_source = 456 ##load point cloud pcd_source = read_point_cloud_bin( os.path.join(input_dir,'point_clouds',f'{idx_source}.bin') ) pcd_target = read_point_cloud_bin( os.path.join(input_dir,'point_clouds',f'{idx_target}.bin') ) pcd_source_points = np.array(pcd_source.points) pcd_source_normals = np.array(pcd_source.normals) pcd_target_points = np.array(pcd_target.points) pcd_target_normals = np.array(pcd_target.normals) # voxel grid进行降采样 source_downsampled_points, source_downsampled_normals, source_downsampled_points_idx = voxel_grid_downsample(pcd_source_points, pcd_source_normals, 0.5) target_downsampled_points, target_downsampled_normals, target_downsampled_points_idx = voxel_grid_downsample(pcd_target_points, pcd_target_normals, 0.5) # iss提取特征点 keypoints_source_idx 是相对于source_downsampled_points中的索引 keypoints_source, keypoints_source_idx = iss(source_downsampled_points, gamma12=0.6, gamma23=0.6, leaf_size=4, iss_radius=0.15, max_nms_points=5000, nms_radius=0.05) keypoints_target, keypoints_target_idx = iss(target_downsampled_points, gamma12=0.6, gamma23=0.6, leaf_size=4, iss_radius=0.15, max_nms_points=5000, nms_radius=0.05) leaf_size = 4 near_radius = 0.5 # 获取所有点的r邻域点 source_downsampled_points_kdtree = KDTree(source_downsampled_points, leaf_size) source_downsampled_near_points_idx = source_downsampled_points_kdtree.query_radius(source_downsampled_points, near_radius) target_downsampled_points_kdtree = KDTree(target_downsampled_points, leaf_size) target_downsampled_near_points_idx = target_downsampled_points_kdtree.query_radius(target_downsampled_points, near_radius) # fpfh: 对提取到的特征点进行fpfh描述 fpfh_source_keypoints = [] for key_point_source_idx in keypoints_source_idx: key_point_idx = key_point_source_idx[0][0] key_source_near_idx_list = list(source_downsampled_near_points_idx[key_point_idx]) # 提前避免fpfh中出现无效数据 if len(key_source_near_idx_list) == 1: continue tmp_fpfh = fpfh(source_downsampled_points, source_downsampled_normals, key_point_source_idx[0][0], source_downsampled_near_points_idx, B=11) fpfh_source_keypoints.append(tmp_fpfh) fpfh_source_keypoints = np.asarray(fpfh_source_keypoints) fpfh_target_keypoints = [] for key_point_target_idx in keypoints_target_idx: key_point_idx = key_point_target_idx[0][0] key_target_near_idx_list = list(target_downsampled_near_points_idx[key_point_idx]) # 提前避免fpfh中出现无效数据 if len(key_target_near_idx_list) == 1: continue tmp_fpfh = fpfh(target_downsampled_points, target_downsampled_normals, key_point_target_idx[0][0], target_downsampled_near_points_idx, B=11) fpfh_target_keypoints.append(tmp_fpfh) fpfh_target_keypoints = np.asarray(fpfh_target_keypoints) # 在fpfh空间上,对提取到的特征点进行RANSAC操作,获取初始的旋转 + 平移 T = ransac_match(keypoints_source, keypoints_target, fpfh_source_keypoints, fpfh_target_keypoints, 10) # 在初始的旋转 + 平移下,对原始点云进行迭代ICP操作,获取最优的旋转 + 平移 R, t = icp(pcd_source, pcd_target, T, 100, 100000) # 将旋转转换成四元数的形式,并写成DataFrame结构 add_to_output(df_output, idx_target, idx_source, R, t) # 将结果写入groundtruths.txt中 write_output(os.path.join(input_dir, 'groundtruths.txt'), df_output)
(4)配准效果
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。