简介:这是一套面向计算机专业本科生及深度学习初学者的YOLOv5果蔬识别实战项目资源,适用于毕业设计、课程设计与期末大作业等教学实践场景,解决目标检测模型从数据准备、训练部署到可视化推理的全流程落地问题。压缩包共56个文件,包含14个Python脚本(涵盖数据划分、图像预处理、模型训练与实时检测)、12张PNG界面图与9张JPG/JPEG原始样本图、6个TXT配置与说明文档、4个XML标注文件,以及MobileNet/CNN双模型H5权重和README指南,整体大小为94.07MB。资源已通过导师审核并实际运行验证,内含window_realtime.py等可直接调用摄像头的推理脚本、heatmap生成与结果可视化模块、异常图像清洗工具(remove_wrong_image.py)及多版本训练日志,结构清晰、模块解耦,便于理解YOLOv5与轻量模型在农业视觉任务中的适配逻辑与优化路径。
1. 项目概述与核心价值
最近在整理过往项目时,翻出了一个我个人觉得完成度相当高的老项目:一个基于YOLOv5的果蔬识别系统。这个项目最初是为了解决一个社区生鲜自助结算柜的品类识别需求而开发的,后来经过多次迭代,形成了一个包含高质量数据集、完整训练源码和详细部署指南的“全家桶”。之所以说它优质,是因为它不仅仅是一个模型训练脚本的堆砌,而是从数据准备、模型训练调优到最终轻量化部署的一整套工程化解决方案。对于想入门计算机视觉,特别是目标检测实战的朋友来说,这个项目能让你避开很多初期必然会踩的坑,直接接触到接近生产环境的流程。
这个系统的核心,就是利用YOLOv5这个当下依然非常流行且高效的目标检测框架,来识别图像或视频流中的各种水果和蔬菜。你可能会问,现在YOLOv8、YOLOv9都出来了,为什么还用v5?我的体会是,v5在速度、精度和易用性上达到了一个非常好的平衡点,其代码结构清晰,社区资源极其丰富,遇到任何问题几乎都能找到解决方案。对于果蔬识别这种对实时性有一定要求(比如结算柜需要快速响应),但同时又需要较高识别精度的场景,YOLOv5是一个非常稳妥且高效的选择。这个项目包里,我不仅提供了自己清洗和标注的涵盖30种常见果蔬的数据集,还包含了从环境配置、数据准备、模型训练、性能评估到模型导出的全流程代码,最后还给出了在本地使用OpenCV进行实时检测的简易部署方案。无论你是学生想做一个课程设计或毕业项目,还是开发者想为自己的应用增加一个视觉识别模块,这套材料都能提供一个坚实的起点。
2. 项目整体架构与设计思路
2.1 为什么选择YOLOv5而非其他版本?
在项目启动时,我对比了当时主流的几个YOLO版本。YOLOv3虽然经典,但精度和速度已被后来者超越;YOLOv4在当时表现强劲,但代码依赖相对复杂,对新手不够友好。而YOLOv5以其纯PyTorch实现、简洁的代码结构和出色的文档迅速吸引了我的注意。它的设计哲学非常“工程化”:一个requirements.txt文件搞定环境,一个train.py脚本启动训练,数据格式采用通用的YOLO格式,大大降低了入门门槛。
更重要的是,YOLOv5提供了从Nano(YOLOv5n)到Extra Large(YOLOv5x)一系列预定义模型,你可以根据你的硬件条件和性能需求灵活选择。对于果蔬识别,我们通常部署在边缘设备或普通工控机上,因此我主要聚焦于YOLOv5s(小模型)和YOLOv5m(中模型)。v5s速度极快,在CPU上也能达到不错的帧率,适合对实时性要求极高的场景;v5m则在精度上有显著提升,而速度代价在可接受范围内,是兼顾精度与速度的“甜点”选择。这个项目源码中,我会展示如何针对这两种模型进行训练和对比。
2.2 数据集构建策略与核心考量
一个检测模型的好坏,七分靠数据,三分靠调参。网络上虽然有一些公开的果蔬数据集,但往往存在类别不均衡、标注质量参差不齐、背景单一或不符合实际应用场景等问题。因此,我决定自己构建一个更贴近真实应用的数据集。
数据采集来源:我主要从三个渠道获取图像:1)使用手机在超市、菜市场、厨房等真实场景下拍摄;2)从一些开源数据集中筛选出高质量、背景复杂的果蔬图片;3)利用数据增强技术,对已有图片进行扩充。总共收集了约5000张原始图像。
类别定义:我选取了30种最常见、最容易混淆的果蔬,例如苹果、香蕉、橙子、西红柿、黄瓜、西兰花等。这里有一个关键点:对于像“青苹果”和“红苹果”,我最初考虑设为两个类别,但考虑到在结算等应用中通常只关心“苹果”这个大类,最终将其合并。同时,将“樱桃番茄”和“普通番茄”也进行了合并。这需要在业务需求和技术实现之间做权衡。
标注规范与工具:采用YOLO格式的标注,即每个标注文件(.txt)与图片同名,其中每一行代表一个物体,格式为:<class_id> <x_center> <y_center> <width> <height>,坐标和宽高都是相对于图片尺寸的归一化值。标注工具我选用的是labelImg,它直接支持YOLO格式导出。在标注过程中,我制定了详细的规范:边界框要紧贴物体边缘;对于部分遮挡的物体,按可见部分标注;对于成串的葡萄、香蕉等,如果单个果实清晰可辨则单独标注,如果紧密簇拥则作为一个整体标注。
数据集划分:按70%(训练集)、15%(验证集)、15%(测试集)的比例进行划分。验证集用于训练过程中的模型评估和早停,测试集则是在所有训练调优完成后,用于最终报告模型性能,确保评估的公正性。
3. 核心代码结构与模块解析
3.1 环境配置与依赖管理
项目的可复现性首先依赖于清晰的环境配置。我使用conda创建独立的Python环境,并通过requirements.txt文件严格管理所有依赖包。核心依赖包括:
torch>=1.7.0和torchvision:PyTorch深度学习框架。ultralytics/yolov5:通过git submodule或直接克隆的方式引入YOLOv5官方源码库。我选择固定某个稳定提交的哈希值,以避免未来官方更新可能带来的不兼容问题。opencv-python:用于图像读取、预处理和结果可视化。pillow、matplotlib、pandas、seaborn:用于数据处理和可视化分析。albumentations:一个强大的数据增强库,我用它来实现更灵活和高效的在线增强。
在requirements.txt中,我会明确每个包的大版本号,例如opencv-python==4.5.5.64。这样可以最大程度保证其他人在不同时间、不同机器上都能成功复现环境。这也是从无数次“在我机器上好好的”的教训中总结出的经验。
3.2 数据准备与配置文件详解
YOLOv5的数据加载需要两个核心配置文件:data.yaml和dataset.yaml(名称可自定义,但结构固定)。
data.yaml:这个文件定义了数据集的元信息。
# 数据集的根目录路径(训练时相对于train.py的位置或绝对路径) path: ../datasets/fruits_vegetables # 训练集、验证集、测试集的图片路径列表文件(相对path的路径) train: images/train val: images/val test: images/test # 类别数量 nc: 30 # 类别名称列表,必须与标注文件中的class_id顺序严格对应 names: ['apple', 'banana', 'orange', 'tomato', 'cucumber', 'broccoli', ...]关键点:path的设定非常灵活。你可以设置为绝对路径,也可以设置为相对于训练脚本的相对路径。在团队协作或部署时,我推荐在代码中通过一个配置文件或环境变量来动态设置这个路径,避免硬编码。
数据增强配置:YOLOv5的训练脚本(train.py)内部集成了一套默认的数据增强策略,包括马赛克增强(Mosaic)、随机透视、色彩抖动等。这些默认配置对于大部分场景已经足够。但在本项目中,由于果蔬图像可能存在光照不均、反光等问题,我通过修改hyp.scratch.yaml(超参数配置文件)中的相关参数进行了微调:
- 将
hsv_h(色调抖动)和hsv_s(饱和度抖动)的增益略微提高,以增强模型对颜色变化的鲁棒性。 - 适度降低
mosaic的概率,因为在一些特写镜头中,马赛克增强可能会破坏单个果蔬的完整特征,我设置为0.8。 - 增加了
mixup增强,这是一种将两张图像线性混合的技术,有助于提高模型对重叠物体和噪声的泛化能力,设置概率为0.1。
3.3 模型训练脚本的核心参数解析
训练启动命令看似简单,但每个参数都至关重要:
python train.py --img 640 --batch 16 --epochs 100 --data ./data/fruits_vegetables.yaml --cfg ./models/yolov5s.yaml --weights yolov5s.pt --name fruits_veg_exp1--img 640:输入图像尺寸。YOLOv5会将所有图像统一缩放到这个尺寸进行训练。640是速度和精度的一个平衡点。如果你的目标物体通常很小,可以尝试增大到832甚至1024,但会显著增加显存消耗和训练时间。--batch 16:批次大小。这个值首先受限于你的GPU显存。在显存允许的前提下,较大的batch size有助于训练稳定。如果出现CUDA out of memory错误,需要减小batch size或使用--img更小的尺寸。--epochs 100:训练轮数。并非越多越好,需要配合早停(Early Stopping)策略。YOLOv5内置了早停逻辑,当验证集指标在指定轮数内不再提升时,会自动停止训练并保存最佳模型。--data:指向你的data.yaml配置文件。--cfg:指定模型结构配置文件。这里使用yolov5s.yaml,如果你想使用v5m,就改为yolov5m.yaml。--weights yolov5s.pt:指定预训练权重。强烈建议使用预训练权重,即使是在ImageNet上预训练的权重,其提取通用特征的能力也能极大加速你的收敛过程,并提升最终精度。这里加载的是官方在COCO数据集上预训练的yolov5s.pt。--name fruits_veg_exp1:本次实验的名称。所有输出(模型权重、日志、可视化结果)都会保存在runs/train/fruits_veg_exp1目录下,方便管理和对比不同实验。
训练过程监控:训练开始后,除了观察命令行输出的损失曲线,更重要的是利用TensorBoard。YOLOv5会自动记录训练日志,只需在另一个终端执行tensorboard --logdir runs/train,即可在浏览器中查看丰富的可视化信息,包括损失函数变化、验证集精度(mAP)、召回率(Recall)、以及模型对验证集图片的预测样例。通过观察这些图表,你可以判断模型是否过拟合、欠拟合,以及何时可以提前终止训练。
4. 模型训练、调优与评估实战
4.1 训练流程与关键节点把控
启动训练后,系统会先进行一个简短的“预热”阶段,对数据加载管道和模型前向传播进行测试。之后,训练循环正式开始。你需要密切关注以下几个关键节点:
第一个Epoch结束时:查看验证集的mAP(平均精度均值)指标。如果此时mAP非常低(例如低于0.1),可能是数据标注格式错误、类别ID不匹配,或者学习率设置得极其不合理。一个正常的、使用预训练权重的训练,第一个epoch后的mAP通常能达到0.2-0.4(取决于任务难度)。
损失曲线观察:训练损失(train/box_loss,train/obj_loss,train/cls_loss)应该随着epoch增加而稳步下降,并在后期趋于平缓。验证损失(val/box_loss等)初期会随训练损失下降,但后期可能会因为模型开始过拟合训练集而有所回升。这是引入早停或调整正则化强度的信号。
早停策略:YOLOv5默认的早停耐心(patience)是100个epoch。对于我们的果蔬数据集,模型通常在50-80个epoch就会收敛。我通常会设置--patience 30,这意味着如果验证集mAP在连续30个epoch内没有提升,训练就会停止,并自动加载验证集mAP最高的那个模型权重作为最终模型。这能有效防止过拟合,并节省计算资源。
4.2 超参数调优实战心得
YOLOv5的默认超参数(在hyp.scratch.yaml中)已经过大量实验优化,对于新任务,不建议进行大刀阔斧的修改。微调应以数据为导向。以下是我针对果蔬识别任务的一些调优经验:
- 学习率(lr0):这是最重要的超参数。默认值0.01对于微调(使用预训练权重)来说可能偏高。我通常从一个较小的值开始,例如0.001,并配合学习率调度器(如余弦退火)。如果训练初期损失下降非常缓慢,再适当调高。项目代码中我提供了一个学习率范围测试(LR Finder)的脚本,可以帮助你找到一个合适的初始学习率范围。
- 优化器选择:YOLOv5默认使用SGD优化器。对于小批量数据或希望更快收敛的场景,可以尝试使用AdamW优化器(通过
--optimizer AdamW指定)。AdamW自适应调整每个参数的学习率,通常能减少一些调参工作量,但最终模型泛化性能可能略逊于精心调参的SGD。我个人的习惯是先用AdamW快速得到一个baseline,再用SGD进行精细调优。 - 数据增强强度:如前所述,通过
hyp.scratch.yaml中的hsv_h,hsv_s,hsv_v,translate,scale,mosaic等参数控制。如果你的数据集背景复杂、物体姿态多变,可以增强这些参数。如果你的数据集质量很高、标注很准,可以适当减弱增强,避免引入过多噪声。
注意:超参数调优切忌同时调整多个参数。应采用“控制变量法”,每次只调整一个参数,观察其对验证集mAP的影响,记录下最佳配置。
4.3 模型评估与性能指标解读
训练完成后,在实验目录下(如runs/train/fruits_veg_exp1)会生成一系列结果文件,其中最重要的是weights/best.pt(最佳模型)和results.png(结果汇总图)。
使用以下命令在测试集上评估最佳模型:
python val.py --data ./data/fruits_vegetables.yaml --weights runs/train/fruits_veg_exp1/weights/best.pt --img 640 --task test评估报告会输出一系列关键指标:
- mAP@0.5 (mAP_0.5):在交并比(IoU)阈值为0.5时的平均精度均值。这是最常用的指标,值越高越好,达到0.85以上通常说明模型性能优秀。
- mAP@0.5:0.95 (mAP_0.5:0.95):在IoU阈值从0.5到0.95(步长0.05)区间内计算的平均mAP。这是一个更严格的指标,衡量模型在不同定位精度要求下的综合表现。
- Precision(精确率):模型预测为正的样本中,真正为正的比例。高精确率意味着模型“错认”的情况少。
- Recall(召回率):所有真实的正样本中,被模型正确预测出来的比例。高召回率意味着模型“漏检”的情况少。
对于果蔬识别,在结算场景下,我们可能更看重精确率,因为错把西红柿当成苹果计费会引起客户投诉。而在库存盘点场景下,可能更看重召回率,不希望漏掉任何一件商品。你需要根据业务需求来权衡。
评估脚本还会生成一个混淆矩阵(confusion_matrix.png),它能直观地展示模型最容易混淆哪些类别。例如,你可能会发现“青椒”和“黄瓜”(切片状态下)、“红苹果”和“西红柿”之间存在一定的误判。这为进一步的数据清洗(增加难例样本)或后处理规则(根据形状、上下文信息)提供了方向。
5. 模型导出与轻量化部署方案
5.1 模型格式转换与优化
训练得到的.pt文件是PyTorch的模型权重,包含了模型结构和参数。为了在不同平台部署,我们需要将其转换为更通用的格式。
导出为ONNX格式:ONNX是一种开放的模型表示格式,被众多推理引擎支持。
python export.py --weights runs/train/fruits_veg_exp1/weights/best.pt --img 640 640 --batch 1 --include onnx--img 640 640指定了输入图片的高度和宽度(必须是32的倍数)。--batch 1指定了批处理大小为1,这是很多边缘设备推理的常见设置。导出的ONNX模型可以方便地用OpenCV DNN、TensorRT、ONNX Runtime等库进行加载和推理。
导出为TensorRT引擎:如果你在NVIDIA Jetson等边缘设备上部署,为了极致性能,需要转换为TensorRT引擎。这个过程稍微复杂一些,通常需要在部署设备上安装TensorRT,然后使用export.py的--include engine选项,或者使用TensorRT的Python API进行转换。转换过程会进行层融合、精度校准(FP16/INT8)等优化,能显著提升推理速度。在项目指南中,我提供了在Jetson Nano上转换和运行TensorRT引擎的详细步骤。
5.2 基于OpenCV的本地实时推理Demo
为了快速验证模型效果,我编写了一个基于OpenCV的Python推理脚本。这个脚本不依赖PyTorch,只依赖OpenCV和NumPy,非常轻量。
import cv2 import numpy as np # 加载模型和类别名称 net = cv2.dnn.readNetFromONNX('best.onnx') with open('data/classes.names', 'r') as f: classes = [line.strip() for line in f.readlines()] # 预处理函数 def preprocess(image, input_size=(640, 640)): # 保持长宽比resize,并在边缘填充灰色 h, w = image.shape[:2] scale = min(input_size[0] / h, input_size[1] / w) new_h, new_w = int(h * scale), int(w * scale) resized = cv2.resize(image, (new_w, new_h)) # 创建画布并填充 canvas = np.full((input_size[0], input_size[1], 3), 114, dtype=np.uint8) canvas[:new_h, :new_w, :] = resized # 归一化、BGR2RGB、HWC转CHW blob = cv2.dnn.blobFromImage(canvas, 1/255.0, swapRB=True) return blob, scale, (h, w) # 后处理函数(解析YOLO输出) def postprocess(outputs, conf_thresh=0.25, iou_thresh=0.45): # outputs是模型输出,需要根据模型结构解析出边界框、置信度、类别 # 这里是一个简化的示例,实际需要根据导出的ONNX模型输出维度进行调整 # 通常包含非极大值抑制(NMS)步骤 # ... return boxes, scores, class_ids # 主循环 cap = cv2.VideoCapture(0) # 打开摄像头 while True: ret, frame = cap.read() if not ret: break blob, scale, orig_shape = preprocess(frame) net.setInput(blob) outputs = net.forward(net.getUnconnectedOutLayersNames()) boxes, scores, class_ids = postprocess(outputs) # 在原始帧上绘制结果 for box, score, cls_id in zip(boxes, scores, class_ids): # 将归一化坐标还原为原始图像坐标 x1, y1, x2, y2 = box label = f'{classes[cls_id]}: {score:.2f}' cv2.rectangle(frame, (x1, y1), (x2, y2), (0,255,0), 2) cv2.putText(frame, label, (x1, y1-10), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0,255,0), 2) cv2.imshow('Fruit&Vegetable Detection', frame) if cv2.waitKey(1) & 0xFF == ord('q'): break cap.release() cv2.destroyAllWindows()这个Demo清晰地展示了部署的核心流程:预处理、网络推理、后处理。后处理中的非极大值抑制(NMS)是关键,它用于剔除冗余的重叠检测框。你需要根据模型的实际输出维度来编写解析逻辑。
6. 常见问题排查与性能优化技巧
6.1 训练过程中的典型问题
问题1:训练损失(Loss)不下降,或者出现NaN。
- 可能原因与排查:
- 学习率过高:这是最常见的原因。过高的学习率会导致优化过程在最优解附近震荡甚至发散。解决方案:立即停止训练,将学习率(
hyp.scratch.yaml中的lr0)降低一个数量级(例如从0.01降到0.001)重新开始。使用预训练权重时,学习率通常需要设得更低。 - 数据标注错误:检查标注文件的格式是否正确,特别是坐标值是否在[0,1]范围内,类别ID是否从0开始连续编号。一个快速检查的方法是使用YOLOv5自带的
detect.py脚本,用训练好的模型(哪怕是初始权重)对训练集图片跑一遍,看看标注框和预测框是否能大致对齐。命令:python detect.py --weights yolov5s.pt --source path/to/train/images --save-txt,它会将预测结果和标签一起显示。 - 数据路径错误:确保
data.yaml中的path、train、val路径设置正确,并且图片和对应的标签文件确实存在。
- 学习率过高:这是最常见的原因。过高的学习率会导致优化过程在最优解附近震荡甚至发散。解决方案:立即停止训练,将学习率(
- 我的经验:遇到Loss为NaN,我首先会检查数据集中是否有损坏的图片(用OpenCV的
imread检查是否能正常读取),或者是否有标注框的坐标超出了图像边界(这可能在数据增强时产生非法坐标)。
问题2:验证集mAP很低,但训练集损失正常下降(过拟合)。
- 可能原因与排查:
- 数据量不足:这是小数据集最常见的问题。模型记住了训练集的所有细节,但无法泛化到新数据。解决方案:加强数据增强(增大
hyp.scratch.yaml中的增强参数),或者收集更多、更多样化的数据。使用albumentations库可以添加更复杂的增强,如随机遮挡、模糊、天气模拟等。 - 模型过于复杂:对于只有几千张图片的数据集,使用
yolov5x这样的大模型很容易过拟合。解决方案:换用更小的模型,如yolov5s或yolov5n。或者在模型配置文件中增加Dropout层、权重衰减(--weight-decay参数)等正则化手段。 - 训练轮数过多:即使有早停,如果耐心值设置过大,模型也可能在验证集指标停止提升后继续“记忆”训练集。解决方案:减少早停耐心
--patience,或手动监控TensorBoard,在验证集mAP开始下降时提前终止训练。
- 数据量不足:这是小数据集最常见的问题。模型记住了训练集的所有细节,但无法泛化到新数据。解决方案:加强数据增强(增大
6.2 部署与推理阶段的性能瓶颈
问题:模型推理速度慢,无法满足实时性要求(如30 FPS)。
- 优化策略层级:
- 模型层面:换用更小的模型(从v5m降级到v5s甚至v5n)。这是提升速度最有效的方法,但会牺牲一定精度。需要进行权衡测试。
- 输入分辨率:降低
--img参数,例如从640降到416或320。分辨率降低会直接减少计算量,但对小物体的检测能力会下降。对于果蔬识别,大部分物体在320x320的输入下依然可辨。 - 推理引擎:将PyTorch模型转换为TensorRT或OpenVINO等优化后的推理引擎,通常能获得1.5倍到数倍的加速。特别是在Intel CPU上,OpenVINO的优化效果非常显著。
- 后处理优化:NMS是CPU上的操作,可能成为瓶颈。可以尝试:
- 调整NMS的置信度阈值(
conf-thres)和IoU阈值(iou-thres)。提高置信度阈值可以直接过滤掉大量低质量预测框,减少NMS的计算量。 - 使用更高效的NMS实现,如Fast NMS或TorchVision的NMS(如果使用PyTorch推理)。
- 调整NMS的置信度阈值(
- 硬件利用:确保推理时GPU被充分利用。使用
torch.cuda.synchronize()和Python的time模块精确测量前向传播时间,排除图像读取、预处理、后处理和显示的时间开销。真正的模型推理时间应该远小于总时间。
问题:模型在真实场景下误检或漏检严重。
- 排查与解决:
- 领域差异:训练数据(超市摆拍)和真实部署环境(厨房昏暗光线、手持抖动)存在差异。解决方案:进行“领域适配”。收集少量真实场景下的图片(即使不标注),与训练集图片一起进行在线增强,让模型在训练时就看到更多样的背景和光照条件。或者,使用生成对抗网络(GAN)进行风格迁移,将真实场景图片“翻译”成类似训练集的风格。
- 难例挖掘:将模型在真实场景中识别错误的图片收集起来,重新标注,加入到训练集中进行第二轮训练。这个过程可能需要迭代几次。
- 集成后处理逻辑:利用业务逻辑。例如,在自助结算柜中,可以结合重量传感器信息:如果视觉识别出一个“苹果”,但重量传感器显示重量极轻,则可能是一次误检,可以结合其他帧的信息进行投票决策。
6.3 数据集构建与管理的经验之谈
数据标注的“脏活”与技巧:
- 多人标注一致性:如果项目需要多人标注,务必先制作详细的标注规范文档,并针对边界案例(如严重遮挡、部分出镜、奇异形状)进行统一规定。然后,让所有标注员对同一批图片进行标注,计算他们之间的一致性(如IoU),对差异大的地方进行讨论和规范统一。
- 主动学习:初始模型训练好后,用它去预测大量未标注的图片。筛选出那些模型置信度低、或者预测结果矛盾的图片(例如,同一类物体有的预测框置信度高,有的低),这些往往是“信息量”大、对模型提升有帮助的难例,优先标注它们,可以高效提升数据集质量。
- 数据清洗:定期检查数据集,剔除模糊、过暗、过亮的低质量图片。检查标注框,修正错误的类别和不准的边界框。一个干净的数据集比一个庞大但嘈杂的数据集更有价值。
这个YOLOv5果蔬识别项目,从数据到部署的完整链路走下来,其价值远不止于识别几种水果蔬菜。它提供了一个标准的计算机视觉项目模板,其中涉及的数据处理、模型训练调优、问题排查和性能优化思路,可以迁移到任何其他的目标检测任务中,比如工业零件检测、交通标志识别、野生动物监测等。
本文还有配套的精品资源,点击获取