ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

YOLOv8果蔬识别实战:从数据集构建到训练调优全指南

YOLOv8果蔬识别实战:从数据集构建到训练调优全指南 简介这是一份以YOLOv8为核心的水果蔬菜识别项目资料涵盖数据集、可运行代码、UI界面与文档说明适合正在完成大作业、毕业设计或想要练习深度学习的计算机相关专业学生。压缩包内含120个文件包含47张jpg和33张png构成的果蔬训练与验证图像集12个py源文件与10个yaml配置负责模型训练、验证及参数设定3个pt权重可直接加载另有csv结果记录、mp4演示和txt标注说明资源总大小约27.18MB体量轻但结构完整。项目经过导师指导并获98分源码已本地编译调试可稳定运行文档详细介绍了系统原理、安装步骤与使用方法ui文件提供可视化操作界面便于直观理解识别流程。资料包中的结果记录与预测标注图能辅助查看训练效果帮助学习者快速掌握YOLOv8在果蔬识别中的落地流程也适合作为课程设计与答辩展示的参考资料。目前已有61人浏览学习是实战入门性价比较高的优质作业资源。1. 果蔬识别大作业为什么难的不是模型而是数据集与工程交付“YOLOv8果蔬识别数据集系统”这个名字最近在课程设计和大作业里出现得越来越多。通俗点讲它是把一组带标注的果蔬图片、一套用YOLOv8训练完成的检测代码以及一份讲清楚“怎么复现、怎么扩展”的文档说明打包在一起交给评审或老师验收。这件事适合深度学习课程大作业、本科毕设也适合刚接触目标检测、想在几千张图像上快速看到真实效果的从业者。一个反直觉的结论是这类作业能不能拿高分模型选得怎么样只占一半另一半取决于数据集是否干净、命令能否复现。很多时候模型精度上不去不是YOLOv8网络结构不够强而是标注框里藏着几个越界坐标或者train和val划分时把同一个采摘场景的连续照片拆到了两边。2. 果蔬识别数据集怎么做从图像采集到VOC/YOLO格式互转YOLOv8训练自己的数据集第一步永远是处理数据而不是急着敲训练命令。果蔬识别数据集系统的核心是一份让模型能直接读懂的标注数据。大作业阶段不需要追求几十万张图五到十类常见果蔬、每类一两百张标注图已经足够跑出一个可信的结果。真正花时间的是把各类来源的图片和标注统一成YOLOv8要求的格式以及保证训练集、验证集之间没有数据泄漏。2.1 果蔬数据集的来源怎么选自己拍、公开数据还是组合扩充常见做法是优先自己拍。手机或相机都可以重点是把同一类果蔬放在不同光照、不同背景下各拍几十张比如白底、桌面、超市货架、纸箱里。拍摄时让目标尽量占画面三分之一以上不要远距离拍一堆小目标否则后期标注和模型学习都会很吃力。自己拍的好处是版权干净、场景可控坏处是类别覆盖有限一张图里可能只有一两个果实模型对密集场景的泛化会弱一些。公开数据方面可以去常见的数据集平台搜fruit detection、vegetable detection这类关键词能找到不少带标注的果蔬检测集。需要注意Fruit-360这类经典果蔬数据集是分类数据集只有类别标签没有检测框你要用它就得自己补框。把公开数据和自己拍的图混在一起时先统一类别名比如apple和Apple要合并成同一个类否则同一个目标会被当成两个类来学预测时几乎必然出错。最后用离线增强做补充旋转、亮度、对比度这几个对果蔬识别最实用不要一上来就用很重的风格化增强。2.2 用Python脚本把VOC XML转成YOLO txt类别编号从0开始不少公开果蔬检测数据集给的是VOC格式也就是每张图对应一个XML文件里面用bndbox记录目标左上角、右下角的像素坐标。YOLOv8训练时需要的是一张图对应一个txt每行写成class cx cy w h其中cx、cy是归一化后的中心坐标w、h是归一化后的宽高。转换脚本几乎是固定模板import xml.etree.ElementTree as ET from pathlib import Path CLASS_MAP {apple: 0, banana: 1, orange: 2, tomato: 3, cucumber: 4} def convert_voc2yolo(xml_file: str, out_dir: str): root ET.parse(xml_file).getroot() width int(root.find(size/width).text) height int(root.find(size/height).text) lines [] for obj in root.iter(object): name obj.find(name).text if name not in CLASS_MAP: print(f[skip] {xml_file} - {name}) continue box obj.find(bndbox) x1 float(box.find(xmin).text) y1 float(box.find(ymin).text) x2 float(box.find(xmax).text) y2 float(box.find(ymax).text) w (x2 - x1) / width h (y2 - y1) / height cx (x1 x2) / 2 / width cy (y1 y2) / 2 / height lines.append(f{CLASS_MAP[name]} {cx:.6f} {cy:.6f} {w:.6f} {h:.6f}) if lines: txt_path Path(out_dir) / (Path(xml_file).stem .txt) txt_path.write_text(\n.join(lines), encodingutf-8) if __name__ __main__: for xml_path in Path(annotations).glob(*.xml): convert_voc2yolo(str(xml_path), labels_all)转换逻辑里有几个点要留意。width和height必须取自size节点而不是用bndbox外接框去反推因为很多标注脚本写的是绝对值像素坐标归一化基准只能是原始图像尺寸。CLASS_MAP的编号直接从0开始并且要和后面fruit.yaml里的names编号一一对应这一点错了不会报错只会让mAP看起来是0但图上依然有框极难排查。脚本里把不在类别表里的对象跳过比如一张果盘图里出现leaf跳过它比强行给它一个编号更安全。2.3 数据划分train/val/test别只按7:2:1要按采集场景分桶很多教程直接让用random.shuffle按7:2:1切分这在果蔬识别场景里是个隐性坑。同一个苹果从不同角度连续拍了几十张随机划分后同果实的图像可能同时进train和val验证集mAP会被推得很高换到真正的新环境立刻打回原形。我一般会按采集批次先分组再以组为单位划分保证同一场景的照片只落在同一个集合里。import random, shutil from pathlib import Path from collections import defaultdict random.seed(42) base Path(.) for split in (train, val, test): (base / fimages/{split}).mkdir(parentsTrue, exist_okTrue) (base / flabels/{split}).mkdir(parentsTrue, exist_okTrue) img_files list((base / images_all).glob(*.jpg)) groups defaultdict(list) for f in img_files: prefix f.stem.rsplit(_, 1)[0] # 去掉末尾序号按拍摄批次分组 groups[prefix].append(f) items list(groups.values()) random.shuffle(items) n len(items) splits { train: items[: int(n * 0.7)], val: items[int(n * 0.7): int(n * 0.85)], test: items[int(n * 0.85):], } for split, group_list in splits.items(): for group in group_list: for img in group: shutil.copy(img, base / fimages/{split}/{img.name}) label base / labels_all / f{img.stem}.txt if label.exists(): shutil.copy(label, base / flabels/{split}/{label.name})参数方面random.seed固定成42保证每次跑结果一致rsplit取前缀是为了按日期或采集批次聚合。images_all和labels_all是我习惯的中间目录避免边转换边切分把原目录弄乱。果蔬识别里test集合不是可选项大作业评审很看重“没参与训练”的新场景照片上的表现所以宁可train少一点也要留出一批独立采样的test。3. 搭环境、跑通最小训练用YOLOv8 train命令把你自己的果蔬数据吃进去网上适合小白的超详细YOLOv8教程非常多但照着做到果蔬识别这一步卡住的往往是环境。YOLOv8训练自己的数据集核心是三件事环境装对、数据yaml写对、训练命令参数给得符合显存。下面这套流程在Windows和Linux上都适用只是路径写法略有差异。3.1 从conda环境到ultralytics安装三个准备要点我一般会为每个项目单独建conda环境避免不同项目的依赖互相污染。Python版本固定在3.10或3.11不要一上来用3.12有些opencv的wheel对3.12的兼容性跟不上装完报导入错误反而浪费时间。装包顺序也有讲究先装PyTorch再装ultralytics这样pip不会自动给你拉一个CPU版torch。conda create -n yolov8 python3.10 -y conda activate yolov8 pip install torch torchvision --index-url https://download.pytorch.org/whl/cu121 pip install ultralytics8.2.0torch版本和CUDA版本要匹配比如GTX1660Ti这类显卡驱动能支持CUDA 12.1的话用上面这行就行。如果你的机器CUDA版本不同改成对应cu118或cu123的地址。装完立刻验证GPU是否可用python -c import torch; print(torch.__version__, torch.cuda.is_available())输出里cuda.is_available()为True说明环境是通的。还要单独验证ultralytics版本python -c import ultralytics; print(ultralytics.__version__)这一步很多人漏掉结果是conda环境里混了两个ultralytics训练时报一堆莫名其妙的属性错误。3.2 写一个fruit.yaml数据集路径、类别名与一张示例图验证YOLOv8不直接读你的文件夹结构而是通过一个YAML文件知道“数据在哪、类别叫什么”。这里最容易翻车的是路径建议直接用绝对路径不要写相对路径因为训练命令的工作目录一旦变化相对路径就会指向不存在的位置。# fruit.yaml path: D:/datasets/fruit_detect train: images/train val: images/val test: images/test names: 0: apple 1: banana 2: orange 3: tomato 4: cucumber注意path后面用正斜杠Windows下反斜杠在YAML里会被转义。names的编号必须和转换脚本里的CLASS_MAP完全一致否则就是一场灾难。yaml写完后可以用下面这段快速验证类别读进来没有from ultralytics import YOLO model YOLO(fruit.yaml) print(model.names)能打印出apple、banana这些才算通过。此时还没真正训练但YOLO能通过yaml创建模型定义说明数据路径和类别映射没有原则性问题。再跑一张示例图预测确认torch和模型权重正常加载就可以开始训练了。3.3 启动训练的完整命令GPU显卡选择、epochs与batch的起步值果蔬识别第一次训练我习惯用yolov8s作为起点而不是最小的yolov8n。命令大概是这样的yolo detect train \ datafruit.yaml \ modelyolov8s.pt \ epochs100 \ batch16 \ imgsz640 \ device0 \ projectruns/detect \ namefruit_s \ seed20240601 \ patience20modelyolov8s.pt如果本地没有会自动下载预训练权重下载成功后会从COCO预训练权重开始微调。batch16配imgsz640在GTX1660Ti这种6GB显存的卡上是很省心的起步组合显存不够就把batch降到8imgsz不要动。epochs100对果蔬这类小数据集足够配合patience20意味着连续20个epoch验证集没有变好就自动早停不用死等100轮跑完。project和name决定训练日志和权重输出目录后面画损失曲线、找best.pt都要来这里看。训练起来之后终端会逐轮打印box_loss、cls_loss、mAP50这些指标runs/detect/fruit_s/weights/下也会同步生成best.pt和last.pt。4. 模型效果上不去的5个关键参数batch、imgsz、学习率与类别权重果蔬识别看起来是简单目标检测但真把训练跑起来会发现mAP卡在某个值上不去时问题经常不是模型不够大而是几个参数之间互相拉扯。这一章把最常用的五个调节点拆开讲每一项都能在损失曲线和验证结果里找到依据不是玄学。4.1 果蔬类别少但形态差异大为什么优先用yolov8s而不是yolov8nYOLOv8n是参数量最小的版本推理最快但mAP通常比s低两到四个点。大作业场景里推理速度不敏感s多出来的计算量让模型对边缘、遮挡、颜色渐变这些果蔬区分点更敏感。从网络结构图上看s和n的主干都是CSP结构s在后几层的通道数更大高维特征的表达能力更强。实际表现是苹果和番茄都是红色圆形青番茄和青苹果也容易混n有时会把两个类别预测成同一个框s的区分度会好一些。如果你的类别是火龙果、山竹这种纹理特征明显的n还能撑住如果是苹果、番茄、橘子这类形状相近的直接上s不要为省训练时间选n。4.2 batch、imgsz与内存的匹配小显存怎么稳训练模型选定了接下来最影响效果的就是分辨率和批大小。果蔬目标通常占画面比例不小imgsz640足够只有当果实很小、标注框高度不足64像素时才需要把imgsz拉到960甚至1280。但imgsz翻倍显存占用按平方增长所以要和batch一起调。显存推荐组合说明6GBbatch16, imgsz640GTX1660Ti这类卡的稳当起步值8GBbatch16, imgsz736可以稍微提升小目标表现12GB以上batch32, imgsz640主要加快训练迭代速度batch-1可以自动探测最大可用的batch但自动值往往偏保守我一般不用。手动指定batch时要注意batch太小会让梯度噪声大损失曲线锯齿明显batch太大又容易提前过拟合。果蔬数据集通常小batch16是可靠下限batch32更好。6GB显存如果batch16还爆就开ampTrue混合精度显存占用能降一半左右。4.3 学习率与warmup果蔬数据小如何避免发散YOLOv8默认lr00.01是给COCO这类大数据集准备的。当训练集只有几百张果蔬图时0.01的学习率常常会让loss在前期冲高甚至报nan。我一般会把lr0降到0.005同时保持warmup_epochs3让模型先在小学习率下预热。果蔬识别还有一个讨巧做法是打开余弦退火yolo detect train \ datafruit.yaml modelyolov8s.pt epochs100 batch16 imgsz640 \ lr00.005 warmup_epochs3 cos_lrTrue训练结束后results.csv里已经记录了每个epoch的loss和mAP。网上经常搜到yolov8画损失函数曲线图的教程核心就是读这个csv再画两条线import pandas as pd import matplotlib.pyplot as plt df pd.read_csv(runs/detect/fruit_s/results.csv) df.columns [c.strip() for c in df.columns] fig, axes plt.subplots(1, 2, figsize(12, 4)) axes[0].plot(df[epoch], df[train/box_loss], labeltrain/box_loss) axes[0].plot(df[epoch], df[val/box_loss], labelval/box_loss) axes[1].plot(df[epoch], df[metrics/mAP50(B)], labelmAP50) axes[0].set_xlabel(epoch); axes[0].legend() axes[1].set_xlabel(epoch); axes[1].legend() plt.tight_layout() plt.savefig(loss_curve.png, dpi200)判断方法很直白train loss下降但val loss后半段往上翘就是过拟合提前停或加强数据增强两个loss都不降先查学习率和标注数据不要急着加模型复杂度。注意results.csv的列名在不同版本里略有差异读出来之后先打印columns看一眼再画。4.4 类别不平衡与遮挡给loss加权重还是靠增强果蔬数据里类别不平衡很常见比如苹果收集了300张火龙果只收集到80张。YOLOv8的官方训练接口没有像分类网络那样直接提供class_weight参数常见做法是离线增强少数类。把火龙果的图片复制三到五份每次复制做不同的亮度、对比度、旋转变化注意这些增强副本只能放进trainval和test必须保持原图。另一种更优雅的做法是把少数类图片粘贴到其他背景上模拟多个目标共存的场景这就是copy_paste增强yolo detect train \ datafruit.yaml modelyolov8s.pt epochs100 batch16 imgsz640 \ copy_paste0.2copy_paste0.2意味着两成概率对训练样本做粘贴增广对遮挡场景尤其友好比如叶子挡住半个苹果、手抓住一束香蕉这类样本模型见过之后视频推理时的漏检会明显减少。不需要人为给loss加权因为增强本身就是一种加权。4.5 mosaic和mixup对果蔬目标的利与弊何时关掉mosaicmosaic把四张图拼成一张是YOLOv8默认开启的强增强对小目标和背景多样性帮助很大。但果蔬目标通常不小mosaic把果实从中间切开模型学到“半个苹果也算苹果”推理时反而把画面边缘被切到的半颗果实误检出来。mixup把两张图叠在一起对果蔬这种外观差异大的类别会造成颜色串扰。这两项的规律是前期开、后期关。ultralytics提供了close_mosaic参数可以在最后N个epoch自动关闭mosaic和mixupyolo detect train \ datafruit.yaml modelyolov8s.pt epochs100 batch16 imgsz640 \ mosaic0.8 mixup0.1 close_mosaic10close_mosaic10表示最后10个epoch不用mosaic和mixup让模型在接近真实分布的数据上做最后的收敛。如果你已经在错误图里看到大量“半个果蔬被框出来”的情况除了把close_mosaic调大还要检查是否因为mosaic导致正确标注框也被截断重新生成损失曲线对比关闭前后的val loss就能定位。5. YOLOv8果蔬识别训练避坑与排查常见报错与血泪经验接下来的内容是我重复踩过的坑每一条都是实际训练中高频出现的现象。按“现象→原因→解决”写适合你训练出问题的时候直接对号入座。5.1 现象训练刚开始就报CUDA out of memory训练脚本跑起来不到一个epoch终端抛出RuntimeError: CUDA out of memory。原因几乎都是batch和imgsz的组合超出显存尤其是在GTX1660Ti这类6GB卡上直接套用别人的batch32配置文件。解决方法是先用小配置跑通再加参数。混合精度也要显式确认yolo detect train \ datafruit.yaml modelyolov8s.pt epochs100 batch8 imgsz640 \ workers4 ampTrueampTrue开启混合精度显存占用能接近减半。workers4控制数据加载进程数减小了瞬时内存峰值。如果batch8依然爆把imgsz降到512果蔬大目标在512下基本不受影响。训练日志里出现AMP字样就说明混合精度生效了。5.2 现象训练能跑但loss一直nanmAP50显示0训练没有报错但每个epoch的loss列表里出现nanmAP50一直是0。最常见原因是标注文件里有非法值比如xmin小于0、xmax超出了图片宽度转换后归一化坐标变成负数或大于1。先把数据校验脚本跑一遍from pathlib import Path for txt in Path(labels_all).glob(*.txt): for line in txt.read_text().strip().splitlines(): parts line.split() if len(parts) ! 5: print(f[bad line] {txt}: {line}) continue cid, cx, cy, w, h map(float, parts) if not (0 cid 5 and 0 w 1 and 0 h 1): print(f[out of range] {txt}: {line})这段代码会检查类别编号是否在0到4之间以及归一化后的宽高是否在(0,1]区间。对中心坐标cx、cy也要做同样的范围判断。如果数据没问题再把lr0降到0.003重新训练nan的另一个高频原因是学习率太大导致loss发散。还有一类隐蔽原因图片带EXIF旋转信息YOLOv8读取时实际宽度和标注时不一致归一化坐标自然错位。常见做法是训练前统一用脚本把图片转成RGB并重存一遍去掉EXIF方向信息。5.3 现象mAP很高但识别视频时框乱跳单张图片测试效果很好跑视频时同一个苹果这一帧有框、下一帧没有或者框在背景上乱飘。原因是预测时的conf阈值太低把噪声当作目标iou阈值太小物体重叠时产生大量重复框。解决方法是预测命令里显式指定这两个参数from ultralytics import YOLO model YOLO(runs/detect/fruit_s/weights/best.pt) results model.predict(test.mp4, conf0.45, iou0.5, saveTrue, vid_stride1)conf提到0.45后误检会明显减少但视频里目标较远时漏检也可能变多这时候优先把imgsz提高到960重新推理而不是一味降低conf。如果视频里果实被手或叶子频繁遮挡可以做一帧简单的框平滑保存上一帧的检测框当前帧没检测到但上一帧存在时沿用旧框最多15帧。这个方法不精致但在果蔬识别这种运动缓慢的场景里很管用。5.4 现象训练时报错Class not in names或KeyError验证阶段报错KeyError: strawberry is not in names脚本瞬间中断。原因很直接XML里存在不属于当前CLASS_MAP的类别转换时没有过滤掉导致labels里出现了和fruit.yaml对不上的编号或名称。解决前先统计整个标注目录里到底有哪些类别import xml.etree.ElementTree as ET from pathlib import Path from collections import Counter counter Counter() for xmlp in Path(annotations).glob(*.xml): root ET.parse(xmlp).getroot() for obj in root.iter(object): counter[obj.find(name).text] 1 print(counter)看到额外类别后要么把它补进CLASS_MAP并分配一个新编号要么在转换时跳过。最忌讳的是转换脚本不过滤、大理至只改fruit.yaml里的names那样train和val的编号会错位模型不是训练失败而是mAP一直异常。选择哪种方案都行关键是转换脚本和fruit.yaml必须使用同一份类别表。5.5 现象导出ONNX后同一张图结果和PyTorch不一致best.pt在torch下识别正常导出ONNX后用同一张图测试置信度明显下降甚至检不出目标。原因通常是opset版本设置不当和导出时imgsz与实际推理尺寸不一致。opset太高部分旧版推理框架不兼容opset太低某些算子被转换成低精度实现。固定一组参数导出yolo export modelruns/detect/fruit_s/weights/best.pt formatonnx opset12 imgsz640然后用onnxruntime快速核对输出置信度是否正常import onnxruntime as ort import cv2 import numpy as np session ort.InferenceSession(best.onnx, providers[CPUExecutionProvider]) img cv2.imread(apple_001.jpg) img cv2.resize(img, (640, 640)) input_data img[:, :, ::-1].transpose(2, 0, 1)[None].astype(np.float32) / 255.0 outputs session.run(None, {session.get_inputs()[0].name: input_data}) det outputs[0][0] # 输出为 [84, 8400]前4行是坐标后面是80类旧COCO分数 conf det[4:].max(axis0) print(max conf:, conf.max())上面只是快速排查脚本完整的输出还需要按类别维度解析出框坐标和后处理逻辑但max conf能直接告诉你导出有没有出大问题。如果在torch里置信度0.9onnx里掉到0.1优先检查输入预处理尤其是resize方式。YOLOv8原生推理用的是letterbox等比例缩放而上面脚本里用了直接拉伸这会让框偏移。实际做ONNX部署时要按ultralytics的预处理方式对齐。6. 训练完成之后用指标验证模型再落成一个可交付的果蔬识别系统训练跑完不是终点还要做两件事一是用更细的指标确认模型短板二是把工程整理成别人能照着复现的结构这恰恰是“优质大作业”和普通作业的差距所在。6.1 训练后先做一次全面验证混淆矩阵、PR曲线与错误案例抽样runs/detect/fruit_s目录下会自动生成confusion_matrix.png、PR_curve.png、results.png。先看混淆矩阵里哪两类最常互相误判果蔬识别里最常见的是青苹果和番茄、橘子和橙子。如果这两类混淆严重不要急着换模型先回去检查标注里这两类的框有没有标错或标漏。验证命令yolo detect val datafruit.yaml modelruns/detect/fruit_s/weights/best.pt它会输出mAP50、mAP50-95、precision、recall这些数字要记进README作为可量化成果。如果后续要部署到RK3588这类边缘板卡用第5.5节导出的ONNX模型即可复用训练阶段不用做额外改动。6.2 把工程整理成“大作业”的文档结构README、requirements、目录树一个能直接交付的果蔬识别系统目录结构建议保持清晰类别和数据不要一股脑堆在一起fruit_detect/ ├── data/ │ ├── images/{train,val,test} │ └── labels/{train,val,test} ├── scripts/ │ └── convert_voc2yolo.py ├── runs/detect/fruit_s/weights/best.pt ├── requirements.txt └── README.mdREADME写四块就够了环境配置命令、数据来源与类别说明、训练与验证命令、结果截图。requirements.txt用pip freeze生成后删掉和项目无关的包只保留torch、ultralytics、opencv等核心依赖。数据集原始压缩包不要塞进交付文件夹动辄几个GB反而干扰评审。我自己这些年做检测项目的习惯是任何一次训练启动前先把数据校验脚本跑完再调参这个习惯帮我避开了大部分翻车。如果你也正在做类似的YOLOv8果蔬识别大作业先把数据清洗和文档写好再去折腾那些花哨的注意力模块效果会实在得多希望帮到你。本文还有配套的精品资源点击获取
返回列表