Skip to content

Perception Algorithm Development Example

This example uses FAST-LIVO2 (Fast, Direct LiDAR-Inertial-Visual Odometry) to implement LiDAR-Inertial-Visual fused SLAM on the Lite3 robot. Developers can obtain real-time 3D point cloud maps, visual feature submaps, odometry estimates, and camera RGB images through this system.

Code repository: DeepRoboticsLab/fast-livo2-deep-robotics

Use the hardware expansion and FAST-LIVO2 video or Bilibili with this example. Its unsynchronized sensors cause a reported failure; choose hardware synchronization or soft-sync using the current repository guide. See video tutorials for context.

The perception system sits in the high-level perception layer of the Lite3 development ecosystem, with responsibilities including:

  • Acquiring 3D point cloud data via Livox Mid-360/Mid-360s LiDAR
  • Acquiring high-frequency inertial measurement data via the LiDAR’s built-in IMU
  • Acquiring RGB image data via Intel RealSense D435i
  • Running FAST-LIVO2 for LiDAR-Inertial-Visual fused odometry estimation
  • Outputting real-time 3D point cloud maps, odometry poses, and visual feature submaps

Upper-layer applications (such as navigation, mapping, path planning) obtain perception results by subscribing to ROS2 topics.

ItemRequirement
OSUbuntu 22.04
ROS2 VersionROS2 Humble
Robot ModelLite3 Venture (hardware extension required only for this model)
Onboard ComputeAGX Jetson Orin (Jetpack 6.1, CUDA 12.6)
LiDARLivox Mid-360 or Mid-360s
CameraIntel RealSense D435i

Lite3 Venture does not come with a high-performance compute unit; an AGX Jetson Orin must be installed separately as the onboard computer.

Required parts:

  • AGX kit
  • Adapter plate bracket
  • Four M2.5 × 55 half-thread hex socket head cap screws

Place the AGX kit on the adapter plate bracket, aligning the four mounting holes. Insert the four M2.5 × 55 screws from the bottom of the adapter plate and tighten evenly to ensure the AGX kit sits flat.

AGX and Adapter Plate Assembly

Mounting the Component to the Lite3 Top Cover

Section titled “Mounting the Component to the Lite3 Top Cover”

Required parts:

  • Assembled AGX + adapter plate component
  • Lite3 top cover
  • Three M3 × 6 hex socket head cap screws

Place the component at the designated position on the top cover, align the three mounting points, install the M3 × 6 screws, and tighten evenly.

AGX Mounted on Lite3 Top Cover

This repository supports two Livox LiDAR variants:

  • Mid-360 (original): Use MID360 launch file and MID360_config.json
  • Mid-360s (newer): Use MID360s launch file and MID360s_config.json

Connection steps:

LiDAR Connection Cable

  1. Insert the XT30 end of the cable into the Lite3’s 24V XT30 port
  2. Connect the Ethernet end to the AGX’s Ethernet port
  3. Open Livox Viewer 2 on the host to confirm the LiDAR is connected

The Intel RealSense D435i connects to the AGX via USB 3.0. Ensure you use the original USB cable for optimal bandwidth.

Terminal window
cd fast-livo2-deep-robotics/src/Livox-SDK2
mkdir build && cd build
cmake .. && make -j
sudo make install
Terminal window
cd fast-livo2-deep-robotics/src/livox_ros_driver2/
source /opt/ros/humble/setup.bash
./build.sh humble

Follow the librealsense official documentation to install on the AGX.

Common Issue — Library Version Conflict:

If rs-enumerate-devices reports bad optional access, there are two versions of librealsense on the system:

PathVersionSourceStatus
/usr/local/liblibrealsense2.so.2.56.5Self-compiled (RSUSB support)✅
/opt/ros/humble/liblibrealsense2.so.2.56.4ROS2 auto-installed (older)❌

Solution:

Terminal window
echo "/usr/local/lib" | sudo tee /etc/ld.so.conf.d/99-realsense-local.conf
sudo ldconfig
echo 'export LD_LIBRARY_PATH=/usr/local/lib:$LD_LIBRARY_PATH' >> ~/.bashrc
source ~/.bashrc

Verify:

Terminal window
ldd $(which rs-enumerate-devices) | grep realsense

The result should point to /usr/local/lib/librealsense2.so.2.56.5.

Terminal window
sudo apt -y install ros-humble-realsense2-*

Verify:

Terminal window
ros2 launch realsense2_camera rs_pointcloud_launch.py

Install ROS2 dependencies:

Terminal window
sudo apt update
sudo apt -y install ros-humble-pcl-ros ros-humble-compressed-image-transport ros-humble-sophus

Compile:

Terminal window
cd fast-livo2-deep-robotics
source /opt/ros/humble/setup.bash
colcon build --packages-select fast_livo --symlink-install
source install/setup.bash

