RTMDet 自定义目标检测训练实战:MMYOLO 训练、评估与可视化

📅 2026/7/15 0:37:31 👁️ 阅读次数 📝 编程学习
RTMDet 自定义目标检测训练实战:MMYOLO 训练、评估与可视化

RTMDet 自定义目标检测训练实战:MMYOLO 训练、评估与可视化


这篇教程根据我复现 RTMDet 自定义目标检测训练流程时整理,重点演示如何安装 MMYOLO 生态、加载预训练权重、训练自定义数据集并做 mAP 评估。

RTMDet 训练和推理都离不开 MMDetection / MMYOLO 的配置体系。本文更适合作为一份把自定义数据接入 OpenMMLab 的实操模板。

本文会重点跑通以下流程:

  • 安装 MMYOLO 及其依赖
  • 运行 RTMDet 预训练模型推理
  • 从数据集后台获取 COCO-MMD 训练数据
  • 生成自定义配置并启动训练
  • 使用混淆矩阵和 mAP 评估微调模型

如果你正在系统学习目标检测、实例分割、OCR、多目标跟踪或视觉大模型,建议收藏本文;配套 notebook、示例图片和运行环境说明后续会继续整理。如果环境配置卡住,可以在评论区说明具体报错。

📚 文章目录

  • RTMDet 自定义目标检测训练实战:MMYOLO 训练、评估与可视化
    • ⚙️ 环境准备
    • 🧩 安装 MMYOLO 依赖
    • 🔍 预训练 RTMDet 推理
    • 📦 从数据集后台获取训练数据
    • 🛠️ 生成自定义配置
    • 🏋️ 启动训练
    • 📏 训练结果评估
    • 🧪 测试集推理
    • 📤 结果汇总
    • 📌 小结
    • 📚 同系列教程汇总

⚙️ 环境准备

先检查运行环境并安装依赖。建议优先使用带 NVIDIA GPU 的环境,避免推理和训练阶段显存不足。

!nvidia-smi
importos HOME=os.getcwd()print("HOME:",HOME)
%cd{HOME}%pip install-U-q openmim !mim install-q"mmengine>=0.6.0"!mim install-q"mmcv>=2.0.0rc4,<2.1.0"!mim install-q"mmdet>=3.0.0rc6,<3.1.0"!git clone https://github.com/open-mmlab/mmyolo.git%cd{HOME}/mmyolo%pip install-e.

🧩 安装 MMYOLO 依赖

RTMDet 的训练与推理依赖 MMYOLO / MMDetection 生态,这一步先把环境搭起来。

!pip install-q supervision==0.13.0
importcv2importosimportjsonimporttorchimportrandomimportsupervisionassvimportnumpyasnpfrommmdet.apisimportinit_detector,inference_detector

🔍 预训练 RTMDet 推理

先跑通一张图片上的预训练推理,确认配置和权重路径没问题。

!mkdir-p{HOME}/weights !wget-P{HOME}/weights-q https://download.openmmlab.com/mmyolo/v0/rtmdet/rtmdet_l_syncbn_fast_8xb32-300e_coco/rtmdet_l_syncbn_fast_8xb32-300e_coco_20230102_135928-ee3abdc4.pth !ls-lh{HOME}/weights
!mkdir-p{HOME}/data# 请从数据集后台下载示例图片,并放到 {HOME}/data 目录。!ls-lh{HOME}/data
DEVICE=torch.device('cuda:0'iftorch.cuda.is_available()else'cpu')CONFIG_PATH=f"{HOME}/mmyolo/configs/rtmdet/rtmdet_l_syncbn_fast_8xb32-300e_coco.py"WEIGHTS_PATH=f"{HOME}/weights/rtmdet_l_syncbn_fast_8xb32-300e_coco_20230102_135928-ee3abdc4.pth"
model=init_detector(CONFIG_PATH,WEIGHTS_PATH,device=DEVICE)
IMAGE_PATH=f"{HOME}/data/dog.jpeg"
image=cv2.imread(IMAGE_PATH)result=inference_detector(model,image)
detections=sv.Detections.from_mmdetection(result)box_annotator=sv.BoxAnnotator()annotated_image=box_annotator.annotate(image.copy(),detections)sv.plot_image(image=annotated_image,size=(10,10))

detections=detections[detections.confidence>0.3].with_nms()box_annotator=sv.BoxAnnotator()annotated_image=box_annotator.annotate(image.copy(),detections)sv.plot_image(image=annotated_image,size=(10,10))

📦 从数据集后台获取训练数据

从数据集后台导出 COCO-MMD 格式数据后,把路径替换成本地解压目录。

