ARTICLE DETAIL

资讯详情

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

YOLOv8实战:食品包装生产日期检测与部署全流程解析

YOLOv8实战:食品包装生产日期检测与部署全流程解析 简介基于YOLOv8的食品包装生产日期识别项目面向计算机、人工智能、自动化等专业的毕业生、课程设计学生以及刚接触目标检测的初学者。资源包为zip格式共8个文件包含3个Python脚本分别负责模型训练、视频检测和可视化页面设计、3个模型权重文件涵盖YOLOv8与YOLO11系列以及2个说明文档压缩包仅15.91MB轻量易用。代码已通过运行验证可自动生成核心指标曲线、混淆矩阵、F1分数曲线、精确率-召回率曲线、验证集预测结果及标签分布图覆盖训练评估全流程。配套的可视化界面简化了操作部署说明则帮助快速搭建环境适合从零复现或在现有基础上扩展功能。目前已有77人学习使用无论是毕业设计还是课程设计都能作为扎实的项目起点。1. 从食品包装生产日期到 YOLOv8这个小目标比你想象的更吃细节做毕业设计、课程设计或产线视觉预研的人看到《基于YOLOv8的食品包装生产日期识别》这类资源时第一反应通常是两个问题它到底能不能跑以及跑出来能不能应付答辩演示。我的答案是能但前提是别把它当成一个“丢张图进去就输出一行日期字符串”的黑匣子。这份资源真正解决的是“生产日期喷码区域定位”这个目标检测问题用 YOLOv8 在包装袋、瓶盖、纸盒上找到日期码所在位置再配合可视化界面完成检测结果回显。适合刚入门深度学习和计算机视觉的人也适合需要快速完成毕业设计或课程设计的学生。我拆完这套资源后最直观的感受是它把数据集、源码、界面、部署教程凑齐了省掉的是最耗时的“从零攒数据”阶段但真正决定分数高低的还是会调参数、能说清踩坑点的人。2. 资源结构与数据准备train/val 目录、data.yaml、标注格式2.1 拿到资源先看这几个文件别急着点 train.py我拿到这类资源时习惯先不碰训练脚本花十分钟把目录结构摸清楚。这种做法不是谨慎过度而是很多毕设资源里往往包含两三套代码有的能直接跑有的只是参考README 里写的主入口未必是真正能跑的那个。先看目录结构能让后面每一步都在可控范围内。这份资源里常见的目录组织是这样的D:/date_project ├─ datasets │ ├─ images/train │ ├─ images/val │ ├─ labels/train │ ├─ labels/val │ └─ data.yaml ├─ weights ├─ gui │ └─ main_window.py ├─ train.py ├─ detect.py └─ requirements.txt先解释一下我的判断datasets里如果已经有images和labels两个平级目录说明标注格式大概率是 YOLO 的 txt 格式这是 YOLOv8 最省事的输入方式如果labels目录里放的是 Pascal VOC 的 xml 文件则说明还需要一步转换不能直接开训。gui/main_window.py是 PyQt5 或 PySide6 的可视化界面入口train.py和detect.py分别是训练和推理脚本。我把这些文件看了一遍后会先用cat datasets/data.yaml确认类别定义再随机翻开一个标注文件看内容。cat datasets/data.yaml type datasets/labels/val/00001.txt第一条命令查看训练配置第二条命令在 Windows 环境查看标注内容。YOLO 标注文件每一行五个数格式是class_id cx cy w h其中 cx、cy 是目标框中心点的归一化坐标w、h 是框宽高归一化到图像尺寸后的值。如果看到的是0 0.513 0.721 0.044 0.028说明类别 id 为 0框中心在图像大约 51% 水平位置、72% 垂直位置。这个细节很重要很多人后面转换标注时把中心点写成了左上角结果模型怎么训都发疯。2.2 验证标注格式和类别先跑通再改数据在正式训练之前我强烈建议先做一遍标注合法性检查而不是直接开始训练。训练跑了一晚上最后发现 loss 不下降往往不是模型问题而是数据格式问题。最简单的方式是写一个校验脚本扫描所有 txt 文件统计每一行是否恰好五个数字以及归一化坐标是否都落在 0 到 1 之间。import glob bad_lines [] for txt in glob.glob(datasets/labels/**/*.txt, recursiveTrue): with open(txt, r, encodingutf-8) as f: for line_no, line in enumerate(f.read().splitlines(), 1): parts line.split() if len(parts) ! 5: bad_lines.append((txt, line_no, 字段数不为5)) continue try: class_id int(parts[0]) cx float(parts[1]) cy float(parts[2]) w float(parts[3]) h float(parts[4]) except ValueError: bad_lines.append((txt, line_no, 数字格式错误)) continue if not (0 cx 1 and 0 cy 1 and 0 w 1 and 0 h 1): bad_lines.append((txt, line_no, 归一化坐标超范围)) print(f共发现 {len(bad_lines)} 处问题) for item in bad_lines[:20]: print(item)这段代码的作用是把所有标注文件过一遍只要有一行格式不对就记录下来。参数说明glob递归匹配labels下的 txt 文件line.split()按空格拆分class_id 必须能转成整数坐标必须能转成浮点数。我把这段脚本放在项目根目录下取名为check_labels.py跑完如果输出为空再往下走如果报错先修数据不要用脏数据训练。2.3 自建数据从抽帧、清洗到 labelImg 转 YOLO 格式如果你打算给这份资源补充自己的生产日期数据最省力的流程是先抽帧再清洗再标注最后转格式。常见做法是用 ffmpeg 从产线视频里抽帧因为食品包装在传送带上会移动直接截图可能全是模糊帧。ffmpeg -i packing_line.mp4 -vf fps2 frame_%04d.jpg-vf fps2表示每秒抽两张30 秒视频能得到大约 60 张候选图。抽帧后我会把模糊、反光、重复度太高的帧删掉保留 200 到 500 张有效图就够了。标注工具用 labelImg 或 labelme 都可以注意 labelImg 默认保存 Pascal VOC 的 xml需要再转成 YOLO txt。转换脚本我一般写成这样import glob import os import xml.etree.ElementTree as ET classes [date] # 与 data.yaml 的 names 顺序保持一致 for xml_path in glob.glob(annotations/*.xml): tree ET.parse(xml_path) root tree.getroot() img_w int(root.find(size/width).text) img_h int(root.find(size/height).text) lines [] for obj in root.findall(object): cls_name obj.find(name).text if cls_name not in classes: continue class_id classes.index(cls_name) box obj.find(bndbox) xmin float(box.find(xmin).text) ymin float(box.find(ymin).text) xmax float(box.find(xmax).text) ymax float(box.find(ymax).text) cx ((xmin xmax) / 2) / img_w cy ((ymin ymax) / 2) / img_h w (xmax - xmin) / img_w h (ymax - ymin) / img_h lines.append(f{class_id} {cx:.6f} {cy:.6f} {w:.6f} {h:.6f}) txt_path os.path.join(labels, os.path.basename(xml_path).replace(.xml, .txt)) with open(txt_path, w, encodingutf-8) as f: f.write(\n.join(lines))这段脚本把 xml 里的 bndbox 左上角和右下角坐标转换成 YOLO 需要的中心点坐标和宽高最后全部归一化到 0 到 1 之间。参数说明classes的顺序就是最终的 class id如果 data.yaml 里0: date这里必须把date放在列表第一位。还有一个容易踩的坑如果可以把标注框画得贴近字符边缘不要为了省事把一整块“生产日期2025-03-14”连商标一起框进去框太松会让模型学到的特征包括大量背景泛化能力反而下降。3. 环境搭建与模型训练从 requirements.txt 到 results.png3.1 环境安装先按 requirements.txt 装再验证 torch 是否可用环境问题是这套资源里最劝退新手的部分。实际上并不复杂只是需要按顺序来。我推荐用 conda 建一个独立环境避免把系统里的其他深度学习环境搞乱。conda create -n date_yolo python3.10 -y conda activate date_yolo pip install -r requirements.txtrequirements.txt里通常会包含 ultralytics、torch、torchvision、PyQt5、opencv-python、numpy、pandas。需要注意torch 版本不能太新也不能太老最好按资源里锁定的版本装。装完后立即验证导入是否正常python -c from ultralytics import YOLO; print(ultralytics ok)如果这里报错先看是不是 torch 和 torchvision 版本不匹配。没有 GPU 的机器也能训练CPU 环境下把batch调小、epochs稍微缩短训练几百张生产日期图是可行的只是更慢。若你的机器只有 CPU还可以单独装 CPU 版 torchpip install torch torchvision --index-url https://download.pytorch.org/whl/cpu这种装法会明显减少安装体积但注意之后不要再装带 CUDA 的 torch否则环境会被覆盖掉。我一般会确认一句“当前这台机器有没有 GPU”再决定是否装 CUDA 版避免装完发现推理时用的还是 CPU。3.2 训练配置data.yaml、预训练权重、输出目录训练前一定要检查data.yaml的路径和类别这是翻车率最高的地方。这份资源自带的datasets/data.yaml大致的结构是path: /home/user/date_project/datasets train: images/train val: images/val names: 0: datepath最好写成项目所在的绝对路径因为在命令行里运行yolo detect train时工作目录可能不是项目根目录相对路径很容易找不到图片。train和val是相对path的目录名names下的类别编号必须和标注文件里的 class id 一致。如果你自己扩了数据类别名改成date_code、expire都可以但不要出现两个类别却共用同一份标注。确认无误后直接跑训练命令yolo detect train modelyolov8n.pt datadatasets/data.yaml epochs100 imgsz640 batch16 patience20 projectruns/train namedate_exp参数说明modelyolov8n.pt是预训练权重首次运行会自动下载data指向 data.yamlepochs100表示最多训练 100 轮batch16是批大小显卡显存不够就降到 8 或 4imgsz640是输入分辨率patience20表示连续 20 轮验证指标没有提升就提前停止project和name决定输出目录最终权重在runs/train/date_exp/weights/下。训练结束后不要只看 terminal 里的日志要看runs/train/date_exp/results.png这张图里有训练集和验证集的 box_loss、cls_loss、mAP50 等曲线。3.3 训练参数怎么调imgsz、batch、epochs、patience 的选择逻辑对于生产日期这种小目标识别我一般会把imgsz从 640 调到 960因为喷码字符在整张包装图里占比很小640 输入下字符特征可能只剩十几个像素。调大输入能让细节更丰富但训练时间和显存占用也会跟着涨。batch的选择优先考虑显存。我的习惯是先设 16如果报 CUDA out of memory就减半如果显存有余量且训练集较大可以提到 32。epochs不要一看默认 100 就安心关键要看验证集停止提升的位置。patience我保留 20 到 30避免权重停在过拟合阶段。训练完随手测一下yolo predict modelruns/train/date_exp/weights/best.pt sourcesamples/test.jpg conf0.25 iou0.5 saveTrue这里的conf是置信度阈值iou是 NMS 的交并比阈值。我通常先用 0.25 看召回如果框多但误检也多再往 0.35 抬高如果漏检严重先不要动阈值回训练侧调数据或调imgsz。阈值只是最后一层过滤真正决定上限的是训练数据的质量和标注框边界。4. 可视化界面与部署从图片/摄像头到结果回显4.1 可视化界面怎么组织入口文件、模型加载、图片选择这份资源里的可视化界面是毕业设计演示最加分的地方。界面代码一般集中在gui/main_window.py核心逻辑并不复杂加载 YOLO 模型用户选择图片或开启摄像头推理后把检测框画在图像上再通过 QLabel 显示。一个最小可用的 PyQt5 界面可以写成这样import cv2 from PyQt5.QtWidgets import QFileDialog, QLabel, QMainWindow, QPushButton from PyQt5.QtGui import QImage, QPixmap from ultralytics import YOLO class DateWindow(QMainWindow): def __init__(self): super().__init__() self.model YOLO(weights/best.pt) self.image_label QLabel(self) self.open_btn QPushButton(选择图片, self) self.open_btn.clicked.connect(self.open_image) self.current_rgb None def open_image(self): file_path, _ QFileDialog.getOpenFileName( self, 选择图片, , Image Files (*.jpg *.jpeg *.png)) if not file_path: return results self.model.predict( sourcefile_path, conf0.25, iou0.5, verboseFalse)[0] img cv2.imread(file_path) for box in results.boxes: x1, y1, x2, y2 [int(v) for v in box.xyxy.tolist()[0]] cv2.rectangle(img, (x1, y1), (x2, y2), (0, 200, 0), 2) cv2.putText(img, fdate {float(box.conf[0]):.2f}, (x1, max(0, y1 - 10)), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 200, 0), 2) rgb cv2.cvtColor(img, cv2.COLOR_BGR2RGB) self.current_rgb rgb h, w, _ rgb.shape qt_img QImage(rgb.data, w, h, 3 * w, QImage.Format_RGB888) self.image_label.setPixmap(QPixmap.fromImage(qt_img).scaled(900, 600))这段代码的逻辑是点击按钮后弹出文件选择框拿到图片路径后直接调用model.predict然后把检测框坐标从结果里取出来用 OpenCV 画矩形框和置信度最后转成 PyQt 能显示的 QImage 上屏。关键参数在于verboseFalse能避免推理时终端刷屏QImage.Format_RGB888必须配合三通道 RGB 数据使用如果直接喂 BGR 图颜色会乱。还有一点要注意QImage底层引用的是 numpy 数组内存所以我把rgb存到self.current_rgb防止局部变量被回收后界面出现花屏或黑图。4.2 界面参数conf、iou 怎么暴露给使用者单纯写完界面不等于好用。我在做演示时遇到过现场环境光线变化固定置信度阈值根本不现实。更好的做法是把conf和iou做成滑块或输入框让使用者自己调。from PyQt5.QtCore import Qt from PyQt5.QtWidgets import QSlider self.conf_slider QSlider(Qt.Horizontal, self) self.conf_slider.setRange(10, 90) self.conf_slider.setValue(25)使用时把滑块值除以 100 当作conf例如滑块在 25 就表示conf0.25。原因很简单生产日期喷码在不同材质的包装上对比度差异很大有些铝箔包装反光严重0.25 会同时框出很多反光区域调到 0.4 以后误检减少但模糊喷码可能漏掉。把阈值交给使用者在界面上实时调节比每次改代码重新跑要高效得多。4.3 部署best.pt 转 ONNX 和最小推理脚本毕业设计只要求本地演示的话直接用 PyTorch 权重就够了。如果希望部署到没有深度学习框架的机器上或者做简单的服务封装我会把best.pt导出成 ONNXyolo export modelruns/train/date_exp/weights/best.pt formatonnx imgsz640 opset12 simplifyTrue dynamicTrue参数说明formatonnx指定导出格式imgsz640要和训练时保持一致否则框位置会偏opset12兼容性较好老一点的 ONNX Runtime 也能加载simplifyTrue会让计算图更干净dynamicTrue允许输入尺寸动态变化代价是推理速度略有下降。导出完成后用 ONNX 权重做最小推理from ultralytics import YOLO model YOLO(runs/train/date_exp/weights/best.onnx) results model.predict(samples/test.jpg, conf0.25, iou0.5) for r in results: for box in r.boxes: print(box.xyxy.tolist(), int(box.cls[0]), float(box.conf[0]))这段脚本输出每个检测框的坐标、类别 id 和置信度。注意box.xyxy.tolist()返回的是像素坐标box.conf是浮点数。如果你脱离 ultralytics 直接用 ONNX Runtime需要自己实现 letterbox也就是先把图片等比缩放再补边转成 RGB 并归一化到 0 到 1输入 tensor 的顺序是NCHW这些细节缺一个都会导致检测结果和 PyTorch 版本不一致。5. 避坑与排查训练到部署的五个常见翻车点这条避坑清单来自我拆这类资源时的实际排查记录。每一条都按“现象、原因、解决”写清楚照着对号入座能省下半天时间。现象训练一开始就报labels not found或者 loss 完全不动。原因data.yaml 里的path写成了相对路径命令行运行目录又不在项目根目录下导致图片路径全部失效另一种可能是标注 txt 里 class id 超出 data.yaml 的 names 数量模型读到非法类别直接放弃学习。解决把 data.yaml 的path改成绝对路径并先用yolo detect train里同样的data参数跑一遍输出检查。或者先执行python check_labels.py确认所有标注行都是五列且 class id 合法。注意 YOLO 格式的第一列是数字 id不是类别名0和date不能混用。现象小目标检测效果差640 输入下日期区域频繁漏检mAP50 很高但实际看不清框在哪。原因生产日期喷码在整张包装图里往往只占几个百分点面积512 或 640 的输入分辨率让字符特征在多次下采样后丢失。训练集和验证集如果都来自同一批包装、同一光线模型会学到“这一版面的固定位置有字”而不是学习字符本身真实场景一换包装就翻车。解决把imgsz提到 960训练时开启适当的数据增强比如随机亮度、对比度、轻微旋转和模糊。更实用的做法是对喷码区域做裁剪放大后再训练一个专门检测器或者把检测框区域直接作为后续 OCR 的输入检测模型只负责精确定位。现象PyQt 界面能打开但一点图片按钮直接卡死或者闪退。原因model.predict被放在 UI 主线程里执行模型加载和大图推理会阻塞 Qt 事件循环表现是窗口无响应另外如果代码里同时调用了cv2.imshowOpenCV 自带的窗口系统会和 PyQt 的事件循环抢资源闪退就来了。解决推理代码放到 QThread 中主线程通过信号接收结果并刷新 QLabel所有结果展示统一用 QImage 转 QPixmap不要再用cv2.imshow。如果只是课设演示可以先把imgsz调低到 640图片缩放后再推理界面流畅度会明显改善。现象用导出的 ONNX 推理时检测框和 PyTorch 结果不一致尤其小目标丢框。原因导出时输入尺寸和推理时不一致或者输入预处理时没有做 letterbox。Ultralytics 在 PyTorch 推理时默认会做等比例缩放补边但 ONNX Runtime 裸调用时只按原始图尺寸喂进去模型输入尺寸不匹配框自然就飘了。解决导出一个固定imgsz640的 ONNX推理前先把图片 resize 到 640×640做同样的 letterbox 操作。我先用同一张图分别跑best.pt和best.onnx把两种结果画在同一张图上做 diff确认差别可以接受后再封装给界面用。现象训练曲线很好看验证集 mAP50-95 却在一半 epoch 后持续下跌。原因这是典型的过拟合模型开始记住日期在固定版面上的位置而不是字符形状。很多课程设计数据集只有一两百张图且全部来自同一产品随机拆分训练集和验证集会加剧这种假象。解决按视频段或拍摄批次划分 train/val不要让同一条传送带连续帧同时出现在两个集合里。训练时保留patience30最终权重用best.pt不是last.pt。如果模型在“没有日期的包装”上硬画框可以补几十张无目标背景图放进训练集让负样本逼模型学会拒绝。6. 进阶采集与分析把识别框按时间戳写入追溯表可视化界面适合现场演示但要说清楚“检测结果有什么用”我建议把识别结果落成 CSV 追溯表。这样不仅答辩时有据可查做产线验证时也能统计白天和夜班的漏检率。我用一个脚本遍历某批图片把每个检测框坐标和置信度按图片路径、检测时间写入 CSVimport csv from datetime import datetime from pathlib import Path from ultralytics import YOLO model YOLO(weights/best.pt) save_dir Path(trace_logs) save_dir.mkdir(exist_okTrue) log_path save_dir / detection_log.csv need_header not log_path.exists() or log_path.stat().st_size 0 for image_path in Path(samples).glob(*.jpg): result model.predict(str(image_path), conf0.25, iou0.5, verboseFalse)[0] boxes [] for box in result.boxes: x1, y1, x2, y2 [round(float(v), 1) for v in box.xyxy.tolist()[0]] conf round(float(box.conf[0]), 3) boxes.append((x1, y1, x2, y2, conf)) row { image_path: str(image_path), detected_at: datetime.now().strftime(%Y-%m-%d %H:%M:%S), box_count: len(boxes), boxes: boxes, } with open(log_path, a, newline, encodingutf-8) as f: writer csv.DictWriter(f, fieldnameslist(row.keys())) if need_header: writer.writeheader() writer.writerow(row)这段脚本把每次推理的图片路径、检测时间、框数量和所有框坐标追加到同一个 CSV 里。need_header用文件大小判断文件是否为空避免每次运行都重复写表头。boxes字段里存的是像素坐标元组后续要接 OCR 时可以直接按(x1, y1, x2, y2)裁剪区域把裁剪图片交给 PaddleOCR 或 Tesseract 做字符识别。对毕设来说这个“检测加追溯”的最小闭环比单独一张检测效果图更有说服力。我自己的习惯是每次拿到新的检测数据集都会强制先抽 200 张没参与训练的现场图跑一遍推理把漏检和错框按类目记进 CSV再回去调数据或参数。确认误检率压到可接受范围后才把权重交给别人演示。希望帮到你。本文还有配套的精品资源点击获取
返回列表