Verify package:

Terminal window
ros2 pkg list | grep livo

Expected output should include fast_livo and livox_ros_driver2.

The Ethernet adapter needs a static IPv4 address on the same subnet as the LiDAR.

Terminal window
# Find Ethernet interface
ip -br address
nmcli -t -f DEVICE,TYPE,STATE,CONNECTION device status
# Configure static IP (e.g., 192.168.1.45; LiDAR IP is 192.168.1.201)
sudo nmcli connection modify "Wired connection 2" \
ipv4.method manual \
ipv4.addresses 192.168.1.45/24 \
ipv4.gateway "" \
ipv4.dns "" \
ipv4.never-default yes
sudo nmcli connection down "Wired connection 2"
sudo nmcli connection up "Wired connection 2"

ipv4.never-default yes prevents the LiDAR connection from replacing the WiFi default route.

Verify:

Terminal window
ip route get 192.168.1.201
ping -c 3 192.168.1.201

Configure IP addresses in the driver config files:

  • Mid-360: src/livox_ros_driver2/config/MID360_config.json
  • Mid-360s: src/livox_ros_driver2/config/MID360s_config.json

Key fields:

"host_ip": "192.168.1.45",
"ip": "192.168.1.201"

FAST-LIVO2 requires the image dimensions to exactly match the camera model. Using D435i RGB stream 640×480×30 as an example:

Modify src/FAST-LIVO2/config/avia.yaml:

common:
img_topic: "/camera/camera/color/image_raw"
lid_topic: "/livox/lidar"
imu_topic: "/livox/imu"

Modify src/FAST-LIVO2/config/camera_pinhole.yaml:

camera:
model: Pinhole
width: 640
height: 480
scale: 1.0
fx: 604.3154296875
fy: 603.8231811523438
cx: 325.12115478515625
cy: 261.0074462890625
d0: 0.0
d1: 0.0
d2: 0.0
d3: 0.0

Obtain camera calibration parameters:

Terminal window
ros2 topic echo --once /camera/camera/color/camera_info

Extract from the K matrix: fx=K[0], fy=K[4], cx=K[2], cy=K[5].

Configure in extrin_calib of avia.yaml:

extrin_calib:
extrinsic_T: [0.04165, 0.02326, -0.0284] # IMU to LiDAR translation
extrinsic_R: [1.0, 0.0, 0.0,
0.0, 1.0, 0.0,
0.0, 0.0, 1.0] # IMU to LiDAR rotation
extrin_calib:
Rcl: [0.00610193,-0.999863,-0.0154172,
-0.00615449,0.0153796,-0.999863,
0.999962,0.00619598,-0.0060598]
Pcl: [0.0194384, 0.104689,-0.0251952]
time_offset:
imu_time_offset: 0.0 # IMU time offset
img_time_offset: 0.1 # Camera time offset (adjust based on actual hardware)
exposure_time_init: 0.0 # Exposure time initial value

LIVMapper is the core class of FAST-LIVO2, responsible for subscribing to sensor data, performing fused SLAM, and publishing results.

LIVMapper mapper(nh, "laserMapping", options);
mapper.initializeSubscribersAndPublishers(nh, it_);
mapper.run(nh);
TopicMessage TypeDescription
/livox/lidarlivox_ros_driver2/msg/CustomMsgLivox point cloud data
/livox/imusensor_msgs/msg/ImuLivox IMU data
/left_camera/imagesensor_msgs/msg/ImageCamera RGB image
TopicMessage TypeDescription
/cloud_registeredsensor_msgs/msg/PointCloud2Registered colored/intensity point cloud
/aft_mapped_to_initnav_msgs/msg/OdometryOdometry pose
/pathnav_msgs/msg/PathTrajectory path
/Laser_mapsensor_msgs/msg/PointCloud2Global map
/cloud_effectedsensor_msgs/msg/PointCloud2Effective feature points
/rgb_imgsensor_msgs/msg/ImageRendered RGB image
/LIVO2/imu_propagatenav_msgs/msg/OdometryIMU preintegration odometry
/mavros/vision_pose/posegeometry_msgs/msg/PoseStampedMAVROS pose

The system automatically selects the operating mode based on configuration:

ModeConditionDescription
LIVOimg_en=1 and lidar_en=1LiDAR + Inertial + Visual fusion
ONLY_LIOimu_en=true and img_en=0LiDAR + Inertial only
ONLY_LONo IMU, no cameraLiDAR only
graph LR
A["Livox LiDAR"] -->|/livox/lidar| B["FAST-LIVO2 /laserMapping"]
C["Livox IMU"] -->|/livox/imu| B
D["RealSense D435i"] -->|/camera/color/image_raw| B
B -->|/cloud_registered| E["RViz / Point Cloud Map"]
B -->|/aft_mapped_to_init| F["Odometry"]
B -->|/path| G["Trajectory"]
B -->|/rgb_img| H["RGB Image"]
void LIVMapper::run(rclcpp::Node::SharedPtr &node) {
rclcpp::Rate rate(5000);
while (rclcpp::ok()) {
rclcpp::spin_some(this->node);
if (!sync_packages(LidarMeasures)) {
rate.sleep();
continue;
}
handleFirstFrame(); // First frame initialization
processImu(); // IMU preintegration
stateEstimationAndMapping(); // State estimation and mapping
}
savePCD(); // Save point cloud
}

