Mapillary街道级序列数据集:构建跨区域视觉定位与SLAM评测的完整指南 📅 发布时间:2026/9/8 1:33:53 👁 浏览次数: 简介Mapillary街道级序列MSLS是面向长期位置识别的大规模街道级图像数据集涵盖160万张图像可为计算机视觉研究者提供外观变化下的训练与评测基准。压缩包共20个文件以9个Python脚本为主涵盖数据加载器、评估脚本和模型训练逻辑另含4个CSV预测结果样例、3个Markdown说明、1个Jupyter Notebook演示及配置文件等整体仅2.21MB轻量便于快速上手。目前已有664人学习使用。数据集按序列组织便于评估跨时间、跨场景的外观鲁棒性。资源中内置PyTorch数据加载器支持数据库/查询图像评估与三元组训练两种模式独立的评估脚本可直接读取文本预测并输出指标配合示例CSV可复现论文中基于ResNet50骨干与广义均值层的基线结果适合刚接触位置识别的读者快速搭建实验流程也可作为基础代码拓展新的训练策略。 去年我在跑跨区域视觉定位实验时卡在一个非常现实的问题上KITTI 跑熟了就那么几条固定路线nuScenes 的传感器套件贵得离谱Oxford RobotCar 虽然序列长但同样被限定在单一城市和固定场景。想验证算法在“真实世界到底行不行”手上却没有一套既免费、又覆盖多区域、还带完整连续帧信息的数据集。后来我把目标转向 Mapillary——这个众包街景平台汇集了全球大量贡献者用手机、行车记录仪和全景相机拍下的海量图像而且每条拍摄轨迹天然就是一个序列。围绕它我做了一套名为 mapillary_slsMapillary Street-Level Sequence枫叶街道级序列数据集的整理管线把零散的众包图像变成了可以直接喂给视觉定位、SLAM 和地点识别任务的序列数据。这篇文章把我从踩坑到跑通的完整过程写出来包括数据源摸底、筛选链路、对齐清洗和实测效果给准备自己做数据集的同学一条能直接参考的路线。1. 为什么偏要折腾一套街道级序列数据集1.1 现有基准数据集在序列任务上的三个尴尬先说痛点。做视觉定位或者序列匹配这类任务理想数据集至少得满足三个条件一是覆盖足够多样化的街道路网不是来回几条环路二是每段轨迹必须有严格的连续帧关系帧与帧之间的位姿变化是可知的三是数据获取成本不能高到普通实验室承受不起。但市面上的数据集总有短板。KITTI 和 Cityscapes 覆盖面窄、路线固定长期用来做评测很容易出现“在训练集上调参”的假象。Oxford RobotCar 确实提供了长达一年的重复轨迹采集但传感器套件和车辆成本摆在那里普通人不可能复刻。nuScenes 更不用说了毫米波雷达加激光雷达加多相机整套下来不是一般团队能碰的。更要命的是这些数据集的时间跨度和天气变化基本是预设好的一旦你的算法要应对“真实世界里毫无规律的光照和季节”泛化能力根本测不出来。1.2 Mapillary 这条众包路径的独特杠杆Mapillary 和以上数据集最大的区别是数据来源它不靠专用采集车而是靠全球无数贡献者日常拍摄积累。有人在车里装行车记录仪有人拿手机边走边拍还有人用 GoPro 类运动相机扫街。平台会把同一条轨迹的图像聚合成一个序列时间戳、GPS 轨迹、朝向信息、相机参数等元数据一应俱全。截至我整理数据的阶段平台上的图像规模已经达到十亿级覆盖的城市数量远超任何单一采集项目。这个规模意味着什么意味着你不用再守着某一条固定路线去验证算法而是可以按区域、城市甚至国家去切分训练集和测试集真正做到跨场景评估。对于做视觉地点识别VPR和视觉重定位的团队来说这种数据源几乎是免费的富矿。我在构建 mapillary_sls 之前先跑了一批小样随手挑了三座不同风格的城市序列的街道类型、建筑立面、植被密度和光照条件差异非常明显这比以往数据集里“换条街换个建筑”的弱变化强太多了。1.3 命名含义与数据集的定位数据集代号叫“枫叶”没有太复杂的含义。我一开始只是觉得这片叶子的脉络形象很适合描述“一条条轨迹像叶脉一样延伸”的序列结构后来沿用下来。mapillary_sls 的定位不是单纯把 Mapillary 的图片打包成一个静态图像集而是把原始图像库加工成一套带轨迹、带时序、带位姿关系的可用序列数据。换句话说它的价值不在单帧图像本身而在帧与帧之间的连续性。这个定位决定了后续所有处理动作的出发点凡是破坏连续性的数据哪怕单帧质量再好也要丢弃凡是能让序列关系更稳定的信息哪怕需要额外计算量也要尽量保留。2. Mapillary 数据源摸底众包采集的机遇和麻烦2.1 一个序列到底长什么样在动手写脚本之前我建议你先去 Mapillary 的 API 文档里弄清楚数据结构的底层逻辑。简单来说Mapillary 会为每一次连续拍摄生成一个sequence_id这个 ID 下面挂着一串图像每张图像都带有captured_at时间戳、经纬度坐标、朝向角compass_angle部分图像还包含相机内参和是否全景的标记。序列的长度差异非常大。有人开车连续拍了二十分钟一个序列里可能有大几百帧有人只是步行穿过一条街序列只有二三十帧。所以拿到原始序列后第一件事就是摸底统计序列帧数分布、轨迹总长分布、GPS 有效点比例、时间戳完整性。我把首批拉取的 10 万帧数据做了统计发现约三成序列的有效轨迹长度不足 30 米还有一成左右存在时间戳空值。这些统计结果直接决定了后面的筛选阈值该怎么定。2.2 众包设备的参差不齐是双刃剑众包采集最大的优势是多样性最大的坑也是多样性。有的贡献者用专业全景相机内参和姿态数据非常规范有的贡献者就是手机随手拍EXIF 信息里可能只有经纬度和时间连镜头焦距都不全。后者在构建序列时麻烦很大尤其是做视觉几何计算时相机内参缺失会直接导致重投影和三角化出问题。我的做法是分两级处理优先保留有完整相机参数的图像这部分用于几何相关的任务内参缺失的序列也不直接扔掉而是统一用针孔模型加估计的水平视场角做粗略标定用于对相机精度要求不高的任务比如基于特征的视觉地点识别。实测下来这种分级保留策略让最终数据集的可用帧数量增加了约四成。2.3 许可以及隐私问题必须提前处理Mapillary 上的图像数据遵循开放地图类许可协议使用前要仔细阅读平台的授权条款确认是否符合自己的用途。这里特别提醒一点如果你的数据集后续要开源或者用于商业项目许可问题是绕不开的法律关卡不是在代码里加个引用就万事大吉。我在整理 mapillary_sls 时专门保留了一份 README逐条记录了数据来源和授权说明。隐私方面Mapillary 平台本身已经对车牌和人脸做了自动模糊处理这部分工作不需要我们重复做。但从实际拉取的数据来看平台的处理偶尔会有遗漏尤其是角度比较奇怪的车牌。如果你要把数据集公开建议加一道自动检测和手动检查的流程。这不是技术问题但一旦出事比任何技术问题都麻烦。3. 从海量图库里筛出可用轨迹完整筛选链路3.1 格网采样先划定范围Mapillary 全球数据量太大不可能一股脑全部拉下来处理。我的做法是用格网采样划定目标区域把地图按经纬度切成 (0.01° \times 0.01°) 的格子大概一公里见方然后按城市或区域选取目标格网在格网内请求序列列表。这一步看似简单但格网大小直接决定了后续数据分布。格子太大市中心和郊区的轨迹混在一起很难控制场景多样性格子太小单个格网内的有效序列太少请求次数爆炸。实测下来一公里见方对城区街道数据比较合适既能保证一个格网内有足够的序列又能通过选择不相邻的格网显著提升场景差异。3.2 轨迹连续性检验把断裂的序列切出来Mapillary 的序列虽然天然连续但我在实际使用中发现两个常见问题一是拍摄者中途停车或设备休眠导致同一序列内出现长时间的时间间隔二是上传时丢帧物理上连续的轨迹在数据上却少了一段。我用两个指标做连续性检验。第一是时间间隔相邻两帧时间戳之差超过十秒就视为断点从断点处把序列切成两段独立子序列第二是空间跳变相邻两帧 GPS 距离除以时间差得到的瞬时速度如果超过 80km/h说明要么 GPS 漂移要么轨迹确实断了。这样处理之后一个原始序列往往能拆出两个甚至三个干净的子序列。宁可让序列变短也不能让序列内部出现不可信的位姿变化。3.3 模糊帧和重复帧过滤质量兜底序列筛选不能只看轨迹几何图像质量同样关键。众包拍摄里常见的模糊帧主要来自快速转动手机、车辆颠簸和夜间长曝光。我用拉普拉斯算子的方差来做模糊检测先把图像归一化到统一分辨率再计算灰度图的拉普拉斯方差低于阈值就判定为模糊。具体阈值需要根据你的图像分辨率调整我这边在 512 宽度的归一化尺度下取的是 80低于这个值的帧基本是肉眼可感知的模糊。重复帧的问题主要出现在低速行驶或等红绿灯场景车辆不动但系统仍然以固定频率出图导致连续十几帧几乎完全一致。这个坑用感知哈希就能解决对每帧图像计算 dHash再和前一帧比较汉明距离距离小于 10 就视为重复帧从序列中剔除。要注意的是重复帧剔除后一定会产生新的时间间隔所以要把这一道工序放在连续性检验之前剔除后再跑一遍断点切分。3.4 序列去冗余保证时空采样均匀经过前几步筛选之后序列基本连续可用了但还有一个问题有些路段被不同贡献者反复拍摄而另一些路段几乎没有覆盖。这种不均匀会严重影响训练和评测的公平性。我采用按空间去冗余的策略把目标区域内的轨迹投影到二维格网上每个格网内最多保留数条轨迹优先保留轨迹长度较长、时间戳跨度较大、图像分辨率较高的序列。这一步做完数据集的“地图覆盖均匀度”会有肉眼可见的提升。后来我在做跨区域检索实验时明显感觉到训练集和测试集之间不再因为区域分布不均而产生虚假的性能波动。4. 目录结构、时间戳与坐标系对齐4.1 一期数据集的目录设计数据筛选完之后最重要的事情就是设计一套清晰、可扩展的目录结构。mapillary_sls 一期版本的目录组织如下mapillary_sls/ README.md data/ seq_0001/ images/ 000000.jpg 000001.jpg ... poses.txt timestamps.txt calibration.json seq_0002/ ... splits/ train.txt val.txt test.txt这里有几个细节值得说明。首先序列 ID 我重新进行了连续编号不再沿用 Mapillary 原始的字符串 ID避免后续脚本处理时需要处理长字符串。其次每个序列独立保存poses.txt和timestamps.txt两个文本文件前者是每帧的位姿后者是对应的采集时间这样训练时可以直接逐行读取不需要解析 JSON。calibration.json单独存放相机内参的另一个原因是同一序列内可能出现不同设备拍摄的混合情况内参并不唯一这种情况下我会在文件里列出多个 camera_id 对应的参数并在poses.txt里增加一列指明每帧使用的相机。4.2 GPS 坐标到局部坐标系的转换Mapillary 提供的是 WGS84 经纬度坐标直接拿来做视觉几何计算非常不方便。我使用 UTM 投影把经纬度转换为平面坐标然后以每个序列的第一帧位置为原点建立局部坐标系。这样每个序列内部的位姿就变成了一个独立的局部坐标便于做视觉定位和 SLAM 的输入。转换本身用pyproj库就能实现几行代码的事。但有一个坑要注意同一序列跨越 UTM 分带边界时直接投影会导致坐标跳变。这种情况虽然少见但一旦出现序列末尾的几十帧坐标会整体偏移。我加了一道检测序列内 UTM 坐标的 x 方向跨度过大或者出现明显不连续跳变时就标记为异常序列人工进一步确认。4.3 时间戳对齐与 GPS 平滑时间戳对齐是序列任务里最容易翻车的环节。Mapillary 的元数据里带有captured_at毫秒时间戳但有一个不太起眼的坑部分图像文件自身的 EXIF 拍摄时间和元数据里的时间不一致相差从几秒到几分钟都有可能。我在处理管线里加入了一层交叉校验。每下载一张图像就读取它的 EXIF 时间和元数据时间戳做差。差值超过一秒的图像数量占整个序列的比例过高时说明这个序列的元数据不可靠直接弃用只有少量帧不一致时以元数据时间为准但在timestamps.txt里做合理插值补齐。GPS 平滑方面众包采集的 GPS 在城市峡谷里有明显的多径效应轨迹会出现锯齿状抖动甚至瞬移。我在筛选阶段用速度阈值剔除了瞬移帧但对剩余的锯齿抖动用滑动窗口的中值滤波把轨迹修平滑。注意这里用中值滤波而不是均值滤波均值滤波会把尖锐的真实转弯也抹掉中值滤波对孤立的离群点更有效同时保留几何特征。5. 在真实任务上跑出来的效果5.1 视觉地点识别序列约束带来的增益数据集的终极检验是放进真实任务里跑。我先把 mapillary_sls 切成城市级训练集和测试集然后跑了一个目前比较经典的视觉地点识别模型 NetVLAD基础配置保持论文原样只把输入换成本数据集的图像序列。单帧检索的结果说实话不算惊艳跨季节和跨光照场景下 top-1 检索精度明显下滑这符合预期。但当我加上序列约束后效果立刻不一样了用相邻两帧的 top-10 候选做投票top-1 准确率比单帧直接提升了不少。原因很好理解单帧图像在大光照变化下视觉特征不稳定但连续帧之间存在明显的地理约束投票机制把错误帧筛掉了。这也说明一个更底层的问题mapillary_sls 的价值不仅仅在于“多”而在于它保留了真实的连续关系让序列级的算法有机会发挥。如果你做的是单帧图像检索用这套数据也能跑但真正吃透它红利的一定是序列级方法。5.2 视觉重定位初始 GPS 噪声的真实挑战在视觉重定位任务上我用了一个传统的 2D-3D 匹配管线给定一张查询图像先用全局描述子检索出候选参考帧然后做特征匹配和 PnP 求解。真实场景里查询帧的初始位置来自 GPS误差少则三五米多则十几米。mapillary_sls 里的众包 GPS 数据正好复现了这种真实噪声。跑下来的体会是GPS 噪声较大时单帧重定位的失败率比较明显但一旦把连续三帧的 PnP 结果做 bundle 优化成功率提升显著。这说明在众包数据上序列间的几何一致性比单帧特征质量更能兜底。5.3 SLAM 里程计评估传感器噪声更接近量产场景我还把部分序列喂给了单目 ORB-SLAM3 做里程计评估。和 KITTI 上丝滑的表现不同这套数据集上的跟踪丢失明显更多主要原因是图像分辨率不一致、运动速度变化大、光照条件恶劣。乍看是劣势但换个角度想这正是量产级视觉 SLAM 的真实工作环境。对于做鲁棒 SLAM 的团队来说mapillary_sls 的这批噪声反而比精心标定的数据集更有评测价值。如果你的系统在这种数据集上能保持较低的丢失率在真实产品里的表现大概率更有保障。6. 文档里不会写的事故现场6.1 时间戳跳变与相机内参缺失的连锁反应第一个让我折腾了一个周末的坑是时间戳跳变。某个序列用筛选脚本检测时一切正常但送入 SLAM 系统后轨迹突然跳了一大截。排查了半天才发现这个序列的元数据里有一帧的captured_at比前后帧晚了整整四分钟肉眼检查时完全看不出来因为它没有导致轨迹断裂只是时间轴上出现了一个孤立异常值。这种孤立异常用速度阈值是查不出来的因为速度计算用的是距离除以时间差时间被放大后速度反而变得“正常”了。我后来在时间戳处理里加了一步把相邻时间差做排序发现某个时间差超过该序列中位数的数倍时就把它标记为异常帧并剔除以一帧为单位而不是把整个序列切断。这个教训是任何指标在用于筛选之前都要先想一想它会不会被另一个异常值“中和”掉。6.2 大批量下载时的断线与续传Mapillary 的图像下载并不复杂但数据量一大必然遇到网络中断和限流。一开始我用单线程循环下载结果跑到一半断开已经下载的文件不会自动跳过重新跑一遍又要浪费几个小时。后来我写了一个带断点续传的脚本核心逻辑很简单下载前先检查本地文件是否存在且大小非零如果存在就直接跳过每下载一个序列就更新一个进度文件重启后从进度文件恢复。不要小看这个粗糙的方案它节省的时间远超写脚本的成本。另外一个更隐蔽的问题是有些图片请求返回的是重定向链接直接保存重定向响应会得到 HTML 而不是图片这里一定要检查响应头和文件魔数。6.3 城市峡谷 GPS 漂移的诡异表现城市里高楼密集区域GPS 漂移的形态比教科书描述的复杂得多。有一个序列明明是在一条直路上行驶但轨迹图上出现了两三米宽的锯齿形抖动看起来像蛇一样在路中间扭动。中值滤波对这种抖动有一定抑制作用但滤波窗口不能开太大否则会把道路转弯处的几何特征抹平。我最后的折中方案是两步走先用速度阈值剔除明显的瞬移点再用窗口大小为七的中值滤波平滑残余抖动。窗口七是在几个序列上实测后的取法太短没效果太长转弯处会出现明显“切割感”。这种调参经验很难从文档里学到只能靠一个序列一个序列地看效果。6.4 不要迷信覆盖量最后一条经验是心态层面的不要见了 Mapillary 的十亿级图像规模就兴奋以为自己能建一个覆盖全球的超级数据集。实际处理下来真正能通过连续性检验、质量过滤、去重和许可确认的数据不到原始数据量的零头。更现实的做法是锁定几个目标城市把每个城市的序列质量做扎实。我一度试图同时处理二十个城市的数据结果每个城市都只做了七八成还得回头补工。后来老老实实一次只做三个城市每个都完整跑完评测再切下一批反而整体进度更快。这个数据集到现在已经迭代了好几版后续我打算把多季节重复访问的序列单独切出来做一套针对跨季节鲁棒性的评测集。如果你正在为视觉定位或地点识别任务缺数据发愁Mapillary 这套路线值得试一次别被前期的清洗工作吓退那些坑趟平之后你就会发现众包数据的大规模性和真实性是实验室采集完全给不了的。本文还有配套的精品资源点击获取