这个项目的初衷是想要在只有ros2的数据环境下运行ros1的一些算法,在想要怎么把这两者合理的通信起来。
对于大型的数据来说(相机,雷达数据),肯定是没办法直接通过话题进行传递的,所以我们需要一个ros1和ros2的沟通的方式,通过查找资料发现,确实是有做这个事的相关的功能包,但是它的局限在于只能满足一些标准的ros消息。
ros1_bridge功能包
比如使用 ros1_bridge,
- 前置条件
你的机器上必须同时安装了 ROS 1(如 Noetic)和 ROS 2(如 Foxy, Humble 等)。
两个版本的 ROS 必须包含相同的消息定义(例如 std_msgs, sensor_msgs 等)。
这个bridge是运行在ros2上的:
sudo apt install ros-<ros2-distro>-ros1-bridge
顺序是:
# 1. 先加载 ROS 1 环境
source /opt/ros/noetic/setup.bash
# 2. 再加载 ROS 2 环境
source /opt/ros/<ros2-distro>/setup.bash
# 3. 检查环境变量(可选,确保 ROS_MASTER_URI 正确)
echo $ROS_MASTER_URI
# 4. 运行动态桥接(Dynamic Bridge)
ros2 run ros1_bridge dynamic_bridge
这样就能把所有的标准消息都桥接起来了。总结来说,就是运行在ros2上:
它是双向的(Bidirectional)
它不仅仅把 ROS 2 转给 ROS 1,它能同时处理两个方向的数据流:
ROS 1 -> ROS 2:如果有 ROS 1 的节点在发数据,ROS 2 的节点在听,Bridge 会把 ROS 1 的消息“搬运”过去。
ROS 2 -> ROS 1:如果有 ROS 2 的节点在发数据,ROS 1 的节点在听,Bridge 也会把 ROS 2 的消息“搬运”回来。
注意:
这里的重点误区(坑点)
误区: “我只要运行了 bridge,所有 ROS 1 的话题就会出现在 ROS 2 里。” 真相: 不是的。 默认情况下(使用 dynamic_bridge),它是**懒惰(Lazy)**的。
只有当 ROS 2 这一侧真的有一个节点在订阅(监听) /camera 话题时,Bridge 才会去 ROS 1 那边把 /camera 搬运过来。
如果 ROS 2 那边没人听,Bridge 就不会通过网络搬运数据,以节省带宽。
身份: 它是一个特殊的 ROS 2 节点,但体内植入了 ROS 1 的基因。
方向: 双向互通。
前提: 必须在同一个 Shell 里让它同时看见两个版本的 ROS 环境变量。
你别说,这个节点还是挺有意思的。
共享内存空间
第二种方法,就是共享内存的形式了,共享内存又分为文件形式和虚拟形式。这里需要注意,真正的共享内存不是文件形式,而是虚拟形式的。
我这里直接先说我的案例:
我需要的是把ros1的slam算法,用ros2的数据跑起来,所以需要将ros2的topic写道共享内存中,然后在ros1中读取共享内存的数据,发布ros1的topic,最后再把这个数据接入到lio_sam的算法中实现闭环。
这里把相应的功能封装到了ros2_to_ros1_ws中:

包含ros1和ros2的两个环境,各自的ros环境进行编译,以下是采集数据过程的一些说明:
通信之间通过共享内存实现消息的共享,从而实现了:
ros1的算法,ros2的数据。
ros2的数据采集
首先我们通过ros2的节点将需要的topic转为共享内存空间.
包括:
1.激光雷达
2.imu
3.gps
4.tf
至于共享内存的两种模式,还没有特别注意…现在也还是基本可以使用.
ros1读取共享内存并发布话题
接着在ros1中接入了ros2的相关话题,得到最终的ros1的话题消息:
1.激光雷达
2.imu
3.gps
4.tf
启动顺序:
首先启动ros2的共享内存节点
ros2 launch data_bridge_ros2 data_bridge.launch.py
接着可以启动ros1读取共享内存的节点
roslaunch data_bridge_ros1 data_bridge.launch
然后运行ros1的算法
roslaunch lio_sam run.launch
此时可以记录一些数据,比如
rosbag record /lio_sam/mapping/odometry /gps_imu_data
然后再播放bag:
ros2 bag play rosbag2_2025_10_11-09_57_36_0.db3
slam建图的轨迹比较
由于项目本身是为了slam而做的,这里遇到的一个非常重要的问题就在于定位建图需要比较slam的轨迹和gps的轨迹的error值。

