最小化重投影误差(BA法)求解PnP

1.引言

PnP算法是什么、用途以及部分求解方法我在PnP算法详解(超详细公式推导)中介绍过,但在那篇文章中基于基于优化的PnP求解方法我没有讲,因为我觉得这个方法比较重要,涉及一些李群李代数求导和非线性优化的知识,所以打算单独写一篇博客,后面也会出一片全c++代码实现的文章。

2.核心思想

重投影误差法,也叫Bundle Adjustment(BA法),顾名思义这个问题的误差项是3D点的投影位置与实际位置作差,如图1所示, p 1 p_{1} p1 p 2 p_{2} p2是同一个空间点P在不同相机姿态下的投影,但由于我们不知道相机姿态。在初始值中。P的投影 p 2 ^ \hat{p_{2}} p2^与实际的 p 2 p_{2} p2有一定的距离。于是我们通过优化的方式调整位姿,使得这个距离变小。不过,由于这个调整需要考虑多个点,所以最后的效果是整体误差的缩小,而每个点的误差通常不会精确为零。

图1

3.模型建立

考虑n个三维空间点 P P P及其投影 p p p,计算相机的姿态 R , t R,t Rt,它的李群表示为 T T T。假设某空间点的坐标为 P i = [ X i , Y i , Z i ] T P_{i}=[X_{i},Y_{i},Z_{i}]^{T} Pi=[Xi,Yi,Zi]T,其投影的像素坐标为 u i = [ u i , v i ] T u_{i}=[u_{i},v_{i}]^{T} ui=[ui,vi]T。根据相机模型,建立空间点位置与像素位置关系如公式(1):
s i [ u i v i 1 ] = K T [ X i Y i Z 1 1 ]   ( 1 ) s_{i}\begin{bmatrix} u_{i}\\ v_{i} \\ 1 \\ \end{bmatrix}=KT\begin{bmatrix} X_{i} \\ Y_{i} \\ Z_{1}\\ 1\\ \end{bmatrix} \ (1) si uivi1 =KT XiYiZ11  (1)

写成矩阵的形式就是公式(2):
s i u i = K T P i   ( 2 ) s_{i}u_{i}=KTP_{i} \ (2) siui=KTPi (2)

该式隐含了一次从齐次坐标到非齐次的转换,否则按矩阵的乘法来说维度是不对的,也就是 T P i TP_{i} TPi是4x1的坐标,取出前三维变成非齐次坐标后再与内参相乘。
现在由于相机位姿位置以及观测点存在噪声(非slam问题可能没有就无需优化),该等式存在一个误差。因此,我们把误差求和,构建最小二乘法问题,然后寻找最优的相机位姿,使公式(3)最小化:
T ∗ = a r g m i n T 1 2 ∑ i = 1 n ∥ u i − 1 s i K T P i ∥ 2 2   ( 3 ) T^{*}=arg min_{T}\frac{1}{2}\sum_{i=1}^{n}\left\|u_{i}-\frac{1}{s_{i}}KTP_{i} \right\|^{2}_{2} \ (3) T=argminT21i=1n uisi1KTPi 22 (3)

对公式(3)进行优化的方法有很多,比如一阶梯度法、二阶梯度法、高斯牛顿法和列文伯格-马夸尔特方法等,这里使用高斯牛顿法,我们首先简单介绍下高斯牛顿法。

4.高斯牛顿法

高斯牛顿法是优化算法中最简单的方法。他的思想是将 f ( x ) f(x) f(x)进行一阶的泰勒展开,也可以说是一阶线性化,注意这里不是目标函数 F ( x ) F(x) F(x)而是 f ( x ) f(x) f(x),否则就变成牛顿法了。
f ( x + Δ x ) ≈ f ( x ) + J ( x ) T Δ x   ( 4 ) f(x+\Delta x)\approx f(x)+J(x)^{T}\Delta x \ (4) f(x+Δx)f(x)+J(x)TΔx (4)

