简介:这是一套面向深度学习入门者与计算机视觉方向学生的YOLOv5果蔬识别完整项目包,围绕土豆、圣女果、大白菜、大葱、梨、胡萝卜、芒果、苹果、西红柿、韭菜、香蕉、黄瓜等十余类常见果蔬的检测任务展开,可用于课程设计、毕业设计或算法练手。压缩包共56个文件,约94.07MB,包含14个Python脚本、12个PNG与9个JPG及6个JPEG图像、6个TXT说明、4个XML标注、2个H5权重文件,以及Markdown文档和工程配置,覆盖数据读取、训练、测试与实时摄像头推理等环节。资源中提供了CNN与MobileNet两套训练流程、训练过程记录、测试记录、热力图与结果图,以及数据划分、错误样本清理等工具脚本,便于读者对照复现模型训练与评估。目前已有3770人学习下载,适合希望快速跑通果蔬检测全流程、理解数据集组织与模型调参的读者参考。
1. 果蔬识别为什么总在分拣线上翻车:从 yolov5 数据集到可复现训练
做过果蔬分拣线视觉项目的人大多有过类似经历:模型在实验室的验证集上 mAP 跑到 0.9,一上产线就频繁把青椒认成黄瓜、把带疤的苹果漏检。问题往往不在网络结构,而在数据集和训练配置这两块被忽视的地方。yolov5 果蔬识别数据集系统+代码+教程这套组合,本质上是把「数据采集标注 → 格式转换 → 训练调参 → 推理部署」这条链路打包成可复现的工程模板,解决的是从零搭一套果蔬检测系统时反复踩坑、反复返工的问题。它适合两类人:一类是想用 yolov5 训练自己数据集、但卡在标注格式和超参数上的算法新手;另一类是要把果蔬识别落到分拣、称重、质检环节的一线工程师。下面按我实际做过的顺序,把数据集怎么建、代码怎么跑、参数怎么调、坑在哪讲清楚。
2. 果蔬数据集怎么建:从采集到 YOLO 格式的完整链路
2.1 果蔬识别的类别划分与采集策略
果蔬识别和通用目标检测最大的区别在于类间差异小、类内差异大。同一个苹果,红富士和黄元帅在颜色上差异明显,但青苹果和青椒在低分辨率下几乎同色;同一颗西红柿,成熟度不同颜色从青到红连续变化。所以类别划分不能只按物种,要按业务需要的粒度来定。
我一般会先问三个问题:分拣线要区分的是品种、成熟度还是缺陷?误检和漏检哪个代价更高?单帧图像里同类目标最多几个?这三个答案直接决定类别数和采集方式。比如做苹果分拣,如果只分「好果/坏果」,那就是二分类检测,数据集压力小很多;如果要分「红富士/黄元帅/嘎啦」再叠加缺陷,类别数会到 6 到 10 类,每类至少需要 800 到 1500 个实例才能稳住。
采集时注意几点血泪经验:一是光照要覆盖产线实际工况,顶光、侧光、背光都拍,否则模型在逆光工位直接翻车;二是遮挡要真实,果蔬堆叠、传送带边缘遮挡、机械臂抓取瞬间的遮挡都要有;三是负样本要够,空传送带、只有包装箱、只有标签的画面都要采,不然模型会把背景纹理当目标。常见做法是每个类别先采 300 到 500 张原始图,再通过旋转、亮度扰动、随机裁剪扩到 1500 张左右,但增强不能替代真实采集,尤其是缺陷类。
2.2 标注规范与 LabelImg 实操
标注质量决定上限。果蔬标注最容易出的问题是边界框贴太紧或太松。贴太紧会把果蔬边缘的阴影切掉,模型学不到完整轮廓;贴太松会把相邻果蔬框进来,导致一个框里两个目标。我的习惯是框到果蔬可见轮廓外扩 2 到 3 个像素,遮挡目标只标可见部分,严重遮挡(可见面积小于 30%)的直接不标,避免引入噪声。
用 LabelImg 标注时,类别名建议用英文小写加下划线,比如apple_red、apple_defect、tomato_green,不要用中文或空格,否则后续转换和训练容易出编码问题。标注文件是 PASCAL VOC 格式的 XML,每张图对应一个 XML,里面记录了 filename、size 和每个 object 的 bndbox。
# 安装 LabelImg(常见做法,Python 环境) pip install labelImg # 启动,指定图片目录和类别文件 labelImg ./images ./classes.txtclasses.txt每行一个类别名,顺序要和后续 data.yaml 里的 names 一致。标注完成后目录结构通常是images/放原图,annotations/放 XML。这里有个容易忽略的点:图片文件名不要有中文和空格,否则 XML 里的 filename 和实际文件对不上,转换脚本会报找不到文件。
2.3 VOC 转 YOLO 格式:转换脚本与四个边界坑
yolov5 训练需要的是 YOLO 格式的 txt 标签,每行class_id x_center y_center width height,全部归一化到 0 到 1。VOC 的 XML 是绝对坐标,必须转换。下面是我常用的转换脚本,处理了果蔬数据集里常见的几个边界情况。
import os import xml.etree.ElementTree as ET # 类别映射,顺序必须和 data.yaml 的 names 一致 classes = ["apple_red", "apple_green", "apple_defect", "tomato_red", "tomato_green"] def convert_bbox(size, box): """VOC 绝对坐标转 YOLO 归一化坐标""" dw = 1.0 / size[0] dh = 1.0 / size[1] 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 def convert(xml_dir, out_dir): if not os.path.exists(out_dir): os.makedirs(out_dir) for f in os.listdir(xml_dir): if not f.endswith(".xml"): continue tree = ET.parse(os.path.join(xml_dir, f)) root = tree.getroot() size = root.find("size") w = int(size.find("width").text) h = int(size.find("height").text) lines = [] for obj in root.iter("object"): cls = obj.find("name").text if cls not in classes: continue # 跳过未定义类别,避免训练时报 index 越界 cls_id = classes.index(cls) bndbox = obj.find("bndbox") box = (float(bndbox.find("xmin").text), float(bndbox.find("xmax").text), float(bndbox.find("ymin").text), float(bndbox.find("ymax").text)) bb = convert_bbox((w, h), box) # 过滤宽高为 0 的异常框 if bb[2] <= 0 or bb[3] <= 0: continue lines.append(f"{cls_id} " + " ".join([f"{x:.6f}" for x in bb])) out_name = os.path.splitext(f)[0] + ".txt" with open(os.path.join(out_dir, out_name), "w") as out: out.write("\n".join(lines)) convert("./annotations", "./labels")逻辑说明:convert_bbox把 VOC 的左上右下坐标转成中心点加宽高的归一化值,这是 YOLO 的标准格式。参数上classes列表的顺序就是类别 id,必须和训练配置里的 names 完全一致,否则模型学到的类别会错位。四个边界坑分别是:类别名不在 classes 里要跳过而不是报错;宽高为 0 的异常框要过滤;图片尺寸以 XML 里的 size 为准而不是实际文件,因为有些标注工具会写错;输出文件名要和图片同名,yolov5 靠文件名配对图片和标签。
转换完成后建议抽查几张,用脚本把归一化坐标还原画框,肉眼确认框位置正确。这一步花十分钟,能省掉训练几小时才发现标签错位的后悔药。
3. yolov5 训练果蔬数据集:环境、配置与超参数怎么定
3.1 环境搭建与目录组织
yolov5 对环境不算挑剔,但版本错配是新手翻车重灾区。我一般用 Python 3.8 到 3.10,PyTorch 1.10 以上,CUDA 版本和显卡驱动匹配即可。不建议一上来就追最新版,果蔬数据集规模通常不大,稳定比新特性重要。
# 克隆 yolov5(用官方仓库,不要用来路不明的 fork) git clone https://github.com/ultralytics/yolov5 cd yolov5 # 安装依赖,建议用虚拟环境 pip install -r requirements.txt # 验证环境 python -c "import torch; print(torch.__version__, torch.cuda.is_available())"目录组织我习惯这样:数据集根目录下分images/train、images/val、labels/train、labels/val,train 和 val 按 8:2 或 7:3 划分。划分时注意同一批次、同一光照条件的图不要跨 train 和 val,否则验证集指标虚高,上线就露馅。果蔬数据集尤其要注意这点,因为同一颗果蔬连拍的多张图如果一半在训练一半在验证,模型等于见过验证集。
3.2 data.yaml 与模型配置的关键字段
data.yaml 是数据集和训练的桥梁,字段不多但每个都关键。
# data.yaml path: ../datasets/fruit_veg # 数据集根目录 train: images/train val: images/val nc: 5 # 类别数,必须和 classes 长度一致 names: ["apple_red", "apple_green", "apple_defect", "tomato_red", "tomato_green"]nc写错是最常见的报错来源,写成 5 但 names 有 6 个,训练直接崩。path用相对路径时要注意是相对于 yolov5 根目录还是 yaml 文件所在目录,不同版本行为有差异,稳妥做法是用绝对路径。
模型配置选yolov5s.yaml起步,果蔬识别通常不需要大模型。如果产线要求实时,s 版本在主流显卡上能到 60 FPS 以上;如果精度不够再换 m 或 l。改模型配置时只需要改nc,其他结构参数不要动,除非你清楚每个参数的含义。
3.3 训练命令与超参数调整
# 单卡训练,果蔬数据集常用配置 python train.py \ --data data/fruit_veg.yaml \ --cfg models/yolov5s.yaml \ --weights yolov5s.pt \ --epochs 150 \ --batch-size 16 \ --img-size 640 \ --hyp data/hyp.fruit.yaml \ --name fruit_veg_v1参数说明:--weights yolov5s.pt用预训练权重,果蔬数据集样本量通常不够从零训,迁移学习能省一半以上 epoch;--epochs 150是经验值,果蔬类别少时 100 到 200 之间,看验证集 mAP 曲线早停;--batch-size 16受显存限制,显存不够就降到 8 并配合--accumulate梯度累积;--img-size 640是默认值,如果果蔬在图中占比很小(比如传送带远景),可以提到 1280,但显存和速度代价明显。
超参数文件hyp.fruit.yaml我一般基于默认 hyp.scratch-low.yaml 改三处:lr0从 0.01 降到 0.005,果蔬数据集小,学习率太大会震荡;mosaic保持 1.0 但mixup降到 0.1,mixup 对果蔬这种需要精确边界的任务有时会引入噪声;hsv_h和hsv_s适当调大,增强对光照和成熟度变化的鲁棒性。这些不是玄学,是果蔬颜色敏感特性决定的。
训练过程中重点看三个指标:mAP@0.5看整体精度,mAP@0.5:0.95看框的紧致程度,val/box_loss看定位是否收敛。如果 mAP 涨但 box_loss 不降,多半是标注框松紧不一致;如果训练 loss 降验证 loss 涨,是过拟合,加数据或加增强。
4. 果蔬识别落地避坑:五条产线踩出来的经验
4.1 现象:验证集 mAP 高,产线漏检严重
原因通常是验证集和产线分布不一致。果蔬数据集如果验证集全是清晰、正面、光照均匀的图,产线上的侧拍、逆光、遮挡样本模型没见过。解决方法是把产线实际采集的难样本按比例混进验证集,或者单独建一个「产线测试集」评估,不要只看验证集指标。
4.2 现象:同类果蔬不同批次识别率波动大
原因是颜色分布漂移。不同季节、不同产地的果蔬颜色差异明显,模型对颜色过拟合。解决方法是训练时加强 HSV 增强,并在推理前做白平衡校正,把输入图像的色温拉到训练集相近的分布。这个坑在跨批次分拣时特别常见。
4.3 现象:小目标果蔬检测不到
原因是下采样丢特征。传送带远景拍摄时单个果蔬可能只有 20 到 30 像素。解决方法是提高输入分辨率到 1280,或者在模型配置里增加 P2 检测层(小目标层),代价是速度下降。如果速度不能降,就调整相机安装距离,让果蔬在画面里占更大比例,这比改模型更划算。
4.4 现象:训练报 CUDA out of memory
原因是 batch size 或 img-size 超过显存。解决方法是先降 batch size 到 8 或 4,配合--accumulate 2保持等效 batch;还不行就降 img-size 到 416。另外检查是否有其他进程占显存,nvidia-smi看一眼就清楚。
4.5 现象:推理结果框重叠、同一果蔬多个框
原因是 NMS 阈值不合适或模型没收敛。果蔬堆叠场景下目标挨得近,NMS IoU 阈值默认 0.45 可能把相邻果蔬的框合并或保留重复框。解决方法是推理时调--conf-thres 0.4 --iou-thres 0.5,并在训练时确保标注没有重复框。如果模型没收敛,先看训练 loss 曲线,别急着调 NMS。
5. 从训练到部署:果蔬识别模型的量化与产线验证技巧
训练完拿到 best.pt 只是开始,真正上产线还要过部署这一关。果蔬识别对延迟敏感,分拣线传送带速度通常 0.5 到 2 米每秒,留给单帧推理的时间可能只有 30 到 50 毫秒。这时候 PyTorch 原生推理往往不够快,需要做模型导出和量化。
我一般先导出 ONNX 验证精度损失,再用 TensorRT 或 OpenVINO 做推理加速。导出命令很简单:
python export.py --weights runs/train/fruit_veg_v1/weights/best.pt --include onnx --img-size 640导出后务必用同一批测试图对比 PyTorch 和 ONNX 的输出,确认框位置和类别一致。常见问题是动态轴设置不对导致 batch 推理出错,或者 opset 版本和推理引擎不匹配。如果目标平台是边缘设备,比如瑞芯微或树莓派这类,量化到 INT8 能再提速 2 到 3 倍,但果蔬识别对颜色敏感,INT8 量化后要重点验证颜色相近类别的区分度,青苹果和青椒这类容易在量化后混淆。
产线验证我习惯分三步:第一步离线跑 500 张产线实拍图,统计每类召回率和误检率;第二步半在线,用视频流跑但不控制分拣,人工核对结果;第三步在线小批量试运行,设置置信度阈值偏高(比如 0.6)先保准确,稳定后再逐步降低阈值提召回。这个过程中记录每个误检案例,反哺数据集迭代,通常两到三轮就能把产线指标做到可用。
一个具体技巧是给模型加一个「拒识」机制:当最高置信度低于阈值且第二类别置信度接近时,输出「不确定」交给人工复核,而不是强行分类。果蔬分拣里错分一个缺陷果的代价远高于人工复核几个可疑果。这个逻辑在推理后处理里加十几行代码就能实现,比一味调模型性价比高得多。
我自己做果蔬项目最大的教训是:别在模型结构上反复折腾,把时间花在数据集清洗和产线难样本回流上,收益大得多。每次上线后我都会留一个「错题本」,把误检漏检的图存下来,下一轮训练直接加进去。希望帮到你。
本文还有配套的精品资源,点击获取