mmdetection自定义数据集接入全流程:格式转换与配置对齐实战指南

发布时间:2026/10/22 9:05:45
mmdetection自定义数据集接入全流程:格式转换与配置对齐实战指南 上周有位朋友发来一段报错日志问我说他照着官方教程跑通了检测demo但换上自己的数据集之后训练Loss直接卡在个位数下不去epoch都快跑完一轮了还在原地踏步。我一看配置文件问题出在数据集标注的class_id和配置文件里的num_classes对不上一个63一个80模型从第一轮开始就在瞎猜。这种问题太常见了说真的mmdetection跑通自带数据集只是热身真正的坎全在“自定义数据集”这一步上。很多从没碰过目标检测框架的人会在数据准备阶段反复折腾好几天其实核心就三件事把标注整理成框架认得的格式、把目录摆对位置、把配置里的参数和真实数据对齐。这篇文章把我实际踩过的坑和完整的接入流程全部摊开讲一遍按这个顺序走基本能绕开绝大多数低级的报错。1. 跑通Demo之后真正的分水岭是数据格式mmdetection本身不关心你的图片是无人机拍的还是监控摄像头截的它只认两类标注格式COCO和VOC。绝大多数教程默认走COCO路线因为它一套JSON就把图片信息、目标框、类别全包进去了而且mmdet的许多内置评估逻辑比如COCO AP都是围绕这套格式写的。你把它想象成快递面单必须按规定的栏位填好收件人、地址、物品清单快递公司才认。数据格式就是这张面单编辑器只认固定字段缺一个、错一个后面全是问题。COCO格式的核心是一个大JSON文件里面有images、annotations、categories三块。images记录每张图片的id、width、height和文件名annotations记录每个目标的image_id、bbox、category_idcategories定义类别id到类别名的映射。最容易被忽略的点是bbox的格式COCO里是[x, y, width, height]也就是左上角坐标加宽高而很多工具默认导出的是[x1, y1, x2, y2]格式。这个坐标系搞错训练不会报错但模型学出来框全是歪的。我自己第一次做自定义数据集时就栽在坐标格式上。当时用标注工具导出的是两点坐标我直接往COCO脚本里塞结果训练出来的模型对每个目标都画一个从左上角延伸到右下角的对角线框——mAP基本是0。排查半天最后是拿一张图可视化标注才发现的。所以第一步不是急着写训练脚本而是先把一张测试图片的标注画出来人眼看着没问题再往下走。这里还牵出一个问题平时团队里标注数据最常见的是用Labelme这类多边形标注工具导出的是每个目标的多边形顶点坐标和COCO的矩形框又不一样。所以转换脚本里通常会做一个“外接矩形”操作把多边形顶点的最小包围盒算出来。这一步也会埋坑如果标注时手滑把点拖到图片外面去了外接矩形的坐标就可能出现负值或者超过图片宽高加载时不报错但数据增强阶段会出现诡异的变形。建议转换完之后统一做一次坐标裁剪把所有越界的框都纠正到图片范围内。2. 从零开始做标注工具选型与格式转换如果你的数据集还没开始标注那么现阶段最顺手的方案是Labelme配一个自写的转换脚本。理由很简单Labelme导出的单个JSON文件里带有多边形坐标和标签名信息量完整后续不管是转COCO还是转VOC都有回旋余地。而另一类工具像LabelImg虽然上手更快但它直接导出VOC XML如果你后面想换一套框架或改需求XML转别的格式还得再绕一圈。我个人的习惯是“原始标注越通用越好展示格式越标准越好”——先把标注存成带多边形的Labelme JSON再写脚本转成目标框架要的格式这套流程换任何框架都能复用。标注阶段有几个实操上的细节值得提醒第一不要在原始大图上直接标先在图片处理软件里把图缩放到合适的分辨率比如单边不超过2000像素否则标注界面卡顿不说后续训练还得再缩一次浪费磁盘。第二每个类别的样本量尽量均衡如果一个类别只有几十个框另一个类别有几千个框模型收敛之后小样本类别的AP会惨不忍睹。第三标注时把遮挡严重的、目标小于十几个像素的都先筛掉这些目标连人都看不清楚模型根本学不会。标注完成之后需要一个格式转换脚本。下面这个脚本是我常用的一版主要做三件事读Labelme导出的JSON、把多边形顶点换算成COCO的bbox、把所有标注汇总成一个大JSON。核心逻辑不复杂但运行时要注意路径别写错。import json import os import glob from PIL import Image from pycocotools import mask as mask_utils def labelme_to_coco(img_dir, labelme_dir, output_path, categories): # categories: dict 映射 标签名 - id images, annotations [], [] ann_id 1 cat_name_to_id {name: i1 for i, name in enumerate(categories)} labelme_jsons glob.glob(os.path.join(labelme_dir, *.json)) for idx, lm_file in enumerate(labelme_jsons, start1): with open(lm_file, r, encodingutf-8) as f: lm json.load(f) img_path os.path.join(img_dir, lm[imagePath]) img Image.open(img_path) w, h img.size image_id idx images.append({ id: image_id, file_name: lm[imagePath], width: w, height: h, }) for shape in lm[shapes]: label shape[label] if label not in cat_name_to_id: continue points shape[points] # [[x,y], [x,y], ...] xs [p[0] for p in points] ys [p[1] for p in points] x_min, y_min min(xs), min(ys) x_max, y_max max(xs), max(ys) # 越界裁剪 x_min max(0, x_min) y_min max(0, y_min) x_max min(w, x_max) y_max min(h, y_max) if x_max x_min or y_max y_min: continue bbox [x_min, y_min, x_max - x_min, y_max - y_min] area bbox[2] * bbox[3] annotations.append({ id: ann_id, image_id: image_id, category_id: cat_name_to_id[label], bbox: bbox, area: area, iscrowd: 0, }) ann_id 1 coco { images: images, annotations: annotations, categories: [{id: v, name: k} for k, v in cat_name_to_id.items()], } with open(output_path, w, encodingutf-8) as f: json.dump(coco, f, ensure_asciiFalse, indent2) print(fdone: {len(images)} images, {ann_id-1} bboxes)这里有几个细节我要单独拎出来说。脚本里的categories列表顺序在后续配置里必须一致因为mmdetection读取类别名称依赖的是配置里的metainfo字段而不是JSON里的顺序。很多人以为“反正JSON里有categories框架会自己读”结果训练时发现日志里的类别名称全是[0, 1, 2, ...]这样的数字看起来能用但最后画可视化图的时候对应不上。其次是iscrowd这个字段一定要写上0某些框架在计算评估指标时会去读它缺了会直接报KeyError。标注转换完成之后强烈建议写个可视化脚本把COCO标注直接画回原图上检查一遍至少抽10张图人眼看一遍重点看三处框是不是紧贴目标、有没有越界、类别名称对不对。这一步花不了十分钟但能帮你拦住90%的格式错误。可视化可以这样写用matplotlib或者cv2把bbox按(x, y, w, h)画出来再把类别名写在框左上角存成一张新图肉眼翻一遍。3. 目录组织与命名规则按mmdet的约定来少走弯路数据格式搞定之后下一个大坑就是目录结构。mmdetection虽然理论上允许你随意指定路径但惯例上大家都是按它预设的数据目录组织方式来摆放因为这样配置文件改动最小、同事之间交流也不会出现“我改了这个路径所以训练好了”这种玄学。我的建议是目录结构越接近官方风格越好不要自己发明创造。一个典型的COCO风格目录大概是这样的data/ └── my_dataset/ ├── annotations/ │ ├── train.json │ └── val.json ├── train/ │ ├── 000001.jpg │ └── ... └── val/ ├── 000001.jpg └── ...这个组织方式的好处是配置里只需要改data_root这一个变量后续的train_dataloader、val_dataloader路径全是相对拼接不容易出错。而很多人喜欢把JSON和图片放一起甚至图片直接平铺在annotations旁边这样不是不行但会导致配置里到处是绝对路径迁移到别的机器上就得改一堆地方。文件命名也值得注意。框架不会要求命名必须连续但建议用%06d.jpg这种带前导零的编号风格避免后续做数据合并时排序错乱。我自己踩过一个坑从网上下载的公开数据文件名带原始ID前缀比如img_99872.jpg直接扔进train目录结果训练日志里看到的文件列表毫无规律排查问题时分不清是哪一批数据出了问题。如果已经有自定义目录需要做一步数据划分。最安全的做法是写脚本按比例随机切分而且保证同一张图片不会同时出现在train和val里。这个切分脚本建议加一个随机种子参数保证每次跑出来的划分结果一致方便日后复现实验。比例上目标检测数据量通常比分类任务要求高样本少的时候宁可val比例大一点比如8:2或7:3否则验证集太小评估指标波动很大根本看不出模型好坏。import os, shutil, random, json random.seed(42) img_dir data/my_dataset/all_images train_dir data/my_dataset/train val_dir data/my_dataset/val os.makedirs(train_dir, exist_okTrue) os.makedirs(val_dir, exist_okTrue) imgs [f for f in os.listdir(img_dir) if f.lower().endswith((.jpg, .jpeg, .png))] random.shuffle(imgs) val_cnt int(len(imgs) * 0.2) for i, name in enumerate(imgs): dst val_dir if i val_cnt else train_dir shutil.copy(os.path.join(img_dir, name), os.path.join(dst, name))划分完之后标注文件也要跟着分train里出现的每张图片在train.json里都有对应的images记录val同理。这一步是纯逻辑操作关键是确保没有交叉否则训练时模型已经见过了val图片评估的mAP会虚高之后部署上线才发现模型实际效果差一大截。4. 配置文件里必须对齐的参数num_classes、数据路径与类别名配置阶段很多人容易犯一个错误就是拿官方现成的config直接用只改了num_classes结果各类报错层出不穷。实际上一个自定义数据集的配置需要对齐的东西至少有四处数据集类型、数据路径、类别名列表、模型头部的类别数。下面我以Faster R-CNN为例把改动点逐个拆开讲。配置的开头通常是这样的_base_ [ ../_base_/models/faster-rcnn_r50_fpn.py, ../_base_/datasets/coco_detection.py, ../_base_/default_runtime.py ]这三个基础配置分别是模型结构、数据集加载、训练调度。其中coco_detection.py负责定义数据集的加载方式它读取环境变量和data_root拼出具体的图片和标注路径。这里要特别注意dataset_type必须写成CocoDataset然后data_root指到你的数据集根目录。dataset_type CocoDataset data_root data/my_dataset/ metainfo { classes: (cat, dog, car, person), # 必须和JSON里categories顺序一致 palette: [(220, 20, 60), (119, 11, 32), (0, 0, 142), (250, 170, 30)] }metainfo里classes的顺序是重中之重它直接影响category_id的映射方式。虽然JSON里也有categories但在当前版本mmdetection中训练时主要依据配置里的classes去构造数据集的类别映射如果两者顺序不一致模型的输出类别和你脑子里想的类别就全对不上了。palette是可视化时用的颜色随便给一组RGB即可但长度必须和类别数一致否则可视化脚本会崩。然后是数据加载器train_dataloader dict( batch_size8, num_workers4, datasetdict( data_rootdata_root, metainfometainfo, ann_fileannotations/train.json, data_prefixdict(imgtrain/) ) ) val_dataloader dict( datasetdict( data_rootdata_root, metainfometainfo, ann_fileannotations/val.json, data_prefixdict(imgval/) ) )这里ann_file和data_prefix是相对data_root的路径注意data_prefix里img指向的目录不要带前面的data/否则路径就变成data/my_dataset/data/my_dataset/train了。很多新手的报错就是从这里来的各种FileNotFoundError其实只是少看了一个层级。模型头部的类别数也要同步改。以Faster R-CNN为例模型配置文件里有roi_head其中bbox_head的num_classes要改成你自己的类别数model dict( roi_headdict( bbox_headdict( typeShared2FCBBoxHead, num_classes4, # 改成自己的类别数 ) ) )如果你用的是带rpn_head的框架RPN头不需要改因为RPN只负责区分前景/背景不区分具体类别。但如果你用的是RetinaNet这类单阶段检测器则要改bbox_head里的num_classes。这里有个隐藏关系num_classes和bbox_head的loss计算维度是配套的改漏一个训练会在loss计算阶段报维度不匹配的错误。很多配置里还有train_cfg、test_cfg这里不建议动。保守起见我一般只改上述几处其余保持官方默认值等模型能正常收敛后再考虑调整。还有一个容易被忽略的点是val_evaluator里的classwise参数val_evaluator dict( typeCocoMetric, ann_filedata_root annotations/val.json, metricbbox, classwiseTrue )classwiseTrue会让评估日志输出每一个类别的AP而不是只有一个综合mAP。这一步对排查样本不平衡问题非常关键你只有看到每个类别的AP才知道究竟是数据量不够还是模型能力不行。5. 训练启动与首轮报错排查我实际走的排查链路配置改完之后训练命令很简单python tools/train.py configs/my_config/faster_rcnn_r50_fpn_custom.py --work-dir work_dirs/my_run如果一切正常日志里会先打印出模型结构、数据集信息、以及第一个epoch开始时的loss。但第一次训练自定义数据集时通常多多少少会有点问题。我把最近一次实际帮别人排查的完整链路写下来整个过程大致是三条线索数据加载、类别对齐、loss发散。先看数据加载。启动命令后没跑几步就报FileNotFoundError日志里给出的路径是data/my_dataset/annotations/annotations/train.json一看就是ann_file和data_root拼接出了问题。这类问题机制很简单data_root末尾带了annotations/ann_file又写了annotations/train.json两个拼一起就重复了。解决方法是统一约定data_root只写到数据集根目录ann_file和data_prefix写成相对路径。这个约定建议从一开始就固定下来。接着是类别对齐。日志正常加载了数据但训练到第一个epoch后发现Loss是nan。排查顺序是这样先看数据有没有问题如果标注里出现面积为零、坐标为无穷大的框加载会直接异常或导致loss爆炸。用可视化脚本检查了一轮没发现问题。再看配置文件里的num_classes发现模型头改成了4但metainfo里classes写了6个名字——显然类别数和名称对不上了。这会导致部分category_id映射到不存在的类别loss在计算时输出了nan。把两者统一成4个类别后loss恢复正常。最后是loss不下降。有一次跑了十几个epochmAP只有个位数。排查第一件事是看数据集的样本量和类别分布发现一个类别占了总标注量的90%另一个类别只有几十个框。这属于典型的类别极端不平衡单靠改配置解决不了要么去补充样本要么先跑一个二分类验证管线是否通畅。我用一个只有两个类别、各100张图的迷你集跑通了全流程确认管线没问题再回头处理数据问题。这个“迷你集验证法”特别管用强烈建议在正式训练前做一次花的时间很少但能把数据问题和代码问题彻底分开。还有一个高频报错是IndexError: index X is out of bounds for axis这通常发生在category_id从1开始而配置里classes列表长度不对的时候。比如JSON里有category_id3但配置里只给了2个类名打包成one-hot的时候就越界了。这种问题只要做一个简单的检查脚本遍历标注里出现的所有category_id和metainfo[classes]做一次比对就能精准定位。关于Loss变成NaN的另一个排查角度是学习率。自定义数据集的数据分布和COCO差异很大时默认学习率0.02可能偏大。一个常见的做法是从官方配置的0.5倍开始试比如设成0.01观察前几个iteration的loss变化。如果loss在快速下降说明学习率合适如果出现NaN降学习率比调其他参数更有效。6. 训练完成后的验证与调参心得训练结束后测试命令是python tools/test.py configs/my_config/faster_rcnn_r50_fpn_custom.py work_dirs/my_run/epoch_12.pth --work-dir work_dirs/my_run/test_out跑完之后会输出一个COCO风格的评估表包括AP0.5、AP0.75、AP_small、AP_medium、AP_large等指标。这些指标不要只看综合的AP0.5要重点看不同尺度的AP差异。如果小目标AP明显偏低说明网络下采样倍数对小目标不友好可以考虑调整输入分辨率的test_pipeline、或者改用带FPN的多尺度检测结构。结果可视化也很简单我习惯用一个几十行的脚本把test输出里的预测框画到原图上输出到一个文件夹里然后人工翻图。这里可以看出来模型的“性格”框的位置准不准、有没有大量重复框、小目标有没有漏检。这些细节是mAP数字看不出来的但对实际业务落地影响很大。调参方面我个人的经验排序是数据质量优先于模型结构模型结构优先于超参数。数据没问题的情况下先看一眼训练曲线里的loss_cls和loss_bbox如果loss_bbox降到一定程度就停滞可以考虑调大loss_bbox的权重或检查是否有太多离群样本。如果训练曲线抖动剧烈优先减小学习率并增加warmup_iters让前期训练更平稳。接下来是过拟合的问题。自定义数据集通常数据量不大几万张都算多的很容易跑到后期验证集AP掉头。最直接的做法是增强数据增强策略比如在train_pipeline里加入RandomFlip、RandomCrop、MixUp等。但注意不要不加控制地堆叠增强有些增强对小目标极其不友好切来切去目标就没了。我的习惯是先开RandomFlip和RandomCrop跑一轮看看效果再逐步加别的。最后还有一个很少人谈到但影响很大的点类别名称里的空格和特殊字符。如果JSON里某个类别名带了空格或混了中文可视化时可能出现字体问题而且后续做模型部署时类别名输出也会带来一堆麻烦。建议从一开始就用纯英文、无空格的类别名比如把“轿车”统一写成sedan而不是car type。7. 后续扩展与效率优化的小结到这一步自定义数据集接入mmdetection的主路径已经很完整了标注格式整理、目录搭建、配置对齐、训练验证。接下来如果想把效率再提一档可以从几个方向做扩展。第一是分布式训练。数据量大了、模型深了单卡跑一个epoch动辄几十分钟这时候把训练改为多卡几乎是必修课。mmdetection的分布式训练命令很简单关键在于设置好--launcher pytorch和GPU数量参数同时把batch_size按卡数成倍放大。注意此时数据加载的num_workers也要跟着调否则多卡训练时数据加载跟不上GPU利用率上不去。第二是预训练权重。如果不想从零开始训练可以直接加载官方在COCO上训练好的checkpoint做finetune。在config里加一行load_from checkpoints/faster_rcnn_r50_fpn_1x_coco_20200130-047c8118.pth模型会初始化成COCO预训练权重然后用自己的数据继续训。这种做法在小数据集上效果立竿见影收敛速度至少快一倍。但要注意finetune的时候不能把num_classes和预训练权重的类别数混在一起——头部类别变了最外层的分类层在加载时会自动跳过属于正常现象。第三是模型导出。训练完之后如果需要部署可以用mmdeploy把模型导成ONNX或TensorRT格式。这里提前说一个坑导出前一定要确认测试集的预处理方式和训练时一致特别是尺寸缩放和归一化参数否则部署后的框和离线评估结果会有肉眼可见的差异。最后再说一个我吃过好几次亏的经验正式训练之前先挑出20~30张图做一个mini-dataset跑通一个完整的训练和验证闭环。这个迷你集不用管什么类别均衡、样本数量关键是验证整个链路是通的。跑通了再全量训练遇到问题也容易定位。很多人一上来就全量数据训十几个小时最后发现某个category_id写错白白浪费一整天。这个习惯我从第一次接触检测框架坚持到现在每次换数据集都先迷你验证获益很大。自定义数据集这件事本质上不是多高深的技巧而是把格式、路径、配置三件事对齐。只要这三件对齐了剩下的训练和调参跟跑官方demo没有本质区别。把这篇文章里的流程走一遍你离“模型跑出靠谱的框”就只差一次迭代实验的距离了。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询