Paper · 2021-02-24 · cs.RO
R2LIVE: A Robust, Real-time, LiDAR-Inertial-Visual tightly-coupled state Estimator and mapping
R2LIVE fuses LiDAR, IMU and camera measurements tightly. An error-state iterated Kalman filter runs odometry at IMU rate, and a sliding-window factor graph refines keyframe poses and visual landmarks. Author-reported: on a 300 m D-GPS sequence the median relative pose error is 0.27° and 0.21%, against 0.41° and 0.34% for FAST-LIO.
Error-state Kalman filterLiDAR-inertial odometryVisual-inertial odometryUnit-quaternion rotationslamstate-estimationsensor-fusionlidarvisual-inertialkalman-filter
arXivPDFCodeMarkdown11 min read
AI-compiled by claude-haiku-5-5 on 2026-10-18, not yet reviewed by a human. Every claim cites its source; check it when a detail matters.
In plain words
A robot or handheld rig that moves through a building needs to know where it is and how it is turning. Three sensors help with that. A spinning or solid-state laser scanner (LiDAR) measures distances to surfaces, but fails in long, bare corridors where every wall looks the same. A camera sees texture, but fails when the image is blurred, dark or blocked. An inertial sensor (IMU) measures acceleration and rotation very often, but drifts if it is trusted alone.
R2LIVE uses all three at once. It predicts the motion from the inertial sensor at every sample and corrects that prediction with each laser scan and each camera frame. This keeps the estimate smooth at high rate. A second, slower stage then tunes the recent camera poses and the 3D points seen by the camera, so the points line up better. The authors tested it in a long subway tunnel full of walking people, with the camera covered and with the laser covered, and at high rotation speeds. They report that it stayed usable in each case and beat FAST-LIO and VINS-Mono in their tests.
TL;DR
R2LIVE is a tightly coupled LiDAR-inertial-visual system with two parts: an error-state iterated Kalman filter (ESIKF) that runs odometry and updates on each LiDAR frame and camera frame §IV-I, and a sliding-window factor graph over image keyframes that refines visual landmarks, keyframe poses and the camera-LiDAR time offset §V. The IMU is propagated between measurements §IV-D. The paper reports real-time operation on an embedded computer (i7-8550U) and a desktop tab. I. The code is open under GPL-2.0 and the paper's numbers are author-reported. Read this card if you need a LiDAR-inertial-visual estimator design, the exact filter update, or to reproduce its update rules.
Problem
LiDAR-only odometry degenerates where there are few geometric features, which is most critical for small-field-of-view LiDARs §I. Prior work adds an IMU to LiDAR odometry §II-B, or also a camera §II-C. The paper targets a tightly coupled fusion of all three sensors that stays real-time on onboard hardware §I, §VI-F. Assumptions: the time offsets between the three sensors are pre-calibrated §IV-B; the LiDAR-IMU extrinsic is known §IV-B; LiDAR points within a frame are treated as measured at one instant after IMU-based motion compensation §IV-B.
Method
The system has two stages §III, fig. 2.
-
Filter-based odometry (front end, §IV). The state holds the IMU pose, velocity, the camera-IMU extrinsic, and the gyro and accelerometer biases §IV-C. Between sensor events, the state is propagated with the IMU readings held constant over each sample period (zero-order hold) §IV-C. The error state has 21 dimensions and lives on the tangent space of the manifold, with the paper's boxplus and boxminus operators §IV-A, eq. 1 §IV-D, eq. 4. At each LiDAR frame or camera frame the propagated prior is fused with the measurements in an iterated MAP update §IV-E, §IV-F, eq. 8-11.
-
Factor graph optimization (back end, §V). Visual landmarks triangulated in the front end are noisy because keyframe pose error propagates into them §V. The back end optimizes camera poses and landmark positions in a sliding window of image keyframes, adds IMU preintegration factors between keyframes, and adds pose constraints from the LiDAR. LiDAR poses stay fixed and raw LiDAR points are not used in the graph §V. The graph is similar to VINS-Mono's, and it also estimates the camera-LiDAR time offset §V.
Continuous model. The IMU kinematics use gravity in the global frame, and the biases follow a random walk §IV-B, eq. 2:
\begin{aligned} {}^G\dot{\mathbf p}_I &= {}^G\mathbf v_I, \\ {}^G\dot{\mathbf v}_I &= {}^G\mathbf R_I(\mathbf a_m - \mathbf b_a - \mathbf n_a) + {}^G\mathbf g, \\ {}^G\dot{\mathbf R}_I &= {}^G\mathbf R_I\,[\boldsymbol\omega_m - \mathbf b_g - \mathbf n_g]_\times, \\ \dot{\mathbf b}_g &= \mathbf n_{bg}, \qquad \dot{\mathbf b}_a = \mathbf n_{ba} \end{aligned} eq. 2
Discrete propagation. The nominal state advances by \hat{\mathbf x}_{i+1} = \hat{\mathbf x}_i \boxplus (\Delta t\, \mathbf f(\hat{\mathbf x}_i, \mathbf u_i, \mathbf 0)) eq. 5 and the error covariance by \boldsymbol\Sigma_{i+1} = \mathbf F_{\delta\hat x}\boldsymbol\Sigma_i \mathbf F_{\delta\hat x}^T + \mathbf F_{\mathbf w}\mathbf Q \mathbf F_{\mathbf w}^T eq. 7. The Jacobians are in app. A of the paper. The paper's text does not define \mathbf Q anywhere; see Pitfalls.
Prior. Propagation stops at the next LiDAR or camera time k+1. The prior is \mathbf x_{k+1} \boxminus \hat{\mathbf x}_{k+1} \sim \mathcal N(\mathbf 0, \boldsymbol\Sigma_{\delta\hat x_{k+1}}) eq. 8. It is re-expressed around the current iterate \check{\mathbf x} with \mathcal H eq. 10, which gives a prior in terms of \delta\check{\mathbf x} eq. 11. \mathcal H is defined in app. B.
Measurements. LiDAR: each planar feature point gives a point-to-plane residual \mathbf r_l = \mathbf u_j^T(\,{}^G\mathbf p_j - \mathbf q_j), with the plane normal \mathbf u_j and a point on the plane \mathbf q_j taken from the nearest map points §IV-G, eq. 12. Its Jacobian is in app. C and its noise term is in eq. 15. Camera: each tracked corner gives a reprojection residual \mathbf r_c = {}^C\mathbf p_s - \boldsymbol\pi({}^C\mathbf P_s), where \boldsymbol\pi is the pinhole projection §IV-H, eq. 16. The corner is tracked with KLT optical flow and FAST corners §IV-H.
Iterated update. The MAP estimate of \delta\check{\mathbf x} minimizes the prior term plus the LiDAR and camera terms eq. 21. The gain is \mathbf K = (\mathbf H^T \mathbf R^{-1}\mathbf H + \mathbf P^{-1})^{-1}\mathbf H^T \mathbf R^{-1} eq. 23, and the iterate is updated as \check{\mathbf x} \leftarrow \check{\mathbf x} \boxplus(-\mathbf K\check{\mathbf z} - (\mathbf I - \mathbf K\mathbf H)\mathcal H^{-1}(\check{\mathbf x}\boxminus\hat{\mathbf x})) §IV-I. The loop repeats until the update is small §IV-I.
# Propagate the state on each IMU sample [§IV-D] [eq. 5]
def propagate_imu(state, cov, imu_samples, dt):
for gyro, accel in imu_samples:
# Mean moves with the zero-noise dynamics [§IV-D] [eq. 5]
state = state_plus(state, dt * dynamics(state, gyro, accel))
# Covariance grows with the state and noise Jacobians [§IV-D] [eq. 7]
jac_state = state_jacobian(state, gyro, accel) # (21, 21)
jac_noise = noise_jacobian(state, gyro) # (21, 12)
cov = jac_state @ cov @ jac_state.T + jac_noise @ noise_q @ jac_noise.T
return state, cov# Iterated error-state update at one LiDAR or camera time [§IV-I] [eq. 21]
def esikf_update(prior_state, prior_cov, measurements):
# Start the MAP iterate at the propagated prior [§IV-F]
iterate = prior_state
for step in range(max_steps): # max_steps is not stated in the paper
# Linearize every measurement at the current iterate [§IV-G] [§IV-H]
residuals, H, R_inv = linearize(iterate, measurements)
# Kalman gain [§IV-I] [eq. 23]
gain = solve(H.T @ R_inv @ H + inv(prior_cov), H.T @ R_inv)
# Correction pulls the iterate toward the measurements and the prior
# [§IV-I]; prior_jac is the paper's calligraphic H [eq. 10]
prior_error = boxminus(iterate, prior_state)
step_vec = (-gain @ residuals
- (eye - gain @ H) @ solve(prior_jac, prior_error))
iterate = boxplus(iterate, step_vec)
# Convergence threshold is not stated in the paper [§IV-I]
if norm(step_vec) < stop_threshold:
break
# Posterior covariance [§IV-I]
posterior_cov = (eye - gain @ H) @ prior_cov
return iterate, posterior_covCamera residual, per corner §IV-H eq. 16:
# Reprojection residual of one tracked corner [§IV-H] [eq. 16]
def camera_residual(state, pixel, landmark_world):
# Landmark in the camera frame through the IMU and the camera extrinsic
# [§IV-H] [eq. 16]
point_cam = (R_GI @ R_IC).T @ landmark_world \
- R_IC.T @ p_GI - p_IC
# Pinhole projection and the difference to the observed pixel [§IV-H]
return pixel - project_pinhole(point_cam)Hyperparameters
Values stated in the paper:
| Name | Value | Where | Notes |
|---|---|---|---|
| IMU rate | 200 Hz | fig. 3 | Illustration of input streams; not a configured parameter |
| Camera frame rate | 20 Hz | fig. 3 | As above |
| LiDAR frame rate | 10 Hz | fig. 3 | As above |
| Camera FoV | 82.9° × 66.5° | §VI-A | Global shutter camera on the handheld rig |
| LiDAR FoV | 70.4° × 77.2° | §VI-A | Livox AVIA |
| Rig weight, minimum system | 2.09 kg | fig. 6 | Caption of fig. 6(a) |
| Max angular speed, Exp-1 | up to 300°/s | §VI-B | From the gyroscope readings |
| Max angular speed, Exp-4 | 130°/s | §VI-E | From the gyroscope readings |
| Tunnel length, Exp-2 | up to 190 m | §VI-C fig. 9 | HKU MTR station |
| Loop length, Exp-3 | 876 m | §VI-D | Loop closed without a loop-closure module |
Not stated in the paper:
| Name | Value | Where | Notes |
|---|---|---|---|
| Sliding-window length | not stated | §V | "a sliding window of image keyframes" only |
| Iteration cap and stop threshold | not stated | §IV-I | "until convergence" only |
| IMU noise and bias random-walk values | not stated | §IV-B | Symbols only |
| Gravity magnitude | not stated | §IV-B | Symbol {}^G\mathbf g only |
| Measurement covariances | not stated | eq. 13 eq. 15 eq. 20 | Defined symbolically from sensor noise |
| Keyframe selection rule | not stated | §V | Only that keyframes are added |
| Feature count, tracking distance, RANSAC threshold | not stated | §IV-H | Code values in the next table |
| Initial time offset | not stated | §V | Estimated in the back end |
Code values (r2live at commit e719ca2, default config config/filr_cam.yaml). These are not in the paper:
| Name | Value | Where | Notes |
|---|---|---|---|
| Sliding window | 7 | parameters.h | WINDOW_SIZE, so 8 states |
| Camera measurement scale | 1e-3 | estimator_node.cpp | Used as CAM_MEASUREMENT_COV in the camera gain estimator_node.cpp |
| Visual iterations | 2 | estimator_node.cpp | Fixed loop count |
| Camera minimum residuals | 10 | estimator_node.cpp | Update skipped below this |
| Visual acceptance gates | 3°, 0.5 m, reprojection 1.0 | estimator_node.cpp | Update discarded if exceeded |
| LiDAR iterations | 4 | lio_config.yaml | fast_lio/max_iteration |
| LiDAR kd-tree distance | 0.5 | lio_config.yaml | maximum_pt_kdtree_dis |
| LiDAR residual distance | 0.3 | lio_config.yaml | maximum_res_dis |
| LiDAR planar check | 0.10 | lio_config.yaml | planar_check_dis |
| LiDAR map voxel size | 0.4 | lio_config.yaml | filter_size_surf |
| Feature max count | 400 | filr_cam.yaml | max_cnt |
| Feature minimum distance | 40 px | filr_cam.yaml | min_dist |
| RANSAC threshold | 1.0 px | filr_cam.yaml | F_threshold |
| Keyframe parallax | 10 px | filr_cam.yaml | keyframe_parallax |
| Back-end max iterations | 4 | filr_cam.yaml, estimator_optimization.cpp | max_num_iterations |
| Camera residual weight | 433 / 1.5 | estimator.cpp | Scale applied to the reprojection residual |
| Robust loss | Huber, 0.5 | estimator_optimization.cpp | Back end only |
| Accel noise, bias walk | 0.1, 0.001 | filr_cam.yaml | acc_n, acc_w |
| Gyro noise, bias walk | 0.01, 2e-5 | filr_cam.yaml | gyr_n, gyr_w |
| Gravity norm | 9.805 | filr_cam.yaml | g_norm |
| Time offset estimation | on | filr_cam.yaml | estimate_td: 1 |
| Camera-IMU extrinsic | fixed | filr_cam.yaml | estimate_extrinsic: 0 |
Results
Author-reported. Hardware: DJI manifold-2c (Intel i7-8550U, 8 GB) on the handheld rig §VI-A; desktop PC with Intel i7-9700K and 32 GB tab. I. Baselines: VINS-Mono (visual-inertial) and FAST-LIO (LiDAR-inertial), run on the same data §VI-C, §VI-E.
Experiment 4, quantitative accuracy against D-GPS RTK on a 300 m sequence, median relative pose error fig. 11:
| Method | Rotation error | Translation error | Source |
|---|---|---|---|
| VINS-Mono | 0.35° | 3.84% | fig. 11 |
| FAST-LIO | 0.41° | 0.34% | fig. 11 |
| R2LIVE | 0.27° | 0.21% | fig. 11 |
Errors are computed over all subsequences of length 50 to 300 m §VI-E.
Run time (average per Experiment, ms, desktop / onboard). LI-Odom is the LiDAR-IMU filter, VI-Odom the visual-inertial filter and FG-OPM the factor graph tab. I:
| Experiment | LI-Odom | VI-Odom | FG-OPM |
|---|---|---|---|
| Exp-1 | 8.81 / 15.98 | 7.84 / 13.92 | 26.10 / 45.35 |
| Exp-2 | 11.54 / 25.30 | 8.84 / 19.10 | 30.20 / 65.25 |
| Exp-3 | 14.91 / 30.64 | 9.57 / 19.56 | 29.25 / 58.08 |
| Exp-4 | 10.92 / 24.37 | 8.99 / 20.16 | 27.98 / 60.43 |
The back end runs in a separate thread and may run at a lower rate §VI-F.
Qualitative results, not numeric:
- Exp-1, aggressive motion and sensor blocking: the trajectory stays smooth when the camera or the LiDAR is covered §VI-B, fig. 7, fig. 8.
- Exp-2, MTR tunnel up to 190 m with pedestrians: the map matches the station street map, and the paper reports the best overall trajectory among the three methods §VI-C, fig. 9. VINS-Mono stopped midway due to feature-tracking failure fig. 9 caption.
- Exp-3, HKU main building, 876 m: the trajectory closes its loop without any loop-closure step §VI-D, fig. 12.
Paper vs code
Official repository: hku-mars/r2live, linked from the abstract and used at commit e719ca2 §I footnote. The LICENCE file is GPL-2.0, so only the descriptions below are used, not code.
- Visual update iterations. The paper iterates until convergence §IV-I. The camera update runs a fixed two iterations, and its convergence flag is set but never read estimator_node.cpp, estimator_node.cpp.
- LiDAR update iterations. The LiDAR update runs up to
max_iteration= 4 and also re-matches correspondences on convergence or on the second-to-last step fast_lio.hpp, fast_lio.hpp. The paper does not mention re-matching. - Convergence test units. Both branches use the same thresholds of 0.01° and 0.015. The LiDAR branch scales the translation by 100 before comparing fast_lio.hpp. The camera branch does not estimator_node.cpp. Treat these as code choices, not paper values.
- Camera measurement weight. The paper weights the camera residual by its pixel noise covariance \boldsymbol\Sigma_{\beta} §IV-H, eq. 20, but gives no value. The code's gain is (\mathbf H^T\mathbf H + (10^{-3}\,\mathbf P)^{-1})^{-1}\mathbf H^T estimator_node.cpp. This equals the paper's gain eq. 23 with \mathbf R = 1000\,\mathbf I on the residual, so the prior carries more weight than the measurement.
- Visual acceptance gates. The code accepts a camera update only if the rotation change is under 3°, the translation change under 0.5 m and the mean reprojection error under 1.0 estimator_node.cpp. It also reverts to the pre-update state when the change exceeds 0.2 m or 2° estimator_node.cpp. The paper has no such gates.
- Covariance after the camera update. The paper's update gives \hat\Sigma = (\mathbf I - \mathbf K\mathbf H)\check\Sigma §IV-I. In the camera branch the stored covariance is set to the value before the update estimator_node.cpp, estimator_node.cpp. I did not find the covariance update applied there. The LiDAR branch does apply it fast_lio.hpp.
- LiDAR poses in the graph. The paper says LiDAR poses are fixed §V. The code adds a 15-dimensional LiDAR prior factor for each window state estimator_optimization.cpp. These are soft priors, not constants.
- Window size. The paper gives no size §V. The code uses 7 parameters.h.
- Extrinsic. The paper says the camera-IMU extrinsic is estimated online §IV-B. The default launch config fixes it filr_cam.yaml, so the default run does not do what the paper describes.
- Noise values. The paper gives none. The code's defaults in
parameters.cppdiffer from the yaml, and the yaml is what the default launch file loads parameters.cpp, filr_cam.yaml, demo.launch. - Gravity. The paper leaves it unstated. The code uses 9.805 from the yaml filr_cam.yaml.
- Index. Eq. 3 writes the next state as t+1, while the rest of the derivation uses i eq. 3 eq. 5.
Pitfalls
- Undefined covariance input. \mathbf Q in eq. 7 is not defined anywhere in the LaTeX source. Take the IMU noise densities from your own sensor calibration. The code's yaml values are the authors' settings, not values the paper derives filr_cam.yaml.
- Iteration and stop rules. The paper gives no iteration cap or stop threshold §IV-I. An implementation must choose both. The code's caps are a reference: 4 on the LiDAR side and 2 on the camera side fast_lio.hpp, estimator_node.cpp.
- Camera measurement scale. The code's camera gain scales the prior by the state covariance times
1e-3estimator_node.cpp. This is a relative weight, not a pixel noise, so the update strength moves with the state covariance. - Gating decides whether the update is applied. Camera updates that fail the gates leave the state at its pre-update value estimator_node.cpp. An implementation that skips the gates will apply updates the authors rejected.
- Time offset. The paper estimates the camera-LiDAR time offset in the back end §V but gives no initial value. The code's default is 0.00 s filr_cam.yaml with online estimation on filr_cam.yaml.
- Back-end cost. Landmarks are triangulated from keyframe poses, which the paper says carry error §V. The paper reports the factor graph at 26 to 30 ms on the desktop and 45 to 65 ms onboard tab. I.
- Real-time claim. The paper reports real-time operation on the onboard i7-8550U with 8 GB §VI-F, tab. I. The paper says the factor graph runs on a separate thread at a lower rate §VI-F.
- Figure and table numbers. Figure and table numbers in this card follow the order in the LaTeX source. I did not compile the paper, so check them against the compiled PDF before relying on them. Equation numbers were derived by counting the numbered environments.
- Scope of tests. All experiments use the authors' handheld rig §VI-A. The paper does not test other sensor setups.
Lineage
- Builds on: FAST-LIO, the authors' earlier LiDAR-inertial filter, whose Kalman gain formula the paper uses §IV-I. VINS-Mono provides the factor-graph structure §V and the global-frame convention §IV-B. The error-state iterated filter follows He et al. §IV-D. Lidar feature extraction follows LOAM and FAST-LIO §IV-G.
- Compared against: VINS-Mono and FAST-LIO in Experiments 2 and 4 §VI-C, §VI-E. LIO-SAM, LINS, LIOM and LiLi-OM are described in §II without experiments against them.
- Superseded by: not known from the content of this wiki.
Source and license
Adapted from “R2LIVE: A Robust, Real-time, LiDAR-Inertial-Visual tightly-coupled state Estimator and mapping” by Jiarong Lin, Chunran Zheng, Wei Xu, Fu Zhang (arXiv:2102.12400), used under CC BY 4.0. Changes: condensed and restructured for implementation, with tables, equations and algorithms taken from the paper and its code, each cited to its source. Not affiliated with or endorsed by the authors. Code citations refer to https://github.com/hku-mars/r2live at commit e719ca266ab5 (GPL-2.0 (copyleft; no code quoted verbatim)). Any quoted code is shown under that license.