【实战指南】从零构建Cityscapes语义分割与实例分割数据管道

Ferrybunny

1. Cityscapes数据集简介与下载

Cityscapes是计算机视觉领域最知名的街景理解数据集之一,包含50个城市在不同季节、天气条件下的街景图像。我第一次接触这个数据集是在2018年做自动驾驶项目时,当时为了找到高质量的街景标注数据,几乎翻遍了所有公开数据集,最终Cityscapes以其精细的标注和丰富的场景成为首选。

数据集主要包含两部分:

  • 精细标注集:3475张训练图像、500张验证图像和1525张测试图像
  • 粗糙标注集:额外19888张图像,标注精度较低

对于大多数语义分割和实例分割任务,我们只需要使用精细标注集。下载时要注意,原始图像(leftImg8bit)和标注数据(gtFine)需要分别下载,总大小约11GB。这里有个小技巧:如果网络不稳定,可以使用wget的-c参数支持断点续传:

bash复制wget -c https://www.cityscapes-dataset.com/file-handling/?packageID=1
wget -c https://www.cityscapes-dataset.com/file-handling/?packageID=3

下载完成后,你会得到两个zip文件:

  • leftImg8bit_trainvaltest.zip - 原始街景图像
  • gtFine_trainvaltest.zip - 精细标注数据

解压时建议使用-j参数跳过中间目录结构,这样能保持文件路径的一致性:

bash复制unzip -j leftImg8bit_trainvaltest.zip -d cityscapes/images
unzip -j gtFine_trainvaltest.zip -d cityscapes/annotations

2. 数据预处理全流程

2.1 官方工具安装与配置

Cityscapes提供了专门的Python工具包cityscapesscripts来处理标注数据,这个工具包需要从GitHub克隆安装:

bash复制git clone https://github.com/mcordts/cityscapesScripts.git
cd cityscapesScripts
pip install -e .

安装完成后,需要设置环境变量指向你的数据集根目录。我在实际项目中发现,最好把这个设置写入.bashrc.zshrc中:

bash复制echo 'export CITYSCAPES_DATASET=/path/to/cityscapes' >> ~/.bashrc
source ~/.bashrc

2.2 理解原始标注结构

解压后的gtFine目录包含这些关键文件:

  • *_color.png - 可视化标注(给人类看的)
  • *_instanceIds.png - 实例分割原始标注
  • *_labelIds.png - 语义分割原始标注
  • *.json - 多边形标注的原始数据

这里有个容易混淆的点:labelIds和后续要生成的labelTrainIds是不同的。原始labelIds包含所有34个类别,而labelTrainIds只包含19个常用训练类别,其他类别会被标记为255(忽略)。

2.3 语义分割标注转换

运行以下命令生成语义分割训练所需的labelTrainIds.png

python复制python cityscapesscripts/preparation/createTrainIdLabelImgs.py

这个脚本会在原标注目录下生成新文件,命名格式为*_labelTrainIds.png。我曾经遇到过这个脚本报错的问题,大多数情况是因为环境变量没设置正确,可以通过在脚本中硬编码路径临时解决:

python复制# 在createTrainIdLabelImgs.py开头添加
import os
os.environ['CITYSCAPES_DATASET'] = "/your/actual/path"

2.4 实例分割标注处理

实例分割需要两步处理:

  1. 生成统一的实例ID
  2. 创建实例边界标注

运行官方提供的脚本:

python复制python cityscapesscripts/preparation/createTrainIdInstanceImgs.py

这个脚本会生成*_instanceTrainIds.png文件,其中每个实例都有唯一的ID值。在实际项目中,我发现摩托车后座上的乘客经常会被错误合并到同一个实例,这时就需要后期人工调整标注。

2.5 自定义类别筛选

Cityscapes默认的19类可能不符合所有项目需求。要修改类别,需要编辑cityscapesscripts/helpers/labels.py。例如,如果我们不需要"摩托车"类:

python复制labels = [
    # ...
    Label('motorcycle',    5,      7,      'vehicle',      False,      True,      255),
    # 改为
    Label('motorcycle',    5,      7,      'vehicle',      False,      True,      255,       True),
    # ...
]

