news 2026/10/20 17:04:42

从零构建自定义语义分割数据集:PaddleSeg与Detectron2全流程深度拆解

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
从零构建自定义语义分割数据集:PaddleSeg与Detectron2全流程深度拆解

1. 从零构建自定义语义分割数据集:两条主流技术路线的深度拆解

语义分割这个方向,做了几年之后你会发现一个很现实的问题:公开数据集上跑出来的指标再漂亮,一落到自己的业务场景里,往往直接“翻车”。道路裂缝、医学影像、工业质检、遥感地物分类,每个场景的类别定义、标注规范、图像特性都完全不同。真正决定项目成败的,从来不是模型结构有多新,而是自定义数据集这一环做得够不够扎实。

这篇内容围绕两条主流路线展开:一条是基于 PaddleSeg 的自定义语义分割数据集全流程,另一条是基于 Detectron2 的自定义语义分割数据集全流程。我会把数据准备、标注格式转换、配置文件编写、训练调参、评估推理这几个环节全部拆开讲透,并且重点解释每一步“为什么这么做”。无论你是刚接触语义分割的新手,还是已经跑通过公开数据集、准备迁移到自己数据上的从业者,都能从里面找到可以直接抄作业的部分。

需要先说明一点:PaddleSeg 和 Detectron2 分属两个不同的深度学习生态,前者是国产飞桨体系下的分割工具库,后者是 Meta 开源的通用目标检测与分割框架。它们在数据组织方式、配置文件风格、训练入口上差异很大,但底层逻辑是相通的——都是把“图像 + 像素级标签”喂给网络,通过损失函数约束每个像素的分类结果。理解了这个共性,再看两条路线的差异,就不会觉得混乱。

我个人的经验是,数据集构建占整个项目 60% 以上的工作量,模型训练反而相对标准化。所以下面会把大量篇幅放在数据环节,尤其是标注格式的坑,这部分踩过一次就再也不想踩第二次。

2. 语义分割自定义数据集的核心概念与常见格式

2.1 语义分割到底在解决什么问题

语义分割的本质是逐像素分类。给定一张 H×W 的彩色图像,模型要输出一张同样 H×W 的标签图,图上每个像素点的值代表它所属的类别。比如一张街景图,天空、道路、车辆、行人各自被涂上不同的类别编号,这就是语义分割的结果。

它和实例分割的区别在于:语义分割不区分同类别的不同个体。图里有三辆车,语义分割只会把它们都标成“车”这一类,而实例分割会把三辆车分别标成车1、车2、车3。所以语义分割更适合“关心区域属性、不关心个体数量”的场景,比如遥感地物分类、医学器官分割、路面病害识别。

理解这一点很关键,因为它直接决定了你的标注方式。如果你的业务需要数出每个个体的数量,那语义分割就不合适,应该考虑实例分割或者目标检测。

2.2 主流标注格式对比

自定义数据集最头疼的就是格式问题。不同工具产出的标注格式不一样,不同框架要求的输入格式也不一样,中间往往需要写转换脚本。下面这张表是我整理的高频格式对照,建议收藏。

格式名称典型产出工具存储形式适用框架特点
PNG 掩膜图LabelMe、Labelme2COCO单通道灰度图PaddleSeg、多数分割框架像素值即类别 ID,最通用
COCO JSONLabelMe、CVATJSON + 原图Detectron2、MMDetection支持多边形、RLE 编码
VOC XMLLabelImgXML + 原图早期检测框架分割支持弱,多用于检测
Cityscapes 格式官方标注工具灰度图 + 颜色映射分割研究有专门的 labelIds 和 trainIds
RLE 编码COCO 生态JSON 中的字符串Detectron2压缩存储,适合密集标注

对于语义分割,PNG 掩膜图是最省心的格式。一张灰度图,像素值 0 代表背景,1 代表类别一,2 代表类别二,以此类推。PaddleSeg 原生就吃这种格式,Detectron2 虽然主推 COCO 格式,但也可以通过转换脚本把掩膜图转成 COCO 的 RLE 编码。

2.3 类别定义与标签规范

在动手标注之前,必须先定好类别体系。这一步看起来简单,实际上最容易埋雷。我见过太多项目因为类别定义模糊,导致标注员理解不一致,最后数据集里同一类物体被标成不同类别,模型怎么训都上不去。

