从概念到实践:Cityscapes街景语义分割数据集完整指南

发布时间:2026/9/3 17:40:07
从概念到实践:Cityscapes街景语义分割数据集完整指南 简介Cityscapes数据集一是面向城市街景语义分割任务的经典数据组件适用于自动驾驶、智能交通等场景的研究者与算法工程师帮助他们获取高质量的像素级真实标注支撑模型训练与评估。该压缩包共含2000个json文件大小约730.44MB全部为gtFine_polygons.json格式的精细多边形标注覆盖亚琛、汉诺威、斯特拉斯堡等欧洲多个城市的街景图像。每个标注文件详细记录图像中物体的边界坐标与类别归属共定义道路、建筑、行人、车辆等30个类别是训练DeepLab、FCN、U-Net等语义分割模型的核心监督信号。包内文件按城市和场景组织便于按需解析、转换为掩膜或直接用于模型预处理流程。已有2130人学习下载适合正在入门或深耕语义分割、需要真实街景标注数据的研究者作为训练与验证数据使用。 我刚开始做街景语义分割时第一反应是跑去下载 Cityscapes结果光是折腾该下哪些包、下载完怎么解压、解压完为什么训练脚本报类别数不对就浪费了快一整天。后来想想这套数据虽然名气大但很多细节不在 README 里社区里也大多是零散经验。这篇就把 Cityscapes 从入门到能用尽量一次说清楚。Cityscapes 是 2016 年 CVPR 放出来的城市街景数据集专门为自动驾驶场景理解设计。它不是那种拿过来就能傻瓜式开训的数据集里面有 fine/coarse 标注、polygon、instance mask、连续帧序列这些不同层次的内容新手很容易在某个环节被卡住。所以这篇先解决最基础也最关键的问题它到底是什么、怎么下载、目录结构怎么理解、标签体系怎么用、主流框架怎么接。1. 为什么一说街景语义分割大家都在提 Cityscapes1.1 三个数字先记住50 座城市、5000 张精标、19 个类别Cityscapes 的采集范围是德国多个城市为主也包含法国、瑞士等周边国家的城市一共 50 个城市。采集车装的是立体相机在正常天气、白天、春夏季节的城区道路上行驶采集了视频序列。官方精选出 5000 张做像素级精细标注其中 2975 张作为训练集、500 张作为验证集、1525 张作为测试集。图像分辨率统一是 2048x1024也就是 2K 级街景图。语义分割的评估类别是 19 类道路、人行道、建筑、墙、栅栏、电线杆、交通灯、交通标志、植被、地形、天空、行人、骑手、汽车、卡车、公交车、火车、摩托车、自行车。这 19 类基本覆盖了城市道路场景里最常见的交通参与者和静态物体。比这三组数字更重要的是它的标注风格。Cityscapes 不是画个框或者抠一张图的轮廓而是对每个物体的多边形轮廓做精细标注还区分了实例。这种标注质量在当年是天花板级别到今天仍然是学术界和工业界做街景任务的基准。1.2 和 VOC、COCO 这些老朋友有什么不同很多入门者会问我又不是没玩过 VOC 和 COCOCityscapes 到底特别在哪我把三套数据放在一起对比一下区别就很直观。对比项PASCAL VOC 2012MS COCOCityscapes定位通用物体识别复杂场景理解城市街景/自动驾驶图片数量约 1.1 万张约 33 万张5000 张精标 2 万张粗标分辨率不固定普遍较小不固定固定 2048x1024主要标注检测框、分割掩码检测框、实例掩码、全景语义掩码、实例掩码、全景类别体系20 类普通物体80 类物体丰富19 类全部面向驾驶场景标注层级不区分实例/语义区分实例/语义同一套图支持语义、实例、全景VOC 和 COCO 本质上是在解决图像里有什么物体通常是图像里的主体背景比较杂。Cityscapes 不一样它关注的是车辆行驶过程中整条街每一个像素分别属于什么连路面、天空、墙体这些背景区域都是重点研究对象。这也是为什么做自动驾驶感知、街景重建、域适应这些方向的人几乎绕不开它。1.3 它能解决的远不止语义分割虽然 Cityscapes 最出名的是语义分割但它其实是一套多任务数据语义分割19 类像素级分类最常用。实例分割car、person、rider、truck 等 8 个物体类别带实例编号可以用来训练 Mask R-CNN、YOLO 这类实例级模型。全景分割把 stuff道路、天空等和 thing车、人等合成一套任务Cityscapes 也有对应 benchmark。双目深度估计采集时用的是立体相机配套的 sequence 数据带左右目图像可做深度估计。视频预测与自监督官方也发布了连续帧序列很多视频语义分割、光流、自监督方法都拿它做验证。也就是说无论你做的是单帧分割、检测还是视频任务、深度估计Cityscapes 都能派上用场。这也是它十多年了还活跃在各类论文里的原因。2. 下载前先想清楚协议、网速和硬盘2.1 注册不是填个邮箱那么简单Cityscapes 官网需要注册下载进去之后要填机构、邮箱、使用用途还要勾选学术使用协议。填完提交后官方会邮件或页面返回下载权限然后才能看到下载链接。很多国内同学嫌麻烦去百度云盘或第三方镜像找现成压缩包。我建议别这么做。Cityscapes 的标注文件命名规范、目录结构在不同版本里可能有细微差别第三方转存的包不一定完整解压后目录对不上训练脚本排查起来更浪费时间。官方下载虽然慢一点但至少能保证结构和元数据是原汁原味的。注册时用途建议如实填写学术研究或者算法评估都会通过。如果是企业内部做商业项目要注意协议限制Cityscapes 最初是面向学术研究的商用前需要确认授权范围。2.2 一堆压缩包到底哪些才是你需要的这是下载环节最容易被绕晕的地方。官网下载页有好多 zip常见的有下面这些压缩包体积内容说明是否建议下载leftImg8bit_trainvaltest.zip约 11GB5000 张原始图片包含 train/val/test语义分割必下gtFine_trainvaltest.zip约 241MBfine 级精细标注只含 train/val语义分割必下gtCoarse.zip约 1.3GBcoarse 级粗标注含 train/val/train_extra做预训练/半监督再下leftImg8bit_trainextra.zip约 45GB与 coarse 对应的额外图片做预训练/半监督再下leftImg8bit_sequence_trainvaltest.zip约 322GB连续帧序列做视频/深度任务再下需要注意一个细节gtFine_trainvaltest.zip名字里写着 trainvaltest但实际下载后 test 目录下并没有 ground truth测试集的标签是不公开的需要通过官方评估服务器提交结果。很多人第一次用的时候以为下坏了其实不是这是 Cityscapes 的常规设定。如果你只是跑语义分割的入门实验下载前两个包就够了总共 12GB 左右。如果硬盘有限可以暂时跳过 trainextra 和 sequence 那些大块头。2.3 下载慢的土办法但很稳官网服务器在欧洲直接浏览器下载经常断。我的做法是拿官方生成的下载链接配合命令行工具下curl -C - -O https://www.cityscapes-dataset.com/file-handling/?packageNameleftImg8bit_trainvaltest.zip curl -C - -O https://www.cityscapes-dataset.com/file-handling/?packageNamegtFine_trainvaltest.zip-C -参数用来断点续传即使中间断了重新执行会从断点接着下不用重头再来。建议再配合 tmux 或者 nohup 挂到后台网速再差挂一个晚上也基本能下完。下完之后别急着删压缩包先把压缩包大小和网页标注的体积比对一下。如果差很多说明下载不完整解压后多半会在训练时莫名报错。3. 解压之后先别急着训练把目录结构看懂3.1 一张图的文件名里藏了多少信息解压后会发现目录是按 train/val/test 分开的每个子目录里又是一个一个城市名城市目录里才是图片文件。比如leftImg8bit/ train/ aachen/ aachen_000000_000019_leftImg8bit.png bochum/ ... val/ test/ gtFine/ train/ aachen/ aachen_000000_000019_gtFine_polygons.json ... val/文件名格式是城市名_序列号_帧号_leftImg8bit.png比如aachen_000000_000019_leftImg8bit.png表示 aachen 城市、第 0 个序列、第 19 帧。标注文件和图片同名只是后缀变成了_gtFine_xxx.png。这个命名规则看起来很朴素但实际有用它保证了图片和标注可以按名字一一对应。自己写 dataloader 时直接替换文件后缀就能配对不需要额外维护映射表。3.2 gtFine 下的五件套要分清gtFine里每一张图都有好几份标注文件刚接触会觉得很冗余其实每份文件服务一种任务文件后缀内容用途_gtFine_color.png彩色可视化标注图给人看不参与训练_gtFine_labelIds.png原始类别 id 图像素值稀疏且不连续一般不直接训练_gtFine_labelTrainIds.png训练用 19 类编号0~18忽略区域为 255语义分割训练直接用它_gtFine_instanceIds.png实例编号图像素按1000*类别ID实例ID编码实例分割、检测_gtFine_polygons.json多边形原始标注可转 mask、可做几何处理很多朋友第一次拿到数据直接把_gtFine_labelIds.png当成训练标签喂进 CrossEntropyLoss预测完发现类别突然多出十几个还怎么调都跑不对。原因就是 labelIds 和 labelTrainIds 不是一回事。3.3 labelIds 和 labelTrainIds 是新手最容易忽略的坑Cityscapes 官方维护了一套完整的类别定义里面有很多细分类别比如 unlabeled、ego vehicle、rectification border、out of roi、static、dynamic、ground 这些。这些不是用来参与评估的而是标注人员在标注时遇到的特殊情况。在_labelIds.png里road 的像素值可能是 7building 是 9这些编号是官方 labels.py 里的原始 id并不连续而且带有大量 ignore 类别。在_labelTrainIds.png里官方重新映射了一套 0~18 的连续编号由评价体系使用trainId类别名称对应颜色0road(128, 64, 128)1sidewalk(244, 35, 232)2building(70, 70, 70)3wall(102, 102, 156)4fence(190, 153, 153)5pole(153, 153, 153)6traffic light(250, 170, 30)7traffic sign(220, 220, 0)8vegetation(107, 142, 35)9terrain(152, 251, 152)10sky(70, 130, 180)11person(220, 20, 60)12rider(255, 0, 0)13car(0, 0, 142)14truck(0, 0, 70)15bus(0, 60, 100)16train(0, 80, 100)17motorcycle(0, 0, 230)18bicycle(119, 11, 32)所有不属于 19 类的像素在 labelTrainIds 里统一是 255也就是训练时需要用ignore_index255跳过的区域。理解了这张表后面很多问题都能自己推出来。4. 从多边形标注到能喂进网络的 mask4.1 polygons.json 里到底存了什么Cityscapes 的原始标注是矢量多边形而不是一上来就给好 mask。打开一个_gtFine_polygons.json结构大概是这样的{ imgHeight: 1024, imgWidth: 2048, objects: [ { label: car, polygon: [[1023, 512], [1030, 515], ...], instanceId: 130123 }, { label: road, polygon: [[0, 600], [10, 620], ...] } ] }objects里每个对象代表一个标注物体polygon是这个物体轮廓的顶点坐标集合。像 car、person 这类有实例概念的类别会带instanceId像 road、building 这类背景类别通常只有轮廓没有实例编号。这里要注意同一个物体的多边形可能不止一个外轮廓有的会有洞有的被遮挡后会分成多段。直接把顶点数组简单填充容易把物体内部的洞也填进去。所以能用官方脚本生成 mask 就尽量别自己造轮子。4.2 官方脚本把 polygon 转成 mask官方在cityscapesscripts工具包里有现成脚本。如果下载的 gtFine 包比较老缺_labelTrainIds.png可以这样生成pip install cityscapesscripts export CITYSCAPES_DATASET/path/to/cityscapes python cityscapesscripts/preparation/createTrainIdLabelImgs.py脚本会读取gtFine下的 polygons按类别优先级画多边形依次生成_labelTrainIds.png。它不是简单填充所有多边形而是先画 ignore 区域再画 stuff 类别最后画 instance 类别确保被遮挡物体不会被后面的多边形盖掉。如果只是临时想转一张图看看也可以自己写一段轻量逻辑import json from PIL import Image, ImageDraw with open(aachen_000000_000019_gtFine_polygons.json) as f: gt json.load(f) mask Image.fromarray(255 * np.ones((1024, 2048), dtypenp.uint8)) draw ImageDraw.Draw(mask) for obj in gt[objects]: train_id label_name_to_train_id[obj[label]] draw.polygon([tuple(p) for p in obj[polygon]], filltrain_id) mask.save(test_labelTrainIds.png)不过自己写的时候要注意绘制顺序如果两个物体的多边形有重叠后绘制的会覆盖先绘制的。这也是我建议优先用官方脚本的原因坐标变换、层级关系这些细节它都处理好了。4.3 训练之前把标注可视化一遍拿到数据后我强烈建议先把标注可视化走一遍。不用多复杂直接用_gtFine_color.png叠加到原图上就能直观看到每个物体的边界和类别。这个习惯能提前暴露很多问题图片和标注是否对齐、类别定义是否符合你的业务需求、ignore 区域是否覆盖了不该覆盖的地方。比如同一个场景在不同城市可能标注风格会漂移有的把土地标成 terrain有的标成 vegetation边界很脏。如果你直接拿这种标签训练模型在边界上的预测会很不稳定。先可视化能让你判断这些噪声是普通现象还是数据异常心里有底。5. 在 mmsegmentation、MMDetection、YOLOv8 里直接上手5.1 mmsegmentation 开箱即用数据路径别放错MMSegmentation 官方已经把 Cityscapes 的支持写好了最大的好处是类别映射、dataloader、评估脚本全部对齐。目录结构只要按约定放data/cityscapes/ leftImg8bit/ train/ val/ test/ gtFine/ train/ val/然后在配置里继承或修改_base_/datasets/cityscapes.py把data_root指到data/cityscapes/再把模型配置里的num_classes设为 19就可以开始训练。seg_map_suffix默认是_gtFine_labelTrainIds.png所以官方包里的 labelTrainIds 就是 mmsegmentation 直接吃的标签。我遇到过一些人把_gtFine_labelIds.png手工重命名成_gtFine_labelTrainIds.png去糊弄路径结果训练时类别突然变成几十个loss 怎么都降不下去。这种低级错误排查起来真的很耗时路径和标签类型务必先核对。5.2 转 YOLO 格式类别要从 0 重新编号别直接抄 trainId用 YOLOv8 训练自己的目标检测或实例分割模型时Cityscapes 没有现成的 YOLO 标注文件需要把 instanceIds 或者 polygons 转成 YOLO 格式。一个常见错误是把_gtFine_labelTrainIds.png当成分割标签然后发现类别 id 是 0~18就直接按这个编号写到 YOLO 的 txt 里。这会导致道路、建筑这类 stuff 也变成检测目标。如果只想检测人、车等交通参与者需要自己定义一个新的类别顺序比如0: person 1: rider 2: car 3: truck 4: bus 5: train 6: motorcycle 7: bicycle转换时遍历_instanceIds.png里的每个实例单独生成掩码再算外接矩形或者提取轮廓多边形归一化之后写入 txt。实例编码是1000 * 类别ID 实例ID取整除以 1000 就能得到类别 ID这个转换逻辑在 batch 处理时要注意。小面积的实例比如只有几个像素的远车建议过滤掉不然 YOLO 训练时会被大量噪声框干扰。5.3 训练时最容易踩的 3 个坑通道顺序、ignore_index、输入尺寸第一通道顺序。不管是 mmseg 还是其他基于 OpenCV 的框架读取图片默认是 BGR但 Cityscapes 官方的 color 可视化是按 RGB 维护的。如果你做可视化或者用 ImageNet 预训练权重做迁移要确认预处理是否和预训练要求一致。这个坑不会让训练崩掉但会让输出图颜色发蓝发红容易误导人以为模型学坏了。第二ignore_index。Cityscapes 的 mask 里大量像素是 255PyTorch 的 CrossEntropyLoss 如果不设置ignore_index255会把 255 也当做一个类别去算 loss那结果就是灾难。用框架时看清楚默认参数自己写训练循环时更要注意。第三输入尺寸。Cityscapes 原图 2048x1024直接整图训练显存需求高得吓人。常规做法是训练时随机缩放加裁剪到 512x1024 或 1024x1024。实测下来在 512x1024 分辨率下训练val mIoU 损失很小训练速度却快很多。后面如果追求高指标再考虑两阶段先用低分辨率训起来再用高分辨率 finetune。6. 这一篇最后聊聊我用 Cityscapes 做实验的心得6.1 类别不均衡比你想象的严重Cityscapes 看起来是一个很均衡的街景数据但实际上 road、sky、vegetation 这几类占了大半图像person、rider、motorcycle 这类小目标占比非常低。mIoU 是 19 类直接平均每类权重一样所以小类别很容易拖后腿。我自己的经验是如果跑基准模型发现 mIoU 卡在某个数值上不去去翻一翻按类别的 IoU通常 rider、motorcycle、wall 这几类是重灾区。可以在 loss 上做调整比如在线难例挖掘 OHEM、Focal Loss或者给尾部类别加权重都比盲目堆模型参数量更有效。6.2 2048x1024 怎么喂给 GPU除非你显存非常宽裕否则别一上来就整图训练。我试过在单张 24GB 显存的卡上训 1024x2048batch size 只能开到很小训练速度也慢得难受。常规做法是训练阶段用 512x1024推理阶段再用原图或者用滑窗。测试集需要交付给官方服务器评测时模型输入尺寸和训练时尽量保持一致不然分辨率变化会引入域偏移分数反而不稳定。6.3 test 集没有 ground truth别白等这一点真的值得再强调一次。Cityscapes 的 test 集只有图片没有公开标签。本地验证用 val 集就够了官方也在 val 上提供了完整的 gtFine 标注。想刷排行榜、看 test mIoU需要把预测结果打包上传到官网评估服务器。所以你的实验流程应该是在 val 上调参验证确认模型稳定后再跑 test 提交。6.4 别被漂亮分数骗了域差异在这套数据上真存在Cityscapes 上刷到很高的 mIoU不代表模型在你的真实业务场景里一定好用。它的采集条件是晴天、白天、西欧城市街道和实际部署中常见的雨天、夜晚、不同地区街道差异很大。很多模型在 Cityscapes val 上能到 80 以上 mIoU换到夜间街景或者雪天数据直接掉到 40 以下这不是模型 bug是数据集本身域太窄。所以我建议不要把 Cityscapes 的分数当成终点它更像是一个基础体检表。想进一步提升真实场景泛化能力可以关注夜间变体、恶劣天气变体、以及用 coarse 标注做半监督预训练这些方向。最后分享一个我自己的习惯拿到任何一个新数据集先别急着丢进训练脚本。我会先花半天把标注可视化看一遍统计每个类别的像素占比搞清楚 ignore 区域是怎么定义的。这件事看起来不起眼但能省掉后面大量排查训练日志的时间。Cityscapes 这套数据值得慢慢啃这一篇先把数据层面理清楚下一篇再继续拆标签细节和训练调参。本文还有配套的精品资源点击获取