这里 J ( x ) T J(x)^{T} J(x)T f ( x ) f(x) f(x)关于x的导数,也是一个雅可比矩阵。

求解增量方程是整个优化问题的核心所在。如果我们能够顺利解出增量方程,那么高斯牛顿法的算法步骤可以写成:

(1).给定初始值 x 0 x_{0} x0
(2).对于第k次迭代,求出当前的雅可比矩阵 J ( x k ) J(x_{k}) J(xk)和误差 f ( x k ) f(x_{k}) f(xk)
(3).求解增量方程
(4).若 Δ x k \Delta x_{k} Δxk足够小,则停止。否则,令 x k + 1 = x k + Δ x k x_{k+1}=x_{k}+\Delta x_{k} xk+1=xk+Δxk,返回第二步。

5.具体实现推导

根据公式(3)设误差项如公式(6)所示:
e i = u i − 1 s i K e x p ( ξ ∧ ) P i   ( 6 ) e_{i}=u_{i}-\frac{1}{s_{i}}Kexp(\xi^{\wedge})P_{i} \ (6) ei=uisi1Kexp(ξ)Pi (6)

e使用齐次坐标形式是三维,但由于最后一维始终为1,所以实际上是2维,由于要进行求导所以进行了一次 T T T的指数映射,也就是 T = e x p ( ξ ∧ ) T=exp(\xi^{\wedge}) T=exp(ξ),这里 ξ \xi ξ是T对应的李代数,维度是 6 × 1 6 \times 1 6×1

根据高斯牛顿法对误差项进行一阶泰勒展开,也就是线性化的过程化:

e ( x + Δ x ) ≈ e ( x ) + J T Δ x   ( 7 ) e(x+\Delta x) \approx e(x) + J^{T} \Delta x \ (7) e(x+Δx)e(x)+JTΔx (7)

由于在高斯牛顿法中,关键是解出增量方程,而增量方程中含有 J J J,所以 J T J^{T} JT是关键。我们固然可以使用数值导数,但如果能够推导出解析形式,则优先考虑解析导数。
现在,由于e是像素坐标的误差(2维),x为相机的姿态,由于是李代数的形式,所以是6维,根据矩阵乘法, J T J^{T} JT是一个 2 × 6 2 \times 6 2×6的矩阵。我们使用IPad进行 J T J^{T} JT形式的推导。

根据公式(14)我们便可以求出 J T J^{T} JT,,然后根据增量方程求解 Δ x \Delta x Δx,那么 Δ x = δ ξ \Delta x= \delta \xi Δx=δξ,然后优化T,使用李代数 T = e x p ( δ ξ ∧ ) ⋅ T T=exp(\delta \xi^{\wedge})\cdot T T=exp(δξ)T,具体步骤在高斯牛顿法处介绍过。

如果需要优化特征点的空间位置,需要讨论e关于空间点P的导数,这个导数矩阵相对容易。仍利用链式法则,有:
∂ e ∂ P = ∂ e ∂ P ′ ∂ P ′ ∂ P   ( 15 ) \frac{\partial e}{\partial P}=\frac{\partial e}{\partial P^{'}}\frac{\partial P^{'}}{\partial P} \ (15) Pe=PePP (15)