几个实操原则:

  • 类别互斥:每个像素只能属于一个类别,不能出现“既算 A 又算 B”的情况。如果业务上确实有重叠,要么合并类别,要么定义优先级。
  • 背景单独成类:背景通常编号为 0,不参与某些评估指标的计算,但必须存在。
  • 类别数量控制在合理范围:类别越多,标注成本越高,模型越难收敛。一般建议先做 5 到 10 类的 MVP 版本,跑通后再扩展。
  • 写一份标注规范文档:把每个类别的定义、边界情况、典型示例图都写清楚,标注员人手一份。这份文档的价值远超你的想象。

提示:类别编号建议从 0 开始连续编号,不要跳号。有些框架对类别 ID 的连续性有隐含假设,跳号可能导致训练时类别权重计算异常。

3. PaddleSeg 自定义数据集全流程实操

3.1 数据目录组织与格式要求

PaddleSeg 对数据集的目录结构有比较明确的要求。推荐的组织方式如下:

dataset/ ├── images/ │ ├── train/ │ │ ├── 0001.jpg │ │ └── 0002.jpg │ └── val/ │ ├── 0003.jpg │ └── 0004.jpg ├── annotations/ │ ├── train/ │ │ ├── 0001.png │ │ └── 0002.png │ └── val/ │ ├── 0003.png │ └── 0004.png ├── train.txt └── val.txt

其中train.txt和val.txt是索引文件,每行一条样本,格式为“图像路径 标注路径”,中间用空格分隔:

images/train/0001.jpg annotations/train/0001.png images/train/0002.jpg annotations/train/0002.png

这里有个细节很多人会忽略:路径建议用相对路径,相对于数据集根目录。这样整个数据集目录可以整体迁移,不会因为绝对路径变化而失效。我早期用绝对路径,换了一台机器就得重新生成索引文件,非常麻烦。

标注图必须是单通道 PNG,不能用 JPG。JPG 是有损压缩,会把像素值改掉,导致类别 ID 错乱。这一点是硬性要求,务必检查。

3.2 标注工具选型与掩膜生成

标注工具我用过不少,LabelMe 是最通用的选择。它支持多边形标注,导出 JSON,然后通过官方脚本labelme2voc.py或社区脚本转成掩膜图。

转换的核心逻辑是:把 JSON 里的多边形坐标填充成对应类别 ID 的区域。LabelMe 导出的 JSON 里,每个 shape 有一个 label 字段,你需要维护一个 label 到 ID 的映射表,转换时按这个表填充。

一个常见的坑是多边形自相交。标注员手抖画出的多边形如果自己和自己交叉,填充算法可能产生意外结果。转换后一定要抽查几张掩膜图,用可视化脚本叠在原图上看看是否吻合。

import numpy as np from PIL import Image # 检查掩膜图的类别分布 mask = np.array(Image.open("annotations/train/0001.png")) unique, counts = np.unique(mask, return_counts=True) for u, c in zip(unique, counts): print(f"类别 {u}: {c} 像素, 占比 {c / mask.size:.2%}")

这段脚本能快速告诉你每张图里各类别的像素占比。如果某个类别占比异常高或异常低,就要怀疑标注是否有问题。

3.3 配置文件编写与关键参数

PaddleSeg 的配置文件是 YAML 格式,主要包含三块:数据集配置、模型配置、优化器与训练配置。下面是一个精简示例:

batch_size: 4 iters: 40000 train_dataset: type: Dataset dataset_root: dataset train_path: dataset/train.txt num_classes: 5 transforms: - type: RandomHorizontalFlip - type: Resize target_size: [512, 512] - type: Normalize mode: train val_dataset: type: Dataset dataset_root: dataset val_path: dataset/val.txt num_classes: 5 transforms: - type: Resize target_size: [512, 512] - type: Normalize mode: val model: type: FCN backbone: type: HRNet_W18 pretrained: https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/HRNet_W18_pretrained.pdparams num_classes: 5 optimizer: type: SGD momentum: 0.9 weight_decay: 4.0e-5 lr_scheduler: type: PolynomialDecay learning_rate: 0.01 power: 0.9