修改后需要重新运行前面的标注生成脚本。这里有个坑:修改labels.py后,必须删除之前生成的*_trainIds.png文件,否则脚本可能不会重新生成。

3. 构建PyTorch数据管道

3.1 数据集目录重构

原始数据是按城市分组的,但训练时我们通常需要按train/val/test组织。下面这个脚本可以帮你重组目录结构:

python复制import os
import shutil
from tqdm import tqdm

def reorganize_dataset(img_src, label_src, dest_root):
    splits = ['train', 'val', 'test']
    for split in splits:
        img_dest = os.path.join(dest_root, 'images', split)
        label_dest = os.path.join(dest_root, 'labels', split)
        os.makedirs(img_dest, exist_ok=True)
        os.makedirs(label_dest, exist_ok=True)
        
        cities = os.listdir(os.path.join(img_src, split))
        for city in tqdm(cities, desc=f'Processing {split}'):
            img_files = os.listdir(os.path.join(img_src, split, city))
            for f in img_files:
                if 'leftImg8bit' in f:
                    # 移动图像文件
                    shutil.move(
                        os.path.join(img_src, split, city, f),
                        os.path.join(img_dest, f)
                    )
                    # 移动对应的标注文件
                    label_f = f.replace('leftImg8bit', 'gtFine_labelTrainIds')
                    shutil.move(
                        os.path.join(label_src, split, city, label_f),
                        os.path.join(label_dest, label_f)
                    )

3.2 高效数据加载器实现

使用PyTorch的Dataset类时,有几点性能优化技巧:

  1. 使用内存映射加速图像加载
  2. 预加载所有文件路径
  3. 使用Albumentations进行快速数据增强
python复制from torch.utils.data import Dataset
import cv2
import numpy as np

class CityscapesDataset(Dataset):
    def __init__(self, img_dir, label_dir, transform=None):
        self.img_dir = img_dir
        self.label_dir = label_dir
        self.transform = transform
        
        # 预加载所有文件路径
        self.img_files = sorted([f for f in os.listdir(img_dir) if f.endswith('.png')])
        self.label_files = sorted([f for f in os.listdir(label_dir) if f.endswith('.png')])
        
        # 验证图像和标注匹配
        assert len(self.img_files) == len(self.label_files)
        for img, lbl in zip(self.img_files, self.label_files):
            assert img.replace('leftImg8bit', 'gtFine_labelTrainIds') == lbl

    def __len__(self):
        return len(self.img_files)

    def __getitem__(self, idx):
        img_path = os.path.join(self.img_dir, self.img_files[idx])
        label_path = os.path.join(self.label_dir, self.label_files[idx])
        
        # 使用OpenCV加速加载
        img = cv2.imread(img_path, cv2.IMREAD_COLOR)
        img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
        label = cv2.imread(label_path, cv2.IMREAD_GRAYSCALE)
        
        if self.transform:
            augmented = self.transform(image=img, mask=label)
            img = augmented['image']
            label = augmented['mask']
            
        return img, label

3.3 高级数据增强策略

对于街景图像,我推荐这些增强组合:

python复制import albumentations as A

train_transform = A.Compose([
    A.RandomResizedCrop(512, 1024, scale=(0.5, 2.0)),
    A.HorizontalFlip(p=0.5),
    A.ColorJitter(brightness=0.2, contrast=0.2, saturation=0.2, hue=0.1, p=0.5),
    A.RandomGamma(gamma_limit=(80, 120), p=0.5),
    A.Blur(blur_limit=3, p=0.2),
    A.RandomShadow(shadow_roi=(0, 0, 1, 1), num_shadows_lower=1, 
                  num_shadows_upper=2, shadow_dimension=5, p=0.2),
    A.Normalize(mean=(0.485, 0.456, 0.406), std=(0.229, 0.224, 0.225)),
    ToTensorV2()
])

val_transform = A.Compose([
    A.Resize(512, 1024),
    A.Normalize(mean=(0.485, 0.456, 0.406), std=(0.229, 0.224, 0.225)),
    ToTensorV2()
])

特别提醒:增强后的标注也需要相应变换。Albumentations会自动处理mask的变换,但如果使用自定义增强,务必确保图像和标注同步变换。

