资讯动态

YOLOv5果蔬识别实战:从数据集清洗到模型训练与桌面部署

发布时间:2026/9/15 2:22:34 来源:尧图企业网站定制
简介一个基于YOLOv5的果蔬识别系统完整项目包面向计算机相关专业正在准备课程设计、毕业设计或进行深度学习实战练习的学生。内含可直接运行的源代码、配套果蔬数据集、训练好的模型文件及详细教程说明项目经导师指导并调试通过评审得分98分难度适中适合快速上手完成考核任务。资源共56个文件打包后约94.07MB涵盖Python脚本数据预处理、模型训练、界面交互等、h5模型权重、xml与txt配置标注文件、jpg/png/jpeg图像样本以及readme教学文档目录结构清晰便于按需取用。目前已有123人学习参考价值经过验证。借助该资源可系统了解YOLOv5在果蔬识别场景的完整落地流程包括数据划分、模型训练、实时检测与结果可视化能大幅节省自行摸索的时间。1. 为什么我把 YOLOv5 果蔬识别做成了“训练—评估—部署”三件套很多人以为果蔬识别就是把 YOLOv5 的权重下载下来对着图片跑一次 predict。实际动手才发现真正的体力活是数据集清洗和类别分布。拿这份项目来说zip 里除了训练好的 mobilenet_fv.h5、cnn_fv.h5还带了一整套 data_split.py、jpeg2jpg.py、remove_wrong_image.py 这类处理脚本以及 window_realtime.py、window_up_camera.py 两个可直接运行的 PyQt 界面。这意味着学习重点不是“调包”而是把一份带噪声的果蔬图片整理成训练集再把模型塞进带界面的系统里。评审分 98 分从代码组织来看作者确实把“能运行”和“好演示”这两件事都考虑进去了。适合准备毕业设计、期末大作业以及想用 YOLOv5 做垂直场景识别但还没跑通全流程的开发者。2. 数据集工程目录划分、格式转换与坏图过滤项目压缩包解开后第一眼不是模型文件而是一堆 jpg、jpeg、png 混放的 images 目录以及 data_split.py、get_data.py、jpeg2jpg.py、remove_wrong_image.py、remove_func.py 几个看起来不起眼的脚本。经验是这些脚本才是决定最终训练集质量的关键。果蔬图片很多是从网上抓的或手机拍的命名混乱、存在半张图、带 EXIF 旋转角度直接丢给 YOLOv5 会浪费大量时间在无效样本上。2.1 用 data_split.py 把数据集切成 train/val/testdata_split.py 最基础的任务是维护类别均衡的三段划分。常见做法是读取 images 下每一个类别子目录按 8∶1∶1 划分到 train、val、test并保持原来的子目录结构。下面是一个简化版的可运行逻辑# data_split.py 的常见做法带固定随机种子 import os import random from shutil import copy2 random.seed(42) def split_dataset(src_dir, dst_dir, ratios(0.8, 0.1, 0.1)): for label in os.listdir(src_dir): label_dir os.path.join(src_dir, label) if not os.path.isdir(label_dir): continue files [f for f in os.listdir(label_dir) if f.lower().endswith((.jpg, .jpeg, .png, .bmp))] random.shuffle(files) train_n int(len(files) * ratios[0]) val_n int(len(files) * ratios[1]) for i, f in enumerate(files): part train if i train_n else ( val if i train_n val_n else test) dst os.path.join(dst_dir, part, label) os.makedirs(dst, exist_okTrue) copy2(os.path.join(label_dir, f), os.path.join(dst, f)) if __name__ __main__: split_dataset(images, yolo_dataset)参数上src_dir 是原始图片根目录dst_dir 是 YOLOv5 需要的训练数据根目录。ratios 默认三个比例类别样本特别少时可以改成 ratios(0.6, 0.2, 0.2)但 val 和 test 至少每类保留 5 张否则评估会波动很大。注意固定 random.seed方便复现与后续对比不然每次跑出来的结果都对不上。脚本主要职责输出data_split.py按比例划分训练/验证/测试集train、val、test 目录树get_data.py生成数据列表或解析标注索引train.txt / valid.txtjpeg2jpg.py统一格式并修正 EXIF转换后的 jpg 文件remove_wrong_image.py删除损坏图片清理后的目录remove_func.py封装修复/删除函数供上面脚本调用2.2 jpeg2jpg.py别让扩展名和 EXIF 方向坑了训练YOLOv5 对图片格式本身不挑但对内存里的解码数据要求一致。手机拍摄的 JPEG 常常带 EXIF 旋转信息直接读取会得到旋转前的像素而标注框是在旋转后的视觉上画的这会让 GT 框和图像内容错位。jpeg2jpg.py 的职责就是把这个隐患一次性清理掉。# jpeg2jpg.py 的常见做法EXIF 转正后强制写为 jpg from PIL import Image, ImageOps from pathlib import Path def convert(input_path: str, output_path: str, quality: int 95): im Image.open(input_path).convert(RGB) im ImageOps.exif_transpose(im) # 根据 EXIF 自动旋转 im.save(output_path, JPEG, qualityquality)im.convert(RGB) 是为了丢掉透明通道或灰度模式深度学习训练时统一三通道输入。exif_transpose 会读取 EXIF 里的 Orientation 字段自动把手机竖拍图转正这一步不做后期画标注框和热力图时方向经常是反的。quality 参数建议 9095太高会增大图片体积太低会丢失纹理果蔬识别对边缘细节比较敏感。2.3 坏图过滤remove_wrong_image.py 与 remove_func.py爬取来的图片最常见的问题不是格式错误而是“半张图”“截断文件”“全黑图”和“纯色背景图”。文件大小不能作为唯一标准一张 200KB 的图可能只有左上角有内容。remove_wrong_image.py 里更可靠的做法是让 Pillow 真正解码一次。# remove_wrong_image.py —— 解码失败就删除 from PIL import Image from pathlib import Path def is_valid_image(path: Path) - bool: try: with Image.open(path) as img: img.load() # 触发完整解码 except (IOError, OSError, SyntaxError, Image.DecompressionBombError): return False return Trueimg.load() 这一步非常关键只做 Image.open 只读取头部文件截断不会报错。另外发现 remove_func.py 里还做了类似“删除高度小于阈值的窄长图”的动作。果蔬图片如果被裁剪成细条YOLOv5 的 anchor 很难匹配训练时会变成永远不参与梯度更新的样本。清理后建议输出每个类别的样本数低于 50 张的类要么补数据要么在配置里降低该类权重。到这里数据集就可靠了。下一步才进入模型选择。3. 模型选择与训练YOLOv5 检测 MobileNet/CNN 分类3.1 为什么一个果蔬识别系统需要两套模型项目里同时出现 YOLOv5 的痕迹和 mobilenet_fv.h5、cnn_fv.h5很多人会困惑到底用哪个按我的理解作者把识别拆成了两条路线。YOLOv5 负责目标检测在一整张桌面上把苹果、香蕉、番茄都框出来解决“哪里有什么”的问题。MobileNet 和 CNN 则作为分类网络解决“这个框里到底是什么”的问题尤其在两个类别外观接近番茄和圣女果、土豆和生姜时单独跑 YOLOv5 的分类头容易在低分辨率小目标上犯错用 224×224 的裁切图再分类一次能显著提高稳定度。两个模型配合的常见流程是YOLOv5 以 0.25 的置信度输出候选框NMS 后把每个框裁剪成正方形再送入 mobilenet_fv.h5 得到细分类结果最后把分类得分乘以检测置信度作为最终分数。这样哪怕是 jetson nano 这类边缘设备也可以把 YOLOv5 换成更轻的版本分类头保持不变。基于 YOLOv5 的水果识别项目很多团队最终都会走到这个“检测细分类”的组合上。3.2 YOLOv5 训练自己的数据集fruit.yaml 与训练命令要复现这个项目首先得把第 2 章切好的 train、val 路径写进 YOLOv5 的数据配置。一份典型的 fruit.yaml 长这样# fruit.yaml —— 放在 yolo_v5/data/ 下 train: ./yolo_dataset/train val: ./yolo_dataset/val nc: 8 names: [apple, banana, orange, tomato, potato, carrot, cucumber, grape]nc 是类别数names 必须和标注 id 一一对应。这里写的是常见果蔬清单实际以项目 readme.md 里记录的类别顺序为准顺序错一位模型就会张冠李戴。启动训练推荐用 yolov5s 作为预训练权重性价比适合学位论文和期末大作业。python train.py --data fruit.yaml \ --weights yolov5s.pt \ --img 640 \ --batch 16 \ --epochs 100 \ --device 0 \ --project runs/train--batch 根据显存调整6GB 显存跑 16 比较安全8GB 可以上 32数据集少于 500 张图时epochs 不建议超过 100否则 val 的 mAP 会先升后降。如果在 CPU 上跑把 --device 换成 cpu同时加 --cache ram 把图片缓存到内存能少一半 IO 等待。YOLOv5 的默认超参数一般不用大改真正影响果蔬这种小样本场景的是 mosaic1.0 和 mixup0.1前者在数据少时可以自动合成新样本后者在样本充足时会稀释纹理特征可以在训练时临时关掉。模型优点适用场景YOLOv5s检测速度快、通用性强整图定位、候选框生成MobileNetV2参数量小、移动端友好边缘设备上的细分类自定义 CNN结构简单、容易可视化教学演示、小类别数任务3.3 用 train_mobilenet.py 训练细分类模型train_mobilenet.py 看起来更像是主训练脚本因为它输出 mobilenet_fv.h5 这种 Keras 权重可以直接被 UI 代码加载。train_cnn.py 对应 cnn_fv.h5两者结构不同但训练套路一致。下面是我据此整理的常见调用方式python train_mobilenet.py --data ./yolo_dataset/train \ --val ./yolo_dataset/val \ --img_size 224 \ --epochs 60 \ --lr 0.0005逻辑上该脚本会以 MobileNet 系列结构为基础加载 ImageNet 预训练权重并把最后的全连接层换成新的类别数。训练侧重点是数据增强随机水平翻转、亮度扰动、轻微噪声因为果蔬拍摄环境的光线变化很大。如果路径是中文目录TensorFlow 在 Windows 下偶尔会拒绝读取最好把数据集统一放在英文路径下。训练结束后会产出一个 h5注意保存时是否带 optimizer 状态推理时只需要权重带 optimizer 会让文件体积变大几 MB。3.4 环境一致性与超参数检查点压缩包里有个细节trash_tf2.3_cpu.iml 和 requirements.txt。前者说明作者在 IntelliJ 系 IDE 里用 TensorFlow 2.3 CPU 调试过后者的关键依赖需要按版本对齐尤其是 tensorflow、PyQt5、opencv-python、numpy。TF2.3 搭配 numpy 1.18/1.19 是常见组合numpy 1.19 之后的二进制兼容性问题会导致 h5 模型加载时报错。建议用 conda 建独立虚拟环境conda create -n fruit_yolov5 python3.7 conda activate fruit_yolov5 pip install -r requirements.txt因为项目里没有给出完整 requirements 细节上述组合是复现时的最小可行方案。Python 3.7 是 TF2.3 支持得最好的版本PyQt5 跑窗口也不容易出现 QPixmap 字体渲染异常。如果自己从头装环境记住四个组件tensorflow-gpu 或 tensorflow-cpu、PyQt5、opencv-python、Pillow。4. 实时识别与桌面 UIwindow_realtime.py 和 window_up_camera.py4.1 三条入口分别对应什么使用场景window_realtime.py、window_up_camera.py、window.py 三个文件看起来都是界面但使用场景完全不同。window.py 更像主窗体和功能模块的容器window_realtime.py 是摄像头实时检测window_up_camera.py 是上传一张图片再识别。这样的拆分很实用答辩演示时用“上传图片”可以精确复现某个案例平时测试用摄像头能直观展示稳定度。入口文件使用场景数据来源window_realtime.py实时摄像头检测cv2.VideoCapture(0)window_up_camera.py单张图片识别QFileDialog 选择文件window.py主界面框架承载上面两个页面4.2 推理线程与 UI 主线程的解耦window_realtime.py 并没有把所有逻辑堆在 Qt 的回调里而是把读帧、推理、画框、刷新 QLabel 拆开。一个可运行的骨架长这样# window_realtime.py 的核心循环简化 class DetectThread(QtCore.QThread): frameReady QtCore.pyqtSignal(object) def __init__(self, camera_id0): super().__init__() self.cap cv2.VideoCapture(camera_id) self.running True def run(self): while self.running: ret, frame self.cap.read() if not ret: continue boxes, labels, scores self.infer(frame) self.frameReady.emit((frame, boxes, labels, scores)) self.msleep(30) def stop(self): self.running False self.cap.release()这里 self.infer(frame) 就是模型推理入口可以换成 YOLOv5 的 detect API也可以换成加载 mobilenet_fv.h5 后的 predict 调用。QThread 的 frameReady 信号会把结果带回主线程再由主线程更新界面避免 Qt 控件跨线程直接操作导致的崩溃。QTimer 每 30ms 触发读取一帧实际帧率取决于模型推理时间若在 CPU 上跑 YOLOv5s可能需要把间隔调大到 60ms 或者把输入尺寸降到 320。4.3 中文路径与图片解码问题window_up_camera.py 走的是文件路径用户点击“选择图片”QFileDialog 返回路径再调用绘图函数。中文路径在 Windows 下很容易让 OpenCV 的 imread 返回 None一般会用字节流解码绕过去# window_up_camera.py 中处理中文路径的常见写法 import numpy as np import cv2 data np.fromfile(file_path, dtypenp.uint8) img cv2.imdecode(data, cv2.IMREAD_COLOR)cv2.imdecode 可以直接从内存解码图片避开文件系统编码问题。解码后如果需要喂给 TF 模型别忘记用 cv2.cvtColor(img, cv2.COLOR_BGR2RGB) 转通道顺序。UI 缩略图可以用 QPixmap.fromImage 显示并在 show 之前把 QImage 的格式转成 Format_RGB888否则颜色会偏蓝红互换。界面布局上视频区占左侧 stretch3右侧放识别结果列表和操作按钮调高 stretch 差能让演示时主画面足够大。5. 模型评估热力图、训练曲线与误检定位5.1 从 results_cnn.png 和训练日志看收敛状态项目的 results 目录里有 cnn 训练过程.txt、mobilenet 训练过程.txt、results_cnn.png、results_mobilenet.png以及 heatmap_cnn.png、heatmap_mobilenet.png。这些文件是评估闭环的关键。在 Windows 下可以直接用 findstr 提取日志中的关键字findstr 准确率 cnn训练过程.txt如果 train accuracy 快速到达 0.9 但 val 抖动说明过拟合如果 loss 一直降而 accuracy 平多半是类别不均衡需要去检查较小类别在每张图里占的像素比例。建议把每个 epoch 的 loss、val_loss、accuracy 记成表格重点关注 loss 是否在某个 epoch 后出现反弹。日志指标观察方法常见问题train loss是否持续下降不降说明学习率过大或数据未归一化val loss是否跟随下降反弹说明过拟合需要加大增强train accuracy是否快速饱和饱和但 val 低说明泛化差val accuracy是否稳定抖动大说明验证集太小或分布不均5.2 用类激活热力图定位模型“看什么”heatmap_mobilenet.png 这类文件是类激活热力图的可视化结果。它的作用是回答一个问题模型到底是靠水果本身的纹理在判断还是靠盘子边缘、桌面背景这些无关信息。简化版的 Grad-CAM 可以这样写# 用中间层卷积特征近似 Grad-CAM import tensorflow as tf from tensorflow.keras import Model def grad_cam(model, img_tensor, class_idx): grad_model Model( inputsmodel.input, outputs[model.get_layer(last_conv).output, model.output] ) with tf.GradientTape() as tape: conv_out, pred grad_model(img_tensor) loss pred[:, class_idx] grads tape.gradient(loss, conv_out)[0] weights tf.reduce_mean(grads, axis(0, 1)) cam tf.reduce_sum(tf.multiply(weights, conv_out[0]), axis-1) return tf.maximum(cam, 0)这里的 last_conv 层名要根据实际模型调整可以先打印 model.summary() 看网络结构。热力值高的区域如果集中在水果表面说明模型是靠谱的如果集中在盘子反光或手指上说明训练集里混入了太多带类似背景的图片此时要增加颜色抖动和背景替换增强。5.3 根据测试记录调整阈值项目的 readme.txt 里提到“测试记录.txt”这种文件通常记录了每个类别的 precision、recall 和具体样本的置信度。调参时可以把默认置信度从 0.25 提到 0.35 再验证果蔬场景里错检比漏检更影响演示效果尤其是一个番茄被框成苹果答辩时会被当场指出来。如果某类样本全部集中在低置信度区间可以单独对该类降低阈值而不是全局降。实现时可以在 UI 层加一个阈值滑块把结果实时反馈出来这是高分项目里很实用的亮点。6. 排错与调参高分项目复现的最后几个细节6.1 三个高频报错与对应解法第一个是AttributeError: NoneType object has no attribute shape这通常出现在摄像头占用的场景排查时先单独跑cv2.VideoCapture(0).read()返回 False 就说明摄像头被任务管理器或其他程序占用。第二个是tensorflow.python.framework.errors_impl.UnknownError常见于中文路径解决办法是把整个项目复制到C:\project\fruit这类纯英文目录下再跑。第三个是窗口黑屏但程序不退出问题多半在frameReady.emit传出的图像格式不对统一转成RGB888再交给 Qt。6.2 把模型导出成 ONNX 后部署如果需要在无 TensorFlow 环境或边缘设备上跑可以把训练好的 YOLOv5 best.pt 导出成 ONNX再把分类 h5 转成 tflite 或 ONNX推理延迟能下降不少。YOLOv5 自带的导出命令是python export.py --weights runs/train/exp/weights/best.pt \ --include onnx \ --img 640导出后记得用 onnxruntime 验证一遍输出确认输出层的 shape 没有丢失。最后把 best.pt、mobilenet_fv.h5、cnn_fv.h5 复制到项目根目录运行 window_realtime.py 验证整个识别链路。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价