简介:本资源为Cityscapes数据集的精细标注子集(第一部分),面向计算机视觉方向的研究者、深度学习工程师及自动驾驶算法学习者,聚焦像素级语义分割任务,支撑模型训练、泛化能力验证与IoU等核心指标评估。压缩包含2000个JSON格式文件,均为gtFine_polygons类型,即基于多边形标注的高精度真值标签,涵盖道路、车辆、行人等30类城市场景对象,可直接用于U-Net、DeepLab等分割网络的数据加载与监督训练。资源大小730.44MB,结构精简、无图像冗余,适配快速下载与本地标注解析需求。目前已有2144人学习下载,读者可直接获取欧洲30城街景的标准化精细标注样本,结合官方训练/验证/测试划分逻辑开展数据预处理、标签映射、损失函数调试及分割结果可视化等全流程实践,是构建鲁棒城市理解模型的关键基础组件。
1. Cityscapes 数据集(一):不是“又一个街景图库”,而是自动驾驶感知模块落地前必须啃透的硬标尺
你手头刚跑通一个语义分割模型,在自制小数据集上 IoU 轻松破 85%,信心满满切到真实道路场景——结果在交叉口区域漏检三辆自行车、把施工锥桶识别成“road”、雨天反光路面直接崩出大片噪声。这不是模型不行,大概率是你还没真正和 Cityscapes 打过照面。它不是公开数据集中最“大”的那个,但却是工业界公认的城市道路场景感知能力校准基线:50 类精细标注(含 19 类主类别+31 类细粒度子类)、2975 张高质量全帧精细标注图 + 20000+ 张粗略标注图、严格按驾驶视角采集(前视双目+GPS+IMU同步)、所有图像均经人工逐像素校验。它不解决“能不能训出来”的问题,专治“训出来后敢不敢上车”的焦虑。适合正在做 ADAS 模块验证、车载视觉算法鲁棒性压测、或准备投稿 CVPR/ICCV 目标检测/分割方向论文的工程师与研究生——尤其当你发现自己的模型在 BDD100K 上表现尚可,但在 Cityscapes val set 上掉点超过 3.5% 时,这份数据集就是你该打开的第一份“诊断报告”。
2. 数据结构解剖:从 raw 文件夹到 gtFine 的三层嵌套逻辑
Cityscapes 的目录设计不是随意堆叠,而是按“采集原始性→标注精细度→任务适配性”三级分层。理解这三层,才能避免后续加载时反复解压、重命名、路径报错。
2.1 第一层:raw —— 原始传感器数据的“时间胶囊”
leftImg8bit/下存放的是未经任何处理的 8-bit 彩色图像,分辨率统一为2048×1024(注意:不是常见的 1920×1080),采用cityscapes/leftImg8bit/train/aachen/aachen_000000_000019_leftImg8bit.png这种四级路径结构。关键点在于:
train/val/test三目录严格物理隔离,test 集无标注,仅用于官方服务器评测;aachen/bochum/...是采集城市名,共 50 个,每个城市下图像按xxx_yyy_zzz编号,其中xxx为序列 ID,yyy为帧序号,zzz为采集设备标识;- 所有图像均为 JPEG 格式,但色域为 sRGB,非 Adobe RGB,若用 OpenCV 读取后直接送入训练,需确认
cv2.cvtColor(img, cv2.COLOR_BGR2RGB)后是否触发 gamma 校正(实测多数框架默认忽略,建议显式转为 float32 并除以 255.0)。
提示:不要试图用
glob.glob("leftImg8bit/**/*png")一次性扫全路径——Cityscapes 官方脚本强制要求路径必须含城市名一级目录,否则csHelpers.py会抛KeyError: 'city'。这是第一处隐性约束。
2.2 第二层:gtFine —— 精细标注的“黄金标准”来源
gtFine/是核心标注目录,结构与leftImg8bit/完全镜像:gtFine/train/aachen/aachen_000000_000019_gtFine_labelIds.png。这里藏着三个关键文件:
labelIds.png:单通道 uint8 图像,像素值 0~33 对应 34 个语义类别(含unlabeled=0,ego vehicle=1,rectification border=2等特殊类),这是训练分割模型的直接标签;instanceIds.png:单通道 uint16 图像,编码格式为class_id * 1000 + instance_id,用于实例分割任务(如区分同一类别的不同车辆);color.png:三通道 RGB 可视化图,仅用于调试,不可用于训练(因含抗锯齿伪影,且部分颜色映射非一一对应)。
import numpy as np from PIL import Image # 正确加载 labelIds.png 的方式 label_path = "gtFine/train/aachen/aachen_000000_000019_gtFine_labelIds.png" label = np.array(Image.open(label_path), dtype=np.uint8) # 必须指定 dtype,PIL 默认 int32 print("Unique labels:", np.unique(label)) # 输出应为 [0 1 2 ... 33]代码说明:
np.array(Image.open(...))若不指定dtype,PIL 会返回 int32 数组,导致后续torch.nn.CrossEntropyLoss输入维度报错(期望 long 类型)。这是新手高频翻车点——看似加载成功,实则数据类型已污染。
2.3 第三层:gtCoarse —— 大规模预训练的“燃料补给站”
gtCoarse/存放 20000+ 张粗略标注图,由众包平台完成,精度约 70%(官方评估)。其价值不在最终评测,而在冷启动阶段的数据增广:
- 与
leftImg8bit/中的train_extra/目录配对(注意:train_extra不在leftImg8bit/train/下,而是平级目录); - 标注格式同
gtFine,但文件名后缀为_gtCoarse_labelIds.png; - 实践中,我通常将
gtCoarse与gtFine的train集合并,按 8:2 划分训练/验证集,再用gtFine/val作为独立测试集——这样既保证验证集纯净,又让模型早期见过更多城市变体。
3. 标签体系深度解析:为什么 19 类主类 + 31 类细类是工业落地的刚需
Cityscapes 的标签设计远超学术竞赛的“19 类分割”表象。它的labelTrainIds.png(位于gtFine/同级目录)才是工程部署的真相入口。
3.1 从 34 类原始标签到 19 类训练标签的映射逻辑
原始labelIds.png含 34 类,但其中out of roi=1,static=3,dynamic=4,ground=5等 15 类被归为ignore(值设为 255),仅保留 19 类有效类别参与 loss 计算。这个映射由csLabelTools.py中的create_trainId_to_evalId_mapping()函数定义。关键参数如下:
| 原始 labelId | 类别名 | trainId | 是否参与训练 | 说明 |
|---|---|---|---|---|
| 0 | unlabeled | 255 | 否 | 全图背景,强制 ignore |
| 1 | ego vehicle | 255 | 否 | 自车部件,遮挡区域 |
| 7 | road | 0 | 是 | 主干道 |
| 8 | sidewalk | 1 | 是 | 人行道 |
| 24 | person | 11 | 是 | 行人(含骑车人) |
| 26 | rider | 12 | 是 | 骑行者(单车/摩托) |
注意:
rider与person分离是 Cityscapes 的核心设计——在高速路口,骑行者行为模式与行人截然不同,混为一类会导致模型在预测“是否需紧急制动”时误判。这是它区别于 COCO 或 Pascal VOC 的本质差异。
3.2 细粒度子类(31 类)如何支撑功能安全验证
gtFine/中的labelIds.png实际包含 31 个细粒度子类(如car,truck,bus,train,motorcycle,bicycle),它们全部映射到trainId=13(即vehicle大类)。但当你做 ASAM(Automotive Safety Analysis Methodology)合规验证时,必须能回答:“模型对卡车的召回率是否显著低于轿车?”。此时需启用子类统计:
# 加载子类标签(需额外下载 gtFine_polygons.json) import json with open("gtFine/train/aachen/aachen_000000_000019_gtFine_polygons.json") as f: data = json.load(f) for obj in data["objects"]: if obj["label"] == "truck": print("Truck polygon points:", obj["polygon"][:3]) # 输出前3个顶点坐标代码说明:
polygons.json提供矢量级标注,比labelIds.png的栅格标注精度高一个数量级,适用于 ISO 21448 SOTIF 场景分析——例如验证模型在“卡车侧方盲区”区域的响应一致性。
3.3 城市风格迁移的隐藏线索:光照/天气/季节标签
Cityscapes 官方未提供元数据 CSV,但通过解析文件名可提取强相关特征:
aachen_000000_000019中000000为序列 ID,同一序列内图像采集时间间隔 < 2s,可视为同场景连续帧;- 所有
train集图像采集于2015–2016 年春季/夏季,val集含少量秋季图像(树叶变色),test集含冬季雪景(需单独下载leftImg8bit_testextra.zip); - 关键技巧:用
exiftool读取 JPEG EXIF,可获取拍摄时间戳(DateTimeOriginal)与 GPS 坐标(GPSLatitude/GPSLongitude),结合 OpenStreetMap API 反查该位置当日天气(晴/多云/小雨),构建光照条件标签。
4. 避坑指南:五个让模型训练突然崩溃的真实现场
这些坑我都在某高校自动驾驶实验室的实车项目里踩过,血泪经验总结如下:
4.1 现象:PyTorch DataLoader 报OSError: Too many open files,进程卡死
原因:Cityscapes 单个train目录含 2975 张图,若num_workers > 4且未设置ulimit -n 8192,Linux 默认文件句柄数(1024)被迅速耗尽。
解决:在训练脚本开头插入import resource; resource.setrlimit(resource.RLIMIT_NOFILE, (8192, 8192)),或在 shell 中执行ulimit -n 8192后再运行。
4.2 现象:模型在val集上 mIoU 突然跳变 ±5%,但train损失曲线平滑
原因:gtFine/val/中部分城市的图像存在labelIds.png与leftImg8bit/val/尺寸不匹配(如2048x1024vs2047x1023),OpenCV 读取后自动 padding 导致标签偏移。
解决:加载时强制校验尺寸:
img = cv2.imread(img_path) label = cv2.imread(label_path, cv2.IMREAD_UNCHANGED) assert img.shape[:2] == label.shape, f"Size mismatch: {img.shape} vs {label.shape}"4.3 现象:使用cv2.resize()对标签图下采样后,验证指标暴跌
原因:cv2.resize(label, (512,256), interpolation=cv2.INTER_NEAREST)中INTER_NEAREST是唯一正确插值方式,若误用INTER_LINEAR会产生非法中间值(如 7.3),torch.nn.CrossEntropyLoss报IndexError。
解决:永远用cv2.INTER_NEAREST处理标签图,且 resize 后需label = np.clip(label, 0, 19)二次校验。
4.4 现象:gtCoarse数据加载后unique labels出现 35+ 值
原因:gtCoarse/中部分图像由不同众包团队标注,存在trainId映射不一致(如某团队将wall标为 18,另一团队标为 19)。
解决:弃用gtCoarse/labelIds.png,改用官方提供的gtCoarse/train_extra/..._gtCoarse_labelTrainIds.png(已统一映射)。
4.5 现象:模型在test集提交后,car类召回率 99.2%,但rider类仅 83.7%
原因:test集中rider样本量仅占train集的 1/12,且多为小目标(< 32×32 像素),而你的数据增强未启用RandomResizedCrop(scale=(0.5, 2.0))。
解决:对rider类样本单独加权采样,并在transforms中加入albumentations.RandomScale(scale_limit=0.5, p=0.7)。
5. 验证 pipeline 构建:用 Cityscapes val 集做一次完整的模型健康体检
拿到一个新模型,别急着跑 test server,先用val集做四层穿透式验证。这套流程我在某公司 L2+ 系统交付前强制执行,曾提前两周发现一个traffic light类在黄昏场景下的系统性漏检。
5.1 第一层:基础指标验证(5 分钟)
用官方cityscapesScripts/evaluation/evalPixelLevelSemanticLabeling.py脚本生成results.json,重点盯三个指标:
road类 IoU ≥ 98.5%(若 < 97%,检查是否误将sidewalk当road);person类 Recall ≥ 92%(若 < 90%,检查ignore区域是否过大);rider类 Precision ≥ 85%(若 < 80%,检查 NMS 阈值是否过高)。
提示:
evalPixelLevelSemanticLabeling.py默认输出confusion matrix,但实际只需关注per class results表格中的IoU列。其他列(如acc)因ignore类占比高而失真。
5.2 第二层:场景切片分析(15 分钟)
将val集按city和time-of-day切片,用自定义脚本统计各 slice 的person类 F1-score:
# city_time_slice.py import pandas as pd from pathlib import Path slices = { "aachen_day": ["aachen_.*_day.*"], "jena_dusk": ["jena_.*_dusk.*"], "zurich_night": ["zurich_.*_night.*"] } results = {} for slice_name, patterns in slices.items(): # 用正则匹配对应图像 matched = [p for p in val_img_paths if any(re.match(pat, p.name) for pat in patterns)] # 计算该 slice 的 F1 results[slice_name] = compute_f1(model, matched) pd.DataFrame(results).to_csv("slice_f1.csv")若zurich_night的riderF1 比aachen_day低 12%,说明模型缺乏低光照泛化能力,需引入LowLightAugmentation。
5.3 第三层:错误模式聚类(30 分钟)
对val集所有person类误检样本,提取其labelIds.png中的误分类像素坐标,用 DBSCAN 聚类:
- 若聚类中心集中在图像底部 10% 区域 → 检查
ego vehicle掩膜是否未正确应用; - 若聚类呈水平条带状(y 坐标集中)→ 检查
resize是否导致标签压缩变形; - 若聚类点与
traffic sign边界重合 → 模型存在上下文混淆,需增加BoundaryAwareLoss。
5.4 第四层:对抗样本压力测试(20 分钟)
用foolbox库生成FGSM对抗样本,攻击目标设为person类:
import foolbox as fb fmodel = fb.PyTorchModel(model, bounds=(0, 1)) attack = fb.attacks.LinfFastGradientAttack() epsilons = [0.001, 0.01, 0.03] raw, clipped, is_adv = attack(fmodel, images, labels, epsilons=epsilons) # 统计各 epsilon 下 `person` 类置信度下降率若epsilon=0.01时person类平均置信度下降 > 40%,说明模型对微小扰动敏感,需在训练中加入AdversarialTraining。
从那以后我每次交付新模型前,都强制走一遍这四层验证——哪怕客户只要求一个 mIoU 数字。因为 Cityscapes val 集不是终点,而是你模型能否走出实验室、真正面对城市街道的第一道安检门。希望帮到你。
本文还有配套的精品资源,点击获取