!mkdir-p{HOME}/mmyolo/data%cd{HOME}/mmyolo/data
fromtypesimportSimpleNamespace# 从数据集后台下载 COCO-MMD 格式数据集后,修改 DATASET_DIR 指向解压目录。DATASET_DIR="/content/dataset"# 修改为数据集后台导出的数据集目录dataset=SimpleNamespace(location=DATASET_DIR,classes={})

🛠️ 生成自定义配置

这一段会把数据集路径、类别和训练参数写进自定义配置文件。

BATCH_SIZE=8MAX_EPOCHS=50
CUSTOM_CONFIG_PATH=f"{HOME}/mmyolo/configs/rtmdet/custom.py"CUSTOM_CONFIG=f""" _base_ = ['../_base_/default_runtime.py', '../_base_/det_p5_tta.py'] # ========================Frequently modified parameters====================== # -----data related----- data_root = '{dataset.location}/' train_ann_file = 'train/_annotations.coco.json' train_data_prefix = 'train/' val_ann_file = 'valid/_annotations.coco.json' val_data_prefix = 'valid/' class_name ={tuple(sorted(project.classes.keys()))}num_classes ={len(project.classes)}metainfo = dict(classes=class_name, palette=[(20, 220, 60)]) train_batch_size_per_gpu ={BATCH_SIZE}# Worker to pre-fetch data for each single GPU during training train_num_workers = 4 # persistent_workers must be False if num_workers is 0. persistent_workers = True # -----train val related----- # Base learning rate for optim_wrapper. Corresponding to 8xb16=64 bs base_lr = 0.004 max_epochs ={MAX_EPOCHS}# Maximum training epochs # Change train_pipeline for final 20 epochs (stage 2) num_epochs_stage2 = 20 model_test_cfg = dict( # The config of multi-label for multi-class prediction. multi_label=True, # The number of boxes before NMS nms_pre=30000, score_thr=0.001, # Threshold to filter out boxes. nms=dict(type='nms', iou_threshold=0.65), # NMS type and threshold max_per_img=300) # Max number of detections of each image # ========================Possible modified parameters======================== # -----data related----- img_scale = (640, 640) # width, height # ratio range for random resize random_resize_ratio_range = (0.1, 2.0) # Cached images number in mosaic mosaic_max_cached_images = 40 # Number of cached images in mixup mixup_max_cached_images = 20 # Dataset type, this will be used to define the dataset dataset_type = 'YOLOv5CocoDataset' # Batch size of a single GPU during validation val_batch_size_per_gpu = 32 # Worker to pre-fetch data for each single GPU during validation val_num_workers = 10 # Config of batch shapes. Only on val. batch_shapes_cfg = dict( type='BatchShapePolicy', batch_size=val_batch_size_per_gpu, img_size=img_scale[0], size_divisor=32, extra_pad_ratio=0.5) # -----model related----- # The scaling factor that controls the depth of the network structure deepen_factor = 1.0 # The scaling factor that controls the width of the network structure widen_factor = 1.0 # Strides of multi-scale prior box strides = [8, 16, 32] norm_cfg = dict(type='BN') # Normalization config # -----train val related----- lr_start_factor = 1.0e-5 dsl_topk = 13 # Number of bbox selected in each level loss_cls_weight = 1.0 loss_bbox_weight = 2.0 qfl_beta = 2.0 # beta of QualityFocalLoss weight_decay = 0.05 # Save model checkpoint and validation intervals save_checkpoint_intervals = 10 # validation intervals in stage 2 val_interval_stage2 = 1 # The maximum checkpoints to keep. max_keep_ckpts = 3 # single-scale training is recommended to # be turned on, which can speed up training. env_cfg = dict(cudnn_benchmark=True) # ===============================Unmodified in most cases==================== # https://mmengine.readthedocs.io/en/latest/api/visualization.html _base_.visualizer.vis_backends = [ dict(type='LocalVisBackend'), # dict(type='TensorboardVisBackend'),] model = dict( type='YOLODetector', data_preprocessor=dict( type='YOLOv5DetDataPreprocessor', mean=[103.53, 116.28, 123.675], std=[57.375, 57.12, 58.395], bgr_to_rgb=False), backbone=dict( type='CSPNeXt', arch='P5', expand_ratio=0.5, deepen_factor=deepen_factor, widen_factor=widen_factor, channel_attention=True, norm_cfg=norm_cfg, act_cfg=dict(type='SiLU', inplace=True)), neck=dict( type='CSPNeXtPAFPN', deepen_factor=deepen_factor, widen_factor=widen_factor, in_channels=[256, 512, 1024], out_channels=256, num_csp_blocks=3, expand_ratio=0.5, norm_cfg=norm_cfg, act_cfg=dict(type='SiLU', inplace=True)), bbox_head=dict( type='RTMDetHead', head_module=dict( type='RTMDetSepBNHeadModule', num_classes=num_classes, in_channels=256, stacked_convs=2, feat_channels=256, norm_cfg=norm_cfg, act_cfg=dict(type='SiLU', inplace=True), share_conv=True, pred_kernel_size=1, featmap_strides=strides), prior_generator=dict( type='mmdet.MlvlPointGenerator', offset=0, strides=strides), bbox_coder=dict(type='DistancePointBBoxCoder'), loss_cls=dict( type='mmdet.QualityFocalLoss', use_sigmoid=True, beta=qfl_beta, loss_weight=loss_cls_weight), loss_bbox=dict(type='mmdet.GIoULoss', loss_weight=loss_bbox_weight)), train_cfg=dict( assigner=dict( type='BatchDynamicSoftLabelAssigner', num_classes=num_classes, topk=dsl_topk, iou_calculator=dict(type='mmdet.BboxOverlaps2D')), allowed_border=-1, pos_weight=-1, debug=False), test_cfg=model_test_cfg, ) train_pipeline = [ dict(type='LoadImageFromFile', backend_args=_base_.backend_args), dict(type='LoadAnnotations', with_bbox=True), dict( type='Mosaic', img_scale=img_scale, use_cached=True, max_cached_images=mosaic_max_cached_images, pad_val=114.0), dict( type='mmdet.RandomResize', # img_scale is (width, height) scale=(img_scale[0] * 2, img_scale[1] * 2), ratio_range=random_resize_ratio_range, resize_type='mmdet.Resize', keep_ratio=True), dict(type='mmdet.RandomCrop', crop_size=img_scale), dict(type='mmdet.YOLOXHSVRandomAug'), dict(type='mmdet.RandomFlip', prob=0.5), dict(type='mmdet.Pad', size=img_scale, pad_val=dict(img=(114, 114, 114))), dict( type='YOLOv5MixUp', use_cached=True, max_cached_images=mixup_max_cached_images), dict(type='mmdet.PackDetInputs') ] train_pipeline_stage2 = [ dict(type='LoadImageFromFile', backend_args=_base_.backend_args), dict(type='LoadAnnotations', with_bbox=True), dict( type='mmdet.RandomResize', scale=img_scale, ratio_range=random_resize_ratio_range, resize_type='mmdet.Resize', keep_ratio=True), dict(type='mmdet.RandomCrop', crop_size=img_scale), dict(type='mmdet.YOLOXHSVRandomAug'), dict(type='mmdet.RandomFlip', prob=0.5), dict(type='mmdet.Pad', size=img_scale, pad_val=dict(img=(114, 114, 114))), dict(type='mmdet.PackDetInputs') ] test_pipeline = [ dict(type='LoadImageFromFile', backend_args=_base_.backend_args), dict(type='YOLOv5KeepRatioResize', scale=img_scale), dict( type='LetterResize', scale=img_scale, allow_scale_up=False, pad_val=dict(img=114)), dict(type='LoadAnnotations', with_bbox=True, _scope_='mmdet'), dict( type='mmdet.PackDetInputs', meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape', 'scale_factor', 'pad_param')) ] train_dataloader = dict( batch_size=train_batch_size_per_gpu, num_workers=train_num_workers, persistent_workers=persistent_workers, pin_memory=True, collate_fn=dict(type='yolov5_collate'), sampler=dict(type='DefaultSampler', shuffle=True), dataset=dict( type=dataset_type, data_root=data_root, metainfo=metainfo, ann_file=train_ann_file, data_prefix=dict(img=train_data_prefix), filter_cfg=dict(filter_empty_gt=True, min_size=32), pipeline=train_pipeline)) val_dataloader = dict( batch_size=val_batch_size_per_gpu, num_workers=val_num_workers, persistent_workers=persistent_workers, pin_memory=True, drop_last=False, sampler=dict(type='DefaultSampler', shuffle=False), dataset=dict( type=dataset_type, data_root=data_root, metainfo=metainfo, ann_file=val_ann_file, data_prefix=dict(img=val_data_prefix), test_mode=True, batch_shapes_cfg=batch_shapes_cfg, pipeline=test_pipeline)) test_dataloader = val_dataloader # Reduce evaluation time val_evaluator = dict( type='mmdet.CocoMetric', proposal_nums=(100, 1, 10), ann_file=data_root + val_ann_file, metric='bbox') test_evaluator = val_evaluator # optimizer optim_wrapper = dict( type='OptimWrapper', optimizer=dict(type='AdamW', lr=base_lr, weight_decay=weight_decay), paramwise_cfg=dict( norm_decay_mult=0, bias_decay_mult=0, bypass_duplicate=True)) # learning rate param_scheduler = [ dict( type='LinearLR', start_factor=lr_start_factor, by_epoch=False, begin=0, end=1000), dict( # use cosine lr from 150 to 300 epoch type='CosineAnnealingLR', eta_min=base_lr * 0.05, begin=max_epochs // 2, end=max_epochs, T_max=max_epochs // 2, by_epoch=True, convert_to_iter_based=True), ] # hooks default_hooks = dict( checkpoint=dict( type='CheckpointHook', interval=save_checkpoint_intervals, max_keep_ckpts=max_keep_ckpts # only keep latest 3 checkpoints )) custom_hooks = [ dict( type='EMAHook', ema_type='ExpMomentumEMA', momentum=0.0002, update_buffers=True, strict_load=False, priority=49), dict( type='mmdet.PipelineSwitchHook', switch_epoch=max_epochs - num_epochs_stage2, switch_pipeline=train_pipeline_stage2) ] train_cfg = dict( type='EpochBasedTrainLoop', max_epochs=max_epochs, val_interval=save_checkpoint_intervals, dynamic_intervals=[(max_epochs - num_epochs_stage2, val_interval_stage2)]) val_cfg = dict(type='ValLoop') test_cfg = dict(type='TestLoop') """
withopen(CUSTOM_CONFIG_PATH,'w')asfile:file.write(CUSTOM_CONFIG)

