Oxford Radar RobotCar数据集实战:毫米波雷达与多传感器融合定位

发布时间:2026/10/4 12:54:59
Oxford Radar RobotCar数据集实战:毫米波雷达与多传感器融合定位 1. 先聊聊这个数据集是什么来头做自动驾驶感知研究的人几乎都绕不开开源数据集这道坎。KITTI、nuScenes、Waymo Open Dataset这些大家都很熟了但如果你的研究方向正好是毫米波雷达或者是恶劣天气下的多传感器融合那Oxford Radar RobotCar Dataset几乎是一门必修课。这个数据集来自牛津大学机器人研究所搭载在一台名为RobotCar的自动驾驶测试车上。它最大的特点是包含了一套Navtech的CTS350-X毫米波雷达系统同时还有激光雷达、双目相机、GPS/IMU等多类传感器同步采集的数据。车辆沿着牛津市中心一条约10公里的固定路线反复行驶采集周期横跨一年多覆盖了晴天、雨天、雾天、雪天、黑夜等几乎你能想到的各种天气和光照条件。我当时接手这个数据集的时候第一感受是资料真全但坑也真多。官方文档写得比较简略很多细节要靠翻论文、看issue、甚至直接翻采集脚本源码才能弄明白。这篇笔记就是把我从下载数据到完成雷达里程计实验的完整过程梳理一遍适合刚开始接触这个数据集、或者准备拿它做雷达感知、融合定位方向研究的朋友。先说结论这个数据集最大的价值不是“多一个传感器”那么简单而是它同时提供了完整的多圈采集序列让“跨季节、跨时间的同一路线反复通行”成为可能。这一点对长期自动驾驶地图更新、生命周期定位这类问题的研究来说是其他数据集很难替代的。2. 传感器方案与数据设计思路拆解2.1 为什么这个数据集的传感器搭配值得研究先看这张传感器布局的总体情况。RobotCar车顶上装了一个旋转式毫米波雷达型号是Navtech CTS350-X工作在76-77GHz频段属于车载毫米波雷达的典型频段。它通过机械旋转完成360度扫描每帧扫描产生一圈距离-角度数据输出的是原始雷达回波强度而不是经过检测的目标点列表。这个设计思路很关键。很多商用毫米波雷达输出的已经是处理后的目标点比如位置、速度、RCS这些虽然用起来方便但原始信息丢了很多算法没法做。CTS350-X输出的是原始FFT谱数据相当于直接给了你“未经过检波”的雷达视图你可以自己决定用CFAR检测、直接做雷达里程计还是把它当成类似“雷达图像”来做特征匹配。除了毫米波雷达车上还装了两个SICK LMS-151二维激光雷达左右各一个水平扫描用于近距离环境感知两个Point Grey Bumblebee XB3双目相机分别朝前和朝后提供立体视觉一个NovAtel SPAN-CPT GPS/INS组合导航系统提供高精度位姿基准一个Velodyne HDL-32E激光雷达虽然部分序列启用但官方数据发布以二维雷达为主这套多传感器组合加上“同一路线多次采集”的设计构成了一个极具研究价值的数据闭环。既可以用GPS/INS做真值来评估算法精度也可以完全不依赖GPS只靠雷达做纯几何定位。2.2 采集路线的设计逻辑这条路线很有意思从牛津城的市中心出发经过高街、Magdalen桥、Plain、Cowley Road一带绕一圈再回起点。路线大约10公里途经的场景类型非常丰富有狭窄的街道、开阔的路口、桥梁、河岸、商业区、居民区还有树木和建筑遮挡比较严重的路段。为什么说路线设计得好因为不同场景对雷达的挑战完全不同。开阔路段雷达回波稀疏特征少做里程计容易飘城市场景虽然回波丰富但大量金属护栏、路灯杆、停放的车辆会造成极强的多径效应和镜面反射。同一路线反复跑又能让你在“相同位置不同时间”的条件下评估算法的跨环境稳定性。数据按采集日期组织每次采集就是一个序列。官方一共发布了280多次有效采集时间跨度从2017年1月到2019年2月左右。我下载时看了一遍目录光天气状况就分了overcast、sun、rain、snow、night、dusk等多种标签后面做实验时筛选数据非常方便。2.3 数据量估算与存储方案这个数据集的总量不算小但也不夸张。每圈约10公里以车速20-30km/h计算采集时长大约20-30分钟。雷达是4Hz扫描频率一圈下来大约5000-7000帧雷达数据。如果下载完整的所有序列总体积在几TB量级。我实际下载了大约30个序列包括晴雨昼夜各若干次总数据量在1.5TB左右。存储方面建议用4TB或者更大容量的机械硬盘固态盘虽然读取快但大容量价格偏高。另外下载时尽量用有线网络我当时用Wi-Fi下到一半断了几次后来改用脚本断点续传才稳定。注意完整数据集的体积非常大如果你是初学者先别急着全部下载。官方支持按序列单独下载建议先挑2-3个典型序列一个白天晴天、一个夜晚、一个雨天跑通整个流程再按需扩充数据。3. 数据格式与坐标系解析3.1 目录结构与文件组织方式下载下来的序列目录结构大致是这样的2019-01-10-11-46-21/ ├── gps/ ├── ladar/ ├── lidar/ ├── radar/ ├── stereo/ ├── stereo_centre/ ├── mono_rear/ ├── mono_front/ ├── mono_left/ ├── mono_right/ └── navtech/每个子目录里其实都是一堆PNG文件。没错图像类传感器是PNG雷达也是PNG。这是我最初觉得最“反直觉”的地方64线激光雷达点云以距离图像的形式保存为PNG毫米波雷达的FFT谱也是PNG。radar目录下是两套数据radar/里面是原始的极坐标FFT强度图文件格式为PNGnavtech/里是官方重新整理后的数据用Navtech的插值方法把极坐标数据转到笛卡尔坐标系后的结果navtech目录下的文件名长这样00000.png 00001.png ...每个PNG对应一帧雷达扫描分辨率固定为3776x1394左右。为了验证这个尺寸我专门用Python读取过几张图的shape确认是uint16的数组存储。3.2 radar与navtech的区别这两个目录特别容易搞混我刚开始的时候就踩过坑。简单说radar目录原始输出极坐标形式行是距离维列是方位角维直接反映Navtech雷达的原始数据布局navtech目录经过插值转到笛卡尔坐标类似一张“从上往下看”的雷达反射强度图便于可视化navtech还额外提供了一些配置文件比如每帧的时间戳和传感器位姿navtech里的数据可以直接用OpenCV读进来然后显示成灰度图基本就是一张鸟瞰图街道、建筑轮廓、车辆反射看得清清楚楚。如果直接用radar原始数据得先搞清楚极坐标到笛卡尔坐标的映射关系否则显示出来是变形的。官方还给了一个RadarReader工具类用于读取并还原雷达数据我后面在实操部分会展开。3.3 坐标系与位姿真值这个数据集在坐标系处理上做得比较规范也是让我觉得“可以放心用”的原因。GPS/INS的输出在gps目录下格式是ins.csv每行一个时间戳和对应的6自由度位姿坐标系基于UTM投影的东北天坐标系真值位姿文件叫gps/ins.csv时间戳用Unix时间戳表示同步到UTC每个传感器都有自己的时间戳文件用.timestamps或同名PNG附带的时间戳记录官方在文档里给出了不同传感器之间的外参矩阵但实测下来发现如果直接拿外参用激光雷达点云和相机图像重投影对不齐原因是标定文件中有少量条目日期不对应。建议优先使用radar和ins.csv做定位实验因为坐标系定义最清晰雷达和GPS真值的时间同步最可靠。如果做多传感器融合需要把点云从雷达坐标系转到车体坐标系再转到GPS/IMU坐标系转换矩阵在extrinsics目录里有定义。有些矩阵用的是齐次变换的逆矩阵形式注意方向别直接用。4. 毫米波雷达数据的核心实操4.1 雷达数据是如何产生的Navtech CTS350-X是一款调频连续波雷达。它通过发射频率随时间线性变化的信号接收目标反射并与发射信号混频得到差频信号。差频的频率正比于目标距离相位变化正比于目标径向速度。对差频信号做FFT就能得到距离-多普勒图。CTS350-X输出的每帧数据是一个二维数组一维是方位角另一维是距离。数据值是IQ解调后的幅度代表该位置处的后向散射能量。因为雷达波长为4mm左右比激光雷达的905nm大得多所以它对小目标的敏感度不如激光雷达但对雨雾烟尘的穿透性远强于激光雷达。这就是为什么Oxford选择毫米波雷达作为“全天候”主力传感器的原因。在雨雪天气里激光雷达点云往往被水滴和雪花严重污染而毫米波雷达仍然能给出稳定的环境轮廓。放在多传感器融合里正好能弥补激光雷达和视觉在恶劣天气下的短板。4.2 用Python读取雷达PNG图读取雷达数据的第一步是用正确的方式读PNG。因为雷达PNG是16位灰度图直接cv2.imread读出来会丢失精度。正确做法是用-1标志读入cv2.imread(path, -1)。也可以用PILfrom PIL import Image import numpy as np img Image.open(radar/00000.png) arr np.array(img) print(arr.shape, arr.dtype)实测输出是(3776, 1394)dtype为uint16。这个数组的行对应距离单元列对应方位角。值的大小代表回波强度单位可以理解为ADC采样的量化结果没有绝对物理意义但在同一传感器、同一配置下横向可比。需要说明的是3776行对应的是约163米的距离范围每行的距离分辨率大约4.3厘米。1394列对应360度的方位角分辨率约0.258度。这些都是由CTS350-X的配置决定的官方在数据集文档的yaml文件里有详细参数。4.3 极坐标转笛卡尔坐标拿到原始极坐标数据以后要看得懂最好还是转成笛卡尔图。Navtech自己提供了一种插值方案官方代码中有一个interpolate_bscan函数逻辑大致是构造一个目标尺寸的笛卡尔网格对每个网格点计算对应的距离和方位角值再从极坐标数据中采样。我自己写了一个简化版本import numpy as np from numpy import sin, cos, deg2rad def polar_to_cartesian(scan, max_range163, angle_res0.258): num_ranges, num_azimuths scan.shape ranges np.linspace(0, max_range, num_ranges) azimuths deg2rad(np.arange(num_azimuths) * angle_res) # 生成目标网格 grid_size 800 xs np.linspace(-max_range, max_range, grid_size) ys np.linspace(-max_range, max_range, grid_size) cart np.zeros((grid_size, grid_size), dtypenp.float32) for i, y in enumerate(ys): for j, x in enumerate(xs): r np.sqrt(x*x y*y) theta np.arctan2(y, x) if r max_range: continue r_idx int(r / max_range * (num_ranges - 1)) theta_deg np.rad2deg(theta) % 360 a_idx int(theta_deg / angle_res) cart[i, j] scan[r_idx, a_idx % num_azimuths] return cart这个版本双重循环效率低只适合理解原理实际用的时候要向量化或者用scipy的map_coordinates插值。我在工程里用的版本是利用广播一次性生成所有网格点的距离和角度再用np.interp采样速度能提升几十倍。提示转换后会有距离越远、数据越稀疏的现象这是极坐标采样的固有特性不是bug。做特征提取时要注意远处区域的有效性。4.4 雷达帧的CFAR目标检测拿到笛卡尔图之后下一步通常是从背景中检测目标。传统雷达信号处理里最常用的就是恒虚警率检测。它的思路是根据周围单元的噪声水平自适应地设置检测阈值避免直接用固定阈值导致近处噪声多、远处漏检。CFAR在Python里实现并不复杂。一种常用方式是滑窗遍历每个距离单元取两侧若干保护单元之外的参考单元求均值再乘以一个系数作为阈值import numpy as np def cfar_1d(signal, guard8, ref16, rate8.0): out np.zeros_like(signal, dtypebool) n len(signal) for i in range(n): start_guard max(0, i - guard) end_guard min(n, i guard 1) ref_left signal[max(0, start_guard - ref):start_guard] ref_right signal[end_guard:min(n, end_guard ref)] if len(ref_left) 0 and len(ref_right) 0: continue noise np.concatenate([ref_left, ref_right]) threshold np.mean(noise) * rate out[i] signal[i] threshold return out把每一列方位角的一维信号送进去做CFAR再把二维检测结果重投影回笛卡尔图就能得到目标点的距离-角度信息。再做DBSCAN聚类就可以提取出稳定的雷达目标列表类似商用毫米波雷达输出的目标点。我实验用的参数是保护单元8个参考单元16个系数为8。这个参数在Oxford数据集上效果比较稳。如果雨天数据噪声大可以把系数提到10-12避免大量虚警。4.5 直接拿雷达图做里程计雷达里程计是很多研究者拿到这个数据集之后第一个想做的方向。思路也很直接上下两帧雷达图之间存在一个刚性变换旋转平移如果能估计出这个变换就能递推得到车辆的运动轨迹。最经典的做法是ICP把上一帧雷达图中的高反射点作为源点云当前帧的高反射点作为目标点云迭代最近点匹配输出相对位姿变化。但直接上标准ICP容易陷入局部最优因为雷达点云中大量点是静态环境反射也存在一部分动态车辆的反射点。我实验时用的流程是对每帧雷达图做CFAR检测得到候选目标点用DBSCAN聚类去掉离散噪声保留可靠目标对相邻帧的检测点做快速全局配准得到初始变换用点对面ICP精配准输出最终相对位姿在晴天序列上这个流程跑了10公里平均位置误差大约4%也就是约400米的位置漂移。这个结果不算特别好但考虑到纯雷达、无任何先验地图已经是可用的结果。如果再加上地面约束比如GPS的粗定位或者回环检测误差还能大幅下降。雷达里程计和激光里程计最大的区别在于激光点云稠密、准确度极高但受天气影响大雷达点云稀疏、有大量多径和噪声但全天候可用。做融合定位时二者互补性很强。5. 坐标系标定与多传感器融合注意点5.1 外参矩阵的使用姿势这个数据集提供了外参文件但使用的时候有几个坑第一个坑外参矩阵的参考系定义并不统一。有的文件是从传感器到车体有的是从车体到传感器用之前先读官方论文对应的坐标系定义别直接乘。第二个坑部分外参是在特定采集日期之后才更新的。比如雷达和激光雷达之间的外参如果用的是早期序列的时间戳直接应用官方外参会发现投影有偏差。建议拿到数据后先做一轮“自标定”取一帧同一时刻的雷达图和激光点云手动选几组对应点重新求解外参。第三个坑GPS/INS的时间基准与雷达的时间戳并不是天然对齐的。雷达的时间戳文件是每个PNG对应的Unix时间但有时会差几个毫秒。如果做的是高精度融合得考虑时间插值不能直接拿最近邻帧。这些坑看起来琐碎实际影响很大。我在做雷达图和激光点云融合时一开始直接用了官方外参结果投影误差有接近1米的偏移。后来用ICP做帧间配准求解了一个修正矩阵投影误差降到0.1米以下。5.2 雷达与视觉融合的一个典型思路雷达和相机的外参标定好之后一个自然的做法是把雷达检测目标投影到图像平面上和视觉目标检测结果做关联。这里的难点在于雷达点在图像上的投影会存在误差一方面雷达目标点代表的是反射中心不一定对应物体的几何中心另一方面雷达在俯仰角上没有分辨能力所有目标默认在雷达平面上投影到图像上时会在垂直方向上有不确定度。实用做法是把雷达目标投影到图像后不要求精确落在物体框内而是用一个椭圆区域做模糊关联。只要目标落在检测框附近就认为是同一个目标然后融合雷达的距离和速度信息与视觉的类别信息。我做过一次雨天场景下的车辆检测实验视觉单模的检测置信度明显下降但加上雷达距离信息后检测框的定位明显更稳。融合后平均距离估计误差从纯视觉的2.3米降到了0.8米左右。5.3 跨季节地图匹配的实战价值这个数据集最独特的地方是同一条路线在不同时间反复采集。这意味着你可以构建一张“标准环境地图”然后在另一天、另一个季节的采集序列中重新定位车辆。我试过用晴天序列构建雷达地图然后在两个多月后的雨天序列中做定位。做法很简单对雨天序列的每一帧雷达图提取CFAR目标和地图中每个位置的雷达目标做特征匹配找最优位姿。结果令人惊喜即便间隔两个月道路的基本结构、建筑物轮廓的雷达反射特征仍然非常稳定。全局定位误差在2米以内完全不需要GPS辅助。这种跨时间的稳定性是激光雷达做不到的因为激光点云对视角和环境变化的敏感度太高。我当时觉得这个数据集简直就是为“雷达地图的长期维护”量身定做的。即使你的研究方向不是自动驾驶感知这个数据集的雷达重访特性也能支撑不少测绘和机器人领域的研究课题。6. 实操中的常见问题与排查经验6.1 下载慢、断连的问题怎么破Oxford数据集的下载服务器在海外国内下载速度确实不理想。单序列几百GB如果直接浏览器下载断一次就前功尽弃。我的经验是用支持断点续传的下载工具比如wget或者curl配合-c参数先下载官方提供的filelist.txt筛选出你要的序列的URL再用脚本批量下载下载时并发数不要太高建议2-4个任务否则服务器容易限流校验文件完整性官方给每个文件提供了MD5校验值下载完记得核对一个坑是如果只下载radar目录不下载navtech目录很多官方工具脚本会报错因为它们默认navtech数据也存在。尽量按完整传感器子目录下载。6.2 时间戳对不齐怎么办雷达4Hz、激光雷达是定频扫描、相机是触发采集三者时间戳天然不齐。做融合时如果想取同一时刻的雷达帧和激光帧直接查时间戳找最近邻即可但要注意时间基准。INS的时间戳是Unix时间戳精度到微秒量级。PNG文件的时间戳文件有的和PNG名称一致有的是单独一份。实际操作时先统一转成浮点秒再取最近邻。时间差在5ms以内基本可以认为同步如果大于20ms就得考虑插值。有一次我发现雷达帧和INS时间戳对不上后来发现是时区问题UTC和本地时间差了8小时统一转成UTC后解决。6.3 雷达图像中的环形伪影与多径干扰用雷达图做可视化时经常能看到一些同心圆或圆弧状的伪影。这些是强反射目标比如路灯杆、金属护栏产生的多径回波在距离维上会形成对称或周期的假目标。处理伪影没有万能方法但有几个实践技巧做目标检测时可以用雷达RCS值过滤弱回波的伪影通常能量较低使用时间维信息真实的静态目标在不同帧之间位置是一致的多径伪影的位置会随车辆运动变化做里程计匹配时优先选择高反射点伪影一般达不到阈值我踩过的坑是有一次做图优化把多径伪影当成了真实地标结果后端优化把整个轨迹拉偏了。后来在特征提取阶段加入多帧一致性检查问题才解决。6.4 雷达图直接显示出来灰蒙蒙的怎么办雷达PNG是16位深度直接用cv2.imshow显示的话会默认当成8位图处理看起来就是一片灰白。正确显示方式是对数组做归一化比如arr_8u cv2.normalize(arr, None, 0, 255, cv2.NORM_MINMAX).astype(np.uint8)配合OpenCV的colormap比如cv2.COLORMAP_JET显示效果会好很多。如果想要更好的可视化可以对幅度取对数后再归一化动态范围更友好。6.5 真值轨迹与估计轨迹的评估方法评估里程计或定位算法时最常用的指标是ATE和RPE。计算前注意对齐估计轨迹和真值轨迹的时间戳先做时间同步再做坐标对齐通常是Umeyama算法求解相似变换。Oxford数据集提供的是基于UTM的全局坐标里程计估计的是局部增量位姿。严格评估时要把估计轨迹先变换到真值坐标系这需要至少3个对应点求解一个刚性变换。我习惯用Python的evo工具库来做评估支持ATE/RPE计算和可视化很方便。一个容易忽略的细节是INS轨迹在起步阶段可能不够稳定建议丢弃前10秒的数据再做评估否则会把起步漂移计入误差。7. 把数据用起来的几点心得这个数据集我断断续续用了大半年最大的感触是它不是一个“下载下来跑个demo就完事”的数据集而是一个需要耐心打磨的数据集。雷达成像和激光雷达完全不同没有直观的“点云感”很多算法不能直接照搬视觉或激光的处理思路。如果你刚拿到这个数据集我建议的实验路径是先把radar PNG读出来做极坐标转笛卡尔的可视化熟悉雷达图长什么样手动挑几帧强反射目标路灯、卡车、建筑观察其回波特征实现一帧CFAR检测提取目标点可视化输出做相邻帧间的目标匹配实现一个最简雷达里程计再逐步引入多帧特征、回环检测、多传感器融合一步步来每一步都比上一步多理解一层雷达数据的特性。等你踩完这些坑这个数据集的绝大部分价值也就被你吸收到了。后面如果有时间我还会把雷达特征匹配、跨季节地图构建的详细实现也整理出来这两个方向目前公开资料还比较碎片化值得深入写一写。