【深度学习实验】—— YOLOv12 水果目标检测实战:从数据集制作、模型训练到视频推理 文章目录1. 简介2. 环境3. 代码实现3.1 前期准备3.1.1 项目结构与导入库3.1.2 数据集划分3.1.3 VOC 标注转换为 YOLO 格式3.1.4 数据配置与标签可视化3.2 模型建立与训练3.2.1 YOLOv12n 模型结构3.2.2 创建模型与训练入口3.2.3 训练参数与损失函数3.2.4 训练记录与模型保存3.3 视频推理实现4. 模型评估4.1 可视化训练过程4.2 最优模型的验证指标4.3 PR 曲线与混淆矩阵4.3.1 PR 曲线4.3.2 混淆矩阵4.4 验证图像预测效果4.5 视频输出与适用范围5. 总结1. 简介本次实验使用YOLOv12n完成四类水果目标检测串联数据集划分、VOC 标注转换、模型训练、验证和视频推理流程。目标检测需要同时回答“图像中有什么”和“目标在哪里”。因此模型既要预测水果类别也要输出对应的边界框。一张图像可以包含多个目标评价时需要同时考虑分类和定位质量。项目内容模型YOLOv12n训练入口为YOLO(yolo12n.yaml)任务四类水果目标检测类别pineapple菠萝、snake fruit蛇皮果、dragon fruit火龙果、banana香蕉数据集200 张图像594 个标注目标数据划分训练集 160 张、验证集 20 张、测试集 20 张训练配置2 个 Epochbatch2imgsz640模型参数量2,568,828约 256.9 万已有最优验证指标mAP0.5 为4.216%mAP0.5:0.95 为1.562%视频输出1280×72025 FPS共 4,051 帧本次训练只进行了 2 轮检测指标仍然较低。实验已经完成整个处理流程但模型还需要进一步训练和验证才能判断其实际应用价值。2. 环境项目使用 Python 脚本运行。当前项目所在虚拟环境为../.venv312复核到的主要版本如下环境项版本或配置操作系统macOSarm64Python3.12.13PyTorch2.14.0torchvision0.29.0Ultralytics8.4.182OpenCV5.0.0.93NumPy2.5.2PyYAML6.0.3训练权重内部记录的 Ultralytics 版本也是8.4.182。当前from ultralytics import YOLO导入的是虚拟环境中的已安装包。在项目根目录中可以使用现有虚拟环境运行脚本../.venv312/bin/python train.py../.venv312/bin/python test.py以上是运行入口说明重新执行训练会生成新的训练目录本文分析的是已存在的runs/detect/train-2/结果。3. 代码实现3.1 前期准备3.1.1 项目结构与导入库项目的主要文件如下Yolo/ ├── data/ │ ├── images/ # 原始图像 │ ├── annotations/ # VOC XML 标注 │ ├── labels/ # 转换后的 YOLO 标注 │ ├── ImageSets/Main/ # 各数据子集的图像编号 │ ├── split_train_val.py # 划分训练、验证、测试集 │ ├── voc_label.py # 标注转换与图像清单生成 │ ├── ab.yaml # 数据集配置 │ ├── train.txt │ ├── val.txt │ └── test.txt ├── yolov12/ # 附带的模型源码 ├── train.py # 训练入口 ├── test.py # 视频推理入口 ├── yolo12n.pt # 另存的 80 类权重 ├── TestVideo.mp4 ├── TestVideo_result.mp4 └── runs/detect/train-2/ ├── args.yaml ├── results.csv ├── results.png ├── BoxPR_curve.png ├── confusion_matrix.png └── weights/ ├── best.pt └── last.pt训练主要使用ultralytics.YOLO视频推理还使用cv2读写视频并使用argparse、pathlib.Path管理参数和路径。可以通过以下代码检查当前解释器和实际导入的库importsysimporttorchimportultralyticsfromultralyticsimportYOLOprint(Python:,sys.version)print(Ultralytics:,ultralytics.__version__)print(导入位置:,ultralytics.__file__)print(CUDA 可用:,torch.cuda.is_available())print(MPS 可用:,torch.backends.mps.is_available())3.1.2 数据集划分原始数据集包含 200 张 PNG 图像每张图像对应一个 XML 标注文件。split_train_val.py先将 90% 的样本划入训练与验证集合再从中抽取 8/9 作为训练集。核心代码如下完整实现见data/split_train_val.pytrainval_percent0.9train_percent8/9numlen(total_xml)list_indexrange(num)tvint(num*trainval_percent)trint(tv*train_percent)trainvalrandom.sample(list_index,tv)trainrandom.sample(trainval,tr)因此实际得到80% 训练集、10% 验证集、10% 测试集。对现有划分文件和 YOLO 标签进行统计结果如下数据子集图像数菠萝蛇皮果火龙果香蕉目标总数训练集16013215778109476验证集201720111058测试集201420151160合计200163197104130594三个子集的图像编号没有交集。蛇皮果的标注数量最多火龙果最少说明各类别的目标数量并不完全均衡。这里还需要区分两处随机性划分脚本使用random.sample()但没有设置随机种子训练参数中则记录了seed0。训练种子不会自动固定另一个脚本的数据划分结果所以复现本次实验时应保留现有清单文件。3.1.3 VOC 标注转换为 YOLO 格式原始 XML 使用xmin、ymin、xmax、ymax描述边界框。YOLO 标签的每一行格式为class_id x_center y_center width height其中class_id从 0 开始其余四个值按图像宽高归一化。项目中的转换函数如下保留了原脚本的函数名conventdefconvent(size,box):dw1.0/size[0]dh1.0/size[1]x(box[0]box[1])/2.0-1y(box[2]box[3])/2.0-1wbox[1]-box[0]hbox[3]-box[2]returnx*dw,y*dh,w*dw,h*dh注意该函数中box的顺序为(xmin, xmax, ymin, ymax)。中心坐标中的-1是原实现保留的坐标偏移本文按项目代码记录迁移其他标注源时应核对其坐标约定。转换脚本还会跳过不在类别列表中的对象和difficult1的对象并将超出图像右边界、下边界的xmax、ymax截断到图像尺寸。例如fruit0.txt包含三个目标0 0.38375000000000004 0.5116666666666667 0.5825 0.48333333333333334 1 0.6525 0.6316666666666667 0.09 0.11 2 0.75375 0.8766666666666667 0.2425 0.24000000000000002它们依次表示菠萝、蛇皮果和火龙果。类别顺序必须与ab.yaml完全一致。准备新数据时两个脚本的执行顺序为../.venv312/bin/python data/split_train_val.py../.venv312/bin/python data/voc_label.py第一步会重新划分数据并覆盖编号清单第二步按照清单转换标签并生成data/train.txt、data/val.txt和data/test.txt。3.1.4 数据配置与标签可视化本次使用的data/ab.yaml中有效配置为train:./train.txtval:./val.txtnc:4names:[pineapple,snake fruit,dragon fruit,banana]train和val指定图像清单nc指定类别数量names给出类别编号对应的名称。例如train.txt中的路径形如./images/fruit0.png。项目虽然生成了测试集清单但当前 YAML 没有启用test字段因此已有训练结果对应验证集不能写成独立测试集成绩。训练过程保存了标签分布图可以用来观察类别数量以及边界框的位置、尺寸分布3.2 模型建立与训练3.2.1 YOLOv12n 模型结构本项目通过模型 YAML 创建网络再由训练器根据数据配置将检测类别设置为 4。从训练后best.pt保存的结构可以看到网络包含卷积、C3k2、A2C2f、上采样、特征拼接和Detect检测头。可以按功能理解为以下几个部分组成部分作用Conv、C3k2提取图像特征并逐步降低空间分辨率A2C2f在相应配置下使用区域注意力等特征处理模块增强特征表达Upsample、Concat融合不同层级的特征信息Detect基于 P3、P4、P5 三个尺度输出目标检测预测对于 640×640 的输入三个检测尺度通常对应 80×80、40×40 和 20×20 的特征图。多尺度预测让模型能结合不同分辨率的特征处理大小不同的目标。训练后模型的参数量实测为2,568,828。这个数值来自本次四类模型的权重而非其他类别数或其他实现版本的模型介绍。3.2.2 创建模型与训练入口项目中的train.py如下fromultralyticsimportYOLOif__name____main__:modelYOLO(yolo12n.yaml)model.train(datar/Users/bareerah/Learn/ML/Yolo/data/ab.yaml,epochs2,batch2,workers2,)这里使用.yaml创建网络结构。脚本没有显式加载根目录下的yolo12n.pt因此不能因为该文件存在或者参数中出现pretrained: true就把本次训练描述成已经加载该预训练权重的迁移学习。两类调用应分清# 本次训练入口从 YAML 构建网络modelYOLO(yolo12n.yaml)# 另一种训练方式显式加载本地权重本文未用它重做训练modelYOLO(yolo12n.pt)根目录下的yolo12n.pt经检查保存了 80 类名称视频脚本默认使用的runs/detect/train-2/weights/best.pt保存的是本项目的四类名称。二者用途和类别空间不同。3.2.3 训练参数与损失函数结合args.yaml和训练权重内部的train_args可以整理出以下配置参数本次记录说明epochs2训练轮次batch2每批图像数量imgsz640训练输入尺寸workers请求值 2权重内最终记录 0初始参数和运行时参数存在调整optimizer初始为 auto最终为 AdamW自动选择优化器lr0初始配置 0.01最终记录 0.00125自动模式调整后的初始学习率weight_decay0.0005权重衰减配置warmup_epochs3.0预热轮次配置长于本次总训练轮次seed0训练随机种子valtrue训练中进行验证本次记录的主要损失项为损失项含义配置权重box_loss边界框定位损失7.5cls_loss目标类别预测损失0.5dfl_lossDistribution Focal Loss辅助边界框回归1.5训练器负责数据加载、前向传播、损失计算、反向传播、参数更新及验证。调用model.train()后无需在训练入口中再次手写这些步骤。配置文件还记录了 HSV 颜色扰动、平移、缩放和水平翻转等增强参数。但配置值不等于每一种增强都在本次短训练中实际生效例如 Mosaic 还受close_mosaic10的训练阶段控制。本文不据配置文件推断增强效果。训练批次可视化如下可检查进入训练流程的图像及边界框3.2.4 训练记录与模型保存完整的两轮指标保存在runs/detect/train-2/results.csv。下表保留 CSV 中的小数值Epoch累计时间/s训练 box_loss训练 cls_loss训练 dfl_lossPrecisionRecallmAP0.5mAP0.5:0.951113.8323.027404.668004.5686000002168.6612.992154.488724.230910.000850.090910.042160.01562time是累计时间不能将两行相加作为总训练时间。最后一行记录的累计时间约为168.7 秒不包含对整个项目所有步骤耗时的统计。训练结束后保存了runs/detect/train-2/weights/best.pt runs/detect/train-2/weights/last.pt第 2 轮具有本次最高的验证 mAP0.5:0.95best.pt和last.pt内部保存的训练指标一致。这里的“最优”只表示本次两轮训练中的相对最优。3.3 视频推理实现项目中的test.py是视频推理脚本不会读取data/test.txt计算测试集指标。脚本通过命令行指定权重、输入视频和输出视频默认值分别为parser.add_argument(--weights,typePath,defaultroot/runs/detect/train-2/weights/best.pt)parser.add_argument(--source,typePath,defaultroot/TestVideo.mp4)parser.add_argument(--output,typePath,defaultroot/TestVideo_result.mp4)读取视频后获取原始尺寸和帧率并使用mp4v编码创建输出文件modelYOLO(str(args.weights))capcv2.VideoCapture(str(args.source))widthint(cap.get(cv2.CAP_PROP_FRAME_WIDTH))heightint(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))fpscap.get(cv2.CAP_PROP_FPS)outcv2.VideoWriter(str(args.output),cv2.VideoWriter_fourcc(*mp4v),fps,(width,height),)核心处理循环如下frames0whileTrue:success,framecap.read()ifnotsuccess:breakresultsmodel(frame)out.write(results[0].plot())frames1model(frame)对一帧图像执行检测plot()将返回的检测框、类别和置信度绘制到图像上再由out.write()写入输出视频。没有通过筛选的检测结果时画面不会出现检测框。完整脚本还检查输入文件、视频尺寸、帧率和写入器状态防止输出路径覆盖输入视频或权重并在finally中释放视频资源。如果已有输出文件需要保留可以指定新的输出路径../.venv312/bin/python test.py\--weightsruns/detect/train-2/weights/best.pt\--sourceTestVideo.mp4\--outputTestVideo_result_new.mp44. 模型评估4.1 可视化训练过程框架保存的训练曲线如下从记录中可以看到三项训练损失均有所下降box_loss3.02740 → 2.99215。cls_loss4.66800 → 4.48872。dfl_loss4.56860 → 4.23091。验证集损失的变化为指标Epoch 1Epoch 2验证 box_loss2.912932.88124验证 cls_loss4.584304.47664验证 dfl_loss4.168224.17233训练损失下降说明参数更新开始产生作用但两轮数据不足以判断模型已经收敛也不足以认定过拟合。结合很低的检测指标更合理的判断是模型仍处于训练早期尚未学到稳定的水果检测能力。4.2 最优模型的验证指标best.pt内部保存的验证指标与 CSV 第 2 轮一致指标原始数值百分比Precision0.000850.085%Recall0.090919.091%mAP0.50.042164.216%mAP0.5:0.950.015621.562%这些指标含义不同Precision预测为目标的结果中有多少是正确检测。Recall真实目标中有多少被正确检测出来。mAP0.5以 IoU0.5 判定定位匹配计算各类别 AP 后取平均。mAP0.5:0.95在 IoU 从 0.5 到 0.95、步长 0.05 的多个阈值下计算平均结果对定位精度要求更高。框架汇总的 Precision、Recall 具有类别平均和置信度选点等统计口径不能简单将它们视为整套数据的单个计数比例。尤其是mAP 不能称为分类准确率4.216% 也不表示“识别正确了 4.216% 的图片”。如需重新评估最优权重可以使用以下代码本文表格引用的是已有记录没有将该示例作为一次新评测fromultralyticsimportYOLO modelYOLO(runs/detect/train-2/weights/best.pt)metricsmodel.val(datadata/ab.yaml,splitval)print(fPrecision:{metrics.box.mp:.6f})print(fRecall:{metrics.box.mr:.6f})print(fmAP0.5:{metrics.box.map50:.6f})print(fmAP0.5:0.95:{metrics.box.map:.6f})若要评估独立测试集需要先在数据配置中加入test: ./test.txt再调用splittest。当前项目没有保存可引用的独立测试集评测成绩。4.3 PR 曲线与混淆矩阵4.3.1 PR 曲线图例中的 AP0.5 显示值为类别AP0.5按图例四舍五入pineapple0.000snake fruit0.000dragon fruit0.169banana0.000all classes0.042当前有效的 AP 主要来自火龙果类别其余三类尚未形成有效的检测表现。火龙果的 PR 曲线也仅覆盖较低的召回率范围模型仍会漏掉较多目标。4.3.2 混淆矩阵图中横轴为真实类别纵轴为预测类别。在这张图采用的匹配和筛选条件下火龙果有 4 个正确匹配另有 2 个被预测为香蕉。菠萝有 1 个被预测为蛇皮果。background行记录了大量未匹配到预测框的真实目标例如 16 个菠萝、20 个蛇皮果、5 个火龙果和 10 个香蕉。background列中存在大量未匹配到真实目标的预测框四类分别为 1,153、1,143、1,178 和 1,166 个说明该统计条件下误检很多。目标检测中的background表示未匹配的真实目标或预测框并不是数据集新增的第五类。混淆矩阵与 PR/AP 的阈值、匹配及汇总方式也有差异不能直接用矩阵计数替代前面的指标。4.4 验证图像预测效果下面两张图来自同一个验证批次可对照真实标注和预测输出真实标注中包含水果边界框而该预测批次未显示有效检测框。这与较低的验证指标一致。这也说明定量指标和可视化需要结合起来看损失下降描述训练过程mAP 衡量检测质量预测图则能直接揭示漏检等具体问题。4.5 视频输出与适用范围对已有输入、输出视频进行元数据检查结果如下项目输入视频输出视频文件TestVideo.mp4TestVideo_result.mp4分辨率1280×7201280×720帧率25 FPS25 FPS视频帧数4,0514,051视频流时长162.04 秒162.04 秒视频编码H.264MPEG-4 Part 2mp4v音频流有AAC无输入文件的容器总时长为 162.10 秒与音频流有关上表对齐比较的是视频流时长。输出帧数、尺寸和帧率与输入一致支持视频读取和写出流程已完整执行的判断。脚本只写入图像帧因此输出没有保留原音频。从已有输出中抽取第 0 秒、第 60 秒和第 120 秒的画面如下这三处抽样画面主要是人物场景没有显示检测框。当前模型的类别是四种水果人物并不属于其检测范围因此不能要求该模型输出人物框也不能用这些画面评价水果识别能力。现有视频结果适合说明视频处理流程可以运行。要验证水果检测效果还需要包含对应水果、并具有人工标注或可核对目标的视频。另外输出为 25 FPS 只表示文件的播放帧率。项目没有保存完整视频推理耗时不能据此认定模型能以 25 FPS 实时处理视频。5. 总结本次实验完成了从原始 XML 标注到视频推理输出的完整流程也暴露出当前训练配置和评估素材的局限。完成四类水果检测的数据准备。200 张图像包含 594 个目标按 160/20/20 划分为训练、验证和测试集并完成 VOC 到 YOLO 的标注转换。完成模型训练与结果保存。本次四类 YOLOv12n 模型有 2,568,828 个参数训练两轮后三项训练损失均下降保存了权重、指标、训练曲线和验证图像。现有检测能力仍然有限。最优验证 mAP0.5 为 4.216%mAP0.5:0.95 为 1.562%。PR 曲线、混淆矩阵和预测图共同表明模型仍存在大量漏检和误检。视频输出验证了工程流程。已有输出保留全部 4,051 帧但抽样人物画面与水果任务不匹配输出文件也没有保留原音频且播放帧率不能代替推理速度测试。后续应优先完善训练与评估。可以显式加载本地预训练权重增加训练轮次并观察验证指标固定数据划分对相近拍摄场景进一步检查必要时按拍摄批次分组完成独立测试集评测再使用水果视频检验实际效果。例如下一轮可以尝试以下训练配置。它是后续实验方案尚无对应结果frompathlibimportPathfromultralyticsimportYOLOif__name____main__:rootPath(__file__).resolve().parent modelYOLO(str(root/yolo12n.pt))model.train(datastr(root/data/ab.yaml),epochs100,batch2,imgsz640,workers2,patience20,seed0,projectstr(root/runs/detect),namefruit_pretrained,)增加轮次和加载权重能提供进一步学习的条件最终效果仍需由新的验证和测试结果确认。通过这次实验可以建立起目标检测项目的基本工作流程并学会结合代码、权重、数值指标和可视化判断模型实际完成到了哪一步。