
简介基于YOLOv5的草莓目标检测项目源自“猛犸杯”比赛赛题因赛事主办方提供的数据量非常少数据预处理与增强便成为提升模型准确率的关键。项目定位于人工智能、计算机、通信工程、自动化等专业方向适用于在校学生、教师或企业开发者的毕业设计、课程设计、作业、竞赛练习及初期项目演示同时对目标检测初学者也足够友好。压缩包共包含507个文件整体大小约979.54MB其中最主要的是402张草莓图像和100个XML格式标注文件可直接组成训练集与验证集另附1个Jupyter Notebook主程序、1个Markdown说明文档以及少量DB缓存文件图像与标签一一对应目录结构清晰便于快速启动YOLOv5训练流程。已有200人浏览学习代码均经过完整测试并成功运行项目在答辩评审中平均分达到96分可靠性较高针对赛题数据不足的问题源码中给出了具体的数据预处理与增强方案能有效改善小样本下的检测效果帮助使用者复现赛题结果用户也可在此基础上修改和扩展用于其他目标检测任务或教学展示。1. 为什么是 YOLOv5 几百张草莓图而不是一上来换 YOLOv8参加过这类“给定几百张草莓图像”的赛题选手大多有个同感跑通 YOLOv5 只是开胃菜真正卡人的是数据标注边界、小目标漏检和超参数选择。草莓果实小、青果与叶片颜色接近、果子之间互相遮挡如果直接照搬默认配置训练mAP0.5 可能只有 0.35 上下而同组选手却能冲到 0.75 以上。拉开差距的往往不是网络结构而是有没有把“目标检测流程”里数据准备和验证调参两个环节做扎实。YOLOv5 在这类基于 YOLOv5 的水果识别场景里仍然是最稳的选择。它有成熟的数据增强管线、自动 anchor 计算、轻量级权重和多尺度训练开关对几百张的小样本数据集非常友好直接换 YOLOv8 反而要处理更多如果没有源码级修改就不直观的分布式训练配置。这篇文章按比赛赛题项目落地路径来写从数据集整理、VOC/COCO 转 YOLO 标签到环境配置、最小训练命令、验证指标解读再到比赛提交前的结果导出与小目标后处理每一步都给可以直接复制的命令和参数。2. 草莓图像数据集整理与 YOLO 标注格式转换2.1 训练集/验证集目录结构与 data.yaml 先定好YOLOv5 训练自己的数据集时不会自动读 XML 或 JSON它期望images/和labels/两个目录且同名图片和 txt 标签一一对应。我一般会在比赛数据下发后先建好固定目录避免后面训练时反复改路径。目录结构如下datasets/strawberry/ ├── images/ │ ├── train/ │ ├── val/ │ └── test/ ├── labels/ │ ├── train/ │ └── val/训练和验证图片分别放在images/train与images/val对应的标注文本放在labels/train与labels/val。测试集可以不放标签因为最终要用训练好的权重去 predict。几百张数据不要直接按文件名乱序划分建议按“同株草莓的连续帧”分到同一集合避免模型在验证时撞见训练图片的相似背景导致指标虚高。接着在yolov5项目根目录下建一个数据集配置文件比如strawberry.yamlpath: ../datasets/strawberry # 相对于 yolov5 项目目录的路径 train: images/train val: images/val nc: 2 names: 0: green_strawberry 1: red_strawberry这里path指向图片根目录train和val是相对于该根目录的子目录名也可以直接写绝对路径。nc是类别数names里的顺序必须与标注 txt 中第一列的类别 ID 严格一致如果比赛赛道只要求检测“草莓”一个类别就把nc改为 1names只写strawberry。类别 ID 从 0 开始这一步出错时训练不会报错但验证阶段的混淆矩阵会变得不可解释。2.2 用脚本把 VOC 或 COCO 标注转成 YOLO txt 标签比赛给的原始数据不一定直接是 YOLO 格式常见的是 VOC 的 XML 文件。手工在 LabelImg 里重标一遍几百张图耗时太长正确做法是写个转换脚本。下面这段 Python 可以将 VOC XML 转为 YOLO 需要的归一化 txtimport xml.etree.ElementTree as ET import os classes [green_strawberry, red_strawberry] def convert_annotation(xml_path, out_dir, classes): tree ET.parse(xml_path) root tree.getroot() size root.find(size) img_w float(size.find(width).text) img_h float(size.find(height).text) lines [] for obj in root.iter(object): name obj.find(name).text if name not in classes: continue cls_id classes.index(name) box obj.find(bndbox) xmin float(box.find(xmin).text) xmax float(box.find(xmax).text) ymin float(box.find(ymin).text) ymax float(box.find(ymax).text) x_center (xmin xmax) / 2.0 / img_w y_center (ymin ymax) / 2.0 / img_h box_w (xmax - xmin) / img_w box_h (ymax - ymin) / img_h lines.append(f{cls_id} {x_center:.6f} {y_center:.6f} {box_w:.6f} {box_h:.6f}) txt_path os.path.join(out_dir, os.path.splitext(os.path.basename(xml_path))[0] .txt) with open(txt_path, w) as f: f.write(\n.join(lines)) xml_dir datasets/strawberry/annotations label_dir datasets/strawberry/labels/train for xml_file in os.listdir(xml_dir): if xml_file.endswith(.xml): convert_annotation( os.path.join(xml_dir, xml_file), label_dir, classes )脚本先把图片宽高从size节点中读出来再将xmin/xmax/ymin/ymax像素坐标换算为中心点加宽高的归一化值。YOLO label 的每个字段是类别ID x_center y_center width height其中坐标全部除以原图宽高。如果原始数据是 COCO 的 JSONbbox字段为[x, y, width, height]此时只需要把x_center换成x width/2再除以图片宽y_center同理不要直接把 json 的x当成归一化中心。转换后随机抽查几个 txt 文件确认没有空文件。另外要注意标签里有difficult1的物体比赛评测一般不区分难度建议保留并且正常归一化删掉会让模型在困难样本上的召回率虚高。2.3 数据集质量检查的 3 个常见坑表格里列出的三个问题是我在处理几百张小样本数据集时最容易遇到的每类都会直接影响训练收敛常见问题表现检查方法坐标越界大于 1 或小于 0训练 loss 震荡mAP 不升直接用脚本遍历所有 txt 判断每行的第 25 个字段是否在[0,1]空标签文件图片参与训练但没有监督信号find labels -name *.txt -size 0列出并删除空文件类别 ID 与data.yaml不一致混淆矩阵出现跨类别噪点统计每个 txt 第一个字段额外检查是否有值等于nc第二点比较容易忽略YOLOv5 会跳过没有标签的图片但如果你在训练前已经用--data指向了包含空标签的目录它默认仍会用空目录构建 dataset cache导致部分图片意外参与 mosaic 增强但不产生任何 loss。我一般在训练前直接清掉空文件find datasets/strawberry/labels -name *.txt -size 0 -delete第三点中的“ID 等于 nc”意味着标注文件里出现了 2但nc却配置成 2这样索引越界训练会在正常迭代几十个 epoch 后突然报错。解决方法是统一改用脚本遍历标签不要靠人工记类别表。3. YOLOv5 环境配置与草莓训练最小命令3.1 YOLOv5 环境配置Python 版本与 PyTorch 安装顺序无论你手头是 NVIDIA 显卡还是纯 CPU 环境都要先把 Python 环境隔离开。我习惯用 conda 创建yolov5专用环境Python 版本不必追求最新3.9 最稳因为部分老版本requirements.txt里的lap、tensorboard在 Python 3.11 上容易出现预编译包缺失。conda create -n yolov5 python3.9 -y conda activate yolov5 cd yolov5 # 进入你已经 clone 下来的 YOLOv5 项目目录 pip install -r requirements.txtrequirements.txt里已经列好 torch、torchvision、opencv-python、numpy 等依赖。需要注意的是这里默认安装的 PyTorch 版本会从 pip 源拉取 CPU 或 CUDA 版本的二进制文件但 CUDA 驱动与 PyTorch 的版本若不对齐torch.cuda.is_available()会返回 False。常见做法是先确认nvidia-smi能输出驱动版本再根据对应 CUDA 版本到 PyTorch 官网选择安装命令。例如本机是 CUDA 11.8就执行pip install torch2.0.0 torchvision0.15.0 --index-url https://download.pytorch.org/whl/cu118这里的--index-url指定了 PyTorch 的 CUDA 预编译索引后续pip install -r requirements.txt时会自动忽略已经被满足的 torch 包不会重复安装成 CPU 版。装完用python -c import torch; print(torch.cuda.is_available())确认输出为 True再进入训练环节。3.2 准备预训练权重与修改草莓数据集配置YOLOv5 的迁移学习思路是从 COCO 预训练权重出发再在自己的草莓数据集上微调。对几百张的赛题数据来说预训练权重是稳住收敛的重要保障不要从随机权重开始从头训练。下载yolov5s.pt放在项目根目录如果你显存不大用yolov5s已经足够因为它比yolov5m快很多而小样本场景下大模型反而更容易过拟合。在训练前确认strawberry.yaml中path路径写对。我将上一章配置的yaml放在yolov5项目根目录下因此path: ../datasets/strawberry可以直接从项目目录跳到同一级数据集目录。train和val的路径不要再加images/前缀因为 YOLOv5 会自动拼接path与train/val字段若重复写会得到不存在的目录。3.3 启动训练的最小命令与参数含义数据准备完成后用下面的最小命令训练python train.py \ --data strawberry.yaml \ --weights yolov5s.pt \ --img 640 \ --batch 16 \ --epochs 120 \ --cache \ --workers 4--img 640表示输入分辨率长边为 640这是 YOLOv5 的默认值对中等大小的草莓图像足够如果草莓在原始图像里只占 20 像素左右可以把--img 896或--img 1280但显存占用会明显上升。--batch 16在 8GB 显存的 RTX 3060/3070 上比较安全显存小的机器降到--batch 8同时开启梯度累积或者关掉--cache。--epochs 120对小数据集偏多但 YOLOv5 默认有patience100的早停机制实际未必跑满。--cache把图片提前加载进内存几百张图内存占用不大能省去每轮读取磁盘的时间。训练开始后项目根目录会生成runs/train/exp里面包含results.png、weights/best.pt和weights/last.pt。last.pt是最后一个 epoch 的权重best.pt是验证集 mAP 最高时的权重比赛提交一律用best.pt。如果训练中 loss 曲线出现先降后升的“沟状反弹”说明学习率偏大应当调低--hyp里的lr0。3.4 训练日志里最值得盯的两个指标训练过程中不建议频繁看图片输出重点看两条曲线训练边界框损失box_loss和验证集mAP0.5。box_loss反映了预测框与真实框的坐标回归误差草莓检测里大量小目标box_loss容易在小数点后三位波动只要整体趋势向下就不用干预。mAP0.5在头 20 个 epoch 通常会快速爬到 0.5 以上随后进入平台期如果它在某个阈值附近反复震荡说明目标尺度分布不均匀需要调整训练分辨率或数据增强参数。4. 小目标检测场景下的验证与 YOLOv5 超参数调优4.1 用 val.py 输出验证集 mAP 与 per-class 指标训练完成后不要直接去跑 detect.py先对验证集做一次完整评估。val.py会输出每个类别的 Precision、Recall 和 mAP还会生成混淆矩阵图confusion_matrix.png这对找草莓检测的误报来源非常直接。推荐命令python val.py \ --data strawberry.yaml \ --weights runs/train/exp/weights/best.pt \ --img 640 \ --task val终端会打印一张表含义如下指标说明草莓赛题里怎么用Precision预测框中有多少是真的草莓框青果误检高时偏低Recall真实草莓中有多少被召回遮挡严重时偏低mAP0.5IoU0.5 下的平均 AP比赛通常以它排名mAP0.5:0.95IoU 从 0.5 到 0.95 取平均小目标评测更严格mAP0.50是比赛成绩最直接的参考但如果赛题规定用 COCO 的mAP0.5:0.95你就要把小目标检测的质量放在第一优先级。查看这次验证的日志会发现green_strawberry与red_strawberry两行的 AP 差很大绿色草莓与叶片颜色太近误检多调参方向应侧重提升召回率比如降低置信度阈值时做额外的 NMS 合并。4.2 修改 YOLOv5 超参数提升草莓召回率YOLOv5 的数据增强开关集中在data/hyps/hyp.scratch-low.yaml中。训练时用--hyp指定这个文件直接修改里面对应的字段即可。针对草莓小目标且背景复杂的特点我一般会做如下调整lr0: 0.003 # 默认0.01小数据集用更小学习率防震荡 lrf: 0.12 # 最终学习率为初始学习率乘0.12 mosaic: 1.0 # Mosaic 增强把4张图拼接再缩放对小目标有帮助 mixup: 0.2 # 混合两张图训练默认通常为0 copy_paste: 0.1 # 把草莓实例复制到新图上增加遮挡样本其中mosaic对草莓检测提升最明显它把四张训练图缩放后拼成一张让模型在训练时看到大量“小草莓”语义缓解小目标占比不足的问题。mixup会把两张图的像素按比例混合标签也做同比例的混合它适合叶片遮挡严重的场景但mixup过大会让模型学到不真实的纹理因此 0.2 左右比较安全。copy_paste是 YOLOv5 7.0 里已有的实例复制增强它会从本批次里随机挑选草莓 mask 粘贴到其他图片上相当于免费扩充几百张数据集。修改之后重新训练时不要再覆盖原来的runs/train/exp可以在train.py后加上--name strawberry-tunepython train.py \ --data strawberry.yaml \ --weights yolov5s.pt \ --img 640 \ --batch 16 \ --epochs 120 \ --hyp data/hyps/hyp.scratch-low.yaml \ --name strawberry-tune4.3 针对草莓目标过小的两个进阶开关如果验证集上大量草莓框被漏掉检查一下标签里目标框的像素宽度分布。常见做法是写个小脚本统计所有标注框的宽高如果中位数小于 15 像素说明原始分辨率不够需要打开 YOLOv5 的多尺度训练python train.py --img 896 --batch 8 --multi-scale --name strawberry-ms--multi-scale会在每个 epoch 随机缩放输入尺寸让模型见过更大范围的尺度空间。代价是训练速度变慢所以 batch 要相应降低。另一个开关是关闭自动 anchor 计算使用手动 Kmeans 重新生成针对草莓尺度的 anchor在train.py命令后加--noautoanchor即可。锚框对密集小目标的影响经常被人忽略。YOLOv5 默认 COCO anchor 偏向中等物体自动计算会基于你的标签分布重新聚类但它只统计训练集的框如果训练集里草莓有大有小且数量不均聚类出来的 anchor 可能全压在大目标上。用--noautoanchor后可以结合utils/autoanchor.py里的check_anchors输出查看 anchor 与标签的匹配度匹配度低于 0.8 时手动增加一组更小的 anchor。5. 比赛提交前的结果导出与最后处理5.1 用 detect.py 批量输出草莓检测到 txt比赛测试集通常不带标注我们需要用训练好的best.pt跑一遍推理。YOLOv5 的detect.py默认会画框保存图片但提交评测只需要标签因此运行时加上--save-txt和--save-confpython detect.py \ --weights runs/train/exp/weights/best.pt \ --source ../datasets/strawberry/test/images \ --img 640 \ --save-txt \ --save-conf \ --project runs/detect \ --name submission每张测试图片会在runs/detect/submission/labels下生成同名 txt每一行是class_id conf x_center y_center width height。注意这里的class_id与训练时的names对应比如 0 代表green_strawberry1 代表red_strawberry提交前要确认比赛方需要的类别顺序是否一致。5.2 把归一化 txt 转换成比赛 JSON 格式多数比赛要求提交 COCO 风格的 JSON而不是 YOLO txt。常见做法是在推理后做一次字段拼接txt 里是归一化中心坐标需要拿着原图宽高还原成像素级别的[x, y, w, h]。下面这段脚本把labels目录下的 txt 转换成可提交的 JSONimport os import json IMG_DIR ../datasets/strawberry/test/images LABEL_DIR runs/detect/submission/labels results [] for txt_file in sorted(os.listdir(LABEL_DIR)): image_name txt_file.replace(.txt, .jpg) img_path os.path.join(IMG_DIR, image_name) from PIL import Image w, h Image.open(img_path).size with open(os.path.join(LABEL_DIR, txt_file)) as f: for line in f: parts line.strip().split() cls_id int(parts[0]) score float(parts[1]) cx, cy, bw, bh map(float, parts[2:]) x (cx - bw / 2) * w y (cy - bh / 2) * h box_w bw * w box_h bh * h results.append({ image_id: int(image_name.split(.)[0]), category_id: cls_id 1, bbox: [x, y, box_w, box_h], score: score }) with open(submission.json, w) as f: json.dump(results, f, indent2)脚本里category_id加 1是因为 COCO 类别从 1 开始而 YOLO 类别从 0 开始。image_id必须对应测试图片名中的数字如果文件名是test_001.jpg可以使用正则提取数字但一定保证 json 里的image_id与评测系统一致否则所有框都会被丢弃。5.3 推理加速与空结果兜底比赛时间紧张时可以用--half开启 FP16 推理在支持半精度的 GPU 上能带来接近一倍的提速python detect.py --weights best.pt --source test/images --save-txt --save-conf --halfbest.pt如果是在 CPU 上训练出的半精度推理可能不稳定建议只对 GPU 训练后的权重使用。另一个容易出问题的地方是模型在部分测试图上没有输出任何框。评测系统一般不允许该图片的预测结果为空这时可以保留所有预测里置信度最高的框并把score改成极小值如 0.01避免整图被判漏。处理完 json 后再检查一遍category_id是否从 1 开始否则评测会全部判 0 分。本文还有配套的精品资源点击获取