ARTICLE DETAIL

资讯详情

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

YOLOv8果蔬识别数据系统实战:从数据集构建到SQLite入库全流程

YOLOv8果蔬识别数据系统实战:从数据集构建到SQLite入库全流程 简介一套基于YOLOv8的果蔬识别数据系统完整项目属于高分优质大作业评审98分面向计算机相关专业正在准备期末大作业、毕业设计的学生以及需要项目实战的深度学习初学者。资源涵盖可直接运行的Python源码、标注好的果蔬图片数据集、YOLOv8模型配置文件与训练权重pt并附有UI界面和文档说明可快速搭建训练、验证与推理流程。压缩包共120个文件以py源码、yaml配置、jpg/png图片数据集、pt权重为主辅以md文档、ui界面、mp4操作录屏等整体大小约27.2MB目录结构清晰便于按类别查阅。已有64人学习下载。使用者可获得完整可复现的实验方案从数据集准备labels.cache缓存、train_batch训练批次图到模型训练验证val_batch预测与标签对比图再到结果分析results.csv训练曲线配合文档说明与操作录屏能有效降低复现门槛适合在此基础上进行改进或拓展。1. 果蔬识别数据系统是什么一套能跑、能查、能答辩的大作业组合如果你正卡在一个叫“YOLOv8实现果蔬识别数据系统代码数据集和文档说明高分优质大作业”的题目上说明你接到的不是单纯的目标检测任务而是一个“三件套”组合检测模型要能跑、结果要能存成一套数据系统、文档要能支撑答辩。很多人在第一步就被劝退不是因为YOLOv8难而是不知道数据从哪来、标注长什么样、训练完怎么交代“数据系统”这四个字。这篇笔记就是照着这套需求捋的从数据集构建、训练参数、SQLite入库到避坑和部署全部按大作业能落地、能演示、能回答老师追问的标准来写。适合手里只有一张入门显卡、正在赶课程设计和毕业设计的学生。2. 把果蔬数据集从零攒到能训练类别清单、标注转换与划分2.1 先定类别清单公开数据集、自采和最少样本量果蔬识别最常见的问题是“类别规划失控”。有人一上来就想做30类结果每类只有几十张图模型全在过拟合。我一般建议把类别控制在8到15类理由有三个一是大作业的演示时间有限类别太多反而让人记不住效果二是每类样本量要达到可训练水平15类每类150张只需要2250张图自采压力小三是答辩时老师问“为什么选这10类”你可以回答“覆盖了超市果蔬秤常见的单品”。数据来源按优先级排能用的公开果蔬检测数据集常见做法是搜Robbwfruit、Fruit-Images-Detection这类公开检测集注意要带标注文件其次是自采手机拍不同角度、光照、远近每类100到200张最后才是用爬虫拉图再清洗因为清洗成本高且容易带水印。不管你用哪种方式数据集目录一定要统一成下面这种结构YOLOv8默认吃这个dataset/ images/ train/ val/ labels/ train/ val/这个结构后面训练配置里直接指到dataset根目录就行。注意一点不要把原图和标注混在一个目录YOLO系训练器是把图片路径和标签路径分开读的混在一起会导致“label缺失”的报错。2.2 把VOC/CSV标注转成YOLO的txt标注坐标归一化与类别从0开始如果你拿到的是VOC格式XML或者CSV坐标表得先转成YOLO格式的txt。YOLO标签格式是每行一个目标class_id x_center y_center width height四个坐标值全部归一化到0到1之间除以图片宽高。新手最容易翻车的地方有两个一是类别编号不是从0开始而是从1开始训练时mAP直接崩二是转了中心坐标却忘了除以图片宽高导致大量标签超出边界被丢弃。下面这个脚本能把CSV格式列名为filename, class_id, x1, y1, x2, y2转成YOLO格式顺手做训练集/验证集随机划分import os import csv import random import shutil # 配置区改这里 CSV_PATH annotations.csv IMG_SRC images_raw # 原始图片目录 OUT_DIR dataset # 输出目录 VAL_RATIO 0.2 # 验证集比例 random.seed(42) # 固定种子保证结果可复现 # 建目录 for split in [train, val]: os.makedirs(f{OUT_DIR}/images/{split}, exist_okTrue) os.makedirs(f{OUT_DIR}/labels/{split}, exist_okTrue) # 读标注并按文件名聚合 records {} with open(CSV_PATH, newline) as f: reader csv.DictReader(f) for row in reader: fn row[filename] records.setdefault(fn, []).append(row) # 划分文件名列表 filenames list(records.keys()) random.shuffle(filenames) val_count int(len(filenames) * VAL_RATIO) val_files set(filenames[:val_count]) # 写图片和标签 for fn, rows in records.items(): split val if fn in val_files else train # 计算归一化坐标 lines [] for r in rows: w int(r[width]) h int(r[height]) x1, y1, x2, y2 map(float, [r[x1], r[y1], r[x2], r[y2]]) x_center ((x1 x2) / 2) / w y_center ((y1 y2) / 2) / h box_w (x2 - x1) / w box_h (y2 - y1) / h # 类别ID必须从0开始名称映射自己维护 lines.append(f{int(r[class_id])} {x_center:.6f} {y_center:.6f} {box_w:.6f} {box_h:.6f}) # 写入标签txt label_path f{OUT_DIR}/labels/{split}/{os.path.splitext(fn)[0]}.txt with open(label_path, w) as f: f.write(\n.join(lines) \n) # 复制图片 shutil.copy2(f{IMG_SRC}/{fn}, f{OUT_DIR}/images/{split}/{fn}) print(f处理 {fn} - {split})这套脚本的关键点有三个固定随机种子、按文件名聚合多目标标注、坐标归一化。注意如果你手中的标注本身已经是归一化的格式就不要再除以宽高否则目标框会变成原尺寸的百分之一训练时模型什么都学不到。2.3 数据划分与类别平衡最少样本量与分布检查数据集转完格式后先用一段代码检查类别分布而不是直接开训。常见的问题是某个类别在训练集里只有20来个目标模型根本学不够或者验证集里某个类别一个样本都没有导致该类mAP显示为0答辩时被问到很尴尬。from collections import Counter import os def check_dataset(label_dir): total Counter() per_image [] for fn in os.listdir(label_dir): path os.path.join(label_dir, fn) with open(path) as f: lines [l.strip() for l in f if l.strip()] image_count len(lines) per_image.append(image_count) for line in lines: cls int(line.split()[0]) total[cls] 1 return total, per_image train_total, train_per_image check_dataset(dataset/labels/train) val_total, _ check_dataset(dataset/labels/val) print(训练集各类别目标数:, dict(train_total)) print(验证集各类别目标数:, dict(val_total)) print(单图平均目标数:, sum(train_per_image) / max(len(train_per_image), 1))从输出里看两个阈值每类目标数低于100的考虑补数据或合并相近类别单图平均目标数低于1的说明大量图片是空标注或漏标需要回去查标注质量。这里多花的十分钟比后面训练完再返工划算得多。3. YOLOv8训练果蔬识别模型环境配置、选型与参数调教3.1 YOLOv8环境配置依赖项与最小验证命令YOLOv8是Ultralytics出的安装方式很统一不需要像早期YOLOv5那样手动配一堆依赖。常见做法是用一个干净的Python环境3.8到3.11都行然后装ultralytics包。显卡驱动和CUDA这块是大部分人第一次翻车的地方我的建议是先不管CUDA版本直接装CPU版torch跑通训练流程再回头装GPU版。别笑很多人的CUDA问题根本原因是torch和驱动不匹配而不是代码问题。# 创建干净环境conda或venv都行 python -m venv venv source venv/bin/activate # Windows用 venv\Scripts\activate # 安装ultralytics自动带torch CPU版 pip install ultralytics # 验证安装 yolo predict sourcehttps://ultralytics.com/images/bus.jpg modelyolov8n.pt --save最后一条命令能跑通说明环境没大问题。如果你确认NVIDIA驱动已装好再手动换GPU版torchpip uninstall torch torchvision -y pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118用GPU版装好后跑一下python -c import torch; print(torch.cuda.is_available())输出True再进下一步。这个最小验证流程能挡掉后面八成环境问题。顺便说一句不用在源码编译上花时间“适合小白的超详细yolov8”配置方案基本都是基于ultralytics包来做的没必要自己拉源码build。3.2 模型尺寸选型为什么大作业推荐yolov8s而不是yolov8lYOLOv8按网络结构复杂度分了n/s/m/l/x五档很多人一上来就选yolov8l甚至x觉得越大越准。但在果蔬识别这个场景下我不推荐超过m档。理由很简单大模型在大规模数据集COCO或自定义的几千张图上的增益需要足够数据量来兑现只有一千来张图时yolov8s和yolov8l的mAP差距往往在1到2个点以内但训练时间和显存占用差了快一倍。你跑大作业不想卡在显存溢出上。大家看到的“yolov8 head改进”这类热词我建议大作业阶段不要碰。改动head意味着要重新训练、重新对齐评估逻辑对拿学分没有边际收益。真想让模型更贴合果蔬场景优先做的应该是检查数据集质量、把epochs拉够、调对训练参数这些工程操作比改网络结构更稳定、更可控。选型上我的默认组合显卡显存6G以下用yolov8n或yolov8s6G到12G用yolov8s或yolov8m12G以上随便。果蔬识别目标不算极小s档的特征表达能力完全够用。3.3 训练命令与关键参数GTX 1660 Ti级别怎么设batch和imgsz训练自定义数据集的命令很固定难点在参数。下面是我在GTX 1660 Ti6G显存上跑果蔬识别的典型配置yolo detect train \ datadataset/fruit.yaml \ modelyolov8s.pt \ epochs120 \ imgsz640 \ batch16 \ device0 \ patience30 \ workers4 \ projectruns \ namefruit_exp1要写配套的fruit.yaml数据配置文件。文件名和路径注意别用中文和空格Windows下尤其容易出诡异问题path: dataset # 数据集根目录相对路径基于当前工作目录 train: images/train val: images/val nc: 10 # 类别数和你的类别清单一致 names: [apple, banana, carrot, tomato, potato, orange, pepper, cucumber, lemon, mango]参数说明imgsz640是兼顾速度与精度的默认值果蔬目标通常占画面比例不小不需要为了小目标上1280batch16在6G显存上是用s模型的甜点值再大就报CUDA out of memory再小梯度噪声大训练不稳patience30表示30轮指标不涨就早停防止你上课期间训练跑飞浪费时间workers4是数据加载线程数Windows下超过8会出DataLoader worker崩溃别贪。如果你要跑更小的n模型batch可以拉到32如果你用m模型batch降到8或者imgsz降到480。这三者之间的换算关系是显存占用大致跟batch和imgsz平方成正比很实用。3.4 画损失函数曲线图results.csv是现成的数据源训练完评论文档里要放训练曲线这是大作业的标配。很多人不知道YOLOv8在训练过程中会自动把metrics写进runs/fruit_exp1/results.csv根本不需要自己在训练循环里埋点。直接读这个文件画图就行import pandas as pd import matplotlib.pyplot as plt df pd.read_csv(runs/fruit_exp1/results.csv) # 列名形如 train/box_loss, val/box_loss, metrics/mAP50(B) df.columns [c.strip() for c in df.columns] fig, axes plt.subplots(1, 3, figsize(15, 4)) axes[0].plot(df[train/box_loss], labeltrain_box_loss) axes[0].plot(df[val/box_loss], labelval_box_loss) axes[0].set_title(Box Loss) axes[1].plot(df[train/cls_loss], labeltrain_cls_loss) axes[1].plot(df[val/cls_loss], labelval_cls_loss) axes[1].set_title(Cls Loss) axes[2].plot(df[metrics/mAP50(B)], labelmAP50) axes[2].plot(df[metrics/mAP50-95(B)], labelmAP50-95) axes[2].set_title(mAP) for ax in axes: ax.legend() ax.grid(True) plt.tight_layout() plt.savefig(training_curves.png, dpi150)把这张图放进大作业报告里能直接回答“训练是否收敛”的问题。正常曲线是box_loss和cls_loss平稳下降val损失在中后期与train损失接近如果val损失升高而train损失还在降就是过拟合说明epochs开大了或者数据集太小回去用patience早停或加数据增强。mAP50曲线在果蔬这种单类目标上一般能做到0.85以上低于0.7就要回头查标签格式和类别分布。4. 把检测结果做成“数据系统”SQLite入库、查询与统计4.1 表结构设计一次检测该记录什么字段标题里的“数据系统”是整个项目中老师最有可能追问的部分。你不能只交一个检测脚本得把每次识别的结果沉淀下来能查、能统计。常见做法是用SQLite文件型数据库零配置交作业时连数据库文件一起提交老师用任何SQLite工具打开就能看不会因为没装MySQL而跑不起来。一张核心表就够了记录每次检测到的目标。字段设计时注意把“图片信息”和“检测信息”分开放便于统计CREATE TABLE detections ( id INTEGER PRIMARY KEY AUTOINCREMENT, image_path TEXT NOT NULL, -- 图片相对路径 image_width INTEGER, image_height INTEGER, class_id INTEGER NOT NULL, -- 类别编号和yaml中names对应 class_name TEXT NOT NULL, -- 冗余存类别名省得join confidence REAL NOT NULL, -- 置信度0~1 bbox_x1 REAL, bbox_y1 REAL, bbox_x2 REAL, bbox_y2 REAL, -- 像素坐标非归一化 detected_at TEXT DEFAULT (datetime(now, localtime)) );冗余存储class_name而不是只存class_id是为了让老师直接打开数据库就能看明白不用去对照yaml文件。这是作业式项目里很实用的设计生产系统会嫌冗余作业系统反而加分。4.2 推理与入库把YOLOv8的预测结果写进SQLite写推理脚本时有一个细节容易被忽略YOLOv8返回的boxes.xyxy是像素坐标而训练时的标签是归一化坐标入库时要存像素坐标因为要能在原图上画出框来核对。下面的脚本遍历一个测试图片目录把每一张图里的所有检测结果写入SQLiteimport sqlite3 from pathlib import Path from ultralytics import YOLO # 1. 连接数据库建表 db_path fruit_system.db conn sqlite3.connect(db_path) conn.execute( CREATE TABLE IF NOT EXISTS detections ( id INTEGER PRIMARY KEY AUTOINCREMENT, image_path TEXT NOT NULL, image_width INTEGER, image_height INTEGER, class_id INTEGER NOT NULL, class_name TEXT NOT NULL, confidence REAL NOT NULL, bbox_x1 REAL, bbox_y1 REAL, bbox_x2 REAL, bbox_y2 REAL, detected_at TEXT DEFAULT (datetime(now, localtime)) ) ) # 2. 加载训练好的模型 model YOLO(runs/fruit_exp1/weights/best.pt) # 3. 遍历待检测图片保留原图不缩放 img_dir Path(test_images) for img_path in sorted(img_dir.glob(*.jpg)): results model.predict(str(img_path), conf0.4, imgsz640, verboseFalse) r results[0] # 单张图结果 boxes r.boxes if boxes is None or len(boxes) 0: continue # 批量插入参数化SQL records [] for box in boxes: cls_id int(box.cls[0]) conf float(box.conf[0]) x1, y1, x2, y2 [float(v) for v in box.xyxy[0]] records.append(( str(img_path), r.orig_shape[1], r.orig_shape[0], cls_id, r.names[cls_id], conf, x1, y1, x2, y2 )) conn.executemany( INSERT INTO detections (image_path, image_width, image_height, class_id, class_name, confidence, bbox_x1, bbox_y1, bbox_x2, bbox_y2) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) , records) print(f写入 {img_path.name}: {len(records)} 个目标) conn.commit() conn.close() print(所有检测结果已写入, db_path)逻辑说明conf0.4是置信度阈值入库前过滤低置信度目标避免数据表里堆一堆误检imgsz640要和训练一致否则检测精度会波动executemany批量插入比一条条insert快得多测试图片多的时候差距明显。如果你想让数据系统更完整可以再加一张图片表记录“拍摄时间、拍摄地点、图片来源”然后在detections里加一个image_id外键但这要看你的项目说明有没有这个要求。4.3 按类别、置信度、时间段做统计三个查询模板数据系统能不能回答老师的问题全看查询语句写得好不好。老师可能会问“这个系统里你识别了多少个苹果置信度分布是怎么样的不同时段的检测结果有什么差异”下面三个查询模板能直接覆盖这些提问-- 按类别统计目标数量 SELECT class_name, COUNT(*) AS cnt, ROUND(AVG(confidence), 3) AS avg_conf FROM detections GROUP BY class_name ORDER BY cnt DESC;-- 置信度区间分布评价检测稳定性 SELECT CASE WHEN confidence 0.9 THEN 0.9~1.0 WHEN confidence 0.8 THEN 0.8~0.9 WHEN confidence 0.7 THEN 0.7~0.8 ELSE below 0.7 END AS conf_band, COUNT(*) AS cnt FROM detections GROUP BY conf_band;-- 按小时统计检测次数观察数据系统使用规律 SELECT strftime(%H, detected_at) AS hour, COUNT(*) AS cnt FROM detections GROUP BY hour;这三个查询跑出来的结果建议直接截图放到文档里。老师看到数据库里不仅有数据还能产出统计结论整个项目的“数据系统”分量就立住了。到此为止YOLOv8检测模型和数据系统两部分已经通了接下来是比训练更耗时间的排错环节。5. 果蔬识别项目避坑五个真实报错与排查记录5.1 训练loss曲线很漂亮但mAP50接近0标签类别没从0开始现象训练20轮后box_loss和cls_loss都在正常下降val mAP50却一直是0.01以下像卡死了一样。原因标注转换脚本里class_id用了1到10而不是0到9模型输出10个类别但标签全是偏移的。解决检查dataset/labels/train下任意一个txt的第一列数字确认最大值等于nc-1。如果最大值等于nc说明偏移了一位转换时把class_id减1重新生成。这个坑几乎每个做过YOLO的人都踩过检查成本不到一分钟。5.2 把桌面、砧板识别成果蔬背景过拟合与欠采样现象测试时真实果蔬能识别但空桌面、砧板、甚至手掌也被标成苹果或番茄。原因训练集里大量图片是固定背景拍的模型学到的是“红色圆形区域番茄”而不是果蔬本身的纹理特征。解决做数据增强时把hsv_h、hsv_s、flipud打开并在训练配置里适当提高mosaic概率更重要的是在采数据时覆盖多种背景、多种光照。这个属于数据质量问题改参数只能缓解补数据才能根治。5.3 CUDA out of memory显存不够时先动哪个参数现象训练跑到第二个epoch时突然报CUDA out of memory重启后又在中途崩。原因显存开销是动态的前期缓存和自动求导图累积后峰值才出现。解决顺序很明确先把batch减半尝试不行再把imgsz从640降到512或480这两步能解决95%的OOM最后才是换更小的模型比如yolov8s换yolov8n。注意workers也会占用显存做数据预处理Windows下把它降到2可以降低峰值但不要先动它因为OOM日志里不会直接提示workers导致的问题。5.4 换机器后训练结果对不上torch/CUDA版本和随机种子现象在实验室电脑训练mAP50有0.9回到宿舍电脑重训只有0.82以为是代码改坏了。原因两个环境里torch版本不同或者自己的代码里没有固定随机种子数据划分、增强顺序都会变。解决把fruit.yaml的划分脚本固定种子训练时在python里设置random.seed(0)和torch.manual_seed(0)同时把requirements.txt导出环境统一到同一个小版本。这个坑在交代码给老师复现时最容易暴露提前固定能保命。5.5 数据系统查不到数据SQLite文件的相对路径没有锚定现象推理脚本在项目根目录跑得好好的把项目文件夹整个移动后重跑数据库能连接但查询结果为空。原因代码里用了sqlite3.connect(fruit_system.db)这个路径依赖当前工作目录而图片路径存在表里的是test_images/xxx.jpg这样的相对路径一旦工作目录变了图片路径全部失效。解决用Path(__file__).resolve().parent锚定项目根目录数据库和图片路径全部在此基础上拼接。作业项目固定用绝对路径锚定不要依赖“当前所在目录”这是数据系统稳定性的底线。6. 把模型从“训完”变成“能演示”导出、批处理与轻量界面训练好的best.pt只是半成品答辩或交作业时要让人能上手用。我给果蔬识别项目的最低可演示配置是三件事导出ONNX格式、写批量推理脚本、用Gradio搭一个上传图片即识别即入库的界面。# 导出ONNX方便脱离torch环境运行 yolo export modelruns/fruit_exp1/weights/best.pt formatonnx imgsz640 opset12ONNX导出后可以脱离PyTorch运行还能拿去边缘设备部署。如果你之后想把这个方向延伸一下常见路径就是把导出的ONNX放到RK3588这类板子上跑流程是先用onnxruntime在PC上验证输出再做板级迁移。不过大作业阶段不用搞这么深能把demo跑顺就行。Gradio是这类大作业的救命稻草三行代码起一个本地Web页面import gradio as gr from ultralytics import YOLO model YOLO(runs/fruit_exp1/weights/best.pt) def detect_and_store(img): results model.predict(img, conf0.4, imgsz640) r results[0] # 入库逻辑复用第4章的写法此处省略但一定要调用 return r.plot() # 返回带标注框的图片 gr.Interface( fndetect_and_store, inputsgr.Image(typepil), outputsgr.Image(typenumpy), title果蔬识别数据系统, description上传图片识别结果自动写入数据库 ).launch()演示时先启动这个脚本浏览器打开本地地址上传一张测试图等标注框出来再打开SQLite的查询界面展示刚刚写入的记录。整个过程一气呵成比在终端里敲命令直观得多。我的习惯是演示前先把results.csv的训练曲线和三类统计SQL的查询结果都截图存档因为这些是老师最爱看的东西不是模型跑出来的框。这个项目做下来我最深的一条教训就是数据集的类别从0开始、数据库路径用绝对锚定、训练曲线提前截图这三件事做好了整个项目就稳了。希望帮到你。本文还有配套的精品资源点击获取
返回列表