ARTICLE DETAIL

资讯详情

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

YOLOv8食材检测与冰箱分层管理:从数据集标注到PyQt5界面部署

YOLOv8食材检测与冰箱分层管理:从数据集标注到PyQt5界面部署 简介面向计算机相关专业学生的毕业设计与课程设计需求这份基于YOLOv8的智能冰箱食材分层管理项目源码包集成了目标检测、深度学习模型训练与可视化界面展示可直接用于毕设答辩或课设演示。压缩包共8个文件包含3个Python脚本分别负责模型训练、视频检测与可视化界面、3个PyTorch模型权重文件含多种预训练与最优模型以及2个说明文档整体大小约15.91MB结构清晰且便于快速部署。目前已有34人学习下载。项目特色在于不仅提供完整数据集和部署教程还内置了指标曲线图、混淆矩阵、F1分数曲线、精确率-召回率曲线等可视化输出能直观呈现模型性能为论文撰写与答辩评审提供有力支撑。所有代码经测试运行成功按指引即可上手适合需要完整方案参考的初学者或进阶调整者。1. 看到“智能冰箱食材分层管理”先别急着训模型先搞清楚分层到底分什么看到《基于YOLOv8的智能冰箱食材分层管理》这个标题第一反应可能是冰箱食材为什么要“分层管理”正常的冰箱分上、中、下层和门架抽屉食材放哪一层是有规律的——上层放饮料、中层放熟食、下层抽屉放果蔬。检测出食材后还要判断它“在哪一层”才能在界面里按层查询和补货。这套打包项目给你源码、可视化界面、完整数据集和部署教程它的工程价值不在于把识别精度做到多高而在于把一条完整链路跑通采集标注、YOLOv8训练、模型导出、GUI识别、分层归类。适合两类人一类是必须快速交付毕业设计或课程设计的同学另一类是正在学 YOLOv8 但还没把它接进可交互界面的从业者。下面我按自己做过同类项目的顺序从数据集和分层规则讲起。2. 把冰箱实拍画面整理成 YOLOv8 能用的食材数据集采集、标注与分层规则2.1 分层管理的核心逻辑先在界面里定义每层区域再做食材识别“分层管理”听起来好像要去训练一个模型识别“这是第几层”实际做的时候没人这么干。冰箱里的物理隔板位置相对固定摄像头也是固定机位所以常见做法是在界面里预先框出几个区域分别命名为“上层”“中层”“保鲜抽屉”。推理阶段拿到检测框后取检测框中心点落在哪个区域就判定这个食材属于哪一层。这样做省掉了对“层”本身的标注而且结果稳定。分层区域一般硬编码在一个 JSON 或配置文件里方便根据摄像头角度调整{ layers: [ { id: 1, name: 上层, roi: [50, 60, 400, 260] }, { id: 2, name: 中层, roi: [50, 280, 400, 520] }, { id: 3, name: 下层抽屉, roi: [50, 540, 400, 700] } ], conf_thres: 0.35, iou_thres: 0.45 }ROI 的四个数字是[x1, y1, x2, y2]代表画面中的像素坐标区域。判断食材层归属时写一个辅助函数def assign_layer(box_center, layers): cx, cy box_center for layer in layers: x1, y1, x2, y2 layer[roi] if x1 cx x2 and y1 cy y2: return layer[id] return 0 # 0 表示未落入任何层一般归为“待确认”这段逻辑之所以用中心点而不是计算检测框与 ROI 的交集面积是因为食材的检测框经常比实际物体大一圈用交集面积容易让相邻两层互相抢归属中心点则只关心“主要位置在哪层”边界分明。配置里的conf_thres和iou_thres会透传给后面的推理模块我一般把它们单独留在界面设置里而不是写死在模型导出步骤中方便答辩的时候现场调。提示层区域要和摄像头机位强绑定。换了拍摄角度ROI 必须重新标一遍否则会出现“明明在上层却归到中层”的错误。2.2 数据采集与标注用 labelme 给冰箱食材打标签冰箱食材的数据集和普通场景照片差别很大。真实冰箱里的拍摄以俯拍和斜 45 度为主箱内 LED 灯会让塑料包装出现大片高光冷气还会让镜头起雾发白。所以采集时有三个原则不同光照开关门各拍一批、不同包装状态有没有保鲜膜、不同摆放角度正放、侧放、堆叠。类别建议选 15 到 30 个常见食材每类至少 150 张有效图。数量不是越多越好覆盖度比绝对数量重要。labelme 标注出来的是 JSON 文件而 YOLOv8 训练需要的是和图片同名的 txt 文件内容是“类别ID 归一化中心x 归一化中心y 归一化宽 归一化高”。转换脚本是每个做过 YOLO 项目的人都会写一次的经典代码import json def labelme_to_yolo(json_path, out_txt, class_map): with open(json_path, encodingutf-8) as f: data json.load(f) img_w, img_h data[imageWidth], data[imageHeight] lines [] for shape in data[shapes]: label shape[label] if label not in class_map: continue # shape[points] 是矩形框的两个角点坐标 xs [p[0] for p in shape[points]] ys [p[1] for p in shape[points]] x_min, x_max min(xs), max(xs) y_min, y_max min(ys), max(ys) w x_max - x_min h y_max - y_min # 格式class_id cx cy w h全部归一化到 0~1 line f{class_map[label]} {(x_min w/2)/img_w:.6f} {(y_min h/2)/img_h:.6f} {w/img_w:.6f} {h/img_h:.6f} lines.append(line) with open(out_txt, w, encodingutf-8) as f: f.write(\n.join(lines))这里的逻辑说明class_map是“标签名 - 数字ID”的字典比如{apple: 0, milk: 1, ...}。shape[points]如果标成了四边形就取所有点的最小外接矩形。归一化是必须的因为 YOLO 训练时不同分辨率图片共用同一套标签表示坐标值超过 1 会导致预处理阶段直接过滤掉这个标注框表现为“漏检”。转换完建议再跑一个校验脚本逐个检查 txt 标签是否都存在且每条数据都能解析出五个字段。图片和标签整理好后按 8:1:1 分成 train、val、test 三个目录。这个比例是给食材这类小数据量任务用的不建议 test 占比太高因为数据总量本来就可能只有一两千张。目录结构长这样datasets/fridge_food/ ├── data.yaml ├── images/ │ ├── train/ │ ├── val/ │ └── test/ └── labels/ ├── train/ ├── val/ └── test/2.3 样本均衡和增强参数训练前必须调好的几个值训练前最容易翻车的地方是类别不平衡。比如“鸡蛋”拍了五百张“草莓”只有三十张训练出来的模型对鸡蛋 mAP 很高草莓几乎测不到。这不是训练参数的问题是样本分布的问题。处理方式优先补拍和复制增强而不是硬调损失函数权重。YOLOv8 自带的增强参数里有几个和冰箱场景强相关hsv_h、hsv_s、hsv_v控制颜色偏移degrees控制旋转translate和scale控制平移缩放fliplr是水平翻转mosaic是四图拼接。冰箱 LED 灯色温偏冷白不同冰箱之间还有偏黄和偏蓝的差异所以我会把hsv_s提到 0.7、hsv_v提到 0.4模拟同一种食材在不同箱体灯下的颜色变化。fliplr默认开启俯拍图左右翻转在真实视角下完全可能出现保留即可。mosaic默认是 1.0但对冰箱食材这种目标分散、背景多为层板的图拼出来的图既不真实又容易让模型学到奇怪的背景纹理数据集小的话直接把mosaic关到 0.2 甚至 0。动手训练前先把各类别的数量分布打出来看一眼import os from collections import Counter def check_class_balance(label_dir): counter Counter() for f in os.listdir(label_dir): if not f.endswith(.txt): continue with open(os.path.join(label_dir, f)) as ff: for line in ff: cls line.split()[0] counter[cls] 1 return counter print(check_class_balance(datasets/fridge_food/labels/train))这段脚本统计每个类别 ID 出现的总次数。如果发现某类数量低于平均值的 1/3我一般回去补拍而不是硬跑训练。补拍时要把冰箱空层、门边置物架也拍进背景否则推理时模型会把层板阴影错认成食材。3. 用 YOLOv8 训练食材检测模型命令、参数曲线与部署前导出3.1 最小可用训练命令从 data.yaml 到 epochs 和 batch训练前先把data.yaml写好这是把数据集喂给 YOLOv8 的唯一入口train: ./datasets/fridge_food/images/train val: ./datasets/fridge_food/images/val test: ./datasets/fridge_food/images/test nc: 15 names: [apple, banana, milk, cucumber, tomato, egg, cola, yogurt, carrot, bread, meat, pepper, orange, strawberry, cheese]train和val路径指向图片目录即可YOLOv8 会自动在同一级目录下找labels文件夹。nc必须和names列表长度一致少一个类或者顺序对不上都会造成标签错位这是训练自己的数据集时最容易出现且最难发现的问题之一。环境方面如果你的机器是 CPU 且没有显卡直接按 CPU 版安装即可。在 Ubuntu20.04 上搭建 YOLOv8 CPU 环境时核心依赖就两行pip install ultralytics pip install labelme opencv-python pyqt5然后开始训练。最小命令长这样yolo detect train \ modelyolov8n.pt \ datadatasets/fridge_food/data.yaml \ epochs80 \ imgsz640 \ batch8 \ devicecpu \ workers0 \ projectruns/detect \ namefridge_lv1参数含义逐一说明modelyolov8n.pt表示用 YOLOv8n 的预训练权重作为起点n 是最轻量版本适合毕设规模的数据和 CPU 机器不要一开始就上yolov8x训练时间成倍增加小数据集还容易过拟合。epochs设为 80 是因为食材检测任务在 40 轮之后 mAP 涨势就明显放缓80 轮能留出足够的收敛余量。batch在 CPU 上如果内存不够比如只有 8G改成 4 或 2。workers0在 Windows 和 CPU 环境下必须显式设置否则数据加载线程可能报错或把 CPU 占满。devicecpu明确告知框架不要尝试调用显卡如果手头有一张 gtx1660ti把它写成device0会把训练速度提升几倍CPU 版本跑一轮可能要十几分钟而 GPU 只要一两分钟。注意data.yaml里的路径不要用中文路径Ultralytics 对中文路径的支持在部分版本上有问题表现为训练时报NotADirectoryError或图片加载为 0 张。项目目录和数据集目录都用英文命名。3.2 损失函数、PR 曲线与混淆矩阵训练完先看哪里训练结束后不要只看最后一行mAP50要把runs/detect/fridge_lv1下的几张图都翻出来。confusion_matrix.png最重要它按“真实类别 x 预测类别”列出了误分类的分布。冰箱食材里形状接近的类别最容易混比如可乐易拉罐和酸奶杯、胡萝卜和黄瓜切成段以后的样子如果混淆矩阵里这两个类互相串得厉害直接合并成一个类“罐装饮料”或“蔬菜块”比硬拆更稳。results.png里画着 loss 曲线。重点看val/cls_loss如果验证集分类损失持续不降或后期反弹基本可以判定标注里有错类或漏标回数据集检查比调参更有效。mAP50 对冰箱食材来说应该能到 0.9 左右因为目标大、类别差异明显如果只有 0.6几乎都是标注和数据集问题先不要怀疑模型结构。mAP50-95 在 0.5 上下浮动是正常的食材有透明包装和反光框对框的 IoU 边界本来就不够干净。还有一个小技巧训练时加上plotsTrue会让每个 epoch 结束自动更新 results 图。你不一定要等全部训练完中途瞄一眼 loss 曲线就能预判这轮训练是不是崩了。如果发现 loss 在 epoch 20 后就不降了直接 CtrlC 停掉回第 2 章看数据不要白等 80 轮。3.3 导出 ONNX 并做部署前的 FP16/FP32 权衡训练完拿到best.pt如果在界面里直接from ultralytics import YOLO加载 pt 文件也可以但整套推理连带 torch 依赖的体积会很大打包 exe 时非常痛苦。更干净的路线是先导出 ONNXyolo export modelruns/detect/fridge_lv1/weights/best.pt formatonnx opset12 imgsz640导出完成后用 onnxruntime 做 CPU 推理。下面的代码演示最小读取流程import cv2 import numpy as np import onnxruntime as ort session ort.InferenceSession(best.onnx, providers[CPUExecutionProvider]) input_name session.get_inputs()[0].name img cv2.imread(test.jpg) img cv2.resize(img, (640, 640)) # 模型输入要求 RGB、归一化到 0~1、shape 为 [1,3,640,640] rgb cv2.cvtColor(img, cv2.COLOR_BGR2RGB) input_img rgb.transpose(2, 0, 1)[None] / 255.0 outs session.run(None, {input_name: input_img.astype(np.float32)})[0] print(outs.shape) # 例如 (1, 19, 8400)19 4 个坐标 15 个类别这里的outs.shape第一维的 19 是 bbox 四个坐标加 15 个类别的分类得分8400 是 YOLOv8 在 640 输入下三个尺度特征图拼出来的候选框总数。拿到outs后还需要自己做 NMS 解码才能得到最终检测框这个解码步骤如果是纯 Python 写CPU 上会拉高延迟所以毕设里更省事的做法是界面端仍然用YOLO(best.pt)的model.predict()ONNX 更多用于 C、C# 或者 rk3588 这类板端部署——板端工具链转换时对 opset 版本和输入尺寸非常敏感转换前把imgsz固定为训练值别在工具链里改尺寸。还有一个 CPU 部署的通用经验导出 ONNX 时如果选择halfTrue生成 FP16 模型CPU 上的 onnxruntime 不一定支持半精度部分机器会报“不支持的算子”或精度骤降。CPU 部署就保留 FP32FP16 是给 GPU 或 NPU 准备的。这个细节不搞清楚导出后界面直接白屏排查半天才发现在这。4. 把模型接进可视化界面PyQt5 界面上的食材分层管理4.1 界面功能拆分显示识别结果与分层面板一个能上答辩台的界面通常左侧是实时画面或静态图片显示区右侧是食材列表和分层管理面板。识别结果必须能映射到“上层 / 中层 / 保鲜抽屉”然后给出统计信息本层有鸡蛋 6 枚、牛奶 1 盒。这里我给界面拆成两个核心类LayerPanel负责层和食材的展示DetectController负责调用 YOLO 并返回检测结果。检测结果用一个 dataclass 传值不给界面到处塞裸列表from dataclasses import dataclass dataclass class Detection: cls_id: int cls_name: str conf: float xyxy: tuple layer_id: int为什么这样拆Dataclass保证界面侧读取字段名清晰不会出现“第一个元素是类别还是置信度”这种低级歧义。layer_id在推理线程里填好界面直接按层分组显示不用在 UI 回调里再算一次归属。如果你做的是课程设计这个结构会被评阅老师认为“工程上有设计感”比全写在一个main.py里好很多。4.2 用 QThread 和队列接 YOLOv8 推理不卡界面的写法如果在按钮的on_click回调里直接写model.predict()PyQt 界面会卡死到推理结束才有响应。画面分辨率稍高一点卡 2 秒以上是常事答辩现场一卡体验直接翻车。正确做法是单独开一个后台推理线程从队列里拿帧把结果用信号发回主线程import queue from PyQt5.QtCore import QThread, pyqtSignal from ultralytics import YOLO class InferenceThread(QThread): result_ready pyqtSignal(object, list) status_changed pyqtSignal(str) def __init__(self, model_path, conf0.35, iou0.45): super().__init__() self.model YOLO(model_path) self.frame_queue queue.Queue(maxsize2) self.conf conf self.iou iou self._running True def run(self): while self._running: try: frame self.frame_queue.get(timeout0.2) except queue.Empty: continue result self.model.predict(frame, confself.conf, iouself.iou, verboseFalse)[0] det_list [] for box in result.boxes: det_list.append(Detection( cls_idint(box.cls[0]), cls_nameresult.names[int(box.cls[0])], conffloat(box.conf[0]), xyxytuple(box.xyxy[0].tolist()), layer_id0 )) self.result_ready.emit(frame, det_list)线程里做了一件关键的事把frame_queue的maxsize限制为 2。摄像头画面一秒钟几十帧而 CPU 推理一秒钟不到一帧如果不丢弃旧帧队列会越积越多界面实时性越来越差。主线程把当前帧put()进去时如果队列已满可以直接queue.put(frame, timeout0)并忽略 Full 异常保证始终用最新的那帧。result_ready信号回到主界面槽函数后统一做两件事一是在画面上画检测框二是把det_list按assign_layer()分组刷新到图层面板。不要在槽函数里再做耗时操作槽函数的执行也会阻塞 UI。4.3 exe 打包与资源路径修正部署给评委演示的坑毕设演示经常要求脱离开发环境运行需要打包成 exe。PyQt5 加 ultralytics 打包是个公认的坑命令上我一般这样写pyinstaller -D main.py -n FridgeManager \ --collect-data ultralytics \ --collect-data torch \ --hidden-importtorch \ --hidden-importcv2--collect-data ultralytics这行必须带否则运行时程序会在深处找不到ultralytics/cfg/default.yaml报错弹窗一闪而过查都查不到。-D生成文件夹模式而不是单文件单文件模式启动时要先解压食材模型加 torch 动不动几百兆启动要等十几秒展示效果很差。排错阶段不要加-w保持控制台窗口能看到 traceback等确认能正常跑再关掉。打包后另一个高频坑是模型和资源文件路径失效。PyInstaller 会把代码解到临时目录直接写Path(__file__).parent会指向临时目录模型文件根本没被打进去。统一用下面的方式处理import sys from pathlib import Path def resource_path(relative: str) - Path: if getattr(sys, frozen, False): base Path(sys.executable).parent else: base Path(__file__).parent return base / relative MODEL_PATH resource_path(models/best.pt)这段代码的思路是打包成 exe 时模型放在 exe 同级的models目录下用sys.executable找到 exe 所在位置未打包运行时用当前脚本所在目录。把best.pt和界面资源放在 exe 外面还有一个好处模型更新时不用重新打包整个程序。提示如果打包后 exe 双击闪退先在命令行窗口运行 exe 看报错。缺 dll、缺 torch 依赖、资源路径找不到是三个最常见原因命令行里的报错信息比任何日志都直接。5. 食材识别的四个高频翻车点与排查方法识别不到、重复框、界面卡死与 CPU 太慢5.1 现象拍出来的食材检测不到或漏检冰箱门一开真实画面比训练集昏暗LED 条灯在塑料包装上打出一片高光模型输出空列表或者只检出零散几个框。原因通常有三个一是训练集里每类图片都是在某一固定角度拍的推理视角大幅偏移二是conf_thres设得偏高中小目标得分一低就被过滤三是推理时图像通道顺序被搞反OpenCV 读出来是 BGR送进模型前又转了一次导致颜色错乱。排查时先把置信度降到 0.1 跑一遍看框是否出现。能出框说明模型没坏把阈值调回 0.3 或 0.35 再试。如果 0.1 还是不出框去检查数据集的验证集里这类食材的 mAP 到底是多少——如果验证集本身就检测不到那是数据问题补拍当前角度图片做 20 轮微调比调阈值有用得多。颜色问题则是低级高频坑推理管线里只保留一次cv2.cvtColor不要调试代码里加一回、正式代码里又加一回。5.2 现象同一个食材出现重复框并来回跳视频推理时同一罐可乐前后帧出来两个重叠框坐标在目标左右抖动。原因主要是iou_thres设得太低NMS 合并不了同一目标的相邻候选框另一种可能是同一帧被重复送进推理或者后处理里没有在连续帧之间做去重。解决方法是把iou_thres固定在 0.45 到 0.5 之间先保证单帧结果干净。如果视频流里还在跳加一个最简单的生命周期机制某个框连续出现 3 帧才显示只出现 1 帧的当噪声丢掉。这个过滤逻辑要在InferenceThread的结果信号发出去之前做而不是在界面里做否则界面侧还得维护状态代码复杂度翻倍。5.3 现象点“开始识别”界面直接假死按钮点了之后窗口转圈拖动无响应等推理结束才恢复期间任何点击都无效。原因不用怀疑推理被放在主线程执行了。这是 PyQt 项目里最常见的翻车现场凡是直接在按钮回调里写了model.predict()的都会卡。也有人在回调里写time.sleep模拟处理一样卡。解决就是用第 4.2 节的 QThread 或QtConcurrent.run把推理挪到后台。另外注意两条信号连到槽函数后不要在后台线程里直接改界面的 QLabel 和 QTableWidget必须通过信号让主线程更新程序退出时要调用推理线程的停止方法否则关掉主窗口后线程还在后台跑进程不会结束表现为“关了窗口但程序还在任务管理器里”。5.4 现象笔记本 CPU 推理要几秒体验太差演示机器没有独显CPU 推理 640 分辨率的 YOLOv8n 一张图要 1 到 2 秒如果换用 yolov8s 直接超过 3 秒。原因除了模型推理本身耗时还有 Ultralytics 的predict()默认自带预处理、NMS、画框后处理每一步在 CPU 上都会被放大。尤其画框时如果还绘制中文标签字体渲染也会占用几十毫秒。解决按顺序尝试一训练和部署都坚持用 n 系列模型不要因为训练时用了 s 效果好就顺手部署 s二推理分辨率从 640 降到 480速度能提升将近一倍前提是你对检测框大小的要求不那么苛刻三如果还嫌慢改用cv2.dnn加载 ONNX 模型并手写后处理把整个预测压到几百毫秒以内四实在顶不住实时视频就把演示模式改成“选择静态图片识别”点击按钮出结果先把功能演完再谈优化。答辩现场宁可操作慢一点也不能让画面卡死在那里。6. 从毕设到真实落地模型验证指标之外的三件小事模型训练完了、界面也能跑了距离一个“能拿去演示”的项目其实还差三件小事这三件事我每次都最后做因为每一件都在答辩现场救过我。第一件事是把训练参数和数据集版本记录成一张表。用哪个data.yaml、跑了多少轮、conf和iou是多少、最终 mAP50 多少全部写进项目的README或train_log.md。评阅提问时最常被问到的不是“你这个模型的创新点在哪”而是“你的数据集怎么划分的、用了什么超参数、模型为什么选 n 不选 s”。没有记录回答就会含糊有记录你还能顺带讲清楚改过哪一轮参数、效果发生了什么变化。这份记录本身就是课程设计报告的一部分。第二件事是做一套“演示故障预案”。我习惯在界面里加一个“载入示例图”按钮点击后在本地加载三张测试图其中一张必须覆盖“食材堆叠、光线偏暗、多层同时出现”的情况。演示现场如果摄像头驱动出问题或者网络差点到模型加载失败点这个按钮仍然能出结果。在开场白里主动说“我先用样例图演示一遍再切实时摄像头”比刻意追求实时流畅要稳妥得多。第三件事是预留一个可改进的口子。食材分层识别做完后往上叠一个“保质期提醒”给每个食材类别设置建议保存天数界面按层统计时顺带标出“已存放超过 5 天的食材”。这个功能不需要改动模型只做规则映射但对整个项目的完成度提升非常明显。评阅一眼就能看出这个系统不是只做了识别还考虑了实际场景里的管理需求。我把这套做法带进每个相似项目里先固定数据基线再调界面最后才处理演示环境和说法。你会发现模型精度差一点靠阈值和角度也能兜住但流程不完整、界面卡死、资源路径缺文件是一眼就能被否掉的问题。希望这篇关于 YOLOv8 冰箱食材分层管理的拆解能帮到你按“数据集 → 训练 → 界面 → 部署”的顺序走比绕开坑直接跑源码更值得。本文还有配套的精品资源点击获取
返回列表