几个关键参数的解释:

  • num_classes:必须和你的类别数一致,包含背景。如果背景 + 4 个前景类,就是 5。
  • target_size:输入网络的尺寸。太小丢失细节,太大显存吃不消。512×512 是个稳妥的起点。
  • batch_size:受显存限制。显存不够就调小,配合梯度累积也能达到类似效果。
  • learning_rate:初始学习率。SGD 一般从 0.01 起步,太大容易震荡,太小收敛慢。

注意:num_classes配错是最常见的报错来源。如果训练时出现 “label out of range” 之类的错误,第一件事就是检查这个参数和掩膜图的实际类别数是否匹配。

3.4 训练启动与日志解读

PaddleSeg 的训练入口是train.py,基本命令:

python train.py \ --config configs/mydataset.yml \ --do_eval \ --use_vdl \ --save_interval 2000 \ --save_dir output

--do_eval表示训练过程中周期性评估,--use_vdl开启可视化日志,--save_interval控制模型保存频率。

训练日志里重点看几个指标:loss、lr、miou、acc。loss 应该是整体下降趋势,如果一直震荡不降,多半是学习率太大或者数据有问题。miou 是语义分割的核心指标,它比单纯的 accuracy 更能反映类别不平衡下的真实性能。

我个人的经验是,前 2000 个 iteration 主要看 loss 是否正常下降,这时候 miou 可能还很低,不用慌。等到 loss 平稳后,miou 才会慢慢爬升。如果训练到一半 miou 突然掉下去,多半是学习率衰减策略或者数据增强出了问题。

4. Detectron2 自定义数据集全流程实操

4.1 COCO 格式注册与数据集准备

Detectron2 走的是 COCO 格式路线。语义分割在 Detectron2 里通常通过COCOInstance或自定义的语义分割数据集注册来实现。核心是把数据组织成 COCO 的 JSON 结构,然后通过DatasetCatalog.register注册进去。

COCO 格式的 JSON 结构大致如下:

{ "images": [ {"id": 1, "file_name": "0001.jpg", "height": 512, "width": 512} ], "annotations": [ {"id": 1, "image_id": 1, "category_id": 1, "segmentation": {"counts": "...", "size": [512, 512]}, "area": 1234, "bbox": [10, 20, 30, 40], "iscrowd": 0} ], "categories": [ {"id": 1, "name": "category_a"}, {"id": 2, "name": "category_b"} ] }

其中segmentation字段用 RLE 编码存储掩膜。如果你手上是 PNG 掩膜图,需要写脚本转成 RLE。Detectron2 提供了pycocotools里的maskUtils.encode来做这件事。

from pycocotools import mask as maskUtils import numpy as np from PIL import Image def png_to_rle(png_path): mask = np.array(Image.open(png_path)) # 二值化,只保留当前类别 binary = (mask == 1).astype(np.uint8) rle = maskUtils.encode(np.asfortranarray(binary)) rle["counts"] = rle["counts"].decode("utf-8") return rle

这里有个容易踩的坑:RLE 编码要求数组是 Fortran 顺序,也就是列优先。如果直接用 numpy 默认的 C 顺序,编码结果会错乱。np.asfortranarray这一步不能省。

4.2 注册数据集与元数据配置

注册数据集是 Detectron2 的核心步骤。你需要告诉框架:数据集叫什么名字、怎么读取、有哪些类别。

from detectron2.data import DatasetCatalog, MetadataCatalog from detectron2.data.datasets import load_coco_json def register_my_dataset(): DatasetCatalog.register("my_seg_train", lambda: load_coco_json("annotations/train.json", "images/train")) MetadataCatalog.get("my_seg_train").set( thing_classes=["category_a", "category_b", "category_c"], evaluator_type="coco" )

thing_classes的顺序必须和 JSON 里category_id的映射一致。如果 JSON 里 category_id 从 1 开始,而 thing_classes 列表从 0 开始索引,中间需要一个映射关系。这个映射错位是 Detectron2 新手最常见的 bug,表现为评估时类别名对不上号。

4.3 模型配置与训练调参

Detectron2 用LazyConfig或CfgNode配置模型。语义分割常用Mask R-CNN或SemanticFPN。以 SemanticFPN 为例:

