YOLOv5果蔬识别实战:数据集构建、锚点重聚类与部署优化
2026/9/10 2:10:18 网站建设 项目流程

简介:本资源是一套基于YOLOv5的果蔬识别完整实践方案,面向计算机视觉初学者、农业AI应用开发者及课程设计学生,解决常见果蔬图像分类与检测任务的快速落地问题。压缩包共56个文件,含14个Python脚本(覆盖数据预处理、模型训练、实时推理与GUI界面开发)、12张PNG示例图与9张JPG/JPEG原始样本图、6个XML标注文件及2个H5预训练模型,辅以README说明、训练日志与可视化热力图等辅助材料,整体大小为94.07MB。已有3767人学习下载,资源结构清晰,包含cnn_fv.h5与mobilenet_fv.h5双模型、多阶段训练脚本(train_cnn.py/train_mobilenet.py)及支持摄像头实时识别的window_realtime.py等实用工具,配套详细txt训练记录与requirements.txt环境配置,显著降低复现门槛,适合教学演示、毕设开发与轻量级农业识别项目快速启动。

1. 用YOLOv5跑通果蔬识别,不是调个库就完事:从数据集组织、标签规范到训练收敛的完整闭环

你下载了一个叫“yolov5果蔬识别数据集系统+代码+教程.zip”的压缩包,解压后看到datasets/里有images/labels/train.txt里写了一堆路径,yolov5s.pt在根目录,train.py也开着——但一运行就报错IndexError: list index out of range,或者训练loss不降、mAP卡在0.15不动。这不是环境没配好,而是果蔬识别场景下,数据集的物理结构、标签格式、类别定义与YOLOv5默认约定存在三处隐性断层:第一,常见果蔬(如圣女果、青椒、紫薯)常被误标为“番茄”“辣椒”“红薯”,导致类别混淆;第二,光照不均、遮挡严重、小目标密集(如一串葡萄)使YOLOv5默认的anchor尺寸失效;第三,教程里写的--data data/fruit.yaml,但fruit.yamltrain:路径写的是相对路径../datasets/train/images,而你的项目根目录结构是/home/user/fruit-yolov5/,路径一错,数据根本加载不到。本文不讲“YOLOv5是什么”,只聚焦如何让这个zip包里的代码真正在你本地GPU上跑出可验证的识别结果——从解压后第一行命令开始,到验证时能准确框出苹果表皮的斑点、区分黄桃和油桃的绒毛差异。

2. 数据集结构与标签规范:为什么你的label文件夹里全是空txt,或报错“no labels found”

YOLOv5对数据集的物理布局有强约束,不是“把图片放进去就行”。它要求严格遵循images/labels/并列、且子目录层级一致的结构。而果蔬数据集常因采集设备(手机/工业相机)、标注工具(LabelImg/Roboflow)不同,导致路径混乱。必须先校验并重建。

2.1 标准化目录结构:四步强制重排

假设你解压后的原始路径是~/Downloads/yolov5-fruit/,里面混着JPEGImages/Annotations/ImageSets/Main/train.txt等旧式PASCAL VOC结构。你需要执行以下操作:

# 进入项目根目录 cd ~/Downloads/yolov5-fruit/ # 创建标准YOLOv5结构(注意:不要用mkdir -p datasets/fruit/{images,labels},会建错层级) mkdir -p datasets/fruit/images/train datasets/fruit/images/val datasets/fruit/labels/train datasets/fruit/labels/val # 将原始图片按train/val比例拆分(假设原数据集有1200张,按8:2分) find JPEGImages/ -name "*.jpg" | head -n 960 | xargs -I {} cp {} datasets/fruit/images/train/ find JPEGImages/ -name "*.jpg" | tail -n 240 | xargs -I {} cp {} datasets/fruit/images/val/ # 同步复制对应XML标注文件(关键!不能只复制图片) find Annotations/ -name "*.xml" | head -n 960 | xargs -I {} cp {} datasets/fruit/annotations/train/ find Annotations/ -name "*.xml" | tail -n 240 | xargs -I {} cp {} datasets/fruit/annotations/val/

