NGSIM跟驰数据提取与清洗:Python实现轨迹片段切分与平滑

NGSIM跟驰数据提取与清洗:Python实现轨迹片段切分与平滑 简介针对NGSIM数据集中的车辆跟驰研究场景这份小型资源提供了一套基于Python的pandas处理程序用于从原始轨迹数据中筛选并提取目标车辆与其对应前车的连续时间戳配对信息适合交通流分析、驾驶行为建模等方向的入门研究者使用。程序对跟驰数据完成初步筛选与关键字段整合但未做平滑和去噪处理使用者可在此基础上继续开展数据清洗和特征工程。资源包整体约36KB以Python脚本和CSV数据文件为主便于直接查看处理逻辑与输出结构。目前已有5123人浏览学习可为后续跟驰模型标定与微观仿真研究提供批量提取原始配对数据的起点。1. NGSIM 跟驰车辆处理的第一道坎数据很干净模型很挑食NGSIM 是微观交通行为建模绕不开的实测数据集。真正落到跟驰模型上时耗时最多的不是算法而是把轨迹文件里各辆车的跟驰片段切对什么时候车辆稳定跟在另一辆车后面什么时候换了目标什么时候已经脱离。最终产出应该是一批能直接喂给 IDM、Gipps、Wiedemann 做参数标定的最小数据单元每段都是一段连续的跟驰记录。第一次接触的人直接对整个 CSV 做统计标定出来的期望间距、反应时间往往离谱问题大多出在提取这一步而不是模型本身。这篇文章讲我处理 NGSIM 跟驰数据的完整流程字段索引、跟驰片段切分、轨迹平滑和结果验证并给出可运行的 Python 代码。适合正在做微观交通流建模的研究生以及做自动驾驶场景库和轨迹评测的工程师。2. 先把 NGSIM 字段读顺再判断谁是跟驰车辆写数据处理程序之前我建议先花十分钟把字段含义和量纲对齐。NGSIM 的字段表不长真正用于跟驰分析的也不超过十个但单位问题和关系字段的含义会在后续一步一个坑地冒出来。2.1 10Hz 轨迹数据里判断跟驰的关键字段NGSIM 公开数据里US-101 和 I-80 两个高速公路场景最常被用来研究跟驰车辆。它们都是通过路侧摄像机连续记录大约 45 分钟再经视频处理得到每 0.1 秒一条的轨迹。每个文件按行存一行就是一帧里的一辆车时刻对齐关系由 Frame_ID 和 Global_Time 共同维护。字段含义单位Vehicle_ID车辆编号-Frame_ID帧编号10Hz 递增帧Global_Time绝对时间戳毫秒Local_X横向坐标英尺Local_Y纵向坐标英尺v_Vel瞬时速度英尺/秒v_Acc瞬时加速度英尺/秒²Lane_ID车道编号-Preceding_Vehicle_ID前导车辆编号0 表示无前车-Following_Vehicle_ID跟随车辆编号-Space_Headway与前车的空间间距英尺Time_Headway与前车的时间间隔秒跟驰数据程序的核心输入并不是图像识别结果而是这几个关系字段。其中 Preceding_Vehicle_ID 最关键它直接告诉我们某一帧里某辆车跟在谁后面。NGSIM 对前导车辆的认定是同一时间、同一路段上位于该车前方且匹配的车换道瞬间该字段就会跳变这恰恰是后续切分片段的依据。2.2 Frame_ID 与 Preceding_Vehicle_ID 共同决定跟驰关系Frame_ID 提供时间维度上的连续性Vehicle_ID 提供对象维度。一个常见误用是把 Frame_ID 当成可以直接索引的行号但原始文件的行序不一定严格按时间和车辆排列读入后必须先排序。跟驰关系成立的三个必要条件同一帧内目标车辆的 Preceding_Vehicle_ID 大于 0该前车 ID 连续保持稳定不能一帧一换两车必须在同一车道且 Space_Headway 在合理范围内。每辆车的(Vehicle_ID, Frame_ID)在完整数据里是唯一键同时每条记录带一个 Global_Time 绝对时间。排序后前后两条记录的 Global_Time 差应在 100 毫秒左右如果出现 200 毫秒以上的间隔说明这段轨迹有丢帧。实际处理里我会把丢帧位置记录下来它既是滤波的断点也是判断片段边界的辅助信号。片段切分的状态描述当某车的 Preceding_Vehicle_ID 由 0 变为正数说明它开始进入跟驰状态当该字段变为另一个正值说明换了跟随对象当变回 0说明脱离。跟驰片段切分就是把这三个状态变化点找出来再按变化的时刻把轨迹切成连续块。2.3 英尺制字段换算不要直接喂给公制模型所有位置和速度字段在 NGSIM 原始文件里都是英尺制。常见做法是在读取后统一乘以 0.3048 转成米和米/秒避免模型标定和对比文献时出现系统性数值偏移。物理量原始字段转公制坐标Local_X / Local_Y×0.3048 得米速度v_Vel×0.3048 得米/秒加速度v_Acc×0.3048 得米/秒²间距Space_Headway×0.3048 得米时间间隔Time_Headway本来就是秒不换算这里有两个坑需要注意。第一Time_Headway 本身就是秒不用乘 0.3048很多脚本喜欢统一遍历所有列结果把时间头距也缩放了。第二Gipps、IDM 等模型的不同实现版本对单位约定不一致换单位后重新标定要比直接套参数可靠得多。做数据程序时我会把换算集中在一个函数里避免各处散落 magic number。3. 用 Python 把 NGSIM 跟驰车辆数据切成可用片段这一章给出一套我常用的切分实现不依赖额外库只靠 pandas 和 numpy。核心思路是按车辆分组在车辆内部扫描前车关系的变化点。3.1 先排序再补全避免把帧序搞乱无论拿到的是 csv 还是固定分隔文本导入之后先不要急着过滤。第一步永远是排序和检查缺帧。import pandas as pd import numpy as np def load_ngsim(file_path): df pd.read_csv(file_path) df df.sort_values([Vehicle_ID, Frame_ID]).reset_index(dropTrue) return df def count_frame_gaps(df): gaps {} for veh_id, group in df.groupby(Vehicle_ID): frame_diff group[Frame_ID].diff().dropna() gap_count int((frame_diff 1).sum()) if gap_count 0: gaps[veh_id] gap_count return gaps逻辑说明排序后对 Frame_ID 做 diff差值大于 1 就说明这两帧之间缺数据。count 的结果用于后面片段切分的断点判断。这里不要用 reset_index 后的行数变化判断缺帧因为缺帧不一定缺行行可能正常但时间不连续。3.2 识别连续跟驰片段的最小实现不依赖复杂状态机在车辆分组内扫描 Preceding_Vehicle_ID。当前后帧的前车 ID 不一致或者帧号不连续就认为跟驰关系中断。def extract_following_segments(vdf, min_len50, gap_tol1): frames vdf[Frame_ID].to_numpy() pres vdf[Preceding_Vehicle_ID].to_numpy() segments [] seg_start, seg_pre frames[0], pres[0] prev_frame frames[0] for f, pre in zip(frames[1:], pres[1:]): if pre ! seg_pre or (f - prev_frame gap_tol): if prev_frame - seg_start min_len and seg_pre 0: segments.append((seg_start, prev_frame, seg_pre)) seg_start, seg_pre f, pre prev_frame f if prev_frame - seg_start min_len and seg_pre 0: segments.append((seg_start, prev_frame, seg_pre)) return segments参数说明seg_start和seg_pre记录正在累计片段的起点和当前前车 ID遇到前车跳变或丢帧就封口。min_len为 50 时表示片段最短 5 秒这个长度能容纳滤波造成的边缘丢弃也够 IDM 参数估计使用做紧急制动分析时可以放到 20 帧但噪声会变大。调用时按车辆分组循环把车辆 ID 一起带回all_segments [] for veh_id, vdf in df.groupby(Vehicle_ID): vdf vdf.sort_values(Frame_ID) for start, end, pre in extract_following_segments(vdf): all_segments.append((veh_id, start, end, pre))这里pre 0的判断保证了前车为 0 的闲散帧不会被当成跟着 0 号车。3.3 片段清洗换道、低速与异常跳变片段切出来之后还是半成品需要再做三道过滤。换道不需要额外处理Preceding 跳变自然会被切成两段低速和异常跳变则需要显式清洗。def clean_segments(df, segments, speed_floor2.0, headway_jump30.0): kept [] for veh_id, start, end, pre in segments: seg df[(df[Vehicle_ID] veh_id) (df[Frame_ID] start) (df[Frame_ID] end)].copy() if seg[v_Vel].median() speed_floor: continue if (seg[Space_Headway].diff().abs() headway_jump).any(): continue seg seg.sort_values(Frame_ID).reset_index(dropTrue) kept.append(seg) return keptspeed_floor按研究目的调整研究停车走停行为时降到 0.5 ft/s 或直接不开只研究稳态跟驰流时建议 8 ft/s 以上信噪比更高。headway_jump对车道变换比较敏感所以这段过滤必须放在换道片段已经切开之后执行否则正常换道会被误杀。4. NGSIM 跟驰轨迹平滑与加速度参数怎么设片段切完了下一步是把速度、加速度处理得能被模型标定接受。NGSIM 原始 v_Acc 字段的噪声很大直接使用会让参数估计很不稳定。4.1 原始加速度的噪声从哪来NGSIM 轨迹数据虽然发表时就带 v_Acc 字段但它是通过视频定位和位置差分计算得到的高频抖动明显。课题组里的常见做法是不直接用原始字段而是用平滑后的速度再差分一次。直接对原始 v_Acc 做移动平均也能用但相位会相对真实急减速事件发生偏移。怎么判断噪声大小取一段稳定跟驰计算 v_Acc 的中位绝对偏差 MAD。如果 MAD 超过 1.5 ft/s²约等于 0.46 m/s²这段数据的差分噪声就偏高原始加速度不能直接参与标定。实际大部分 NGSIM 片段都超过这个值所以才需要滤波。平滑窗口的选择不是越大越好。跟驰模型标定时真正有效的信息是驾驶员如何对前车速度差和间距做反应急减速的峰值位置和大小会直接影响反应时间的估计。窗口从 5 帧加到 15 帧峰值加速度会被压低一个明显的量级这个偏差在标定 IDM 的 b 参数时会显著反映出来。4.2 移动平均与 Savitzky-Golay 的经验参数区间方法推荐参数适用数据注意移动平均窗口 5~9 帧速度 v_Vel窗口越大峰值削得越狠Savitzky-Golay窗口 9~15阶数 2速度或加速度阶数太高会保留噪声中心差分dt0.1s由速度求加速度首尾帧需单独处理移动平均窗口 5~9 帧即 0.5~0.9 秒适合压住 10Hz 数据的抖动。Savitzky-Golay 用窗口 9~15、阶数 2 是稳妥组合比移动平均强在峰值保持更好急减速的最大减速度不会被打折太多。窗口再大的话真实制动响应也会被平滑成一条缓慢下降的曲线反而不利于模型辨识。4.3 从速度差分回算加速度的边界处理def smooth_and_accel(v, window7, dt0.1): kernel np.ones(window) / window v_s np.convolve(v, kernel, modesame) half window // 2 v_s[:half] v[:half] v_s[-half:] v[-half:] a np.zeros_like(v_s) a[1:-1] (v_s[2:] - v_s[:-2]) / (2 * dt) a[0] (v_s[1] - v_s[0]) / dt a[-1] (v_s[-1] - v_s[-2]) / dt return v_s, a参数说明window7对应 0.7 秒适合 10Hz 数据modesame的卷积在边缘会衰减所以用原值回填前后各 half 个点再做差分。a[1:-1]用中心差分一阶精度比前向差分高相位延迟也更小。dt0.1是 NGSIM 的固定采样间隔如果数据经过重采样需要按新间隔传入。算完加速度后和原始 v_Acc 字段做一次对比相对差异超过 30% 的帧占比如果超过一成说明平滑窗口或差分方式不合适。这时先检查单位有没有换算错再调窗口大小。5. 用时空图和车头时距分布验证跟驰片段质量5.1 单段跟驰轨迹的时空图对照切完片段和滤波后不建议直接进入模型标定先做图形检查。最有效的是拿一个跟驰对画 Local_Y 对 Frame_ID 两条曲线。理想情况下前车和跟车两条线几乎平行纵向间距在局部比较稳定如果曲线交叉或间距突然拉开说明 Preceding_Vehicle_ID 切段时混入了不属于同一前车的帧或者前车在片段中换道但没有被跳变完整捕捉。import matplotlib.pyplot as plt def plot_pair(df, veh_id, pre, start, end): mask ((df[Vehicle_ID].isin([veh_id, pre])) (df[Frame_ID] start) (df[Frame_ID] end)) seg df[mask].sort_values([Vehicle_ID, Frame_ID]) for vid, g in seg.groupby(Vehicle_ID): plt.plot(g[Frame_ID], g[Local_Y], labelfvehicle {vid}) plt.xlabel(Frame_ID) plt.ylabel(Local_Y (ft)) plt.legend() plt.show()这里直接用原始英尺字段方便和 NGSIM 自带的坐标体系对齐。肉眼检查五到十秒的片段两条线不能交叉同时看速度曲线跟车速度应该在前车速度上下小幅波动如果出现长时间的单向偏离说明片段头尾没对齐。5.2 用时间头距分布发现单位和切分错误全部片段合并后计算每一帧的跟车车头时距即 Time_Headway 字段或等价推导值。正常稳态跟驰流的 time headway 集中在 0.8~2.5 秒区间。如果大量值低于 0.3 秒说明要么单位换算出了问题要么速度被错误地放大了需要回溯到字段转换步骤而不是继续调模型。另一种有效检查是对固定车道和固定片段画间距-相对速度散点横轴 Space_Headway纵轴前车速度减跟车速度。跟驰模型的标定域应该是一个有明显对角边界的云团边界覆盖不到的部分往往是异常片段或者换道残留。分布合理后再把所有片段拼接成训练集。最后提一个排错技巧把平滑后的速度和重算后的加速度同时输出成一份新 CSV并在文件名里标记窗口参数模型标定出问题时可快速反查数据来源省掉重复跑全流程的时间。本文还有配套的精品资源点击获取