里程计轨迹记录器(odom_trace_recorder)

把「车开过的这一段」记录下来:订阅 /odom 边收边记,到点写成 CSV,再回放成 nav_msgs/Path 并与一条参考圆弧逐点比对,最后打印误差直方图与四条断言。

不依赖 CARLA、不需要显示器、不需要任何 pip 包,只要有 rospy 就能跑。

为什么不是「算一次就打印」

仓库里已经有不少「读参数 → 算一次 → 打印报告」的校验模块,它们的输入是参数, 输出是结论。这个包不一样:

一次算完的校验模块 本模块
输入 参数 /odom 话题(时间序列)
状态 无 边收边累积采样点
磁盘 不写文件 轨迹落盘成 CSV,可读回来复算
输出 报告文本 报告 + nav_msgs/Path 回放 + CSV
判定依据 参数之间的自洽 记录下来的轨迹 vs 参考轨迹

所以它验证的是「记录这件事本身有没有丢掉信息」:点数够不够、里程对不对、 插值往返是否稳定、与参考轨迹差多少。

目录结构

odom_trace_recorder/
├── CMakeLists.txt
├── package.xml
├── README.md
├── config/
│   └── trace_recorder.yaml      # 13 个默认参数
├── launch/
│   └── main.launch              # 13 个带 doc 的 arg
└── scripts/
    ├── main.py                  # 入口
    ├── trace_core.py            # 纯逻辑:采样点/轨迹/重采样/比对/直方图
    └── trace_node.py            # ROS 收发:订阅 /odom,发布 /trace/replay 与 /trace/report

运行

# 默认:记录 8 s,内置模拟车辆按半径 6 m、速度 2 m/s 画一段圆弧
roslaunch odom_trace_recorder main.launch

# 改参考半径,让「误差在限」失败(验证比对环节真的在工作)
roslaunch odom_trace_recorder main.launch reference_radius:=4.0

# 要求 500 个采样点,让「采样完整」失败
roslaunch odom_trace_recorder main.launch min_samples:=500

# 只订阅外部 /odom(例如接 carla_ros_bridge 或自己 rostopic pub)
roslaunch odom_trace_recorder main.launch use_mock:=false

节点会自己结算并退出,不需要手动 Ctrl+C。

真实输出(默认参数)

记录:161 个点,时长 8.00 s,里程 16.000 m
起点:(0.000, 0.000),终点:(2.649, 11.359)
包围盒:x ∈ [0.000, 5.950],y ∈ [0.000, 11.359]
落盘:/tmp/trace.csv(161 行数据)
参考:mock_r6.00(161 个点,16.000 m)
比对:最大误差 0.0972 m,平均误差 0.0573 m,最差点第 161 个
断言:4 通过 / 0 失败(共 4 条)
  [PASS] 采样完整 记录 161 个点,要求 >= 100
  [PASS] 里程合理 里程 16.000 m,参考 16.000 m(容差 0.500 m)
  [PASS] 回放一致 加密 2 倍再回采样,往返偏差 0.0002 m(阈值 0.200 m)
  [PASS] 误差在限 最大误差 0.0972 m(第 161 点,2.65, 11.36),阈值 0.200 m
结论:PASSED

误差直方图(单位 mm):
[ 0.000, 12.149) ################                         15
[12.149, 24.299) ################                         15
[24.299, 36.448) ################                         15
[36.448, 48.597) #################                        16
[48.597, 60.746) ###################                      18
[60.746, 72.896) #####################                    20
[72.896, 85.045) #########################                24
[85.045, 97.194) ######################################## 38

误差不是 0 是刻意的:内置车辆用定步长积分开圆弧(integration_trace), 参考轨迹用解析公式(mock_trace),两者的差就是离散化误差,随走过的弧长 单调增长——直方图的右端偏重正是这个原因。

四条断言

断言 判什么 失败时的常见原因
采样完整 采样点数 ≥ check/min_samples 记录时长太短、采样率太低、外部 /odom 频率不稳
里程合理 记录里程与参考里程之差 ≤ check/length_tolerance /odom 数据被丢弃、时间戳乱序
回放一致 加密 2 倍再回采样的往返偏差 ≤ check/max_error 重采样插值实现有误
误差在限 与参考轨迹逐点比对的最大误差 ≤ check/max_error 参考半径与实际行驶半径不符

参数

参数 默认 说明
record/seconds 8.0 记录时长(秒),到点自动结算
record/rate 20.0 采样率(Hz),也是内置车辆发 /odom 的频率
mock/enabled true true 时节点自己发 /odom,false 时只订阅
mock/radius 6.0 内置车辆转弯半径(m)
mock/speed 2.0 内置车辆线速度(m/s)
check/min_samples 100 采样点下限
check/max_error 0.20 逐点误差上限(m)
check/length_tolerance 0.50 里程相对偏差(m)
check/reference_radius 6.0 参考圆弧半径(m)
trace/csv_path /tmp/trace.csv CSV 落盘路径,目录不存在会自动创建
topic/odom /odom 里程计话题
topic/replay /trace/replay 回放用的 nav_msgs/Path
topic/report /trace/report 报告文本(latched)

话题

方向 话题 类型 说明
订阅 /odom nav_msgs/Odometry 只用 pose.pose 的位置与朝向
发布 /trace/replay nav_msgs/Path 记录的轨迹,latched,结算后发布一次
发布 /trace/report std_msgs/String 报告全文,latched
发布 /odom nav_msgs/Odometry 仅 mock/enabled:=true 时,由内置车辆发出

离线回归测试

python3 tests/test_odom_trace_recorder_local.py

覆盖采样点校验、CSV 往返、解析真值与积分真值的差异、重采样、比对、四条断言、 直方图、参数读取与工程文件约定;不需要 ROS、不需要安装任何包。

与仓库里其他模块的分工

模块 职责
twist_to_control 把 /cmd_vel 换成转角与轮速(控制量换算)
turning_sweep_check 转弯时车身占多宽(横向包络)
braking_distance_check 从看见到停住要几米(纵向距离)
speed_profile_check 这一段路该怎么开(时间-速度曲线)
odom_trace_recorder 车实际开过的轨迹记下来、存下来、放回去、比出来

常见问题

Q:为什么 CSV 写在 /tmp? /tmp 所有用户可写,避免仓库里出现运行产物;要换位置就传 csv_path:=/home/xxx/trace.csv。

Q:use_mock:=false 时没有任何输出? 说明 /odom 没数据。先 rostopic hz /odom 确认频率,再检查 topic/odom 是否改过名。

Q:Melodic 上打印中文日志报 UnicodeEncodeError? 节点里的 _log() 已经把消息统一转成 byte str,这是 Melodic(Python 2.7)上给 rosgraph 打非 ASCII 日志的唯一安全写法。自己改代码时不要直接 rospy.loginfo(unicode)。

Q:能和真正的 CARLA 一起用吗? 可以,use_mock:=false 后把 topic/odom 指向 carla_ros_bridge 发布的里程计即可; 本包只要求 /odom 是 nav_msgs/Odometry。

参考

大模型声明

本模块的代码、测试与文档在编写与整理过程中使用了大模型辅助,提交内容由本人负责。