简介:这是一份基于YOLOv5的草莓目标检测项目,源自猛犸杯比赛赛题,面向目标检测初学者、计算机相关专业学生及竞赛参与者。项目包括可直接运行的Python源码与训练脚本、主办方提供的草莓图像数据集(几百张)及对应XML标注文件,重点演示在数据量受限时如何通过数据清洗、数据增强与标注转换等预处理环节提升模型准确率。压缩包共507个文件,以402张JPG原图和100个XML标注文件为主,另有IPYNB主程序、3个数据库文件及说明文档,整体约979.54MB。已有200人下载学习,并提供远程教学支持,便于解决运行中的实际问题。适合作为课程设计、毕业设计或算法练习的参考,也可在此基础上二次开发,应用于自定义目标检测任务。
1. 从比赛赛题看YOLOv5草莓目标检测的落地路径
草莓目标检测这类赛题,通常给几百张已经标注的图片,要求在测试集上识别出成熟和未成熟果实,并按IoU阈值计算AP。很多人看到“几百张”第一反应是数据不够,实际上这个量级配合预训练权重和在线增强,比赛里完全能跑出mAP 0.8以上的结果。YOLOv5的优势是训练脚本、验证脚本和导出脚本一体,Python源码可控,换数据只需要改yaml和标注格式。我们按比赛准备的顺序,把环境、标注、训练、评估和微调几个环节的命令与参数逐个说清,遇到报错也知道去哪一行改。
2. YOLOv5环境搭建与草莓数据集格式预处理
YOLOv5的源码环境依赖是固定的,第一步先把Python版本和依赖库锁住。如果直接用系统Python 3.13去跑requirements.txt,大概率在opencv-python或pandas的wheel编译上卡住。我一般用Miniconda建独立环境,Python指定3.8,装完依赖后训练、验证、导出都在同一个环境里,不会出现“本机能跑、换机器崩”的情况。
2.1 用Miniconda创建YOLOv5专用Python环境
conda create -n yolov5 python=3.8 -y conda activate yolov5 cd yolov5 pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simplerequirements.txt锁定的是YOLOv5官方仓库在release/v6.0之后维护的依赖版本,torch和opencv-python是重头。指定清华镜像可以缩短安装时间,如果是内网环境,提前把wheel包下载好再离线安装也一样。安装完成后检查GPU是否可用:
python -c "import torch; print(torch.cuda.is_available(), torch.cuda.device_count())"输出True 1表示CUDA版torch已生效。如果输出False,说明装成了CPU版,需要去PyTorch官网按CUDA版本重新安装,再补装requirements里其他依赖。这一步装错,后面训练时间会差几十倍,几百张图的数据集可能从一小时变成十小时。
2.2 草莓图像数据集目录组织与YOLO标注格式转换
YOLOv5要求数据集按images和labels两个顶层目录组织,train和val子目录两边对应。比赛给的数据常见是VOC的XML标注,少数是COCO的JSON。下面是一段VOC转YOLO格式的核心代码。
# voc_to_yolo.py import os import xml.etree.ElementTree as ET def convert(width, height, box): dw = 1.0 / width dh = 1.0 / height x = (box[0] + box[1]) / 2.0 y = (box[2] + box[3]) / 2.0 w = box[1] - box[0] h = box[3] - box[2] return x * dw, y * dh, w * dw, h * dh class_map = {'immature': 0, 'mature': 1} for xml_file in os.listdir('annotations'): tree = ET.parse(os.path.join('annotations', xml_file)) root = tree.getroot() width = int(root.find('size/width').text) height = int(root.find('size/height').text) out_name = os.path.join('labels', xml_file.replace('.xml', '.txt')) with open(out_name, 'w') as f: for obj in root.iter('object'): name = obj.find('name').text if name not in class_map: continue box = obj.find('bndbox') x0 = float(box.find('xmin').text) y0 = float(box.find('ymin').text) x1 = float(box.find('xmax').text) y1 = float(box.find('ymax').text) x0 = min(max(x0, 0), width) x1 = min(max(x1, 0), width) y0 = min(max(y0, 0), height) y1 = min(max(y1, 0), height) if x1 <= x0 or y1 <= y0: continue cx, cy, w, h = convert(width, height, [x0, y0, x1, y1]) f.write(f"{class_map[name]} {cx:.6f} {cy:.6f} {w:.6f} {h:.6f}\n")convert函数把左上右下坐标转成中心点加宽高的归一化形式。坐标越界处理放在转换前,因为比赛数据里偶尔会出现轻微越界的框,直接过滤会导致目标丢失,clip后再判断是否反向框,能最大化保留有效标签。转换后抽查几个txt,确认每行5列数字且坐标都在0到1之间,再进入下一步。
2.3 自定义data.yaml与train/val划分
在项目根目录创建datasets/strawberry目录,结构如下:
datasets/strawberry/ ├── images/ │ ├── train/ │ └── val/ ├── labels/ │ ├── train/ │ └── val/ └── data.yaml划分训练集和验证集用一段简短的Python脚本:
# split_data.py import os, random, shutil random.seed(42) src_images = 'images/all' src_labels = 'labels/all' for split in ['train', 'val']: os.makedirs(f'datasets/strawberry/images/{split}', exist_ok=True) os.makedirs(f'datasets/strawberry/labels/{split}', exist_ok=True) files = [f for f in os.listdir(src_images) if f.endswith('.jpg')] random.shuffle(files) split_index = int(len(files) * 0.8) for f in files[:split_index]: shutil.copy(f'{src_images}/{f}', 'datasets/strawberry/images/train/' + f) shutil.copy(f'{src_labels}/{f.replace(".jpg", ".txt")}', 'datasets/strawberry/labels/train/') for f in files[split_index:]: shutil.copy(f'{src_images}/{f}', 'datasets/strawberry/images/val/' + f) shutil.copy(f'{src_labels}/{f.replace(".jpg", ".txt")}', 'datasets/strawberry/labels/val/')这里用随机采样而不是按目录顺序切分,因为比赛数据往往按拍摄批次存放,直接取前80%会导致训练集和验证集光照分布不均匀。固定random.seed保证重复运行结果一致。如果数据里有明显重复的近景图,先把重复图去重再做划分。
data.yaml按以下内容编写,路径使用相对项目根目录的写法。
# datasets/strawberry/data.yaml path: datasets/strawberry train: images/train val: images/val nc: 2 names: ['immature', 'mature']需要警惕的地方是nc必须和labels里的类别id最大值加1一致,YOLOv5不会自动校验这一点。常见报错是“indices should be between 0 and nc-1”,出现时直接检查标注txt里是否有超过nc-1的id。
3. 用YOLOv5s训练草莓检测模型:参数设置与训练命令
数据准备好之后进入训练环节。比赛场景不用追求大模型,几百张草莓图用YOLOv5s在单卡上能稳定收敛,参数量小、推理速度快,评测阶段的硬件限制基本可以忽略。
3.1 为什么选YOLOv5s而不是YOLOv5m或YOLOv5x
先看一组官方结构默认参数:
| 模型权重 | 深度倍数 | 通道倍数 | 显存占用约 | 速度参考 |
|---|---|---|---|---|
| yolov5s.pt | 0.33 | 0.50 | 3.7GB | 最快 |
| yolov5m.pt | 0.67 | 0.75 | 5.6GB | 较快 |
| yolov5l.pt | 1.0 | 1.0 | 9.2GB | 中等 |
| yolov5x.pt | 1.33 | 1.25 | 14GB | 慢 |
几百张的小数据,YOLOv5l以上的模型在训练集上容易把注意力集中到背景纹理,验证集反而掉点;YOLOv5s叠加迁移学习的表现通常不比大模型差,训练时间只有五分之一。我一般先跑一版s确认baseline,再按上一版的结果决定要不要换m。
3.2 下载预训练权重并启动训练
本次使用yolov5s预训练权重来初始化参数。train.py检测到本机没有yolov5s.pt时会自动下载,网络受限的环境可以手动把权重文件放到weights目录。
# 在yolov5项目根目录执行 python train.py \ --weights yolov5s.pt \ --data datasets/strawberry/data.yaml \ --epochs 200 \ --batch-size 16 \ --img 640 \ --device 0 \ --cache \ --patience 40 \ --project runs/train \ --name strawberry_exp参数说明:
--img 640训练时会将图像resize到640x640。草莓果实体型小,如果原图分辨率较高,可以降到608或512,减少小目标被压缩的损失。--batch-size 16在8GB显存下属于安全值;显存不够则改成8,并把cache去掉。--patience 40是早停策略,验证集指标连续40个epoch不提升就停,适合比赛期间反复试参数。--cache把图像缓存到内存,几百张图只占几百MB,能明显加快数据加载。
YOLOv5默认开启mosaic和旋转这类在线增强,因此不用在训练命令里额外写增强参数。若数据中草莓重叠非常密集,常见做法是到数据增强配置里把copy_paste从0调高到0.5,让本图中的目标复制粘贴到另一位置,增强遮挡样本。训练时终端会周期性打印loss和精度信息,只要看到mAP_0.5在逐步上升就可以放着跑。
3.3 训练过程中的日志监控与权重选择
训练结束后,runs/train/strawberry_exp目录下会生成best.pt、last.pt和results.csv。比赛提交时优先用best.pt,因为它是验证集指标最高的权重;last.pt是最后一个epoch的权重,如果前面出现过更高点,last未必更好。
很多参赛者不看results.csv就直接提交,这是最容易丢分的习惯。验证集指标在表格里的对应关系:
- metrics/precision 精确率
- metrics/recall 召回率
- metrics/mAP_0.5 主评测目标
- metrics/mAP_0.5:0.95 更严格的综合指标
# 用训练日志画mAP曲线 python -c " import pandas as pd import matplotlib.pyplot as plt df = pd.read_csv('runs/train/strawberry_exp/results.csv') plt.plot(df['epoch'], df['metrics/mAP_0.5']) plt.xlabel('epoch') plt.ylabel('mAP@0.5') plt.savefig('map_curve.png') "这段脚本只展示results.csv的读取方式,比赛里查看训练曲线能判断是欠拟合还是过拟合。如果mAP_0.5曲线还在缓慢上升,说明训练没有完全收敛,可以加大epoch数继续训练;曲线已经平坦反而说明早停设置合理。
4. 用验证集评估草莓检测模型的性能与错误分析
训练完成后不要急着跑测试集。先用验证集做一轮detect,确认置信度阈值、NMS参数和类别分布符合比赛要求,再决定要不要调整策略。
4.1 单张草莓图的测试命令与参数
python detect.py \ --weights runs/train/strawberry_exp/best.pt \ --source datasets/strawberry/images/val/ \ --imgsz 640 \ --conf-thres 0.25 \ --iou-thres 0.45 \ --save-txt \ --project runs/detect \ --name val_out--source支持单个图片、图片目录或视频文件,val目录下所有jpg会被逐张推断;--conf-thres 0.25过滤掉置信度低于0.25的框,草莓目标尺寸较小,不推荐把这个值调到0.4以上,容易漏检;--iou-thres 0.45是NMS去重的IoU阈值,果实重叠严重的测试图可以提高到0.5,减少重复框。
如果推理时内存不足或速度变慢,优先检查--imgsz是否和训练一致。训练用的608,这里设640,边长变化超过10%会直接影响mAP,两者保持一致最稳妥。
4.2 使用val.py计算精确率、召回率和mAP
验证脚本会输出与比赛评分一致的指标:
python val.py \ --weights runs/train/strawberry_exp/best.pt \ --data datasets/strawberry/data.yaml \ --img 640 \ --task val \ --iou 0.5 \ --conf-thres 0.001这里有两处关键设置:
--iou 0.5指定主评测mAP的IoU阈值,和比赛要求保持一致。--conf-thres 0.001统计mAP时把置信度阈值压低,让AP曲线完整计算。实际推理时可以用更高的conf-thres过滤噪声。
输出表中Speed和mAP_0.5一目了然。如果mAP_0.5低于0.6,先看precision和recall哪一项拖后腿。recall低说明漏检多,优先增强小目标;precision低说明误检多,需要回查标注是否有漏标或类别边界模糊。
4.3 从混淆矩阵和目视图诊断误检
val.py会在runs/val/exp目录生成confusion_matrix.png。草莓比赛里最常见的混淆项是背景被误认为mature,原因通常是未成熟青果和叶片颜色相近,标注时归类不一致。处理方式不是调阈值,而是回看标注,把验证集里混淆的样本单独挑出来,并统计各类别框数量分布。
# analyz_labels.py import os from collections import Counter counts = Counter() for txt in os.listdir('datasets/strawberry/labels/train'): with open(os.path.join('datasets/strawberry/labels/train', txt)) as f: for line in f: cls_id = int(line.split()[0]) counts[cls_id] += 1 print(counts)类别严重不平衡时,比如mature只占20%,训练会偏向多数类。解决手段是在训练配置里设置类别权重,或者做简单的过采样:把少数类图片复制一份到训练目录,让比例接近1:1。过采样不改变标注格式,只是同一份数据多放几份,在几百张数据量下代价很低。
5. 针对比赛小数据集的增强、TTA与微调技巧
几百张草莓图做比赛,核心不是堆模型大小,而是怎么让有限的样本产生足够的分布覆盖。最后这部分是几个常见且见效快的技巧。
5.1 在hyp配置里针对小目标改数据增强参数
YOLOv5的超参数文件位于data/hyps/hyp.scratch-low.yaml。草莓果实偏小,推荐重点调整翻转和HSV变换:
# data/hyps/hyp.scratch-low.yaml 针对草莓的推荐值 hsv_h: 0.015 hsv_s: 0.7 hsv_v: 0.4 degrees: 0.0 flipud: 0.5 fliplr: 0.5 mosaic: 1.0 mixup: 0.15 copy_paste: 0.5草莓的颜色是成熟度标注的关键特征,hsv_h调太大会把红色改成蓝色,破坏类别可区分性,保持0.015左右;hsv_s和hsv_v提升到0.7和0.4可以模拟田间不同光照。degrees保持0,草莓没有明显方向性,旋转只会让背景噪声增多。flipud和fliplr各开0.5,在俯拍草莓垄的图像上适用。mixup和copy_paste在小数据集上能降低过拟合,但mixup不要超过0.2,否则训练出的框中心位置和大小分布会与真实数据偏差过大。
验证修改是否生效的方法是重新训练时看前几个epoch的输出图像。YOLOv5会把增强后的示例图保存到runs/train/exp*/train_batch*.jpg,检查增强后的草莓是否仍保持真实的颜色比例即可。
5.2 评测时开启TTA提升分数
比赛评分以测试集为主时,TTA是低成本提分手段。detect.py内置了augment参数:
python detect.py \ --weights runs/train/strawberry_exp/best.pt \ --source test_images \ --img 640 \ --augment \ --conf-thres 0.25加--augment后,每张图会做原图、横向翻转、纵向翻转、双翻转后分别推理,结果合并时置信度取平均。TTA在草莓这种小目标尺寸上通常能带来小幅mAP提升,代价是推理时间翻倍,如果比赛有时间限制,只在最终评测阶段开启即可。
5.3 用低置信度检测结果定位漏检目标
如果TTA之后mAP仍不稳定,把conf-thres降到0.15保存txt,与标注文件做差值,能快速找到哪些目标在低置信度下才被检出:
python detect.py \ --weights runs/train/strawberry_exp/best.pt \ --source val \ --conf-thres 0.15 \ --save-txt再用脚本比对低阈值检测txt和标注txt,找出稳定丢失的果实框位。这些丢失目标通常是小果或遮挡果,此时单独增加该区域的copy_paste增强,比盲目调置信度更有效。提交前记住一个原则:用固定seed重新跑最终的训练,保证结果可复现,比赛答辩时也能对每个参数的选择依据给出解释。
本文还有配套的精品资源,点击获取