🏋️ 启动训练

训练器和数据加载器准备好后,就可以直接开始训练。

%cd{HOME}/mmyolo !python tools/train.py configs/rtmdet/custom.py
# %load_ext tensorboard# %tensorboard --logdir /content/mmyolo/work_dirs/custom/20230808_081105

📏 训练结果评估

训练完成后,用测试集和混淆矩阵检查模型效果。

CUSTOM_WEIGHTS_PATH=f"/content/mmyolo/work_dirs/custom/epoch_{MAX_EPOCHS}.pth"
model=init_detector(CUSTOM_CONFIG_PATH,CUSTOM_WEIGHTS_PATH,device=DEVICE)

🧪 测试集推理

抽一张测试图,看模型在真实样本上的输出是否稳定。

ds=sv.DetectionDataset.from_coco(images_directory_path=f"{dataset.location}/test",annotations_path=f"{dataset.location}/test/_annotations.coco.json",)images=list(ds.images.values())
image=random.choice(images)result=inference_detector(model,image)detections=sv.Detections.from_mmdetection(result)detections=detections[detections.confidence>0.4].with_nms()box_annotator=sv.BoxAnnotator()labels=[f"{ds.classes[class_id]}{confidence:0.2f}"for_,_,confidence,class_id,_indetections]annotated_image=box_annotator.annotate(image.copy(),detections,labels=labels)sv.plot_image(image=annotated_image,size=(10,10))

