
做目标检测的人迟早会碰上一件绕不过去的事跑通了 MMDetection 的 demo用的是官方的 COCO 预训练权重和自带数据集一切都很顺。可一旦要把自己的数据丢进去训练很多人就卡在第一步了标注格式怎么整理配置文件怎么改训练跑起来之后报一堆错又该怎么排查这篇文章就基于 MMDetection3.0完整走一遍从零构建自定义数据集的训练流程把数据准备、配置修改、训练监控和常见坑全部串起来说清楚。这篇内容适合两类人一类是刚入门目标检测、在用自己的图片做实验的学生或算法工程师另一类是想把 MMDetection3.0 真正落到具体业务场景里的开发者。读完你不仅能跑通流程还能理解每一步背后的原因——比如为什么要选 COCO 格式、为什么类名要注册、为什么学习率要按 batch size 调整。废话不多说直接开始。1. 先搞清楚整体流程再动手1.1 训练一次自定义目标检测模型的完整链路很多人在第一次搞自定义数据集的时候容易陷入“东改一下、西试一下”的局面。今天改标注格式明天调配置文件后天发现数据加载不对然后又回头检查。其实整个训练流程的链路是固定的你只需要按顺序走数据采集与整理 - 数据标注 - 标注格式转换为 COCO JSON - 准备完整的数据目录结构 - 安装并验证 MMDetection3.0 环境 - 编写代码注册自定义数据集类 - 修改配置文件 - 启动训练 - 监控训练过程 - 评估与导出模型。这里面的每一个环节都有明确的输入输出。比如说“数据标注”的输入是原始图片输出是标注文件而“格式转换”的输入是标注软件的导出结果输出是 COCO 标准的 training data包括annotations里的instances_train.json和instances_val.json。只要你不越级操作严格按照顺序执行所有问题基本都能对号入座。我见过很多同学直接把标注文件扔进data/coco/annotations/文件夹然后想着“应该可以吧”结果训练时各种报错。你要记住MMDetection3.0 的核心逻辑是先通过dataset类型找到对应类再通过该类读取配置里指定的ann_file最后把标注信息映射成训练用的张量。任何一步没对齐都会导致数据加载阶段直接失败。1.2 数据格式选择COCO 还是 VOCMMDetection3.0 支持很多数据集格式包括 COCO、VOC、Cityscapes以及通过自定义注册的格式。对于绝大多数自定义场景我强烈推荐先把数据转成 COCO 格式原因有三个第一COCO 格式的标注信息是集中在一个 JSON 文件里的包括图片信息、标注框、类别映射关系结构统一排查问题容易。标注框格式是[x, y, width, height]顶点坐标是左上角宽高都是像素值不用像 VOC 那样再把 XML 解析成各种标签再拼接。第二MMDetection3.0 内置的 CocoDataset 是最成熟的实现支持过滤无标注图片、自动生成segmentation、进行类别筛选等操作稳定性比其他格式高很多。VOC 虽然也能用但在处理大量小目标或者类别不均衡时COCO 的评估脚本和数据增强配合更好。第三以后如果想做实例分割COCO 标注里可以带多边形 segmentationVOC 格式的 polygon 或 mask 转换反而麻烦。一开始转成 COCO JSON后面扩展任务会很顺。当然如果你的团队已经有大量 VOC 格式的历史数据直接用 PascalVOCDataset 也不是不行。但我的建议是写一个脚本一次性转成 COCO哪怕多花半小时后面省下的时间绝对值得。转换脚本的核心思路遍历每张图片及其 XML 标注为每张图分配一个id每个目标框分配一个annotation id同时收集所有类别名构建categories最后拼装成images、annotations、categories三个列表。1.3 环境与版本的前置检查开始之前先把环境理清楚。MMDetection3.0 和旧版 2.x 在 API 上有一些差异如果你之前用过 2.x需要特别注意。3.0 的核心依赖是mmcv2.0.0、mmengine0.7.0并且要求 PyTorch 版本在 1.8 以上建议直接用 PyTorch 2.0 或更高版本。推荐使用 conda 环境安装conda create -n openmmlab python3.8 -y conda activate openmmlab pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118 pip install -U openmim mim install mmengine mim install mmcv2.0.0 git clone https://github.com/open-mmlab/mmdetection.git cd mmdetection pip install -v -e .这里有几个细节需要注意mmcv一定要通过mim安装因为官方编译好的预编译包是按本机 CUDA/PyTorch 版本匹配的。如果直接pip install mmcv可能会装到 CPU 版本后面训练时直接报设备不匹配。pip install -v -e .中的-e表示以可编辑模式安装这样改源码里的文件会立刻生效对调注册代码很有用。安装完建议先跑一个 demo 验证环境是否正常python demo/image_demo.py demo/demo.jpg configs/rtmdet/rtmdet_tiny_8xb32-300e_coco.py --weights 对应权重路径。如果 demo 能顺利框出落日下的牛说明环境基本没问题。2. 数据准备标注、整理与格式改写2.1 标注软件与标注规范标注是整个流程中最耗人力、也最容易出问题的环节。工具选择上个人用户和小团队我用得比较多的是 LabelImg 和 X-AnyLabeling。LabelImg 适合做矩形框检测轻量稳定输出 VOC 格式 XML 或者 YOLO 格式 txt。X-AnyLabeling 则多了自动标注功能对半透明物体或者细小目标有辅助效果。不过无论用哪种工具标注规范必须提前定好。我自己在项目开始前一定会先写一份标注规则至少包括类别列表和每个类别对应的中文/英文名保证团队里所有人认识一致遮挡目标的处理方式如果目标被遮挡超过 50%是标还是不标极小目标的处理方式小于 16x16 像素的目标是否纳入重叠目标的处理方式两个目标重叠时是都标还是只标前面的。这些规则看起来无关紧要实际影响非常大。数据标注的一致性直接决定了模型训练的上限。我见过某团队标注时“凭感觉”标同一个类别的框有的严丝合缝有的外扩半个车身结果训练出来的模型 mAP 特别低再看标注才发现问题。与其后面花时间清洗数据不如一开始统一规范。2.2 从“标注结果”到“COCO JSON”的转换细节标注完成后通常得到的是一个文件夹的 XML 文件或者几个 JSON 文件。接下来要转换成 COCO JSON。以 LabelImg 导出的 XML 为例转换脚本的核心逻辑如下import os import xml.etree.ElementTree as ET import json from PIL import Image def xml_to_coco(xml_folder, img_folder, output_json): images [] annotations [] categories [] cat_map {} ann_id 1 img_id 1 for xml_file in os.listdir(xml_folder): if not xml_file.endswith(.xml): continue tree ET.parse(os.path.join(xml_folder, xml_file)) root tree.getroot() filename root.findtext(filename) img_path os.path.join(img_folder, filename) width int(root.findtext(size/width)) height int(root.findtext(size/height)) images.append({ id: img_id, file_name: filename, width: width, height: height }) for obj in root.findall(object): name obj.findtext(name) if name not in cat_map: cat_map[name] len(categories) 1 categories.append({ id: len(categories) 1, name: name }) bndbox obj.find(bndbox) x1 float(bndbox.findtext(xmin)) y1 float(bndbox.findtext(ymin)) x2 float(bndbox.findtext(xmax)) y2 float(bndbox.findtext(ymax)) w x2 - x1 h y2 - y1 annotations.append({ id: ann_id, image_id: img_id, category_id: cat_map[name], bbox: [x1, y1, w, h], area: w * h, iscrowd: 0 }) ann_id 1 img_id 1 coco_data { images: images, annotations: annotations, categories: categories } with open(output_json, w, encodingutf-8) as f: json.dump(coco_data, f, ensure_asciiFalse, indent2)转换的时候要注意几个坑第一XML 里的difficult字段需要在循环里单独判断通常设置为 1 的目标就不要转进 COCO 了否则会影响评估。第二COCO 的bbox只接受[x, y, width, height]千万不要把格式写成[x1, y1, x2, y2]。第三area字段建议直接用框的宽高乘积不要随意填 0否则在训练时的面积跳层分类中会有问题。第四iscrowd统一填 0如果标了群体目标要单独处理。2.3 训练集、验证集的划分与目录组织数据转换完成后目录结构需要符合 MMDetection3.0 默认的读取惯例。官方 CocoDataset 默认读取data/coco下指定路径。目录结构建议如下data/ └── coco/ ├── annotations/ │ ├── instances_train.json │ └── instances_val.json ├── train2017/ │ ├── img_001.jpg │ ├── img_002.jpg │ └── ... └── val2017/ ├── img_001.jpg └── ...这里我刻意忽略年份因为自定义数据不需要真的叫train2017但如果你不想改配置文件里的data_root路径保持这个命名方式是省事的。如果你习惯用别的目录名也可以通过修改data_root解决。划分训练验证集的时候建议尽量按“场景、时间段、地点”分层切分不要纯随机。举个例子如果你在收集夜晚和白天两个场景的数据随机划分容易让某一场景数据只出现在训练集或只出现在验证集里。简单做法是先按图片文件名前缀或拍摄时间分组再在组内随机抽样确保两个集合中的场景分布接近。经验上训练集占比 70%-80%验证集 20%-30% 是比较合理的范围。如果数据量总共不到 1000 张考虑用 K 折交叉验证的方式替代单次切分不然验证结果波动会非常大。3. MMDetection3.0 的配置与注册机制3.1 为什么修改注册文件和 config 是必经之路MMDetection3.0 的架构核心是注册器机制。简单说你在训练时指定model dict(typeCascadeRCNN, ...)框架会通过注册器找到对应的模块类并实例化。这意味着如果你的数据集类没有被注册配置里写什么类型都没用。默认情况下CocoDataset已经注册了所以理论上你可以直接把标注文件放到对应位置然后在配置里指定typeCocoDataset和classes(person, car)就能训练。但这个方案有两个限制一是metainfo里的类名和 COCO 原生的 80 类如果冲突会被覆盖或者出现类别数量对不上二是如果想在数据集类里加一些自定义逻辑比如按比例筛选采样、检查图片损坏就不好实现了。所以我的建议是不管简单项目还是复杂项目都写一个完整的自定义数据集文件然后注册到框架里。这样你能完全掌控训练的输入逻辑后续很多问题也更容易定位。3.2 编写数据集注册文件在 MMDetection3.0 中自定义数据集及类的注册需要两步写一个 Python 文件定义数据集类然后确保这个模块在训练前被导入。数据集类可以直接继承CocoDataset只需要覆盖METAINFO字典即可# mmdet/datasets/my_dataset.py from mmdet.registry import DATASETS from mmdet.datasets.coco import CocoDataset DATASETS.register_module() class MyDataset(CocoDataset): METAINFO { classes: (person, car, bicycle), palette: [(220, 20, 60), (119, 11, 32), (0, 0, 142)] }这里有两个细节需要说明第一METAINFO中的类名顺序必须和转换 JSON 时categories列表的顺序保持一致。否则训练时类别 id 对不上验证集的mAP会出现奇怪的低值。第二palette是可视化用的颜色列表不参与训练逻辑但建议还是写清楚方便后面用tools/test.py可视化推理结果时区分不同类别。自定义模块编写完成后一定要确保这个文件在训练入口被执行。最稳妥的方式是在训练脚本里显式 importimport mypath.my_dataset # 确保注册发生 from mmengine.runner import Runner如果你用的是命令行方式python tools/train.py config.py那就需要保证my_dataset.py在 Python 搜索路径中并且在 config 里设置custom_imports dict(imports[mypath.my_dataset], allow_failed_importsFalse)。这个方法我在项目里用得多因为可以不改动 mmdetection 的源码直接把自定义模块放在项目目录下。3.3 写一个实用的训练配置文件MMDetection3.0 的配置基于mmengine所有配置都是 Python 文件。相比旧版的复杂继承新版配置更清晰但多数新手还是会对着官方 config 一头雾水。推荐的做法是复制官方一个基础配置然后逐项修改。以 RTMDet 为例我整理了一份最小可用的自定义训练配置_base_ ../rtmdet/rtmdet_tiny_8xb32-300e_coco.py data_root data/coco/ metainfo { classes: (person, car, bicycle), palette: [(220, 20, 60), (119, 11, 32), (0, 0, 142)] } num_classes 3 model dict( bbox_headdict( typeRTMDetSepBNHead, num_classesnum_classes)) train_dataloader dict( batch_size8, datasetdict( data_rootdata_root, metainfometainfo, ann_fileannotations/instances_train.json, data_prefixdict(imgtrain2017/))) val_dataloader dict( datasetdict( data_rootdata_root, metainfometainfo, ann_fileannotations/instances_val.json, data_prefixdict(imgval2017/))) test_dataloader val_dataloader val_evaluator dict(ann_filedata_root annotations/instances_val.json) test_evaluator val_evaluator optim_wrapper dict(optimizerdict(lr0.0001))这里需要注意修改num_classes后要检查 bbox head 的每个num_classes参数通常位于模型的bbox_head下。如果你用的是 Cascade R-CNN 等两阶段模型还需要同步调整roi_head里的bbox_head数量。很容易漏改漏了训练就会直接报维度错误。metainfo同时出现在数据集和 model 的配置里吗不用。只需要在 dataloader 的数据集部分指定一次metainfo评估器里只传ann_file。model里的num_classes是纯模型结构层面的参数跟数据层面的类名无关。train_dataloader默认继承_base_里的配置所以如果没有特殊需求只需要覆盖dataset子项。如果你在覆盖时少写了某个字段mmengine默认会合并两个配置而不是直接覆盖这个行为要记住——否则你改了ann_file却还带着原来pipeline里的一些旧路径就麻烦了。另外建议设置工作目录和 checkpoint 保存间隔方便断点续训work_dir ./work_dirs/rtmdet_custom default_hooks dict( checkpointdict(typeCheckpointHook, interval5, max_keep_ckpts2))4. 开始训练启动、监控与断点续训4.1 训练命令与日志解读配置写好之后训练指令非常简单python tools/train.py configs/rtmdet/rtmdet_tiny_8xb32-300e_coco.py如果你把配置文件放在自己的目录下直接写对应路径就行。第一次启动时你会看到大量日志输出。需要特别关注几个关键信息Environment info确认 PyTorch、CUDA、MMCV 版本都匹配Config确认metainfo里的类名顺序Dataloader初始化时会有类似Load annotations from ...的提示开始迭代后日志里会显示loss_cls、loss_bbox等损失值以及data_time和iteration_time。data_time太高说明数据加载是瓶颈通常是因为num_workers设置太小、图片读取太慢或者磁盘 IO 有瓶颈。默认配置用的是 4 个 worker如果机器性能允许可以调大一点。训练过程中如果loss直接变成nan优先排查学习率和数据标准化问题。自定义数据集经常会在预处理时把归一化参数搞错导致输入数据出现异常值。4.2 验证与 TensorBoard / wandb 监控MMDetection3.0 默认会在每个val_interval周期后自动跑一次验证集评估。RTMDet 的配置默认是每 1 个 epoch 验证一次输出 COCO 格式的 mAP 指标。你需要在日志中关注coco/bbox_mAP这是衡量模型训练状态的核心指标。如果你希望可视化监控可以在配置里加上 wandb 或 TensorBoard 的钩子。TensorBoard 的配置很简单visualizer dict( typeDetLocalVisualizer, vis_backends[dict(typeLocalVisBackend), dict(typeTensorboardVisBackend)])启动后TensorBoard 里能看到 loss 曲线、学习率变化曲线以及验证集上的 box mAP。我个人更推荐在训练前 50 个 step 就打开 TensorBoard 看曲线变化趋势比等第一个验证 epoch 结束再回头看反馈快得多。如果你想在训练过程中直接可视化预测框可以用 3.0 自带的预测可视化类不过那一块适合在检测阶段用。训练阶段主要还是靠曲线和评估指标来判断。4.3 断点续训与多卡训练训练到一半断电、崩溃或者 boss 让你换个模型跑是家常便饭。MMDetection3.0 支持断点续训命令是python tools/train.py config.py --resume work_dirs/rtmdet_custom/epoch_50.pth恢复后优化器状态、学习率调度器状态都会从上一次保存的 checkpoint 中恢复不需要重新跑前 50 个 epoch。多卡训练则用bash tools/dist_train.sh config.py 4这里第二参数是 GPU 数量。多卡训练时要稍微注意 batch size 和学习率的关系。比如原配置是8xb32表示 8 卡每卡 32 张。你用 4 卡跑默认每卡 32 张总 batch 变成 128学习率可能就需要按比例调整。常见的做法是保持每卡的 batch 和原配置一致减少卡数时同步把学习率降下来。实际上更多人的做法是直接把train_dataloader的batch_size改小因为显存不够用。此时也要记得同步调学习率否则模型收敛会不稳定。5. 常见问题与排查手册5.1 KeyError 与类名不匹配问题最常见的一类报错就是KeyError: xxx is not in the dataset registry或者AssertionError: Theclassesindatasetshould be the same as inmodel...。出现这类问题的原因有三个自定义数据集模块没有在训练前被 importmetainfo的classes顺序和 JSON 里categories顺序不一致多个配置文件之间存在类名覆盖比如_base_里已经有一个classes你在子配置里覆盖时写错了字段名。排查方法很简单训练前先写一个小脚本加载数据集检查类别映射from mmdet.datasets import build_dataset cfg dict(typeMyDataset, ...) dataset build_dataset(cfg) print(dataset.metainfo) print(len(dataset))这样能直接看到注册后的类别列表、数据集总长度因为大部分数据加载问题都能在这步提前暴露。5.2 显存不够、训练崩溃问题自定义数据集场景下显存溢出通常是因为图片尺寸太大或者 batch size 太高。最常见的做法是把测试和训练 pipeline 里的img_scale改成小一点控制在 1333x800 以内。RTMDet 官方默认使用 640x640如果你用 512x512 会显著减少显存占用不过精度会小幅下降。如果改了尺寸还是 OOM就要考虑梯度累积。MMDetection3.0 支持通过配置来模拟更大的 batchtrain_dataloader dict(batch_size4, num_workers4) train_cfg dict(typeEpochBasedTrainLoop, max_epochs300, val_interval1) optim_wrapper dict( typeOptimWrapper, optimizerdict(typeSGD, lr0.0001), accumulative_counts4)accumulative_counts4表示每 4 个 step 更新一次梯度相当于 batch size 从 4 变成了 16 的效果。学习率调整需要考虑到这一点因为有效 batch 变了。另外pin_memory设为 False、persistent_workers设为 True也能缓解训练过程中的 CPU 内存峰值。如果数据图片分辨率跨度较大还可以在 pipeline 里加入MinIoURandomCrop等方法不过这些对显存本身没有直接改善。5.3 评估指标为 0 或数据没加载训练完了验证时发现 bbox_mAP 一直是 0。这个问题特别让人崩溃而且很多时候不是模型结构问题而是数据读取逻辑的问题。我碰到过的一个典型案例是训练时 loss 在下降可视化样例也正常但验证集 mAP 始终为 0。后来排查发现验证集的标注 JSON 里类别 id 和训练集不一致——训练集里person的 id 是 1验证集里person的 id 是 0。这种情况下评估器返回的结果必然为 0。把验证集的categories重新映射成和训练集一致后指标立刻恢复正常。另一个案例是数据加载后所有图片都是黑图原因是data_prefix里的 img 路径写错实际加载到的是空路径代码可能不会报错只是会自动填充零矩阵。这种问题特别隐蔽不通过可视化图片很难发现。所以训练前一定要遍历几个样本跑一次show_result或者在 dataloader 里打印 tensor 的均值。还有一个常见问题是标注框坐标是相对坐标如 YOLO 的归一化坐标直接转换成了 COCO 的 JSON结果框的位置完全错误。检查方法是用标注工具随便看一张图的标注框如果框都挤在左上角多半就是坐标转换出了问题。COCO 格式里必须是绝对像素坐标。5.4 学习率过大或数据增强过于激进导致不收敛有时候训练 loss 一直在下降但验证集 mAP 却上不去甚至出现震荡。这种情况往往不是数据问题而是学习率设置不当。自定义数据集通常规模比 COCO 小很多如果直接沿用 COCO 预训练配置里的学习率很容易出现“过拟合和欠拟合之间的反复横跳”。我的经验是当你的训练数据少于 5000 张时初始学习率设置成原配置的 1/10 起步然后耐心观察前 2000 步的 loss 下降速度。如果 loss 下降太慢再试着把学习率调回 1/5。如果 loss 直接发散就继续降。另外MMDetection3.0 默认的Mosaic和MixUp数据增强在 RTMDet 等配置里是默认开启的。这些增强虽然能提升精度但对于小数据集过度增强会把目标切的七零八落导致模型学不到稳定特征。所以刚开始跑通流程时我建议先把train_pipeline里的Mosaic、MixUp注释掉等逻辑通了再逐步加回来。6. 我的实操体会与避坑记录写到这再分享一些我在真实项目里攒下来的经验。第一永远不要跳过训练前的数据检查。哪怕只是写几行代码把ann_file里的类别数量、图片数量、框数量统计一遍都比盲开训练有用得多。很多问题在数据阶段防住比在训练阶段去排查要省几小时。第二配置文件尽量“显式”一点。MMDetection3.0 里_base_继承很方便但也容易让人忘记自己改了哪些字段。我的习惯是重要的自定义内容全都在当前 config 里写全而不是依赖继承同时用 git 管理配置文件的每次修改。这样模型复现、回归对比都很方便。第三做自定义数据集项目最好从一开始就固定一个版本的数据集文件。很多团队训练到一半发现标注有误重新生成了 JSON 文件结果训练了几十个 epoch 的模型和新的数据集对不上之前的对比实验全部作废。数据版本号其实很重要。第四如果条件允许先用一个小样本集跑通整个训练流程。我的做法是先拿 50 张图做成 mini 数据集设置max_epochs2跑一遍确认所有环节无错之后再上全部数据正式训练。这一步能帮你规避各种低级配置错误也能让你快速迭代调试。第五训练完成后用官方提供的tools/test.py输出检测结果和可视化图不要只看 mAP。mAP 只能说明整体情况具体到某类目标错检漏检的表现只有看可视化样例才能感知。我通常在验证集里挑 20 张比较有代表性的图把预测框画出来和 GT 放在一起对比。这个环节能帮你发现标注规范问题、小目标漏检问题、类别混淆问题比调模型参数更重要。最后再补充一个关于模型导出和部署的小提醒MMDetection3.0 训练出的 checkpoint 里除了模型权重还包含优化器状态和完整配置。如果只是做推理用tools/model_converters/publish_model.py发布模型能把文件体积减小很多也避免把训练信息带到生产环境里。以上就是从零构建自定义数据集训练流程的全部内容。整个流程不复杂但每一步都有容易踩的坑。按顺序走下来先保证能跑再考虑优化这是我一直坚持的做事方法。希望这篇分享能让你少走一些弯路。