4. 实战技巧与排坑指南

4.1 多GPU训练的数据加载优化

当使用多GPU时,DataLoader的配置很关键。这是我的推荐配置:

python复制from torch.utils.data import DataLoader

def get_loaders(train_dir, val_dir, train_label_dir, val_label_dir, 
               batch_size, num_workers, pin_memory=True):
    train_ds = CityscapesDataset(
        img_dir=train_dir,
        label_dir=train_label_dir,
        transform=train_transform
    )
    
    val_ds = CityscapesDataset(
        img_dir=val_dir,
        label_dir=val_label_dir,
        transform=val_transform
    )
    
    train_loader = DataLoader(
        train_ds,
        batch_size=batch_size,
        num_workers=num_workers,
        pin_memory=pin_memory,
        shuffle=True,
        drop_last=True
    )
    
    val_loader = DataLoader(
        val_ds,
        batch_size=batch_size,
        num_workers=num_workers,
        pin_memory=pin_memory,
        shuffle=False,
        drop_last=False
    )
    
    return train_loader, val_loader

几个关键参数说明:

  • num_workers:通常设为GPU数量的4倍
  • pin_memory:在CUDA设备上必须设为True
  • drop_last:训练时设为True避免最后不完整的batch

4.2 类别不平衡问题处理

Cityscapes中"道路"类像素占比很大,而"摩托车"类很少。解决方法有:

  1. 加权交叉熵损失
python复制class_weights = torch.tensor([...]) # 计算每个类的权重
criterion = nn.CrossEntropyLoss(weight=class_weights)
  1. 采样时类别平衡
python复制from torch.utils.data import WeightedRandomSampler

class_counts = [...] # 每个类的像素总数
class_weights = 1. / torch.tensor(class_counts, dtype=torch.float)
sample_weights = [class_weights[c] for c in train_labels]

sampler = WeightedRandomSampler(
    weights=sample_weights,
    num_samples=len(sample_weights),
    replacement=True
)

loader = DataLoader(..., sampler=sampler)

4.3 自定义评估指标实现

官方评估脚本比较复杂,这里给出简化版的mIoU实现:

python复制def compute_iou(pred, target, n_classes=19):
    ious = []
    pred = pred.view(-1)
    target = target.view(-1)
    
    for cls in range(n_classes):
        pred_inds = pred == cls
        target_inds = target == cls
        intersection = (pred_inds[target_inds]).long().sum().item()
        union = pred_inds.long().sum().item() + target_inds.long().sum().item() - intersection
        
        if union == 0:
            ious.append(float('nan'))
        else:
            ious.append(float(intersection) / float(union))
    
    return np.nanmean(ious)

使用时注意:

  1. 忽略类别(255)需要提前过滤
  2. 测试时需要像官方那样按1024x2048分辨率评估

4.4 模型部署前的数据规范化

训练时使用的归一化参数必须与部署时一致:

python复制# 训练时的归一化
transform = A.Normalize(
    mean=(0.485, 0.456, 0.406), 
    std=(0.229, 0.224, 0.225)
)

# 部署推理时也必须使用相同的参数
def preprocess(image):
    image = image / 255.0
    image = (image - [0.485, 0.456, 0.406]) / [0.229, 0.224, 0.225]
    return image.transpose(2, 0, 1).astype(np.float32)

建议把这些参数写在配置文件中,确保训练和推理使用相同的预处理流程。

内容推荐

