当前位置:   article > 正文

三维点云处理之点云配准_点云匹配

点云匹配

1. ICP

(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=1Ny∣∣xiRyit2
流程

​ 获取两个点云之间的关联点一般是以二者距离最近的两个点为相互关联点,但若两初始点云相距较远,此时通过距离最近找到的关联点不一定是真正的关联点。在具有较好的初始位姿前提下,可通过迭代的方式,将当前迭代中变换后的点作为下一迭代的起始点,直到两关联点云达到预期的尽可能重合状态(较好的初始位姿是为了使经过迭代后两点云相互靠近,否则即使迭代多次后两点云仍不会达到预期重合状态)。其流程如下所示:
在这里插入图片描述
(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=1Ny∣∣xiRyitux+Ruy+uxRuy2                  =Ny1i=1Ny(∣∣xiuxR(yiuy)+(uxRuyt)2)                    =Ny1i=1Ny(∣∣xiuxR(yiuy)2+∣∣uxRuyt2           +2(xiuxR(yiuy))T(uxRuyt))                     =Ny1i=1Ny(∣∣xiuxR(yiuy)2+∣∣uxRuyt2)
​ 其中, 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=1Nxxi          uy=Ny1i=1Nyyi
​ 上述推导中去掉两点云的中心,其目的是: 中心到中心之间的距离就是两点云之间的平移,去中心之后两点云之间的约束只剩下旋转,这样可将平移和旋转解耦开。

​ 令 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=1Ny(∣∣xiuxR(yiuy)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=1Ny∣∣uxRuyt2

那么, 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,=xiux y i , = y i − u y y_i^,=y_i-u_y yi,=yiuy
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=1Ny∣∣xiuxR(yiuy)2=Ny1i=1Ny∣∣xi,Ryi,2    =Ny1i=1Ny(xi,Txi,+yi,TRTRyi,2xi,TRyi,)=Ny1i=1Ny(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=1Nyxi,TRyi,=i=1NyTrace(xi,TRyi,)=i=1NyTrace(Ryi,xi,T)=Trace(RH)

    • 对于等式(1),由于 x i , x_i^, xi, y i , y_i^, yi,均为点的三维坐标,故均为 ( 3 , 1 ) (3,1) (3,1)的列向量,相应地 x i , T x_i^{,T} xi,T ( 1 , 3 ) (1,3) (1,3)的行向量。又 R R R ( 3 , 3 ) (3,3) (3,3)的旋转矩阵,因此 x i , T R y i , x_i^{,T}Ry_i^, xi,TRyi, ( 1 , 1 ) (1,1) (1,1)的常数,常数的迹等于它自身。
    • 对于等式(2) t r ( A B ) = ∑ i = 1 m ∑ j = 1 n a i j b j i = ∑ i = 1 m ∑ j = 1 n b j i a i j = t r ( B A ) tr(AB)=\sum_{i=1}^m\sum_{j=1}^na_{ij}b_{ji}=\sum_{i=1}^m\sum_{j=1}^nb_{ji}a_{ij}=tr(BA) tr(AB)=i=1mj=1naijbji=i=1mj=1nbjiaij=tr(BA)
    • 对于等式(3),令 H = ∑ i = 1 N y y i , x i , T H=\sum_{i=1}^{N_y}y_i^,x_i^{,T} H=i=1Nyyi,xi,T

​ 因此,问题转换为寻找合适的 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)=iaiT(Bai)      其中,aiA的列向量
​ 根据柯西-施瓦茨不等式(两向量乘积 小于等于 两向量模的乘积)
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)=iaiT(Bai)iaiTai=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=uxRuy

思考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的改进

数据点选取

  • 随机选取点进行ICP操作,而不是对所有点都进行ICP操作,加快计算速度
  • 使用Voxel Grid进行降采样,对降采样后的点进行ICP操作,同样是为了加快计算速度
  • 对点云进行特征点提取,对提取到的特征点进行ICP操作,这样也可加快计算速度

数据关联

  • 通过kd-tree/octree的最近邻查找,获取离当前点最近的点
  • 通过特征描述子进行匹配,获取离当前点最近的点

剔除外点

  • 对于某一匹配对pair,若其中的两点距离过大,则认为匹配的点是外点,需要去除
  • 直接按照比例去除距离较大的匹配点

损失函数

  • Point-to-Point:通过点到点之间的距离计算损失函数
  • Point-to-Line:通过点到线之间的距离计算损失函数
  • Point-to-Plane:通过点到面之间的距离计算损失函数

2. NDT

(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[

txtyϕz
\right]^T p=p3=[txtyϕz]T

3D模型下,需要求解沿x, y, z方向的平移和绕x, y, z方向的旋转,即 p = p 6 = [ t x t y t z ϕ x ϕ y ϕ z ] T p=p_6=\left[

txtytzϕxϕyϕz
\right]^T p=p6=[txtytzϕxϕyϕz]T

  • 求解target栅格内点的分布

​ 均值: μ = 1 N x ∑ i = 1 N x x i \mu=\frac{1}{N_x}\sum_{i=1}^{N_x}x_i μ=Nx1i=1Nxxi协方差: Σ = 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 Σ=Nx11i=1Nx(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=1Nyf(X,T(p,yi))=i=1Ny2π ∣Σ∣1exp(2(yi,μ)TΣ1(yi,μ))ln Ψ=i=1Ny(2(yi,μ)TΣ1(yi,μ)+ln2π ∣Σ∣1)      

  • 目标函数
    m a x   Ψ = m a x   l n Ψ = m i n ∑ i = 1 N y ( y i , − μ ) T Σ − 1 ( y i , − μ ) y i , = T ( p , y i ) = R y i + t           待求参数 : R , t max \ \Psi = max \ ln\Psi = min\sum_{i=1}^{N_y}(y_i^,-\mu)^T\Sigma^{-1}(y_i^,-\mu) \\ y_i^,=T(p,y_i)=Ry_i+t \ \ \ \ \ \ \ \ \ \ 待求参数:R,t max Ψ=max lnΨ=mini=1Ny(yi,μ)TΣ1(yi,μ)yi,=T(p,yi)=Ryi+t          待求参数:R,t
    经过以上分析,NDT问题可转化为目标函数的优化问题。将目标函数进行化简:

令: 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=1Ny(yi,μ)TΣ1(yi,μ)=mini=1NyFi(p)
​ 迭代优化过程为:在迭代中通过不断调整 p → p + Δ p p \rightarrow p+\Delta p pp+Δ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=1NyFi(p)=i=1NyeiT(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
雅可比矩阵求解

  • 2D场景

​ 令 p = [ t x t y ϕ z ] T p=\left[

txtyϕz
\right]^T p=[txtyϕz]T ,则:
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[
cosϕzsinϕzsinϕzcosϕz
\right]y_i+\left[
txty
\right] = \left[
cosϕzsinϕzsinϕzcosϕz
\right]\left[
yi1yi2
\right]+\left[
txty
\right] \\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ = \left[
cosϕzyi1sinϕzyi2+txsinϕzyi1+cosϕzyi2+ty
\right] \\ e_i=y_i^,-\mu = \left[
cosϕzyi1sinϕzyi2+txμxsinϕzyi1+cosϕzyi2+tyμy
\right] \\ J_i = \left[
ei1txei1tyei1ϕzei2txei2tyei2ϕz
\right] = \left[
10yi1sinϕzyi2cosϕz01yi1cosϕzyi2sinϕz
\right]
yi,=T(p,yi)=[cosϕzsinϕzsinϕzcosϕz]yi+[txty]=[cosϕzsinϕzsinϕzcosϕz][yi1yi2]+[txty]                                                                   =[cosϕzyi1sinϕzyi2+txsinϕzyi1+cosϕzyi2+ty]ei=yi,μ=[cosϕzyi1sinϕzyi2+txμxsinϕzyi1+cosϕzyi2+tyμy]Ji=[txei1txei2tyei1tyei2ϕzei1ϕzei2]=[1001yi1sinϕzyi2cosϕzyi1cosϕzyi2sinϕz]

  • 3D场景

p = [ t x t y t z ϕ x ϕ y ϕ z ] T p=\left[

txtytzϕxϕyϕz
\right]^T p=[txtytzϕxϕyϕz]T e i = y i , − μ e_i=y_i^,-\mu ei=yi,μ。其中, y i , y_i^, yi,表达如下:
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[
cyczcyszsycxsz+sxsyczcxczsxsyszsxcysxszcxsyczcxsysz+sxczcxcy
\right]y_i+\left[
txtytz
\right]
yi,=T(p,yi)=RxRyRzyi+t= cyczcxsz+sxsyczsxszcxsyczcyszcxczsxsyszcxsysz+sxczsysxcycxcy yi+ txtytz

​ 雅可比矩阵: J i = [ 1 0 0 0 c f 0 1 0 a d g 0 0 1 b e h ] J_i=\left[
1000cf010adg001beh
\right]
Ji= 1000100010abcdefgh
,其中参数表达如下:
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(sxczcxsysz)+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(cxczsxsysz)+yiy(cxszsxsycz)                          h=yix(sxcz+cxsysz)+yiy(cxsyszsxsz)                             
(3)NDT与ICP的比较

​  ICP是要求两点云经转换后尽可能接近,NDT是要求两点云经转换后点的分布尽可能接近,终止条件相较于ICP而言没那么严格,因此在一定程度上NDT的鲁棒性要好过于ICP。另外,NDT的效率高于ICP,主要体现在:NDT中只用计算一次target点云的分布(均值和方差),后续迭代中会不断复用这一分布,以此作为衡量两点云匹配的吻合程度;而ICP需要在每一次迭代中使用上一位姿更新点云位置,因此效率低于NDT。

3. 实践:点云配准

(1)整体思路

  • 数据预处理:降采样、移除噪点。
  • 获取初始位姿:不管是ICP还是NDT,都需要一个较好的初始位姿。在实际中,初始位姿往往是未知的。可使用特征点提取 + 特征点描述 + 特征点匹配 + RANSAC的方式获取初始位姿。
  • ICP/NDT精细配准

(2)实现步骤

  • 读取数据
  • 对点云进行降采样
  • 使用ISS算法进行关键点提取
  • 使用FPFH算法进行关键点描述
  • 使用RANSAC进行粗配准,得到初始旋转平移矩阵
    • 在特征空间建立关键点的correspondences
    • 随机选择correspondences进行模型拟合
    • 迭代,选择出矩阵T作为ICP的初始矩阵
  • 使用ICP对初始旋转平移矩阵进行迭代,优化配准
    • 建立所有点的correspondences,移除outlier pairs
    • 计算 R R R t t t
    • 迭代,直至算法收敛

(3)代码实现

  • RANSAC
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]

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • ISS
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

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • FPFH
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

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • ICP
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

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • registration
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)

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123

(4)配准效果
在这里插入图片描述

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/不正经/article/detail/122691?site
推荐阅读
相关标签
  

闽ICP备14008679号