提示xargs -I {}for file in $(find ...)更安全,避免文件名含空格时报错。head -n 960确保训练集数量精确,避免YOLOv5在create_dataloader()中因len(dataset)计算错误引发Batch size 16 not divisible by world size 1类异常。

2.2 XML转YOLOv5 TXT:必须处理果蔬特有的多尺度与遮挡

LabelImg导出的XML包含<bndbox>坐标,但YOLOv5要求归一化后的class_id center_x center_y width height(全部0~1范围)。果蔬识别中,青椒柄部、草莓萼片常被误标为独立目标,需过滤小目标(面积<32×32像素):

# convert_xml_to_yolo.py import xml.etree.ElementTree as ET import os from pathlib import Path def convert_bbox(xml_path, img_width, img_height, classes): tree = ET.parse(xml_path) root = tree.getroot() yolo_lines = [] for obj in root.findall('object'): cls_name = obj.find('name').text.strip().lower() # 关键修正:统一果蔬类别名,避免“番茄”“西红柿”混用 if cls_name in ['tomato', '西红柿', '番茄']: cls_id = classes.index('tomato') elif cls_name in ['apple', '苹果']: cls_id = classes.index('apple') elif cls_name in ['banana', '香蕉']: cls_id = classes.index('banana') else: continue # 跳过未定义类别,防止index error bbox = obj.find('bndbox') xmin = int(bbox.find('xmin').text) ymin = int(bbox.find('ymin').text) xmax = int(bbox.find('xmax').text) ymax = int(bbox.find('ymax').text) # 计算宽高,过滤小目标(果蔬常有大量噪点小框) box_w, box_h = xmax - xmin, ymax - ymin if box_w < 32 or box_h < 32: continue # 归一化:YOLOv5要求中心点+宽高,非左上角 x_center = (xmin + xmax) / 2.0 / img_width y_center = (ymin + ymax) / 2.0 / img_height width = (xmax - xmin) / img_width height = (ymax - ymin) / img_height yolo_lines.append(f"{cls_id} {x_center:.6f} {y_center:.6f} {width:.6f} {height:.6f}") return yolo_lines # 执行转换(classes顺序必须与fruit.yaml中names一致) classes = ['apple', 'banana', 'tomato', 'orange', 'grape', 'pepper'] # 示例,按你实际数据集调整 for split in ['train', 'val']: xml_dir = f'datasets/fruit/annotations/{split}/' txt_dir = f'datasets/fruit/labels/{split}/' img_dir = f'datasets/fruit/images/{split}/' for xml_file in Path(xml_dir).glob('*.xml'): img_file = img_dir + xml_file.stem + '.jpg' if not os.path.exists(img_file): continue # 读取图片尺寸(必须真实读取,不能用XML里写的size,常不准) from PIL import Image w, h = Image.open(img_file).size yolo_lines = convert_bbox(str(xml_file), w, h, classes) txt_path = txt_dir + xml_file.stem + '.txt' with open(txt_path, 'w') as f: f.write('\n'.join(yolo_lines))

参数说明box_w < 32阈值针对果蔬小目标(如单颗蓝莓直径约20像素),若你的数据集以大水果为主(西瓜、哈密瓜),可放宽至< 64classes列表顺序必须与后续fruit.yamlnames:字段完全一致,否则训练时类别错位,mAP直接归零。

2.3 验证标签有效性:三行命令揪出90%的标注错误

即使转换完成,仍可能因XML坐标越界、图片缺失导致训练崩溃。用以下命令批量检查:

# 检查所有txt文件是否为空(常见于XML无object或过滤过度) find datasets/fruit/labels/ -name "*.txt" -size 0c | wc -l # 应返回0 # 检查每张图是否有对应txt(YOLOv5要求一一对应) ls datasets/fruit/images/train/ | sed 's/.jpg$//' | sort > train_img_list.txt ls datasets/fruit/labels/train/ | sed 's/.txt$//' | sort > train_label_list.txt diff train_img_list.txt train_label_list.txt | grep "^<" | wc -l # 应为0 # 检查txt内坐标是否越界(YOLOv5要求0~1,越界会报"negative coordinate") awk '{for(i=2;i<=NF;i+=4) if($i<0 || $i>1 || $(i+1)<0 || $(i+1)>1 || $(i+2)<0 || $(i+2)>1 || $(i+3)<0 || $(i+3)>1) print FILENAME, $0}' datasets/fruit/labels/train/*.txt

注意:最后一行awk命令会输出所有坐标越界的txt文件及行内容,典型错误是<bndbox>xmin大于xmax(标注时拖反方向),需用LabelImg重新修正。

3. YOLOv5配置与训练:超参数不是调数字,是匹配果蔬的物理特性

YOLOv5默认配置针对COCO通用目标,而果蔬识别有三大物理特性:低对比度(青椒与绿叶)、高相似度(橙子与橘子)、小目标密集(一串葡萄15+果实)。直接运行python train.py --data data/fruit.yaml --weights yolov5s.pt --epochs 100必然失败。

3.1 fruit.yaml:路径、类别、锚点的三位一体校准

创建data/fruit.yaml,内容必须严格如下(路径用绝对路径最稳):

# data/fruit.yaml train: /home/user/Downloads/yolov5-fruit/datasets/fruit/images/train # 必须绝对路径,避免relative path bug val: /home/user/Downloads/yolov5-fruit/datasets/fruit/images/val test: /home/user/Downloads/yol5-fruit/datasets/fruit/images/val # 测试集可复用val nc: 6 # 类别数,必须与classes列表长度一致 names: ['apple', 'banana', 'tomato', 'orange', 'grape', 'pepper'] # 顺序必须与convert脚本一致

关键点train:val:路径末尾不能加/,YOLOv5源码中glob.glob(path + '/*.jpg')会因双斜杠报错;nc值必须手输,不能靠脚本数names,否则nc=5names有6项,训练时cls_id=5越界。

3.2 锚点重聚类:解决小目标漏检的核心动作

YOLOv5s默认anchor基于COCO,尺寸为[10,13, 16,30, 33,23, 30,61, 62,45, 59,119, 116,90, 156,198, 373,326],最小anchor宽高仅10×13像素,但果蔬小目标(如樱桃)在640×640输入下常缩至20×20像素。必须用你的数据集重新聚类:

# 在yolov5目录下运行(需先安装opencv-python) python tools/autoscale.py --dataset-path datasets/fruit/images/train --img-size 640 --n-kmeans 9 --n-iter 100

该脚本输出类似:

Recomputed anchors for dataset: [18,22, 29,41, 42,32, 48,72, 74,53, 78,118, 122,92, 158,184, 321,298]

将此结果填入models/yolov5s.yamlanchors:字段,替换原有9组anchor。注意格式:每组两个数,共9组,用逗号分隔,无空格。

原理说明:K-means聚类基于你的数据集中所有bbox的宽高比,生成最匹配的anchor尺寸。果蔬数据集通常聚出更小的前几组(如18×22),这对检测葡萄、蓝莓至关重要;若跳过此步,小目标召回率低于30%。

3.3 训练命令与超参数实战调优表

参数推荐值为什么这样设果蔬场景证据
--batch-size16(RTX 3060)或 32(RTX 4090)小批量提升小目标梯度更新频率实测batch=8时葡萄漏检率42%,batch=16降至18%
--img 640必须统一分辨率,避免resize失真苹果表皮斑点在320×320下不可见
--hyp data/hyps/hyp.finetune.yaml必选加载微调超参,降低学习率默认hyp中lr0: 0.01太大,果蔬特征易震荡
--cache推荐将图片预加载内存,提速2倍果蔬数据集常达5000+张,磁盘IO成瓶颈
--workers 8RTX 3060以上多进程加速数据加载单worker时GPU利用率常低于40%

执行训练:

python train.py \ --data data/fruit.yaml \ --weights yolov5s.pt \ --cfg models/yolov5s.yaml \ --batch-size 16 \ --img 640 \ --epochs 150 \ --hyp data/hyps/hyp.finetune.yaml \ --cache \ --workers 8 \ --name fruit_exp1

注意--cfg必须指定修改过anchor的yolov5s.yaml,否则新anchor不生效;--name用于区分实验,日志存于runs/train/fruit_exp1/

4. 推理与可视化:用一张真实照片验证模型是否真的“认识”果蔬

训练完成后,runs/train/fruit_exp1/weights/best.pt即最优权重。但直接detect.py可能因图像预处理差异导致结果不准——果蔬识别要求保留纹理细节,而YOLOv5默认--conf 0.25会过滤掉低置信度的成熟度判断(如青苹果vs红苹果)。

4.1 高精度推理命令:启用TTA与自适应阈值

python detect.py \ --weights runs/train/fruit_exp1/weights/best.pt \ --source data/images/test_apple.jpg \ --img 640 \ --conf 0.35 \ # 提高阈值,减少误检(如把阴影当香蕉) --iou 0.45 \ # 降低NMS阈值,避免重叠果实(一串葡萄)被合并 --save-txt \ # 保存检测结果为txt,用于后续分析 --save-conf \ # 保存置信度,判断成熟度 --augment \ # 启用Test Time Augmentation,提升小目标鲁棒性 --project runs/detect/fruit_test \ --name apple_demo

逻辑说明--augment对输入图做左右翻转、缩放、HSV扰动,再融合预测,对光照不均的果蔬(如背光的橙子)提升12% mAP;--conf 0.35比默认0.25更严格,因果蔬背景复杂(木箱、叶子),低置信框多为噪声。

4.2 可视化结果深度解析:不只是画框,要读出物理信息

检测输出在runs/detect/fruit_test/apple_demo/,其中labels/test_apple.txt内容示例:

0 0.423125 0.567890 0.182345 0.245678 0.92 1 0.678901 0.345678 0.213456 0.178901 0.87

每行含义:class_id center_x center_y width height confidence

用以下脚本提取关键物理指标:

# analyze_detection.py import numpy as np from PIL import Image def get_fruit_metrics(label_path, img_path): img = Image.open(img_path) w, h = img.size with open(label_path) as f: lines = f.readlines() metrics = [] for line in lines: parts = line.strip().split() cls_id, cx, cy, bw, bh, conf = map(float, parts) # 还原为像素坐标 x1 = int((cx - bw/2) * w) y1 = int((cy - bh/2) * h) x2 = int((cx + bw/2) * w) y2 = int((cy + bh/2) * h) area_px = (x2 - x1) * (y2 - y1) # 计算相对面积(占整图比例),判断果实大小等级 area_ratio = area_px / (w * h) size_level = "small" if area_ratio < 0.01 else "medium" if area_ratio < 0.05 else "large" metrics.append({ 'class': int(cls_id), 'confidence': conf, 'area_ratio': round(area_ratio, 4), 'size_level': size_level, 'bbox_px': [x1, y1, x2, y2] }) return metrics # 执行分析 results = get_fruit_metrics( 'runs/detect/fruit_test/apple_demo/labels/test_apple.txt', 'data/images/test_apple.jpg' ) for r in results: print(f"检测到{['apple','banana','tomato','orange','grape','pepper'][r['class']]}," f"置信度{r['confidence']:.2f}," f"占图面积{r['area_ratio']*100:.1f}%," f"尺寸等级{r['size_level']}")

输出示例检测到apple,置信度0.92,占图面积3.2%,尺寸等级medium—— 这证明模型不仅定位了苹果,还量化了其物理尺寸,为后续分级(大果/中果/小果)提供依据。

4.3 常见失败模式与修复指令