第一项在前面已经做了推导,关于第二项,按照定义:
P ′ = ( T P ) 1 : 3 = R P + t   ( 16 ) P^{'}=(TP)_{1:3}=RP+t \ (16) P=(TP)1:3=RP+t (16)

P ′ P^{'} P P P P求导后只剩下R。于是:
∂ e ∂ P = − [ f x Z ′ 0 − f x X ′ Z ′ 2 0 f y Z ′ − f y Y ′ Z ′ 2 ]   ( 17 ) \frac{\partial e}{\partial P}=-\begin{bmatrix} \frac{f_{x}}{Z^{'}} &0 &-\frac{f_{x}X^{'}}{Z^{'2}} \\ 0&\frac{f_{y}}{Z^{'}} &-\frac{f_{y}Y^{'}}{Z^{'2}} \\ \end{bmatrix} \ (17) Pe= Zfx00ZfyZ2fxXZ2fyY  (17)

后面博客写一篇代码实现。 \color{red}{后面博客写一篇代码实现。 } 后面博客写一篇代码实现。

  • 6
    点赞
  • 44
    收藏
    觉得还不错? 一键收藏
  • 8
    评论
Bundle Adjustment 是一种用于同时估计多个相机位置,姿态和三维点云的优化算法。下面是一个用Python编写的简单的Bundle Adjustment玩具程序: ```python import numpy as np import scipy.sparse as sp import scipy.sparse.linalg as splinalg # 生成测试数据 num_cameras = 3 num_points = 5 num_observations = 10 noise_scale = 0.1 # 相机的位置和姿态 camera_positions = np.random.randn(num_cameras, 3) camera_orientations = np.random.randn(num_cameras, 3) # 三维点云 points_3d = np.random.randn(num_points, 3) # 观测到的2D点 observed_points = np.zeros((num_observations, 2)) camera_indices = np.zeros(num_observations, dtype=int) point_indices = np.zeros(num_observations, dtype=int) for i in range(num_observations): camera_idx = np.random.randint(num_cameras) point_idx = np.random.randint(num_points) observed_points[i] = np.random.randn(2) + \ np.dot(camera_orientations[camera_idx], points_3d[point_idx]) + \ camera_positions[camera_idx] observed_points[i] += np.random.randn(2) * noise_scale camera_indices[i] = camera_idx point_indices[i] = point_idx # 构建稀疏矩阵 A = sp.lil_matrix((num_observations * 2, num_cameras * 6 + num_points * 3)) b = np.zeros(num_observations * 2) for i in range(num_observations): camera_idx = camera_indices[i] point_idx = point_indices[i] point_3d = points_3d[point_idx] camera_pos = camera_positions[camera_idx] camera_orient = camera_orientations[camera_idx] # 计算投影误差 projected_point = np.dot(camera_orient, point_3d) + camera_pos projected_point /= projected_point[2] projected_point = projected_point[:2] error = observed_points[i] - projected_point # 构建雅可比矩阵 J_camera = np.hstack((point_3d, np.zeros(3), np.eye(3))) J_point = np.hstack((np.zeros(2), camera_orient[:2].reshape(2, 1) * point_3d.reshape(1, 3))) J = np.vstack((J_camera, J_point)) A[i * 2: (i+1) * 2, camera_idx * 6: (camera_idx+1) * 6] = J[:, :6] A[i * 2: (i+1) * 2, num_cameras * 6 + point_idx * 3: num_cameras * 6 + (point_idx+1) * 3] = J[:, 6:] b[i * 2: (i+1) * 2] = error # 优化 x0 = np.hstack((camera_positions.ravel(), camera_orientations.ravel(), points_3d.ravel())) res = splinalg.lsmr(A, b, x0=x0) optimized_positions = res[0][:num_cameras * 3].reshape(num_cameras, 3) optimized_orientations = res[0][num_cameras * 3: num_cameras * 6].reshape(num_cameras, 3) optimized_points_3d = res[0][num_cameras * 6:].reshape(num_points, 3) print("Original Camera Positions:\n", camera_positions) print("Original Camera Orientations:\n", camera_orientations) print("Original 3D Points:\n", points_3d) print("Optimized Camera Positions:\n", optimized_positions) print("Optimized Camera Orientations:\n", optimized_orientations) print("Optimized 3D Points:\n", optimized_points_3d) ``` 这个程序生成了一些随机的相机位置、姿态和三维点云,然后随机生成了一些观测到的2D点,并添加了一些高斯噪声。程序使用Bundle Adjustment来估计相机和3D点云的位置,并输出优化后的结果。
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值