Lie groups
Camera poses live in and rotations in — these are manifolds, not vector spaces. You cannot add two rotation matrices and get a rotation, so standard “update ” optimization does not directly apply. Lie theory bridges this gap: each Lie group (the curved space of rotations/poses) has an associated Lie algebra (a flat vector space tangent to the group at identity), connected by the exponential and logarithm maps. Optimizers work in the flat algebra and map updates back onto the group.
so(3) and SO(3). The Lie algebra consists of skew-symmetric matrices , parameterized by a vector (axis times angle). The exponential map is Rodrigues’ rotation formula:
The logarithm map is its inverse — it recovers the rotation vector from a rotation matrix.
se(3) and SE(3). For rigid-body poses, elements of are parameterized by (translation part , rotation part ):
where is the left Jacobian of :
Why this parameterization wins. A pose has exactly 6 degrees of freedom, and is a minimal, singularity-managed parameterization for local updates: no constraints to maintain (unlike a matrix or a unit quaternion), no gimbal lock (unlike Euler angles at their singularity). In SLAM optimization, each iteration solves for a small update in the algebra and applies it as a perturbation on the group:
Jacobians of residuals (e.g., reprojection error) are derived with respect to , evaluated at .
A worked perturbation Jacobian
The whole calculus reduces to one move: expand to first order, , and use . For a rotated point under a left perturbation:
so . The right-perturbation version follows the same two lines and gives . Chain this with the projection Jacobian and you have derived the reprojection-error Jacobian used by every bundle-adjustment implementation — no matrix calculus tables required.
In code
Sophus is the standalone C++ implementation of this machinery (Eigen-based), and its API mirrors the math one-to-one:
#include <sophus/se3.hpp>
Eigen::Matrix<double, 6, 1> xi = ...; // twist in se(3)
Sophus::SE3d T = Sophus::SE3d::exp(xi); // exp map: algebra -> group
Eigen::Matrix<double, 6, 1> back = T.log();// log map: group -> algebra
T = T * Sophus::SE3d::exp(delta); // right-perturbation update step
The same pattern is baked into every SLAM library: g2o’s SE3 vertices, Ceres’ manifolds (local parameterizations), and GTSAM’s Pose3. The machinery extends to (pose + scale), which monocular SLAM uses for loop closure because scale drifts along the trajectory.
Common pitfalls
- Small-angle numerics: the -style coefficients in , , and are at ; implementations must switch to Taylor expansions near zero (libraries do this — your own scratch implementation must too).
- near : recovering the axis from a rotation by almost is ill-conditioned; be careful when averaging or interpolating large rotations.
- Left vs. right conventions: papers and libraries freely mix left/right perturbations; the Jacobians differ (see the worked example), and silently mixing conventions is a classic source of “optimizer converges to garbage” bugs.
- Quaternion double cover: and encode the same rotation; residuals and interpolation must handle the sign, or errors near appear.
- Forgetting the manifold in the solver: feeding a raw 4-parameter quaternion or 9-parameter rotation matrix to an optimizer without a local parameterization lets the update leave the manifold — Ceres’ manifold API, g2o’s vertex implementations, and GTSAM’s types exist precisely to prevent this.
Why it matters for SLAM
Every optimization-based SLAM component — bundle adjustment, pose graph optimization, IMU preintegration, direct image alignment — differentiates residuals with respect to poses, and Lie group machinery is how that is done correctly. Residuals between poses are themselves expressed via (as in pose graph costs ). If you cannot read //perturbation notation fluently, back-end papers are unreadable; once you can, they all look pleasantly alike.