LiDAR
factors::lidar — scan registration. Correspondences are established upstream:
these factors evaluate one already-matched pair, so the data association is the
front end’s job.
Two graph topologies coexist deliberately. The two-pose family
(distance_field, edge, and the LidarPlaneFactor alias) registers a scan
against a reference frame with the query point baked into the factor. The
pose-and-point family (plane, point_to_point, gicp) treats the
body-frame point as a variable. Pick the one matching how your front end
parameterizes the scan.
The derivative every pose-and-point factor needs is SE3::act’s:
IcpFactor<F> — point-to-field
Aligns a query point against a distance or occupancy field defined in another
frame. Generic over F: DistanceField, which returns the field value and its
gradient at a query point.
Blocks — residual 1, Jacobian . Whitens internally with .
Error
Dividing by converts the field value into an approximate metric distance to the surface, so one is meaningful across fields of different scaling.
Jacobian
The residual and Jacobian are zeroed where the field query fails or — an undefined gradient carries no information, and a huge one is numerical noise.
LidarPlaneFactor — LOAM point-to-plane, two-pose
Not a distinct residual: it is the type alias
IcpFactor<PrecomputedPlane>, where the field is a plane through
with unit normal :
so and the Jacobian
above specializes exactly. Build one with lidar_plane_factor_isotropic.
LidarEdgeFactor — LOAM point-to-line
The only genuinely distinct LiDAR residual here.
Blocks — residual 3, Jacobian . Whitens internally.
Error
With a matched edge line through with unit direction ,
the vector rejection of the offset onto the line’s orthogonal complement. The scalar cross-product form would measure the same distance but is non-differentiable at its own zero, which is precisely where the optimizer converges.
Jacobian
The projector is constant, so
, chained with the same
blocks as IcpFactor.
PointToPlaneFactor — plane as a measurement
Blocks — residual 1, Jacobian .
Takes an external NoiseModel.
for a target plane with .
One constraint per correspondence, along the normal only — which is why three non-parallel planes are needed to fix a translation.
PoseToPointFactor
Blocks — residual 3, Jacobian .
The plain point-to-point correspondence, and the special case of GICP.
GicpFactor — plane-to-plane
Generalized ICP: the same point-to-point residual, but Mahalanobis-whitened by the combined surface covariance, so tangential directions (flat in both clouds) contribute almost nothing and the normal direction dominates. Plane-to-plane registration emerges from point-to-point with the right metric.
Blocks — residual 3, Jacobian . Whitens internally.
Error
is the symmetric inverse square root, with eigenvalues below clamped to zero so a rank-deficient covariance yields a pseudo-inverse root rather than non-finite entries.
Jacobian
is evaluated once at construction from a caller-supplied rotation hint and then held fixed, so the rotation-dependence of is dropped from the Jacobian. That is deliberate: callers rebuild correspondences (and these factors) every scan-matching iteration, so the frozen covariance never goes stale, and freezing it keeps the residual/Jacobian pair exactly consistent.