BiSeNetV2训练自定义数据集实战指南:从数据准备到模型优化

1次阅读
没有评论

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

image.webp

背景与痛点

BiSeNetV2 作为轻量级实时语义分割网络的代表,通过双路结构和细节增强模块,在保持高推理速度(1024×2048 分辨率下可达 156FPS)的同时,实现了与大型网络相当的精度。但在实际自定义数据集训练中,开发者常遇到以下问题:

  • 格式兼容性问题:大多数公开代码默认支持 Cityscapes 格式,而自定义数据集往往采用 VOC 或 COCO 标注标准
  • 训练不稳定:小样本数据容易导致梯度爆炸或模型震荡
  • 过拟合严重:当数据量小于 1000 张时,验证集 mIoU 可能骤降 20% 以上

技术方案详解

1. 数据准备规范

推荐使用 Labelme 进行标注,其生成的 JSON 格式可通过以下脚本转换为 BiSeNetV2 所需的灰度标注图(示例为 8 分类任务):

import numpy as np
import json
from PIL import Image

def convert_labelme_to_mask(json_path, output_dir):
    with open(json_path) as f:
        data = json.load(f)

    # 创建空白 mask(注意 BiSeNetV2 要求背景类为 0)h, w = data['imageHeight'], data['imageWidth']
    mask = np.zeros((h, w), dtype=np.uint8)

    # 映射关系示例:{'person':1, 'car':2,...}
    class_mapping = {'background':0, 'class1':1, 'class2':2} 

    for shape in data['shapes']:
        label = shape['label']
        points = shape['points']
        # 实际应使用多边形填充算法
        cv2.fillPoly(mask, [np.array(points)], class_mapping[label])

    Image.fromarray(mask).save(f"{output_dir}/{Path(json_path).stem}.png")

关键注意点:

  • 必须保证 mask 像素值为连续整数(0~N-1)
  • 推荐图片尺寸调整为 1024×512 的倍数
  • 数据集目录结构应符合:
    dataset/
    ├── images/
    │   ├── train/
    │   └── val/
    └── labels/
        ├── train/
        └── val/

2. 配置文件关键修改

configs/bisenetv2_city.py 为例,必须修改以下参数:

data_cfg = dict(
    train=dict(
        img_dir='dataset/images/train',
        ann_dir='dataset/labels/train',
        # 类别数 +1(背景)n_classes=9,  
    ),
    val=dict(
        img_dir='dataset/images/val',
        ann_dir='dataset/labels/val',
    ),
    # 自定义类别颜色映射(可视化用)palette=[[128,64,128], [244,35,232], ... 
    ]
)

# 调整学习率策略(小数据集建议降低初始 LR)optimizer_cfg = dict(
    lr=1e-3,  # 原配置通常为 5e-3
    policy='poly',
    power=0.9,
)

3. 训练优化策略

数据增强配置

pipeline.py 中添加适合自定义数据的增强:

train_pipeline = [dict(type='RandomRotate', degree=15, prob=0.5),
    dict(type='RandomCrop', crop_size=(512,1024)),
    # 小样本推荐使用颜色抖动
    dict(type='PhotoMetricDistortion', 
         brightness_delta=32,
         contrast_range=(0.8,1.2)),
]

损失函数调优

对于类别不平衡数据,建议组合使用 OHEM 和 Dice Loss:

loss_cfg = dict(
    type='OhemCELoss',
    thresh=0.7, 
    ignore_index=255,
)
aux_loss = [dict(type='DiceLoss', weight=0.4)
]

代码实现关键点

数据加载器修改

需继承 BaseDataset 实现自定义数据读取(关键代码片段):

class CustomDataset(BaseDataset):
    def __getitem__(self, idx):
        img_path = self.img_paths[idx]
        label_path = self.label_paths[idx]

        img = cv2.imread(img_path)
        label = cv2.imread(label_path, 0)  # 必须灰度读取

        # 应用增强
        if self.pipeline:
            data = {'img': img, 'label': label}
            data = self.pipeline(data)
            img, label = data['img'], data['label']

        # 转换为 Tensor
        img = torch.from_numpy(img).float().permute(2,0,1)
        label = torch.from_numpy(label).long()

        return img, label

多 GPU 训练启动

python -m torch.distributed.launch --nproc_per_node=4 \
    tools/train_amp.py --config configs/bisenetv2_custom.py \
    --fp16  # 启用混合精度

优化与问题排查

TensorBoard 监控

典型健康训练曲线应呈现:
– 训练 loss 平稳下降(初期可能有震荡)
– 验证 mIoU 持续上升(若波动大于 5% 需警惕过拟合)

BiSeNetV2 训练自定义数据集实战指南:从数据准备到模型优化

常见问题解决方案

  1. Loss 震荡严重
  2. 检查学习率是否过大(建议初始值 1e-4~1e-3)
  3. 增加 batch size(至少保证单卡 8 以上)

  4. 显存不足

  5. 启用梯度累积(--accumulate-grad 2
  6. 使用更小的 crop size(如 768×768)

生产环境部署

模型导出为 ONNX

torch.onnx.export(
    model, 
    dummy_input,
    "bisenetv2.onnx",
    opset_version=11,
    input_names=['input'],
    output_names=['output'],
    dynamic_axes={'input': {0: 'batch'}, 'output': {0: 'batch'}}
)

边缘设备优化

  • TensorRT 加速

    trtexec --onnx=bisenetv2.onnx \
            --fp16 \
            --workspace=2048 \
            --saveEngine=bisenetv2_fp16.engine

  • 量化部署(适用于 Jetson):

    model = torch.quantization.quantize_dynamic(model, {torch.nn.Conv2d}, dtype=torch.qint8
    )

延伸思考

如何提升小目标分割效果?可以尝试:
1. 在 Detail Head 后增加 PPM 模块捕获多尺度特征
2. 使用 Focal Loss 抑制简单样本的梯度
3. 对高分辨率原图采用滑动窗口推理

完整代码示例已开源在:https://github.com/example/bisenetv2-custom-training

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