📤 结果汇总

把 mAP、混淆矩阵和逐类结果一起汇总,方便后续回看。

CONFIDENCE_THRESHOLD=0.35NMS_IOU_THRESHOLD=0.7
ds=sv.DetectionDataset.from_coco(images_directory_path=f"{dataset.location}/test",annotations_path=f"{dataset.location}/test/_annotations.coco.json",)print('dataset classes:',ds.classes)print('dataset size:',len(ds))
defcallback(image:np.ndarray)->sv.Detections:result=inference_detector(model,image)detections=sv.Detections.from_mmdetection(result)returndetections[detections.confidence>CONFIDENCE_THRESHOLD].with_nms(threshold=NMS_IOU_THRESHOLD)confusion_matrix=sv.ConfusionMatrix.benchmark(dataset=ds,callback=callback)_=confusion_matrix.plot()

mean_average_precision=sv.MeanAveragePrecision.benchmark(dataset=ds,callback=callback)print('mAP:',mean_average_precision.map50_95)
per_class_map=mean_average_precision.per_class_ap50_95.mean(axis=1)forclass_name,valueinzip(ds.classes,per_class_map):print(f"{class_name}:{value:.2f}")

📌 小结

RTMDet 的难点主要是配置文件和数据路径,而不是模型本身。把 data_root、类名和训练输出目录对齐之后,后面的训练和评估会顺很多。

这一类 notebook 建议按“先环境、再数据、再单样例、最后批量推理”的顺序复现。遇到报错时,优先检查 GPU、依赖版本、数据集目录和模型权重路径。

后续我会继续按源项目顺序整理同系列中的目标检测、实例分割、OCR、多目标跟踪和视觉大模型教程。

📚 同系列教程汇总

  • Google Gemini 3.5 Flash 零样本目标检测教程:从提示词到可视化结果

  • GLM-OCR 文档识别实战教程:从验证码、公式到车牌 OCR

  • RF-DETR + ByteTrack 多目标跟踪实战教程:从命令行到 Python 视频轨迹可视化

  • SAM 3 图像分割实战教程:文本、框和点提示的多种分割方式

  • RTMDet 自定义目标检测训练实战:MMYOLO 训练、评估与可视化-本文