从零到一:基于DataX3.0与DataX-Web构建企业级可视化数据同步平台
本文详细介绍了如何基于DataX3.0与DataX-Web构建企业级可视化数据同步平台,涵盖技术选型、集群化部署、核心配置、可视化运维及性能调优等关键环节。通过异构数据源兼容性、可视化运维和资源利用率优化等核心优势,帮助企业高效实现分布式ETL数据处理,提升数据同步效率与稳定性。
I2C(IIC)---EEPROM时序模拟与实战代码解析
本文深入解析I2C(IIC)协议与EEPROM通信原理,提供51单片机GPIO模拟I2C时序的实战代码,包括起始信号、应答检测及EEPROM读写操作。通过AT24C02案例详解页写功能、地址配置及常见问题排查,帮助开发者高效实现数据存储与读取,提升嵌入式系统开发能力。
【模型剪枝实战】利用DepGraph依赖图与Torch-Pruning,三步实现复杂模型无损压缩
本文详细介绍了如何利用DepGraph依赖图与Torch-Pruning工具实现复杂模型的无损压缩。通过三步实战流程,包括环境准备、基线模型训练和执行剪枝操作,帮助开发者高效完成模型剪枝,显著减少模型体积并提升推理速度,同时保持模型精度损失最小。特别适用于手机端图像分割等资源受限场景。
从gensim到PyTorch:手把手把腾讯词向量变成可训练的Embedding层
本文详细介绍了如何将腾讯AI Lab的预训练词向量高效整合到PyTorch模型中,涵盖从gensim加载词向量、构建词汇表映射到创建可训练的Embedding层等关键步骤。通过实战示例和高级优化技巧,帮助开发者解决工程化过程中的常见问题,提升NLP模型效果。
Allegro PCB设计效率倍增:从系统快捷键到个性化自定义全解析
本文详细解析了Allegro PCB设计中的快捷键系统与自定义设置技巧,帮助工程师大幅提升工作效率。从系统默认快捷键到个性化自定义方案,涵盖alias和funckey两种核心类型,并提供实战案例展示如何优化高频操作,如布线、视图控制和铜箔处理。通过合理设置,项目周期可缩短15个工作日以上。
微信小程序蓝牙通信避坑大全:从UUID获取到数据收发,我踩过的坑你别再踩
本文详细解析微信小程序蓝牙通信开发中的常见问题与解决方案,涵盖UUID识别、数据收发格式转换、Notify机制配置等关键环节。特别针对跨平台兼容性问题,提供Android与iOS的差异化处理方案,帮助开发者避开蓝牙模块开发中的典型陷阱,实现稳定可靠的数据发送与接收。
MBD_进阶_在VSCode中高效构建与调试S32K工程
本文详细介绍了如何在VSCode中高效构建与调试S32K工程,提升嵌入式开发效率。通过配置VSCode环境、优化编译任务和调试设置,开发者可以充分利用VSCode的智能代码提示和插件生态,同时保留S32DS工具链的优势,实现编译速度提升30%以上。
安路FPGA IP核实战:从内部振荡器(OSC)到串口通信(UART)的完整开发流程
本文详细介绍了安路FPGA开发中IP核的应用实践,从内部振荡器(OSC)配置到串口通信(UART)实现的完整流程。通过具体代码示例和调试技巧,帮助开发者快速掌握安路FPGA的IP核使用方法,提升开发效率。重点讲解了OSC时钟分频、UART数据回环测试等关键技术点。
别再只盯着DCT了!聊聊视频编码H.266里的隐藏王牌:DST-VII
本文深入探讨了H.266/VVC视频编码标准中的隐藏王牌——DST-VII(离散正弦变换),揭示了其在处理锐利边缘和复杂纹理时相比传统DCT的显著优势。通过分析数学原理、工程实现及实测数据,展示了DST-VII如何提升压缩效率,特别是在4×4块尺寸和特定帧内预测模式下表现突出。文章还提供了实战技巧,帮助开发者最大化DST-VII的编码效益。
uni-app安卓应用从开发到上架:一站式打包与分发实战指南
本文详细介绍了uni-app安卓应用从开发到上架的全流程,包括环境配置、manifest.json深度优化、真机调试技巧、正式包打包与优化、分发方案选择以及上架前的终极检查清单。通过实战经验分享,帮助开发者高效完成应用打包与分发,特别适合需要快速上架uni-app安卓应用的开发者。
ThinkPad P53 BIOS固件升级:从风险规避到性能提升的完整指南
本文详细介绍了ThinkPad P53 BIOS固件升级的全过程,从风险规避到性能提升的完整指南。通过实际案例和测试数据,展示了BIOS升级如何解决硬件兼容性问题并显著提升性能,包括启动时间缩短23%、SSD读写速度提升8%等。同时提供了企业级批量管理方案和常见问题解决方案,帮助用户安全高效地完成升级。
保姆级避坑指南:在Ubuntu 22.04上用Kolla-Ansible部署OpenStack Yoga(含国内源配置)
本文提供在Ubuntu 22.04上使用Kolla-Ansible部署OpenStack Yoga的详细指南,特别针对国内网络环境优化配置,包括国内源设置、Docker版本锁定和常见问题解决方案。通过分步实战教程,帮助用户高效完成部署并避免常见陷阱,确保云平台稳定运行。
别再乱用set_multicycle_path了!一个真实案例讲透SDC中的多周期约束(含-start/-end选项详解)
本文深入解析SDC中`set_multicycle_path`命令的正确使用方法,通过真实案例详细讲解多周期路径约束的本质,特别是`-start`和`-end`选项的区别与应用场景。帮助工程师避免常见误用,确保静态时序分析(STA)的准确性,提升芯片设计的可靠性和性能表现。
计算机科学十大奠基者:从理论基石到开源革命
本文回顾了计算机科学领域的四位关键奠基者:阿兰·图灵(理论奠基)、冯·诺依曼(体系结构)、林纳斯·托瓦兹(开源实践)和理查德·斯托曼(自由软件),探讨了他们对现代计算技术发展的深远影响。从图灵机理论到Linux开源革命,这些先驱者的贡献构建了当今数字世界的基石。
别再傻傻查表了!用Python写个贴片电阻丝印速查小工具(附完整代码)
本文介绍如何用Python开发一个智能贴片电阻丝印解码工具,帮助电子工程师快速识别EIA-96标准的三位代码。通过构建高效的字典查询系统和用户友好的交互功能,实现电阻值的即时转换,并提供了多种部署方案和高级扩展功能,显著提升工作效率。
【Autosar MCAL实战】S32K14x ICU模块:从滤波器配置到双边沿捕获的精准信号测量实践
本文详细解析了S32K14x的ICU模块在Autosar MCAL环境下的精准信号测量实践,涵盖滤波器配置、双边沿捕获等关键技术。通过实际项目案例,展示了如何在汽车电子噪声环境中实现稳定信号捕获,并提供了EB Tresos配置指南和调试技巧,助力开发者提升PWM信号测量精度。
K7系列FPGA远程更新实战:基于STARTUPE2原语的FLASH时钟接管与配置后控制
本文详细介绍了K7系列FPGA在远程更新过程中如何通过STARTUPE2原语实现FLASH时钟接管与配置后控制。文章深入分析了CCLK信号的重要性及其在Master SPI模式下的行为特点,提供了STARTUPE2原语的实例化代码和时钟切换策略,并分享了实际调试技巧与常见问题解决方案,帮助开发者高效完成FPGA远程更新功能。
从《现代大学英语精读》到真实成长:用Erikson心理发展理论解读你的大学四年
本文通过Erikson心理发展理论解析大学四年成长历程,涵盖身份探索、分离-个体化、亲密关系构建、认知升级和价值观塑造等关键阶段。结合真实案例和心理学研究,为大学生提供实用的成长策略,帮助他们在多元环境中实现自我认同与人格发展。
别再只用CrossEntropyLoss了!PyTorch实战:Focal Loss与GHMC Loss解决样本不平衡的保姆级教程
本文深入探讨了PyTorch中Focal Loss与GHMC Loss在解决样本不平衡问题中的应用。通过对比CE Loss的缺陷,详细解析了Focal Loss的双参数调节机制和GHMC Loss的梯度密度协调方案,并提供了完整的PyTorch实现代码与实战技巧,帮助开发者在目标检测等场景中有效提升模型性能。
从原理到实战:Python bcrypt库如何用盐值守护你的密码安全
本文深入探讨了Python bcrypt库如何通过盐值处理(Salt Hashing)技术提升密码存储安全性。从密码存储的常见误区入手,详细解析了bcrypt的自动化盐值处理流程、抗暴力破解机制,并提供了Flask实战示例,帮助开发者构建安全的认证系统。文章还涵盖了生产环境最佳实践、bcrypt安全设计原理以及常见问题解决方案,是提升密码安全性的必备指南。
已经到底了哦
精选内容
热门内容
最新内容
STM32H743外挂W5500做UDP通信,一个Socket端口如何同时处理多个客户端?
本文深入解析了STM32H743通过W5500以太网模块实现单Socket多客户端UDP通信的技术方案。详细介绍了UDP协议特性、W5500硬件架构、SPI接口配置及内存管理策略,提供了完整的代码实现和性能优化技巧,帮助开发者在资源受限的嵌入式系统中高效处理多客户端通信需求。
别再乱选LOD了!CesiumLab通用模型切片实战:小场景 vs 八叉树,手把手教你根据数据量选对策略
本文深入探讨了CesiumLab模型切片技术在小场景与八叉树处理器之间的科学选择策略。通过分析数据规模与业务需求,提供详细的配置建议和优化技巧,帮助开发者根据项目需求选择最佳切片方案,显著提升三维GIS和BIM可视化性能。
Halcon HSmartWindowControl控件详解:如何用最少的代码在C#里搞定图像浏览(鼠标滚轮缩放+右键复位)
本文详细介绍了Halcon HSmartWindowControl控件在C#中的高效应用,通过极简代码实现图像浏览的鼠标滚轮缩放和右键复位功能。对比HWindowControl,HSmartWindowControl内置交互逻辑,大幅降低开发成本,提升工业视觉项目的开发效率。
内核性能调优实战:ktime_get与ktime_sub精准定位驱动耗时瓶颈
本文详细介绍了如何使用Linux内核中的ktime_get和ktime_sub函数精准定位驱动性能瓶颈。通过XDMA驱动的实际案例,展示了如何测量代码执行时间、分析耗时瓶颈,并提供了优化中断处理等高级技巧,帮助开发者提升内核驱动性能。
工业自动化实战:IDEC和泉RU2S/RU4D继电器选型、接线与锁存功能详解
本文详细解析了IDEC和泉RU2S/RU4D继电器在工业自动化中的选型、接线与锁存功能应用。通过实战经验分享,指导工程师如何避免选型误区,正确接线以及充分利用机械锁存功能提升系统可靠性,特别适用于电机控制、安全回路等场景。
从零到一:实战YOLO-NAS自定义数据集训练全流程
本文详细介绍了YOLO-NAS目标检测器的自定义数据集训练全流程,从环境搭建、数据准备到模型训练与部署。通过实战案例展示YOLO-NAS在精度与速度上的优势,帮助开发者快速掌握这一先进目标检测技术,适用于工业质检、智能监控等多种场景。
从VCF到SFS:利用easySFS高效构建位点频谱的实战指南
本文详细介绍了如何利用easySFS工具从VCF文件高效构建位点频谱(SFS),适用于群体遗传学研究。通过实战案例和优化技巧,帮助研究人员快速处理大规模SNP数据,提升后续fastsimcoal2等分析工具的效率。内容包括环境配置、投影值选择、多维SFS生成及常见问题解决方案。
告别‘Access Denied’:树莓派5/Zero 2W新手必看的SSH+VNC远程配置保姆级避坑指南
本文提供树莓派5/Zero 2W的SSH+VNC远程配置完整指南,涵盖系统烧录、IP地址发现、SSH连接排查及VNC优化等关键步骤。特别针对新手常见问题如'Access Denied'和连接拒绝,给出实用解决方案,帮助用户快速搭建高效的远程开发环境。
VMware Workstation 17 实战:手把手教你部署macOS Sonoma 14及性能调优
本文详细介绍了在VMware Workstation 17上部署macOS Sonoma 14的完整流程及性能调优技巧。从环境准备、虚拟机配置到系统安装,逐步指导用户解决常见问题,并提供针对CPU、内存、网络等关键性能的优化方案,帮助用户在非苹果硬件上高效运行macOS系统。
别再手动注册参数了!PyTorch中nn.Parameter的正确打开方式与3个实战场景
本文深入解析PyTorch中`nn.Parameter`的核心用法与实战技巧,帮助开发者避免手动注册参数的繁琐操作。通过3个典型场景(视觉Transformer位置编码、通道注意力机制、Gumbel-Softmax温度参数)的代码示例,展示如何高效利用这一特性构建可训练模型组件,同时提供参数初始化、共享和调试的实用指南。