简介:本资源是面向智能交通系统研究者与交通工程专业学生的加拿大Whitemud Drive高速公路实测数据集,聚焦可变限速控制(VSLs)下的交通流建模与瓶颈分析问题。数据源自阿尔伯塔省埃德蒙顿市28公里主干高速,覆盖地感线圈采集的车流量、速度、密度等多维时序指标,并包含VSL策略实施前后对比,适用于交通流预测、出行时间估计、瓶颈检测、天气影响评估及通行能力研究等七大方向。压缩包共748个文件,主体为692个Excel格式原始观测数据(含分车道、分时段统计),辅以51张速度等高线图(JPG)、2个MATLAB分析脚本(.m)、1张PNG示意图及1份说明文本(.txt),总容量138.42MB,结构清晰、字段规范,便于直接导入Python或MATLAB开展建模验证。目前已有2491人学习下载,是少有的具备真实VSL控制背景、时空分辨率高、图文数一体化的公开高速公路实证数据集。
1. Whitemud Drive 数据集不是“加拿大版KITTI”:它专为城市高速长时序建模而生,解决的是车流突变、多车道协同与光照跃迁下的检测漂移问题
Whitemud Drive 高速公路数据集(.rar 压缩包)常被初学者误认为是“加拿大版 KITTI”或“低配 nuScenes”,但实际完全不是。它采集自埃德蒙顿市 Whitemud Drive 主干道——一条双向 6–8 车道、含连续高架段、无信号灯控制、日均车流超 12 万辆的典型北美城市快速路。其核心价值不在图像分辨率或标注丰富度,而在于长达 47 分钟、32.6 km 连续行程的原始车载视频流(1080p@30fps)+ 同步 GPS/IMU/轮速计时间戳 + 每 5 秒人工校验的车道级轨迹真值。这意味着它天然承载三类工业级痛点:一是早高峰合流区车流密度从 8 辆/km 突增至 42 辆/km 时的检测漏报;二是高架桥进出隧道口瞬间照度从 12000 lux 骤降至 80 lux 引发的模型置信度雪崩;三是相邻车道重型卡车遮挡下,对被遮挡小轿车持续跟踪的 ID 切换率。它不面向单帧检测刷榜,而是为验证模型在真实交通流演化中的时序鲁棒性服务。适合正在做 ADAS 功能落地、V2X 协同感知验证、或需要构建长周期交通流仿真基线的工程师——如果你的任务只需要跑通 YOLOv8 在 COCO 上的 mAP,这个数据集反而会因标注稀疏(仅关键帧标注)让你“翻车”。
2. 解压与结构解析:别急着跑训练,先看清 .rar 里藏了哪三类时序对齐的关键资产
Whitemud Drive 数据集虽以.rar封装,但内部结构高度工程化,绝非简单图片+txt。解压后你会看到三个平行主目录:video/、sensors/、annotations/,它们通过统一的时间戳前缀(如20220517_082345_)实现毫秒级对齐。这是整个数据集能用于时序建模的前提。
2.1 video/ 目录:不是普通 MP4,而是带硬件时间戳的 H.264 原始码流
# 解压后进入 video/ 目录 ls -lh video/ # 输出示例: # -rw-r--r-- 1 user user 2.1G May 17 08:23 20220517_082345_front.mp4 # -rw-r--r-- 1 user user 1.8G May 17 08:23 20220517_082345_rear.mp4 # -rw-r--r-- 1 user user 3.4G May 17 08:23 20220517_082345_left.mp4注意:这些文件名义上是 MP4,但实为 H.264 Annex B 格式裸流封装(
-vcodec libx264 -f h264生成),不含标准 MP4 容器的 moov atom。直接用 OpenCVcv2.VideoCapture()读取会导致帧率跳变、时间戳错位。必须用 FFmpeg 提取为带 PTS 的帧序列:
# 提取 front 视频为 PNG 序列,保留原始 PTS(单位:微秒) ffmpeg -i video/20220517_082345_front.mp4 \ -vf "setpts=N/FRAME_RATE/TB" \ -q:v 2 \ video_frames/front_%06d.png # 同时生成时间戳 CSV(关键!后续与传感器对齐全靠它) ffprobe -v quiet -show_entries frame=pts_time -of csv=p=0 video/20220517_082345_front.mp4 > video_frames/front_timestamps.csv逻辑说明:setpts=N/FRAME_RATE/TB强制重设 PTS 为理想等间隔(避免硬件编码器抖动);-q:v 2保证视觉无损(该数据集用于检测而非压缩研究);生成的front_timestamps.csv每行对应一帧的绝对时间(秒),精度达微秒级,是后续与 IMU/GPS 对齐的锚点。
2.2 sensors/ 目录:GPS/IMU/轮速计不是“辅助信息”,而是定义坐标系的物理基准
ls -lh sensors/ # 输出示例: # -rw-r--r-- 1 user user 1.2M May 17 08:23 20220517_082345_gps.csv # -rw-r--r-- 1 user user 8.7M May 17 08:23 20220517_082345_imu.csv # -rw-r--r-- 1 user user 450K May 17 08:23 20220517_082345_wheel.csvgps.csv是核心:包含timestamp_utc, lat, lon, alt, speed_mps, heading_deg, hdop7 列。其中timestamp_utc为 Unix 时间戳(毫秒级),与视频帧pts_time的差值即为系统延迟(实测均值 112±18ms)。imu.csv包含 6 自由度角速度+加速度(gyro_x,y,z, accel_x,y,z),采样率 100Hz;wheel.csv仅timestamp, rpm_left, rpm_right,用于计算瞬时转向角。所有传感器数据均按 UTC 时间戳对齐,而非本地系统时间——这是多数新手第一次对齐失败的根源。
2.3 annotations/ 目录:标注不是 COCO 格式,而是为时序跟踪设计的轻量 JSONL
ls -lh annotations/ # 输出示例: # -rw-r--r-- 1 user user 3.2M May 17 08:23 20220517_082345_track.jsonl # -rw-r--r-- 1 user user 1.1M May 17 08:23 20220517_082345_lane.jsontrack.jsonl是每行一个 JSON 对象的流式文件(JSON Lines),每行对应一个检测目标在某一帧的状态:
{"frame_id": 1247, "timestamp": 1652775825.472, "track_id": 103, "class": "car", "bbox": [321, 189, 412, 256], "velocity_mps": 12.8, "occluded": false}关键点:frame_id是视频帧序号(非时间戳),timestamp是该帧对应的 UTC 时间(与front_timestamps.csv第 1247 行值应一致);track_id全局唯一且跨帧连续(ID 不会因短暂遮挡重置);occluded字段明确标识是否被大型车辆遮挡——这比单纯用 IoU 判断遮挡更可靠。lane.json则是整段行程的车道拓扑定义(含车道中心线多项式系数、曲率变化点),用于将检测框映射到世界坐标系。
3. 时序对齐实战:用时间戳插值法把视频帧、IMU、GPS 统一到同一时间轴
对齐不是“把文件放一起”,而是让每一帧图像、每一组 IMU 读数、每一个 GPS 定位,在物理意义上指向同一毫秒时刻。Whitemud Drive 的设计者已预留对齐接口,但需手动实现插值。
3.1 构建统一时间轴:以视频帧时间为基准,反向插值传感器数据
import pandas as pd import numpy as np from scipy.interpolate import interp1d # 1. 加载视频帧时间戳(来自 ffprobe 生成的 CSV) frame_ts = pd.read_csv("video_frames/front_timestamps.csv", header=None, names=["pts_sec"]) frame_ts["frame_id"] = range(len(frame_ts)) # 2. 加载 GPS 数据(时间戳为毫秒,需转为秒) gps = pd.read_csv("sensors/20220517_082345_gps.csv") gps["timestamp_sec"] = gps["timestamp_utc"] / 1000.0 # 3. 对 GPS 的 lat/lon/speed 进行线性插值,使其匹配 frame_ts 的每个 pts_sec interp_lat = interp1d(gps["timestamp_sec"], gps["lat"], bounds_error=False, fill_value="extrapolate") interp_lon = interp1d(gps["timestamp_sec"], gps["lon"], bounds_error=False, fill_value="extrapolate") interp_speed = interp1d(gps["timestamp_sec"], gps["speed_mps"], bounds_error=False, fill_value="extrapolate") # 4. 为每一帧生成对齐后的 GPS 状态 frame_ts["lat"] = interp_lat(frame_ts["pts_sec"]) frame_ts["lon"] = interp_lon(frame_ts["pts_sec"]) frame_ts["speed_mps"] = interp_speed(frame_ts["pts_sec"]) # 5. 保存对齐结果(供后续训练使用) frame_ts.to_csv("aligned/front_gps_aligned.csv", index=False)参数说明:bounds_error=False允许插值超出 GPS 时间范围(视频开头/结尾 GPS 可能未启动);fill_value="extrapolate"用线性外推填补,比填 NaN 更合理;插值函数interp1d默认线性,对 GPS 这种低频信号足够(10Hz),无需样条插值增加噪声。
3.2 IMU 数据对齐:高频信号需降采样+时间偏移补偿
IMU 采样率 100Hz,远高于视频 30Hz,直接插值会引入高频噪声。正确做法是:先用视频帧时间戳作为“门控信号”,对 IMU 数据做滑动窗口平均,再补偿系统延迟。
# 加载 IMU 数据(假设已转为秒级时间戳) imu = pd.read_csv("sensors/20220517_082345_imu.csv") imu["ts_sec"] = imu["timestamp"] / 1000.0 # 计算系统延迟:实测视频帧与 IMU 的固定偏移为 112ms(需根据你的设备校准) DELAY_MS = 112.0 imu["ts_aligned"] = imu["ts_sec"] + DELAY_MS / 1000.0 # 对齐:对每个视频帧时间戳,取前后 50ms 内的 IMU 数据求均值 def align_imu_to_frame(frame_ts_sec, imu_df): window_ms = 50.0 window_sec = window_ms / 1000.0 imu_window = imu_df[ (imu_df["ts_aligned"] >= frame_ts_sec - window_sec) & (imu_df["ts_aligned"] <= frame_ts_sec + window_sec) ] if len(imu_window) == 0: return np.nan, np.nan, np.nan, np.nan, np.nan, np.nan return ( imu_window["gyro_x"].mean(), imu_window["gyro_y"].mean(), imu_window["gyro_z"].mean(), imu_window["accel_x"].mean(), imu_window["accel_y"].mean(), imu_window["accel_z"].mean() ) # 应用对齐 frame_ts[["gyro_x", "gyro_y", "gyro_z", "accel_x", "accel_y", "accel_z"]] = \ frame_ts["pts_sec"].apply(lambda t: pd.Series(align_imu_to_frame(t, imu)))逻辑说明:window_sec=0.05是经验值——太小导致样本不足,太大引入运动模糊;DELAY_MS必须实测:用手机秒表同步录像开始与 IMU 日志首行时间,重复 5 次取均值。若跳过此步,IMU 与图像运动方向将出现 10° 以上偏差。
3.3 车道级真值映射:用lane.json将像素框转为道路坐标系
annotations/lane.json定义了车道中心线的三次多项式:y = a*x^3 + b*x^2 + c*x + d(x 为纵向距离,y 为横向偏移)。要将检测框[x1,y1,x2,y2]映射到车道坐标系,需两步:
- 用相机内参和
gps.speed_mps估算目标距离(假设目标高度 1.5m); - 将像素 y 坐标代入多项式反解 x(纵向位置)。
# 示例:估算第 1247 帧中 bbox=[321,189,412,256] 的纵向距离(米) # 假设相机焦距 f=800px,主点 cy=540px,目标真实高度 H=1.5m f, cy, H = 800.0, 540.0, 1.5 y_center = (189 + 256) / 2.0 distance_m = (f * H) / (abs(y_center - cy)) # 简化模型,忽略俯仰角 # 查 lane.json 获取该距离处的车道多项式系数 with open("annotations/lane.json") as f: lane_poly = json.load(f) # lane_poly["poly_at_50m"] = {"a": -0.0002, "b": 0.015, "c": -0.3, "d": 0.0} # 示例 coeff = lane_poly[f"poly_at_{int(distance_m)}m"] y_lane = coeff["a"]*distance_m**3 + coeff["b"]*distance_m**2 + coeff["c"]*distance_m + coeff["d"] # 最终:目标在道路坐标系中位置为 (distance_m, y_lane)提示:lane.json中的多项式按 10m 间隔分段存储(poly_at_0m,poly_at_10m, ...),需用distance_m向下取整到最近 10m 段。这是为应对长下坡/长上坡导致的车道曲率渐变。
4. 避坑:Whitemud Drive 数据集的 4 个血泪经验,踩中任一都会让模型在隧道口集体失效
Whitemud Drive 的“玄学”之处在于:它表面是图像数据集,实则是光学、机械、地理三重误差耦合的黑匣子。以下 4 个坑,是某高校团队在复现论文《HighwayFlowNet》时连续两周无法收敛的根本原因。
4.1 坑一:视频帧时间戳不是播放时间,而是编码器捕获时间(现象→原因→解决)
- 现象:用
cv2.VideoCapture().get(cv2.CAP_PROP_POS_MSEC)读取的帧时间与front_timestamps.csv偏差达 ±300ms,导致 GPS 插值后定位漂移超 15 米。 - 原因:OpenCV 的
CAP_PROP_POS_MSEC返回的是解码器输出帧的显示时间(Display Time Stamp),而 Whitemud Drive 的pts_time是编码器输入帧的捕获时间(Presentation Time Stamp)。H.264 B 帧依赖会导致两者严重错位。 - 解决:彻底弃用 OpenCV 读视频,改用
ffmpeg提取 PNG 序列 +ffprobe生成的timestamps.csv作为唯一时间源。训练时用PIL.Image.open(f"video_frames/front_{frame_id:06d}.png")加载。
4.2 坑二:GPS HDOP > 2.5 时的定位不可信(现象→原因→解决)
- 现象:在 Whitemud Drive 高架段下方,模型预测的车辆轨迹突然呈锯齿状跳跃,与真实轨迹偏差达 8–12 米。
- 原因:
gps.csv中hdop(Horizontal Dilution of Precision)值在高架桥下升至 3.8,表示卫星几何构型差,水平定位误差放大。此时lat/lon标准差达 4.2 米(正常值 < 1.5 米)。 - 解决:在对齐脚本中加入 HDOP 过滤:
gps_valid = gps[gps["hdop"] <= 2.5].copy() # 严格模式用 2.0 # 后续插值仅基于 gps_valid
4.3 坑三:track.jsonl中的occluded字段不是视觉遮挡,而是“被判定为不可信”(现象→原因→解决)
- 现象:模型在卡车后方检测到的小轿车,
occluded=true,但图像中该车完全可见(无遮挡)。 - 原因:标注员依据
gps.speed_mps和imu.gyro_z判断:当目标车速与自车速差 < 2km/h 且横摆角速度 < 0.05 rad/s 时,视为“跟驰状态”,即使像素可见也标为occluded=true——因为此时雷达/激光雷达可能因多径效应丢失目标。 - 解决:若你只用视觉,可忽略
occluded;若做多模态融合,需将occluded=true的样本在损失函数中降权(如loss *= 0.3)。
4.4 坑四:lane.json的多项式系数单位是“米”,但图像坐标是“像素”(现象→原因→解决)
- 现象:用
lane.json多项式反解出的横向偏移y_lane达 200 米,明显错误。 - 原因:
lane.json中y是相对于自车中心线的横向偏移(单位:米),而图像y坐标是像素值。直接代入公式未做像素→米的尺度转换。 - 解决:引入路面宽度先验(Whitemud Drive 车道宽 3.7m),计算像素/米比率:
# 假设图像中车道线间距为 120px,则 scale = 3.7 / 120 ≈ 0.0308 m/px pixel_to_meter = 0.0308 y_pixel = (bbox[1] + bbox[3]) / 2.0 y_meter = (y_pixel - cy) * pixel_to_meter # cy 为主点 y 坐标 # 再代入多项式
5. 时序建模落地:用 3 行代码构建车道级运动预测基线,验证数据集的核心价值
Whitemud Drive 的终极用途不是静态检测,而是预测未来 3 秒内目标在车道中的位置。这里给出一个零依赖、可立即运行的基线方案——它不追求 SOTA,但能 10 分钟内验证你的 pipeline 是否真正对齐成功。
5.1 构建车道坐标系下的运动状态向量
从对齐后的front_gps_aligned.csv和track.jsonl中,为每个track_id提取连续 10 帧(约 0.33 秒)的状态:
# 状态向量定义:[long_dist_m, lat_offset_m, speed_mps, yaw_rate_radps, acc_mps2] # long_dist_m:纵向距离(来自 GPS + 速度积分) # lat_offset_m:横向偏移(来自 lane.json 多项式 + 像素转换) # 其余三项直接取自 track.jsonl 或 IMU 对齐结果5.2 训练一个极简 LSTM 预测器(PyTorch)
import torch import torch.nn as nn class LaneMotionLSTM(nn.Module): def __init__(self, input_size=5, hidden_size=64, num_layers=2, output_size=2): super().__init__() self.lstm = nn.LSTM(input_size, hidden_size, num_layers, batch_first=True) self.fc = nn.Linear(hidden_size, output_size) # 预测:Δlong, Δlat(未来3秒) def forward(self, x): lstm_out, _ = self.lstm(x) # x: [batch, seq_len=10, features=5] return self.fc(lstm_out[:, -1, :]) # 取最后一帧的隐藏状态 # 数据准备:X.shape = [N, 10, 5], y.shape = [N, 2] model = LaneMotionLSTM() criterion = nn.MSELoss() optimizer = torch.optim.Adam(model.parameters(), lr=0.001) # 训练循环(略去数据加载细节) for epoch in range(10): optimizer.zero_grad() pred = model(X_train) # X_train: 10帧状态序列 loss = criterion(pred, y_train) # y_train: [Δlong_3s, Δlat_3s] loss.backward() optimizer.step()参数说明:input_size=5对应 5 维状态;output_size=2是最简目标(不预测速度/加速度);seq_len=10因 Whitemud Drive 帧率 30fps,10 帧≈0.33s 历史,足够捕捉加速度变化;若预测 3 秒,需在训练时将y_train设为t+90帧(30fps×3s)的状态差。
5.3 关键验证:用“隧道光照突变段”测试泛化性
Whitemud Drive 的20220517_082345行程中,frame_id 14200–14800(约 7 分 45 秒处)为进出隧道段。此处应做专项测试:
# 提取隧道段预测误差 tunnel_pred = model(X_tunnel) # X_tunnel: frame_id 14200-14790 的10帧序列 tunnel_true = y_tunnel # 真实 Δlong, Δlat(来自 track.jsonl 中 frame_id 14800 的状态) tunnel_mae = torch.mean(torch.abs(tunnel_pred - tunnel_true)).item() # 对比常规路段(如 frame_id 5000-5500) normal_mae = ... # 同理计算 print(f"隧道段 MAE: {tunnel_mae:.3f}m | 常规路段 MAE: {normal_mae:.3f}m") # ✅ 健康指标:tunnel_mae / normal_mae < 1.8(光照突变不应导致误差翻倍)我的习惯是:每次修改对齐逻辑后,必跑一次隧道段 MAE。如果这个比值 > 2.0,说明时间戳没对齐好,或者
lane.json的多项式没正确应用——宁可花 2 小时 debug,也不让模型在黑暗中盲目学习。Whitemud Drive 教会我的最重要一课是:在真实交通场景中,数据质量永远比模型复杂度重要十倍。希望帮到你。
本文还有配套的精品资源,点击获取