CamVid数据集实战指南:从数据预处理到语义分割模型训练

1次阅读
没有评论

共计 2815 个字符,预计需要花费 8 分钟才能阅读完成。

image.webp

自动驾驶中的语义分割基石

CamVid(Cambridge-Driving Labeled Video Database)作为经典的街景语义分割数据集,包含 700 多张行车记录仪拍摄的图片,标注了道路、车辆、行人等 32 类目标。在自动驾驶系统中,这种像素级理解能力直接决定了车辆能否准确识别可行驶区域(Drivable Area)和障碍物。与 ImageNet 等分类数据集不同,CamVid 的标注采用 PNG 格式的彩色掩码图——每个像素点的颜色值对应特定类别,这种标注方式在工业界非常普遍。

CamVid 数据集实战指南:从数据预处理到语义分割模型训练

数据集目录结构解密

下载解压后的 CamVid 文件夹应包含以下结构(示例路径为简化版):

CamVid/
├── train/
│   ├── 0001TP_006690.png       # 原始 RGB 图像
│   └── ...
├── train_labels/
│   ├── 0001TP_006690.png       # 彩色标注掩码
│   └── ...
├── val/
└── val_labels/
  • 关键细节:标注 PNG 采用 24 位 RGB 色彩空间,例如 ” 道路 ” 类用 (128,64,128) 表示。需要注意的是,原始数据集中部分标注存在边缘模糊问题(标注员主观判断导致),这是后续需要数据清洗的重点区域。

PyTorch 数据管道搭建

1. 自定义 Dataset 类实现

核心任务是读取原始图片和对应的彩色掩码,并将其转换为模型可处理的张量格式。关键步骤包括:

import torch
from torch.utils.data import Dataset
import numpy as np
from PIL import Image

class CamVidDataset(Dataset):
    # 预定义颜色编码表(示例仅包含部分类别)CLASS_COLORS = {'road': np.array([128, 64, 128]),
        'sky': np.array([128, 128, 128]),
        'building': np.array([70, 70, 70])
    }

    def __init__(self, img_dir, label_dir, classes):
        self.img_paths = sorted(Path(img_dir).glob('*.png'))
        self.label_paths = sorted(Path(label_dir).glob('*.png'))
        self.classes = classes

    def __getitem__(self, idx):
        img = np.array(Image.open(self.img_paths[idx]))
        label = np.array(Image.open(self.label_paths[idx]))

        # 将彩色掩码转换为类别索引图
        h, w = label.shape[:2]
        class_map = np.zeros((h, w), dtype=np.uint8)

        for i, (_, color) in enumerate(self.classes.items()):
            matches = np.all(label == color, axis=-1)
            class_map[matches] = i

        return torch.FloatTensor(img).permute(2,0,1)/255., \
               torch.LongTensor(class_map)

2. 数据增强策略

语义分割任务的数据增强需要同步处理图像和标注掩码:

from torchvision import transforms

train_transform = transforms.Compose([transforms.RandomHorizontalFlip(p=0.5),  # 水平翻转
    transforms.RandomCrop(480),  # 随机裁剪到固定尺寸
    # 注意:不能对标注图做归一化或颜色变换
])

# 使用时需同步应用增强
img, mask = train_transform(img), train_transform(mask)

3. 类别权重计算

解决类别不平衡的经典方法是加权交叉熵损失(Weighted CrossEntropy):

def calculate_class_weights(dataset):
    pixel_counts = {i:0 for i in range(len(dataset.classes))}

    # 统计每个类别的像素总数
    for _, mask in dataset:
        for class_idx in pixel_counts:
            pixel_counts[class_idx] += (mask == class_idx).sum().item()

    total_pixels = sum(pixel_counts.values())
    weights = [total_pixels/(count + 1e-6) for count in pixel_counts.values()]
    return torch.FloatTensor(weights)

UNet 模型训练关键代码

import torch.nn as nn

model = UNet(num_classes=len(classes))
weights = calculate_class_weights(train_dataset)
criterion = nn.CrossEntropyLoss(weight=weights)
optimizer = torch.optim.Adam(model.parameters(), lr=1e-4)

for epoch in range(100):
    for inputs, labels in train_loader:
        outputs = model(inputs)
        loss = criterion(outputs, labels)

        optimizer.zero_grad()
        loss.backward()
        optimizer.step()

选择加权交叉熵而非 Dice Loss 的原因:在 CamVid 这种存在明显类别不平衡(如天空占比大)但需要保证小物体(如交通标志)分割精度的场景,加权交叉熵更容易通过调整权重平衡各类别贡献。

生产环境注意事项

  1. 标注一致性检查:
  2. 使用 OpenCV 的 findContours 检测标注图中的孤立像素点
  3. 对比相邻帧的标注差异,排查标注员主观性导致的突变

  4. 显存优化技巧:

  5. 采用梯度累积(Gradient Accumulation):每 4 个小批次更新一次参数
  6. 使用混合精度训练(AMP):torch.cuda.amp.autocast()

  7. 指标解读陷阱:

  8. 不要仅关注整体准确率(Accuracy),要看各类别的 IoU
  9. 测试时需保持与训练相同的数据预处理流水线

延伸思考

尝试将本流程迁移到 Cityscapes 数据集时,需要处理以下新挑战:
1. 更高的图像分辨率(2048×1024 vs CamVid 的 960×720)
2. 细粒度标注(如将 ” 车辆 ” 细分为轿车、卡车等)
3. 视频序列的时序一致性要求

建议从调整数据加载策略入手,逐步解决显存消耗和计算效率问题。

正文完
 0
评论(没有评论)