Each cycle executes in sequence:

  1. Data Synchronization (sync_packages): Align LiDAR, IMU, and image data by timestamp
  2. IMU Processing (processImu): Preintegration, debiasing, state propagation
  3. State Estimation (stateEstimationAndMapping): Dispatch to VIO or LIO processing based on mode

The Livox Mid-360s LiDAR’s LiDAR and IMU use different hardware clocks with no shared reference. Without correction, the timestamp offset between them drifts at approximately 1 second/second, causing FAST-LIVO2 to stop working after about 20 seconds.

Hardware synchronization (PTP, GPIO) is not feasible in this scenario: PTP can reach the Livox unit but not its internal IMU oscillator; the RealSense D435i’s RGB and depth sensors are on different PCBs, so GPIO synchronization cannot reach the RGB stream.

Time Synchronization Pipeline

graph TB
A["Livox IMU<br/>Own clock, drifts"] --> B["EMA Offset Filter<br/>α = 0.01, tracks drift"]
C["Livox LiDAR<br/>Reference clock"] --> D["sync_packages()<br/>Mutex-protected merge"]
E["RealSense D435i<br/>Host clock"] --> F["Static Offset<br/>Fixed YAML value"]
B --> D
F --> D
D --> G["FAST-LIVO2 SLAM"]

An Exponential Moving Average (EMA) filter (α=0.01\alpha = 0.01) is used in imu_cbk to continuously re-estimate the clock offset:

// Detect IMU-to-LiDAR timestamp deviation
if (abs(last_timestamp_imu - stamp2Sec(msg->header.stamp)) > 1.0) {
double timediff_imu_wrt_lidar = last_timestamp_imu - stamp2Sec(msg->header.stamp);
RCLCPP_INFO(this->node->get_logger(),
"Self sync IMU and LiDAR, HARD time lag is %.10lf", timediff_imu_wrt_lidar - 0.100);
}

RealSense streams are corrected using a fixed img_time_offset, since the driver already timestamps based on the host clock:

time_offset:
img_time_offset: 0.1 # Set in YAML, not computed at runtime

The original logic that discards IMU/image frames when timestamp jumps are detected has been replaced with warning log messages, preventing the pipeline from stalling due to transient jitter.

BranchApplicable Scenario
mainSensors are hardware-synchronized, or running public datasets (e.g., Retail_Street.db3)
soft-syncIMU, LiDAR, and camera use independent unsynchronized clocks

Switch branch:

Terminal window
git checkout soft-sync

Download the FAST-LIVO2 original dataset (Retail_Street):

Terminal window
# Install sqlite3 tool
sudo apt update && sudo apt install sqlite3
# Update topic types in the dataset (for livox_ros_driver2 compatibility)
sqlite3 Retail_Street.db3 "UPDATE topics SET type = 'livox_ros_driver2/msg/CustomMsg' WHERE name = '/livox/lidar';"

Run the algorithm:

Terminal window
cd fast-livo2-deep-robotics
source install/setup.bash
ros2 launch fast_livo mapping_avia.launch.py use_rviz:=True

Play the dataset:

Terminal window
source install/setup.bash
ros2 bag play Retail_Street.db3

Terminal 1 — LiDAR (Mid-360s):

Terminal window
cd fast-livo2-deep-robotics
source /opt/ros/humble/setup.bash
source install/setup.bash
ros2 launch livox_ros_driver2 msg_MID360s_launch.py

For Mid-360, use msg_MID360_launch.py.

Terminal 2 — D435i RGB camera:

Terminal window
ros2 launch realsense2_camera rs_launch.py \
enable_rgbd:=false \
enable_sync:=false \
align_depth.enable:=false \
enable_color:=true \
enable_depth:=false \
rgb_camera.color_profile:=640x480x30

Confirm the launch output shows Width: 640, Height: 480, FPS: 30.

Terminal 3 — FAST-LIVO2 and RViz:

Terminal window
cd fast-livo2-deep-robotics
source /opt/ros/humble/setup.bash
source install/setup.bash
ros2 launch fast_livo mapping_avia.launch.py use_rviz:=True

Note: Keep the sensor components stationary for several seconds after startup to complete IMU initialization. After initialization is complete, slowly move the robot to build the map.