from detectron2.config import get_cfg from detectron2 import model_zoo cfg = get_cfg() cfg.merge_from_file(model_zoo.get_config_file("COCO-SemanticSegmentation/semantic_fpn_R_50_1x.yaml")) cfg.DATASETS.TRAIN = ("my_seg_train",) cfg.DATASETS.TEST = ("my_seg_val",) cfg.MODEL.WEIGHTS = model_zoo.get_checkpoint_url("COCO-SemanticSegmentation/semantic_fpn_R_50_1x.yaml") cfg.SOLVER.IMS_PER_BATCH = 4 cfg.SOLVER.BASE_LR = 0.001 cfg.SOLVER.MAX_ITER = 40000 cfg.MODEL.SEM_SEG_HEAD.NUM_CLASSES = 3

NUM_CLASSES同样要和实际类别数一致。BASE_LR在 Detectron2 里通常比 PaddleSeg 小一个量级,因为它的默认优化器和 batch 设置不同。

训练启动:

python train_net.py --config-file configs/my_seg.yaml --num-gpus 1

Detectron2 的日志会输出 total_loss、loss_mask、loss_cls 等分项。语义分割主要关注 loss_sem_seg,其他分项如果模型结构里没有对应头,可能不显示。

4.4 推理与结果可视化

训练完成后,用DefaultPredictor做推理:

from detectron2.engine import DefaultPredictor predictor = DefaultPredictor(cfg) outputs = predictor(image) sem_seg = outputs["sem_seg"].argmax(dim=0).cpu().numpy()

sem_seg是每个类别的概率图,取 argmax 得到最终的类别图。可视化时建议用固定的颜色映射表,保证不同图片之间颜色一致,方便对比。

5. 两条路线的对比与选型建议

5.1 核心差异对照

维度PaddleSegDetectron2
数据格式PNG 掩膜图为主COCO JSON + RLE
配置方式YAML 文件Python 代码 / YAML
生态定位专注分割检测、分割、关键点通用
上手难度较低,文档中文友好中等,需要理解注册机制
模型丰富度分割模型齐全分割模型相对少
部署支持Paddle Inference 完善依赖 TorchScript / ONNX

5.2 什么场景选哪条路线

如果你的项目纯做语义分割,且团队对飞桨生态不排斥,PaddleSeg 是更省心的选择。它的数据格式简单,配置文件直观,中文文档齐全,从训练到部署的链路很顺。

如果你的项目同时涉及检测、实例分割、语义分割,或者团队已经在用 PyTorch 生态,Detectron2 的通用性优势就体现出来了。一套框架解决多个任务,数据注册机制虽然学习曲线陡一点,但灵活度更高。

我个人的做法是:快速验证阶段用 PaddleSeg,因为改配置就能跑,迭代快;需要和检测任务联动时切到 Detectron2,避免维护两套数据管线。

6. 常见问题与排查技巧实录

6.1 训练不收敛的排查思路

训练不收敛是最高频的问题。按下面这个顺序排查,基本能定位到原因:

  1. 检查数据:掩膜图类别 ID 是否连续?是否有像素值超出 num_classes 范围?用前面那段统计脚本跑一遍。
  2. 检查学习率:太大导致震荡,太小导致下降缓慢。可以先跑几百个 iteration 看 loss 曲线。
  3. 检查归一化:图像是否做了 Normalize?均值和方差是否和预训练模型匹配?
  4. 检查类别不平衡:如果背景占比 95% 以上,模型会倾向于全预测背景。这时候需要加类别权重或者用 Dice Loss。

6.2 评估指标异常的定位方法

miou 很低但 accuracy 很高,这是典型的类别不平衡表现。accuracy 被背景类拉高了,miou 才是真实水平。解决办法:

  • 在损失函数里给稀有类别更高权重
  • 用 OHEM(在线难例挖掘)聚焦难分像素
  • 数据层面做重采样,增加稀有类别样本

6.3 常见报错速查表

报错信息可能原因解决方法
label out of rangenum_classes 配小检查掩膜最大像素值,调大 num_classes
CUDA out of memorybatch_size 或输入尺寸太大调小 batch_size 或 target_size
KeyError: 'category_id'JSON 结构不完整检查 COCO JSON 的 annotations 字段
RLE counts 解码失败编码时未用 Fortran 顺序用 np.asfortranarray 转换
miou 为 0类别映射错位核对 thing_classes 与 category_id

6.4 独家避坑经验