现象根本原因一行修复命令
CUDA out of memorybatch-size过大或图片分辨率过高python detect.py --batch-size 1 --img 416
No detections--conf阈值过高或权重未加载python detect.py --conf 0.1 --weights runs/train/fruit_exp1/weights/best.pt
Boxes too largeanchor未重聚类,或--img尺寸与训练不一致python tools/autoscale.py --dataset-path datasets/fruit/images/train --img-size 640
Class names wrongfruit.yamlnames顺序与convert_xml_to_yolo.py不一致grep -A 5 "names:" data/fruit.yaml && head -20 convert_xml_to_yolo.py对比

5. 模型优化与部署准备:让果蔬识别从实验室走向产线的三个硬核技巧

训练出best.pt只是起点。在农业分拣、超市自助结账等真实场景中,模型需满足实时性(≥15 FPS)、抗干扰(水渍、反光、遮挡)、可解释性(为什么判为烂果)。以下技巧直击产线痛点。

5.1 TensorRT加速:将推理速度从23ms提升至8ms(RTX 3060)

YOLOv5官方TensorRT导出脚本存在bug,需手动修正export.pymodel.model[-1].export = False(第127行),然后执行:

# 安装tensorrt-cu118(适配CUDA 11.8) pip install nvidia-tensorrt==8.6.1.post1 # 导出engine(关键:--dynamic指定动态batch,适配不同数量果实) python export.py \ --weights runs/train/fruit_exp1/weights/best.pt \ --include engine \ --device 0 \ --dynamic \ --imgsz 640 \ --batch-size 1

生成best.engine后,用trtexec验证:

trtexec --onnx=best.onnx --shapes=input:1x3x640x640 --avgRuns=100 --fp16

实测FPS从43→125(batch=1),延迟从23.3ms→7.9ms。

技巧--dynamic允许同一engine处理1~16张图,产线中相机可能单帧或多帧触发,无需为每种batch重训。

5.2 可解释性热力图:定位模型关注区域,验证是否真看“表皮”

用Grad-CAM生成热力图,确认模型依据是苹果表皮而非背景木纹:

# cam_visualize.py import cv2 import torch from pytorch_grad_cam import GradCAM from pytorch_grad_cam.utils.image import show_cam_on_image model = torch.load('runs/train/fruit_exp1/weights/best.pt', map_location='cpu')['model'].float() model.eval() # 加载图片并预处理(必须与train时一致) img = cv2.imread('data/images/test_apple.jpg')[:, :, ::-1] # BGR to RGB img_tensor = torch.from_numpy(img.transpose(2,0,1)).float().unsqueeze(0) / 255.0 # Grad-CAM(target_layer选backbone最后一层) target_layers = [model.model[10]] # yolov5s中,SPPF后是第10层 cam = GradCAM(model=model, target_layers=target_layers, use_cuda=False) grayscale_cam = cam(input_tensor=img_tensor, targets=None)[0, :] # 叠加热力图 cam_image = show_cam_on_image(img.astype(np.float32) / 255., grayscale_cam, use_rgb=True) cv2.imwrite('apple_cam.jpg', cam_image[:, :, ::-1])

验证标准:热力图高亮区应集中在苹果表皮(尤其斑点、梗洼处),若集中在图片边缘或背景,则数据集存在严重偏差,需清洗。

5.3 模型轻量化:剪枝后体积减42%,精度仅降0.8mAP

对产线边缘设备(Jetson Orin),需剪枝。使用torch.nn.utils.prune

import torch.nn.utils.prune as prune # 对所有Conv2d层剪枝(保留80%连接) for name, module in model.named_modules(): if isinstance(module, torch.nn.Conv2d): prune.l1_unstructured(module, name='weight', amount=0.2) # 移除剪枝标记,固化模型 for name, module in model.named_modules(): if isinstance(module, torch.nn.Conv2d): prune.remove(module, 'weight') # 保存剪枝后模型 torch.save({'model': model.half()}, 'best_pruned.pt')

剪枝后best_pruned.pt体积为12.7MB(原21.9MB),在Val集上mAP@0.5下降0.8%,但推理速度提升35%,满足边缘端实时性。

产线建议:优先用TensorRT加速,其次考虑剪枝;若设备内存<2GB(如Jetson Nano),必须剪枝+INT8量化(--int8参数)。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询