共计 3018 个字符,预计需要花费 8 分钟才能阅读完成。
背景与痛点
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% 需警惕过拟合)

常见问题解决方案
- Loss 震荡严重:
- 检查学习率是否过大(建议初始值 1e-4~1e-3)
-
增加 batch size(至少保证单卡 8 以上)
-
显存不足:
- 启用梯度累积(
--accumulate-grad 2) - 使用更小的 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
正文完