坑一:标注图用 JPG 存。JPG 有损压缩会改变像素值,导致类别 ID 错乱。必须用 PNG。

坑二:训练集和验证集有重叠。如果同一张图既在训练集又在验证集,验证指标会虚高。生成索引文件时务必去重。

坑三:忽略图像和掩膜的文件名对应。图像叫 0001.jpg,掩膜叫 0001_mask.png,转换脚本里如果按文件名匹配,很容易漏掉。建议统一命名规则。

坑四:数据增强同时作用于图像和掩膜。翻转、旋转、缩放必须同步应用到图像和掩膜上,否则标签和图像对不上。PaddleSeg 和 Detectron2 的 transforms 都处理了这一点,但自己写脚本时要特别注意。

坑五:验证集太小。验证集至少占总数据的 10% 到 20%,且要覆盖所有类别。如果某个类别在验证集里一个样本都没有,miou 计算会出问题。

7. 数据集迭代与持续优化

数据集不是一次做完就完事的。模型训练完,看混淆矩阵,找出哪些类别容易混,然后针对性补充这类样本。这个迭代过程通常要跑两到三轮,miou 才能到一个可用的水平。

我一般会准备一个badcase 收集流程:推理一批新图,把预测结果和原图叠在一起,人工快速过一遍,把明显错的挑出来,重新标注后加入训练集。这样数据集会越来越贴合真实场景,模型性能也会稳步提升。

另外,标注一致性检查也很重要。同一个类别,不同标注员的理解可能有偏差。定期抽样复查,统一标准,比盲目增加数据量更有效。

最后分享一个小技巧:先用小数据集跑通全流程,再规模化标注。我见过太多团队一上来就标几千张,结果格式错了、类别定义改了,全部返工。先用 50 到 100 张跑通训练和评估,确认流程没问题,再批量标注,能省下大量时间。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/20 17:04:32

本体驱动开发:为AI应用构建可推理的知识骨架

1. 从“本体”说起:为什么你的AI应用需要一个知识骨架第一次听到“本体驱动开发”这个词,很多人脑子里冒出来的大概是哲学课上的本体论,或者某个晦涩的学术论文标题。我当初也是这个反应。但真正动手做过几个AI原生应用之后,我越来…

作者头像 李华
网站建设 2026/10/20 17:03:31

智能体技术原理与工程实践指南

我无法根据当前输入内容生成符合要求的博文。 原因如下: 输入中仅提供了项目标题“Manus再出发:一笔超5亿美元融资,一个更大的智能体野心”及相关热搜词占位符(实际为空),但 缺少所有必要结构化字段 &a…

作者头像 李华
网站建设 2026/10/20 17:01:49

PMSM转速环超螺旋滑模控制:从原理到仿真对比

这段时间一直在折腾永磁同步电机(PMSM)的转速环控制。双闭环PI在空载、额定工况下表现确实很稳定,但一旦加上负载扰动、把转动惯量调大,或者让定子电阻漂移一点,转速曲线就开始拖泥带水。于是我把目光转向了滑模控制&a…

作者头像 李华
网站建设 2026/10/20 17:01:45

MySQL输入密码闪退怎么办?从报错定位到重置密码的完整排查指南

先说个十万火急又特别接地气的事:高高兴兴双击MySQL命令行客户端,输入root密码,回车,结果窗口就像被鬼吹了一口,“嗖”一下关没了。这种“mysql输入密码闪退”的场面,我敢说至少有一半MySQL初学者都碰到过&…

作者头像 李华
网站建设 2026/10/20 17:01:38

基于微信小程序与Spring Boot的文旅宣传系统实践与解析

做这类"小程序Spring Boot"的文旅宣传项目,我前后接触过好几个,河湟文化旅游景点宣传系统算是里面比较典型的一个。河湟地区的历史底蕴和民族文化特色非常鲜明,景点分散、类型多样,面向的游客群体又偏年轻化&#xff0c…

作者头像 李华
网站建设 2026/10/20 17:01:21

TortoiseSVN安装配置全指南:版本选择、组件勾选与避坑排查

简介:TortoiseSVN(俗称小乌龟)是一款面向Windows用户的Subversion版本控制客户端,适合个人开发者与团队协作场景,用于解决代码版本管理、多人协同修改与历史追溯等问题。资源以rar压缩包形式提供,整体约16.…

作者头像 李华