比如我这里是需要记录gps_imu_data数据以及/lio_sam/mapping/odometry,实时记录这两条轨迹,至于怎么比较呢?
这个我通过一个py来读取bag数据来重新进行融合.
evaluate_trajectory.py:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import rosbag
import numpy as np
import math
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D # 引入3D绘图工具
from scipy.spatial.transform import Rotation as R
def read_bag_data(bag_path, gps_topic, slam_topic):
"""
读取bag文件,提取GPS和SLAM的 (time, x, y, z) 数据
"""
gps_data = []
slam_data = []
print(f"开始读取 Bag 文件: {bag_path} ...")
try:
bag = rosbag.Bag(bag_path)
except Exception as e:
print(f"无法打开bag文件: {e}")
return None, None
for topic, msg, t in bag.read_messages(topics=[gps_topic, slam_topic]):
if topic == gps_topic:
# 提取自定义 GPS 消息
timestamp = msg.header.stamp.to_sec()
# 使用用户指定的 x, y, z 字段
x = msg.x
y = msg.y
z = msg.z
gps_data.append([timestamp, x, y, z])
elif topic == slam_topic:
# 提取 nav_msgs/Odometry
timestamp = msg.header.stamp.to_sec()
pos = msg.pose.pose.position
slam_data.append([timestamp, pos.x, pos.y, pos.z])
bag.close()
# 转换为 Numpy 数组并按时间排序
gps_data = np.array(gps_data)
slam_data = np.array(slam_data)
# 按时间戳排序
if len(gps_data) > 0:
gps_data = gps_data[gps_data[:, 0].argsort()]
if len(slam_data) > 0:
slam_data = slam_data[slam_data[:, 0].argsort()]
print(f"读取完成。GPS点数: {len(gps_data)}, SLAM点数: {len(slam_data)}")
return gps_data, slam_data
def synchronize_trajectories(ref_data, target_data):
"""
时间同步
"""
start_time = max(ref_data[0, 0], target_data[0, 0])
end_time = min(ref_data[-1, 0], target_data[-1, 0])
ref_mask = (ref_data[:, 0] >= start_time) & (ref_data[:, 0] <= end_time)
ref_sync = ref_data[ref_mask]
if len(ref_sync) == 0:
print("错误:两个轨迹没有时间重叠!")
return None, None
query_times = ref_sync[:, 0]
target_interp_x = np.interp(query_times, target_data[:, 0], target_data[:, 1])
target_interp_y = np.interp(query_times, target_data[:, 0], target_data[:, 2])
target_interp_z = np.interp(query_times, target_data[:, 0], target_data[:, 3])
target_sync = np.column_stack((query_times, target_interp_x, target_interp_y, target_interp_z))
return ref_sync, target_sync
def compute_rigid_transform(source_points, target_points):
"""
SVD 计算 rigid transform
"""
centroid_source = np.mean(source_points, axis=0)
centroid_target = np.mean(target_points, axis=0)
source_centered = source_points - centroid_source
target_centered = target_points - centroid_target
H = np.dot(source_centered.T, target_centered)
U, S, Vt = np.linalg.svd(H)
R_matrix = np.dot(Vt.T, U.T)
if np.linalg.det(R_matrix) < 0:
Vt[-1, :] *= -1
R_matrix = np.dot(Vt.T, U.T)
t_vec = centroid_target.T - np.dot(R_matrix, centroid_source.T)
return R_matrix, t_vec
def calculate_error(slam_xyz, gps_aligned_xyz):
errors = np.linalg.norm(slam_xyz - gps_aligned_xyz, axis=1)
rmse = np.sqrt(np.mean(errors**2))
mean_error = np.mean(errors)
max_error = np.max(errors)
return errors, rmse, mean_error, max_error
def main():
# ================= 配置区域 =================
bag_file = '2025-11-27-16-44-35.bag' # 请修改为你的bag文件名
gps_topic = '/gps_imu_data'
slam_topic = '/lio_sam/mapping/odometry'
# ===========================================
# 1. 读取数据
gps_raw, slam_raw = read_bag_data(bag_file, gps_topic, slam_topic)
if gps_raw is None or len(gps_raw) < 10 or len(slam_raw) < 10:
print("数据不足,无法计算。")
return
# 2. 同步数据
slam_sync, gps_sync = synchronize_trajectories(slam_raw, gps_raw)
if slam_sync is None:
return
slam_xyz = slam_sync[:, 1:4]
gps_xyz = gps_sync[:, 1:4]
# 3. 计算变换矩阵 (GPS -> SLAM)
# T_gps_to_slam: 把世界坐标(UTM)转到局部坐标(SLAM)
R_mat, t_vec = compute_rigid_transform(gps_xyz, slam_xyz)
# 组装 4x4 变换矩阵 T
T = np.eye(4)
T[:3, :3] = R_mat
T[:3, 3] = t_vec
print("\n" + "="*30)
print("矩阵 A: GPS (UTM) -> SLAM 的变换矩阵:")
print("(用于将 GPS 坐标拉回局部坐标系进行误差对比)")
print("="*30)
np.set_printoptions(suppress=True, precision=6)
print(T)
# 也可以打印一下旋转的欧拉角,方便直观理解
r = R.from_matrix(R_mat)
print(f"\n旋转 (欧拉角 xyz, degrees): {r.as_euler('xyz', degrees=True)}")
print(f"平移 (x, y, z): {t_vec}")
# 4. 应用变换计算误差
gps_aligned = np.dot(gps_xyz, R_mat.T) + t_vec
errors, rmse, mean_err, max_err = calculate_error(slam_xyz, gps_aligned)
print("\n" + "="*30)
print("定位偏差评估结果 (米):")
print("="*30)
print(f"RMSE (均方根误差): {rmse:.4f} m")
print(f"Mean Error (平均误差): {mean_err:.4f} m")
print(f"Max Error (最大误差): {max_err:.4f} m")
# =========================================================
# 新增部分:计算逆矩阵 (SLAM -> GPS) 并输出
# =========================================================
print("\n" + "="*30)
print("矩阵 B (逆矩阵): SLAM -> GPS (UTM) 的变换参数:")
print("(即 SLAM 原点在地图上的实际坐标和朝向)")
print("==============================")
# 计算逆矩阵
T_inv = np.linalg.inv(T)
# 提取平移
x_final = T_inv[0, 3]
y_final = T_inv[1, 3]
z_final = T_inv[2, 3]
# 提取旋转并转为欧拉角
R_inv = T_inv[:3, :3]
r_inv_obj = R.from_matrix(R_inv)
# 使用 'xyz' 顺序输出 roll, pitch, yaw
euler_inv = r_inv_obj.as_euler('xyz', degrees=True)
print(f"x: {x_final:.10f}")
print(f"y: {y_final:.10f}")
print(f"z: {z_final:.10f}")
print(f"roll: {euler_inv[0]:.10f}")
print(f"pitch: {euler_inv[1]:.10f}")
print(f"yaw: {euler_inv[2]:.10f}")
print("==============================\n")
# 6. 绘图结果
fig = plt.figure(figsize=(14, 7))
# 轨迹对比图 (3D)
ax1 = fig.add_subplot(121, projection='3d')
ax1.plot(slam_xyz[:, 0], slam_xyz[:, 1], slam_xyz[:, 2],
'b-', label='Lidar SLAM', alpha=0.7, linewidth=1)
ax1.plot(gps_aligned[:, 0], gps_aligned[:, 1], gps_aligned[:, 2],
'r--', label='Aligned GPS', alpha=0.7, linewidth=1)
ax1.set_title('Trajectory Comparison (3D View)')
ax1.set_xlabel('X (m)')
ax1.set_ylabel('Y (m)')
ax1.set_zlabel('Z (m)')
ax1.legend()
# 误差随时间变化图
ax2 = fig.add_subplot(122)
time_axis = slam_sync[:, 0] - slam_sync[0, 0]
ax2.plot(time_axis, errors, 'g-', label='3D Error', linewidth=1)
ax2.set_title(f'Localization Error over Time (RMSE={rmse:.3f}m)')
ax2.set_xlabel('Time (s)')
ax2.set_ylabel('Error (m)')
ax2.grid(True)
plt.tight_layout()
plt.show()
if __name__ == "__main__":
main()
这个bag包的两个话题数据对应的消息内容为:
cyun@cyun:~$ rostopic info /gps_imu_data
Type: car_interfaces/GpsImuInterface
Publishers:
* /play_1764251971042717169 (http://cyun:36305/)
Subscribers: None
---
nav_flag: "2"
x_acc: 0.10093999654054642
y_acc: -0.06272000074386597
x_gyro: 0.009999999776482582
y_gyro: -0.10000000149011612
z_acc: 9.97052001953125
z_gyro: 0.2800000011920929
acc: 9.971228216286516
gps_week: 2387.0
gps_ms: 525521959.6250057
nav_flag_g: "2"
header:
seq: 0
stamp:
secs: 1760147921
nsecs: 959624960
frame_id: "gps_link"
gps_time: 1760147921959624960
PosLon: 117.29809985
PosLat: 38.99651669
PosAlt: -3.32
lat_g: 38.99651669
lon_g: 117.29809985
east_speed_g: 0.39500001072883606
north_speed_g: -2.260999917984009
up_speed_g: -0.0020000000949949026
alt_g: -3.32
heading_g: -80.44999694824219
VelE: 0.39500001072883606
VelN: -2.260999917984009
VelU: -0.0020000000949949026
Vel: 2.2952450060069522
AngleHeading: -80.44999694824219
AnglePitch: 0.019999999552965164
AngleRoll: 0.07000000029802322
system_state: 2
GpsNumSatsUsed: 12
satellite_status: 0
GpsAge: 0.1
state: True
posX: 525814.2787988522
posY: 4316432.294969826
posZ: -3.32
process_time: 3.8623809814453125e-05
Gps_Cfault: False
x: 525814.2787988522
y: 4316432.294969826
z: -3.32
pitch: 0.019999999552965164
roll: 0.07000000029802322
yaw: -80.44999694824219
vel: 2.2952450060069522
lat: 38.99651669
lon: 117.29809985
state_ndt: True
---
cyun@cyun:~/Documents/backup_9.22/9.22/ros2_to_ros1_ws/ros1$ rostopic info /lio_sam/mapping/odometry
Type: nav_msgs/Odometry
Publishers:
* /play_1764251971042717169 (http://cyun:36305/)
Subscribers: None
---
header:
seq: 436
stamp:
secs: 1760147957
nsecs: 679423332
frame_id: "odom"
child_frame_id: "odom_mapping"
pose:
pose:
position:
x: 141.18804931640625
y: 40.92380905151367
z: 11.70459270477295
orientation:
x: -0.018105755638900916
y: -0.019853375077504946
z: 0.24379445744093267
w: 0.9694546341277954
covariance: [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
twist:
twist:
linear:
x: 0.0
y: 0.0
z: 0.0
angular:
x: 0.0
y: 0.0
z: 0.0
covariance: [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
---
为了比较轨迹,是需要用到时间戳的,在同一时间下来比较两条轨迹之间的不同。
运行py之后即可看到两条轨迹之间的偏差:

同时得到了两个矩阵:
==============================
矩阵 A: GPS (UTM) -> SLAM 的变换矩阵:
(用于将 GPS 坐标拉回局部坐标系进行误差对比)
==============================
[[ -0.105942 -0.992226 -0.065304 4338655.191419]
[ 0.993562 -0.102975 -0.047236 -77929.614784]
[ 0.040145 -0.069887 0.996747 280564.759149]
[ 0. 0. 0. 1. ]]
旋转 (欧拉角 xyz, degrees): [-4.010763 -2.30073 96.08635 ]
平移 (x, y, z): [4338655.191419 -77929.614784 280564.759149]
==============================
定位偏差评估结果 (米):
==============================
RMSE (均方根误差): 2.8448 m
Mean Error (平均误差): 2.7005 m
Max Error (最大误差): 5.5193 m
==============================
矩阵 B (逆矩阵): SLAM -> GPS (UTM) 的变换参数:
(即 SLAM 原点在地图上的实际坐标和朝向)
==============================
x: 525809.2958430700
y: 4316508.2026283247
z: -3.2533143504
roll: -2.7132511843
pitch: 3.7442862653
yaw: -96.0944834560
==============================
两种矩阵的含义:
这两种矩阵分别是解决什么问题,请清楚的说明一下。比如已知gps坐标,转到slam系下之类的具体的例子
这两种矩阵(我们暂时称为 矩阵 A 和 矩阵 B)互为逆矩阵。清楚地理解它们对于自动驾驶和机器人开发至关重要。
以下是具体的场景说明:
- 矩阵 A:GPS (UTM) → SLAM
(世界坐标系 → 局部坐标系)
这是代码中 compute_rigid_transform 直接计算出来的矩阵。
输入:一个巨大的 GPS/UTM 坐标(例如:x=525844.5, y=4316346.1)。
输出:一个很小的 SLAM 局部坐标(例如:x=5.0, y=2.0)。
数学特征:平移向量 t 通常非常巨大且为负值(或者经过旋转后变大),目的是把几百万的坐标值“减去”,拉回到 0 附近。
“我知道地球上的经纬度,我想知道它在我当前雷达地图里的哪个位置。”
- 矩阵 B:SLAM → GPS (UTM)
(局部坐标系 → 世界坐标系)
这是矩阵 A 的逆矩阵(np.linalg.inv(T))。
输入:一个 SLAM 局部坐标(例如:x=0, y=0,即建图原点)。
输出:一个 GPS/UTM 坐标(例如:x=525844.6, y=4316346.1)。
数学特征:平移向量 t 就是 SLAM 原点在地球上的实际坐标。旋转部分就是 车辆起始的朝向(方位角)。
注意这两个矩阵是完全不同的,所以我们平时会看到两种不同的变换,不要感到奇怪。

被折叠的 条评论
为什么被折叠?



