basalt视觉因子构建
在视觉因子的雅克比求导过程中主要进行了视觉残差对相对位姿的导数与特征点参数uv和逆深度(这里的uv不是像素坐标而是特征点在赤平面的投影坐标)。
在优化函数中(optimize函数)中会构建视觉因子,里面会调用linearizeHelper函数里面会调用linearizeHelperStatic函数对视觉因子的雅克比矩阵构建。
流程
- 遍历所有的观测,把所有主帧的id放到obs_tcid_vec中,并为返回数据初始化大小
- 对主帧序列进行并行循环计算(每一个主帧对所有的观测进行计算)
- 计算相对位姿对绝对位姿的导数
- 计算视觉残差以及计算计算视觉残差对相对位姿的导数、视觉残差对三维点参数的导数
- 构建H和b矩阵进行保存
对视觉残差对相对位姿以及三维点参数进行说明
视觉残差定义为在像素残差 r i t = z i t − π c ( ( T i c ( T t i w ) − 1 ∗ T h i w ) T c i ∗ p h c ( u , v , d ) ) r_{it} = z_{it}-\pi_{c}((T^c_i(T^w_{ti})^{-1} * T_{hi}^w) T^i_c * p_{hc}(u, v, d)) rit=zit−πc((Tic(Ttiw)−1∗Thiw)Tci∗phc(u,v,d))过程就是把三维点参数反投影成三维点,再把三维点从host的相机坐标系转换到target的相机坐标系再进行按投影模型进行投影做残差的过程。
注意问题:1、3d点参数是(u, v, invD) 但是上边式子 p h c p_{hc} phc是齐次坐标(x, y, z, 1/d)的形式的(虽然差一个d的比例但是没关系的)。
链式法则:残差先对 π c ( P t c ) \pi_c(P_{tc}) πc(Ptc)导数,再有 π c ( p ) \pi_c(p) πc(p)对相对位姿和三维点参数导数
-
残差对 P t c P_{tc} Ptc导数
前面basalt前端中分析了其投影模型,在投影模型中 u = X d + Z u = \frac{X}{d+Z} u=d+ZX v = Y d + Z v = \frac{Y}{d+Z} v=d+ZY 其中 d = X 2 + Y 2 + Z 2 d = \sqrt{X^2 + Y^2 + Z^2} d=X2+Y2+Z2分析一个比如v的残差对x的导数
∂ e r r v ∂ X = ∂ Y X 2 + Y 2 + Z 2 + Z ∂ X = Y ∗ ( − 1 ) ∗ ( X 2 + Y 2 + Z 2 + Z ) − 2 ∗ ( 1 / 2 ) ∗ ( X 2 + Y 2 + Z 2 ) − 1 / 2 ∗ 2 X \frac{\partial err_v}{\partial X} = \frac{\partial \frac{Y}{\sqrt{X^2 + Y^2 + Z^2} + Z}}{\partial X} = Y *(-1) *(\sqrt{X^2 + Y^2 + Z^2} + Z)^{-2}*(1/2)*(X^2 + Y^2 + Z^2)^{-1/2}*2X ∂X∂errv=∂X∂X2+Y2+Z2+ZY=Y∗(−1)∗(X2+Y2+Z2+Z)−2∗(1/2)∗(X2+Y2+Z2)−1/2∗2X
化简得到 X Y ∗ 1 / ( X 2 + Y 2 + Z 2 + Z ) 2 ∗ X 2 + Y 2 + Z 2 XY * 1/(\sqrt{X^2 + Y^2 + Z^2} + Z)^2 * \sqrt{X^2 + Y^2 + Z^2} XY∗1/(X2+Y2+Z2+Z)2∗X2+Y2+Z2 这也是与代码对应的,其余的只是展开就好,过程稍微麻烦一些而已。但这里对最后一维度(逆深度)的导数是0的,因为最后一维度并没有参与运算过程。
-
残差对相对位姿导数
对相对位姿,也就是对 ( T i c ( T t i w ) − 1 ∗ T h i w ) T c i (T^c_i(T^w_{ti})^{-1} * T_{hi}^w) T^i_c (Tic(Ttiw)−1∗Thiw)Tci的导数, 把 p h c ( u , v , d ) ) p_{hc}(u, v, d)) phc(u,v,d))用符号 p h c p_{hc} phc替代,把 ( T i c ( T t i w ) − 1 ∗ T h i w ) T c i (T^c_i(T^w_{ti})^{-1} * T_{hi}^w) T^i_c (Tic(Ttiw)−1∗Thiw)Tci用符号 T h c t c T_{hc}^{tc} Thctc替代。采用 S E 3 SE3 SE3左扰动形式进行证明。 e r r = [ ( I + δ ϕ × ) R h c t c ( I + δ ϕ × ) t + δ ρ 0 1 ] [ x y z 1 / d ] − [ R h c t c t 0 1 ] [ x y z 1 / d ] err = \begin{bmatrix} (I + \delta \phi^{×}) R_{hc}^{tc} & (I + \delta \phi^{×})t+\delta \rho \\ 0 & 1\end{bmatrix} \begin{bmatrix} x \\ y \\ z \\ 1/d \end{bmatrix} - \begin{bmatrix} R_{hc}^{tc} & t \\ 0 & 1\end{bmatrix} \begin{bmatrix} x \\ y \\ z \\ 1/d \end{bmatrix} err=[(I+δϕ×)Rhctc0(I+δϕ×)t+δρ1] xyz1/d −[Rhctc0t1] xyz1/d
化简得到 [ δ ϕ × R h c t c δ ϕ × t + δ ρ 0 1 ] [ x y z 1 / d ] \begin{bmatrix} \delta \phi^{×} R_{hc}^{tc} & \delta \phi^{×} t + \delta \rho \\ 0 & 1\end{bmatrix} \begin{bmatrix} x \\ y \\ z \\ 1/d \end{bmatrix} [δϕ×Rhctc0δϕ×t+δρ1] xyz1/d 因此很容易推倒出来和basalt一样的形式:
∂ P t c ∂ T h c t c = [ I 3 × 3 P h c [ 3 ] − [ R h c t c P h c [ 0 : 2 ] + t h c t c ] × 0 0 ] \frac{\partial P_{tc}}{\partial T_{hc}^{tc}} = \begin{bmatrix} I_{3×3} P_{hc}[3] & -[R_{hc}^{tc} P_{hc}[0:2] + t_{hc}^{tc}]^{×} \\ 0 & 0\end{bmatrix} ∂Thctc∂Ptc=[I3×3Phc[3]0−[RhctcPhc[0:2]+thctc]×0]
-
残差对u、v、invd的导数
这部分的导数要分两步看对uv的导数在进行unproject的过程算出来的Jup代表[x,y,z,1/d]对uv的导数,根据前面的投影模型 [ x , y , z ] = [ η u , η v , η − 1 ] [x, y, z] = [\eta u, \eta v, \eta -1] [x,y,z]=[ηu,ηv,η−1]以及 η = 2 1 + u 2 + v 2 \eta = \frac{2}{1+u^2 + v^2} η=1+u2+v22两个式子,这里在证明的时候使用复合求导,对 ∂ x ∂ u = ∂ η ∂ u u + η = − 2 ∗ ( 1 + u 2 + v 2 ) − 2 ∗ 2 ∗ u ∗ u + η \frac{\partial x}{\partial u} = \frac{\partial \eta}{\partial u} u + \eta = -2*(1+u^2 + v^2)^{-2}*2*u *u + \eta ∂u∂x=∂u∂ηu+η=−2∗(1+u2+v2)−2∗2∗u∗u+η。残差对其他的导数计算也是这样的,因为在反投影过程深度与uv是没有关系的,因此这里深度对uv的导数是0。残差中与逆深度相关的只有位姿矩阵的最后一列。
basalt源码
// 特征点残差的导数
template <class Scalar, class CamT>
inline bool linearizePoint(
const Eigen::Matrix<Scalar, 2, 1>& kpt_obs, const Keypoint<Scalar>& kpt_pos,
const Eigen::Matrix<Scalar, 4, 4>& T_t_h, const CamT& cam,
Eigen::Matrix<Scalar, 2, 1>& res,
Eigen::Matrix<Scalar, 2, POSE_SIZE>* d_res_d_xi = nullptr,
Eigen::Matrix<Scalar, 2, 3>* d_res_d_p = nullptr,
Eigen::Matrix<Scalar, 4, 1>* proj = nullptr) {
static_assert(std::is_same_v<typename CamT::Scalar, Scalar>);
// Todo implement without jacobians
Eigen::Matrix<Scalar, 4, 2> Jup;
Eigen::Matrix<Scalar, 4, 1> p_h_3d;
p_h_3d = StereographicParam<Scalar>::unproject(kpt_pos.direction, &Jup);
p_h_3d[3] = kpt_pos.inv_dist;
const Eigen::Matrix<Scalar, 4, 1> p_t_3d = T_t_h * p_h_3d;// R
Eigen::Matrix<Scalar, 2, 4> Jp;
bool valid = cam.project(p_t_3d, res, &Jp);
valid &= res.array().isFinite().all();
if (!valid) {
return false;
}
if (proj) {
proj->template head<2>() = res;// 前维度是重投影残差
(*proj)[2] = p_t_3d[3] / p_t_3d.template head<3>().norm();// 1 / d
}
res -= kpt_obs;
if (d_res_d_xi) {
Eigen::Matrix<Scalar, 4, POSE_SIZE> d_point_d_xi;
d_point_d_xi.template topLeftCorner<3, 3>() =
Eigen::Matrix<Scalar, 3, 3>::Identity() * kpt_pos.inv_dist;
d_point_d_xi.template topRightCorner<3, 3>() =
-Sophus::SO3<Scalar>::hat(p_t_3d.template head<3>());
d_point_d_xi.row(3).setZero();
*d_res_d_xi = Jp * d_point_d_xi;
}
if (d_res_d_p) {
Eigen::Matrix<Scalar, 4, 3> Jpp;
Jpp.setZero();
Jpp.template block<3, 2>(0, 0) = T_t_h.template topLeftCorner<3, 4>() * Jup;
Jpp.col(2) = T_t_h.col(3);
*d_res_d_p = Jp * Jpp;
}
return true;
}