common:
img_topic: "/left_camera/image" # Camera topic
lid_topic: "/livox/lidar" # LiDAR topic
imu_topic: "/livox/imu" # IMU topic
img_en: 1 # Enable camera (0=disable)
lidar_en: 1 # Enable LiDAR (0=disable)
ros_driver_bug_fix: false # ROS driver timestamp fix
preprocess:
point_filter_num: 1 # Point cloud downsampling factor
filter_size_surf: 0.1 # Voxel filter size (meters)
lidar_type: 1 # 1=Livox Avia
scan_line: 6 # Number of scan lines
blind: 0.8 # Blind zone distance (meters)
vio:
max_iterations: 5 # Maximum iterations
outlier_threshold: 1000 # Outlier threshold
img_point_cov: 100 # Image point covariance
patch_size: 8 # Image patch size
patch_pyrimid_level: 4 # Pyramid levels
normal_en: true # Normal constraint
exposure_estimate_en: true # Exposure estimation
lio:
max_iterations: 5 # Maximum iterations
voxel_size: 0.5 # Voxel size (meters)
max_layer: 2 # Maximum voxel layers
max_points_num: 50 # Max points per voxel
imu:
imu_en: true # Enable IMU
imu_int_frame: 30 # IMU initialization frames
acc_cov: 0.5 # Acceleration covariance
gyr_cov: 0.3 # Angular velocity covariance
b_acc_cov: 0.0001 # Acceleration bias covariance
b_gyr_cov: 0.0001 # Angular velocity bias covariance
Terminal window
# Check topic types
ros2 topic type /livox/lidar # Expected: livox_ros_driver2/msg/CustomMsg
ros2 topic type /livox/imu # Expected: sensor_msgs/msg/Imu
ros2 topic type /camera/camera/color/image_raw # Expected: sensor_msgs/msg/Image
# Check data rates
ros2 topic hz /livox/lidar # Expected: ~10 Hz
ros2 topic hz /livox/imu # Expected: ~200 Hz
ros2 topic hz /camera/camera/color/image_raw # Expected: ~30 Hz
Terminal window
ros2 topic hz /cloud_registered # Registered point cloud
ros2 topic hz /aft_mapped_to_init # Odometry
ros2 topic hz /path # Trajectory
Terminal window
ros2 node list
# Expected to include: /livox_lidar_publisher, /camera/camera, /laserMapping
ros2 topic info -v /livox/lidar
# Expected: 1 publisher, /laserMapping as subscriber
IssueTroubleshooting Steps
No point cloud in RVizCheck ros2 node list for all expected nodes
No LiDAR dataConfirm msg_MID360s_launch.py matches the physical device
No camera dataConfirm resolution in camera_pinhole.yaml matches actual settings
IMU initialization failureKeep sensors stationary for several seconds; check IMU topic frequency
Timestamp jumpsCheck for multiple Livox driver instances competing for UDP ports
Driver bind failureConfirm host_ip is assigned to a local interface
Library version conflictCheck ldd output; ensure /usr/local/lib version is loaded
Stops after 20 secondsSwitch to soft-sync branch to enable software time synchronization
pcd_save:
pcd_save_en: true # Enable point cloud saving
colmap_output_en: false # Colmap format output
filter_size_pcd: 0.15 # Voxel filter size on save
interval: -1 # -1 = all frames saved to one file

Save paths: Log/PCD/all_raw_points.pcd and Log/PCD/all_downsampled_points.pcd.

pcd_save:
colmap_output_en: true
interval: -1

Output path: Log/Colmap/sparse/0/points3D.txt.

evo:
seq_name: "CBD_Building_01" # Sequence name
pose_output_en: true # Enable pose output

Output path: Log/result/<seq_name>.txt.

FAST-LIVO2 supports dynamic object detection and removal:

TopicDescription
/dyn_objDetected dynamic object point cloud
/dyn_obj_removedPoint cloud after removing dynamic objects
/dyn_obj_dbg_histDynamic object debug history
uav:
imu_rate_odom: true # Enable high-frequency IMU preintegration odometry

Output topic: /LIVO2/imu_propagate (250Hz).

  1. Hardware Installation Safety: When installing the AGX kit, ensure screws are tightened evenly to avoid loosening.
  2. Cable Management: Ensure all cables are securely fastened to prevent disconnection during robot motion.
  3. Thermal Management: The AGX Jetson Orin generates significant heat under heavy load; ensure adequate cooling.
  4. Network Isolation: Set ipv4.never-default yes for the LiDAR Ethernet connection to avoid disrupting normal networking.
  5. Sensor Protection: LiDAR and camera are precision optical devices; avoid collisions and direct exposure to intense light.
  6. Software Synchronization Limitations: Software time synchronization is an approximate solution; sharp turns may cause camera-LiDAR frame misalignment, and accumulated drift in large environments may exceed the filter’s correction capability.