1. 工业场景语义分割入门指南
第一次接触工业场景的语义分割任务时,我被停车场里那些杂乱无章的停车位、地锁和减速带搞得头大。传统的图像处理方法在这些复杂场景下完全不够用,直到发现了mmsegmentation这个神器。它就像是一个百宝箱,里面装满了各种现成的语义分割工具,而UperNet+Swin-T的组合更是让我眼前一亮。
mmsegmentation是OpenMMLab家族的一员,专门为语义分割任务量身打造。它最大的优势在于模块化设计,把数据加载、模型构建、训练流程都拆成了可插拔的组件。举个例子,你想换一个主干网络?改两行配置就行。需要尝试不同的损失函数?换个参数就能搞定。这种设计对于工业场景的快速迭代特别友好。
说到UperNet+Swin-T这个黄金组合,简直就是为工业场景量身定制的。UperNet作为解码器,能很好地融合多尺度特征;而Swin-T作为骨干网络,通过局部窗口注意力机制,既保持了计算效率,又能捕捉长距离依赖关系。我在停车场场景实测下来,这个组合在识别各种地锁状态(开启/关闭)时,准确率比传统方法高出至少20%。
2. 数据准备与标注技巧
准备数据集是语义分割最耗时的环节。在停车场项目中,我们需要识别五种关键物体:减速带、限位器、开启的地锁、关闭的地锁和人行道。这里分享几个踩坑后总结的经验:
首先,标注工具推荐使用Labelme,它生成的JSON格式可以直接转换为mmsegmentation需要的mask图像。标注时有个小技巧:对于地锁这种有明确状态(开/关)的物体,一定要确保不同状态被标注为不同类别。我曾经因为把开锁和闭锁混为一类,导致模型完全无法区分这两种状态。
数据增强是提升模型泛化能力的关键。在mmsegmentation的配置文件中,我通常会这样设置pipeline:
train_pipeline = [ dict(type='LoadImageFromFile'), dict(type='LoadAnnotations'), dict(type='Resize', img_scale=(2048, 1024), ratio_range=(0.5, 2.0)), dict(type='RandomCrop', crop_size=(512, 512), cat_max_ratio=0.75), dict(type='RandomFlip', prob=0.5), dict(type='PhotoMetricDistortion'), dict(type='Normalize', mean=[123.675, 116.28, 103.53], std=[58.395, 57.12, 57.375]), dict(type='Pad', size=(512, 512), pad_val=0, seg_pad_val=255), dict(type='DefaultFormatBundle'), dict(type='Collect', keys=['img', 'gt_semantic_seg']), ]特别要注意的是cat_max_ratio这个参数,它控制着裁剪时单类像素的最大占比。在停车场场景中,地面(背景)往往占据大部分画面,把这个值设为0.75能有效防止模型只学习识别大面积背景。
3. 模型配置与调参实战
拿到第一批标注数据后,我迫不及待地想试试UperNet+Swin-T的效果。在mmsegmentation中,配置文件是核心所在。以configs/swin/upernet_swin_tiny_patch4_window7_512x512_160k_ade20k.py为基础,需要修改几个关键部分:
首先是模型定义部分:
model = dict( type='EncoderDecoder', backbone=dict( type='SwinTransformer', embed_dim=96, depths=[2, 2, 6, 2], num_heads=[3, 6, 12, 24], window_size=7, ape=False, drop_path_rate=0.3, patch_norm=True, out_indices=(0, 1, 2, 3)), decode_head=dict( type='UPerHead', in_channels=[96, 192, 384, 768], in_index=[0, 1, 2, 3], pool_scales=(1, 2, 3, 6), channels=512, dropout_ratio=0.1, num_classes=5, # 修改为你的类别数 norm_cfg=dict(type='SyncBN', requires_grad=True), align_corners=False, loss_decode=[ dict(type='CrossEntropyLoss', loss_weight=1.0), dict(type='DiceLoss', loss_weight=3.0) ]), auxiliary_head=dict( type='FCNHead', in_channels=384, in_index=2, channels=256, num_convs=1, concat_input=False, dropout_ratio=0.1, num_classes=5, norm_cfg=dict(type='SyncBN', requires_grad=True), align_corners=False, loss_decode=dict(type='CrossEntropyLoss', loss_weight=0.4)), )损失函数的选择很有讲究。在工业场景中,类别不平衡是常见问题。比如停车场里,地锁的数量远少于普通地面。我测试过多种组合,发现CrossEntropyLoss+DiceLoss(3:1)的效果最好。DiceLoss能有效缓解类别不平衡问题,而CrossEntropyLoss则保证了基础分类性能。
学习率设置也很关键。Swin-T作为Transformer架构,需要使用较小的学习率。我通常从3e-4开始,配合线性warmup和余弦退火策略:
optimizer = dict( type='AdamW', lr=3e-4, betas=(0.9, 0.999), weight_decay=0.05) optimizer_config = dict(grad_clip=dict(max_norm=1, norm_type=2)) lr_config = dict( policy='CosineAnnealing', warmup='linear', warmup_iters=1000, warmup_ratio=1.0/10, min_lr=1e-5)4. 训练技巧与性能优化
在实际训练过程中,有几个技巧可以显著提升模型性能:
首先是混合精度训练。在mmsegmentation中开启FP16训练非常简单,只需要在配置文件中添加:
fp16 = dict(loss_scale=512.)这能让训练速度提升2-3倍,同时显存占用减少约40%。不过要注意,有些操作(如Softmax)在FP16下可能不稳定,需要监控训练过程。
第二个技巧是在线难样本挖掘(OHEM)。在停车场场景中,有些地锁被车辆部分遮挡,成为难以识别的样本。在decode_head中添加OHEM采样器可以强制模型关注这些困难样本:
decode_head=dict( sampler=dict( type='OHEMPixelSampler', thresh=0.7, min_kept=100000))关于batch size的设置,我的经验是:在显存允许的情况下尽可能大。Swin-T的显存占用相对较高,在RTX 3090上,512x512的输入尺寸,batch size可以设到16。如果显存不足,可以尝试梯度累积:
optimizer_config = dict( type='GradientCumulativeOptimizerHook', cumulative_iters=4)训练过程中的监控也很重要。我习惯用mmsegmentation内置的日志系统配合TensorBoard:
python tools/train.py configs/swin/your_config.py \ --work-dir ./work_dirs \ --load-from ./pretrained/swin_tiny_patch4_window7_224.pth \ --deterministic \ --options log_config.interval=50 \ --tensorboard这样就能实时查看训练损失、验证mIoU等关键指标的变化趋势,及时调整训练策略。