基于YOLOv8与PySide6的条码保质期识别系统实战

基于YOLOv8与PySide6的条码保质期识别系统实战 前一阵在做一个商品库存盘点的小工具核心需求很简单拿到一张商品图片自动识别条形码并把生产日期、保质期、到期日一起提取出来。真做起来才发现单靠 OpenCV 或者普通扫码 SDK很难同时处理模糊、倾斜、小尺寸条码尤其是画面里同时出现多个商品时传统图像处理流程几乎不可维护。后来换成 YOLO 做目标检测定位再用解码库解析条码最后用 PySide6 包一个桌面界面整套流程才稳定下来。本文会把这条完整链路拆开讲清楚如何准备条码和日期区域数据集如何训练 YOLOv8 模型如何用 pyzbar 解码条形码如何从 OCR 文本中解析生产日期和保质期以及如何用 PySide6 写出可操作的桌面程序。适合想入门 YOLO 目标检测、想把深度学习模型封装成桌面工具的开发者也适合正在做商品识别、仓库盘点、食品溯源相关项目的同学参考。1. 背景与系统设计思路1.1 为什么需要条形码保质期识别系统传统商品信息的录入方式主要靠人工观察包装再手动把条码、生产日期、保质期输入系统。这种方式有几个明显问题效率低一个熟练员工录入一件商品也要几秒钟遇到字体小、印刷模糊的日期还要反复辨认。容易出错人工抄录数字很容易看错尤其“2026”和“2025”、“01”和“07”这类近似数字。难追溯如果想把历史批次、到期日、仓库位置关联起来纯靠表格管理非常麻烦。如果做一个半自动识别系统操作员只需要把商品放在摄像头前或者上传一张商品图片系统就能自动完成“条码定位 - 条码解码 - 日期区域识别 - 保质期计算”整套流程不仅省人力还能把结果统一导出到数据库。这也正是本文系统要解决的核心问题。1.2 技术选型分析整个系统可以分为三部分每部分都有对应的技术方案。功能模块选用技术选型理由目标检测定位YOLOv8 / YOLOv5既能检测条形码区域也能检测生产日期区域对小目标和倾斜目标效果不错条码解码pyzbar免费开源支持 EAN-13、Code128、QR 码等多种格式Python 调用简单日期文本识别pytesseract可选 正则解析日期区域通常结构固定用 OCR 提取后再用正则计算简单可靠桌面 GUIPySide6Qt 官方 Python 绑定界面美观和 OpenCV 图像数据配合方便图像处理OpenCV裁剪、缩放、转灰度、摄像头读取等基础操作都依赖它其中 YOLOv8 是 ultralytics 团队维护的新一代 YOLO 系列训练命令简洁数据格式统一也自带推理、验证、导出函数。如果项目里用的是 YOLOv5训练过程和本文类似只是需要切换到 YOLOv5 自己的代码仓库。本文主要以 YOLOv8 为例演示从训练到部署的完整流程。1.3 整体识别流程把整个系统拆成一条流水线理解起来会清晰很多。输入图片或摄像头帧。YOLOv8 模型检测出两个目标类别barcode条码区域和date生产日期/有效期区域。根据检测框裁剪出对应区域。对barcode区域调用 pyzbar 解码得到条码字符串。对date区域做 OCR 识别再用正则提取“生产日期”和“保质期”。根据生产日期和保质期计算到期日。在 PySide6 界面上展示识别结果。这套流程的好处是每个环节都可以单独替换。比如 pyzbar 解不出来可以换成 OpenCV 的 barcode 模块pytesseract 识别效果不好可以换成 PaddleOCR 或者深度学习文字识别模型。2. 环境准备与依赖安装2.1 开发环境说明本文示例在 Windows 11 上测试通过Linux 系统的操作基本一致只需注意 pyzbar 需要额外安装系统库。Python 版本建议使用 3.9 或 3.10因为 PySide6 和 ultralytics 对高版本 Python 的兼容性更好。硬件方面训练模型建议使用 NVIDIA GPU显存最好在 6G 以上。我之前用 GTX 1660 Ti 6G 跑过 YOLOv8n 和 YOLOv8s把batch调小后也能正常训练。如果没有 GPUCPU 也能跑只是速度和显存无关训练时间会明显增加。2.2 创建虚拟环境并安装依赖首先创建虚拟环境避免和系统 Python 环境冲突。python -m venv venv venv\Scripts\activate激活环境后新建requirements.txtultralytics8.2.0 PySide66.7.0 opencv-python4.9.0.80 pyzbar0.1.9 pytesseract0.3.10 Pillow10.3.0 numpy1.26.4然后执行安装pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple这里需要注意pyzbar在 Windows 下依赖 Visual C 运行库如果导入报错需要安装 Microsoft Visual C Redistributable。在 Linux 下则需要安装系统库sudo apt-get update sudo apt-get install libzbar02.3 验证环境是否正常安装完成后运行下面这个脚本确认关键库都能正常导入import ultralytics import PySide6 import cv2 import pyzbar import pytesseract print(ultralytics:, ultralytics.__version__) print(PySide6:, PySide6.__version__) print(OpenCV:, cv2.__version__)如果打印出版本号说明环境已经准备好了。pytesseract 还依赖 Tesseract OCR 引擎本身需要单独安装。Windows 下可以下载安装包安装后把路径配置到代码里import pytesseract pytesseract.pytesseract.tesseract_cmd rC:\Program Files\Tesseract-OCR\tesseract.exe3. 数据集准备与 YOLOv8 模型训练3.1 标注目标与数据集结构模型要检测两个类别barcode条码区域包括一维条码和二维码。date生产日期、有效期、保质期等日期文字区域。准备数据集时尽量采集不同角度、不同光照、不同距离下拍摄的商品包装图。标注工具可以使用 LabelImg 或 LabelMe导出为 YOLO 格式的 txt 文件。数据集目录结构如下datasets/ ├── barcode_expiry.yaml ├── train/ │ ├── images/ │ │ ├── 001.jpg │ │ └── 002.jpg │ └── labels/ │ ├── 001.txt │ └── 002.txt └── val/ ├── images/ │ └── 101.jpg └── labels/ └── 101.txtYOLO 格式的标注文件中每一行代表一个目标class_id center_x center_y width height比如0 0.5234 0.6710 0.2450 0.1200 1 0.4100 0.8500 0.3200 0.0800class_id从 0 开始这里 0 代表barcode1 代表date。3.2 编写数据集配置文件在datasets/barcode_expiry.yaml中写入path: datasets/ train: train/images val: val/images names: 0: barcode 1: date这里path可以写绝对路径或相对路径如果程序在项目根目录运行相对路径即可。3.3 使用 YOLOv8 脚本训练模型在项目根目录创建train.pyfrom ultralytics import YOLO if __name__ __main__: model YOLO(yolov8n.pt) model.train( datadatasets/barcode_expiry.yaml, epochs100, imgsz640, batch16, device0, workers4, namebarcode_expiry, patience20, )参数解释data数据集配置文件路径。epochs训练轮数小数据集 100 轮足够。imgsz输入图片尺寸一般用 640条码较小可以试 960。batch批次大小根据显存调整显存不够就调成 8 或 4。device0 表示第一张 GPU没有 GPU 可以写cpu。patience早停轮数验证指标不再提升时提前停止。如果使用 YOLOv5训练命令需要切换到 YOLOv5 仓库但数据集格式一致核心思路是一样的。3.4 训练结果分析训练完成后结果保存在runs/detect/barcode_expiry/目录下重点关注results.png和weights/文件夹。results.png中包含了训练过程的多条曲线train/box_loss边界框损失越低越好。train/cls_loss分类损失越低越好。metrics/mAP50IoU 阈值 0.5 时的平均精度越高越好。metrics/mAP50-95更严格的综合精度指标。weights/best.pt是验证集上效果最好的权重后续推理就使用这个文件。如果发现模型对条码小目标检测不到优先增加数据集中小尺寸条码的图片比例或者把imgsz适当调大。如果数据量很少可以先下载 YOLOv8 预训练权重再在自建数据集上进行增量训练能明显加快收敛速度。3.5 测试和导出模型训练完成后可以先在测试图片上验证效果from ultralytics import YOLO model YOLO(runs/detect/barcode_expiry/weights/best.pt) results model.predict(sourcetest.jpg, conf0.35, saveTrue, imgsz640)如果需要在其他平台部署可以导出为 ONNX 格式model.export(formatonnx, imgsz640)导出的best.onnx可以用 ONNX Runtime 加速推理。4. 模型推理与条码识别核心逻辑模型负责“找到条码在哪、日期在哪”但真正读出条码内容、解析日期还需要另外封装逻辑。我建议把这块独立成detector.py和utils.py方便后期替换算法。4.1 加载模型并进行目标检测创建detector.pyimport cv2 import numpy as np from ultralytics import YOLO class Detector: def __init__(self, weights_path: str): self.model YOLO(weights_path) def detect(self, frame: np.ndarray, conf: float 0.35): results self.model.predict( sourceframe, confconf, imgsz640, verboseFalse, ) boxes [] classes [] confs [] for r in results: if r.boxes is None: continue xyxy r.boxes.xyxy.cpu().numpy() cls r.boxes.cls.cpu().numpy().astype(int) cnf r.boxes.conf.cpu().numpy() boxes.extend(xyxy.tolist()) classes.extend(cls.tolist()) confs.extend(cnf.tolist()) return boxes, classes, confs def crop(self, frame: np.ndarray, box): x1, y1, x2, y2 [int(v) for v in box] return frame[y1:y2, x1:x2]detect方法的返回结果中boxes是每个目标的左上角和右下角坐标classes是类别索引confs是置信度。这几个信息足够后续裁剪和展示。4.2 条形码解码逻辑对检测到的barcode区域先用 OpenCV 转成灰度图再调用 pyzbar 解码import cv2 from pyzbar.pyzbar import decode from PIL import Image def decode_barcode(crop_bgr: np.ndarray): if crop_bgr is None or crop_bgr.size 0: return None, None rgb cv2.cvtColor(crop_bgr, cv2.COLOR_BGR2RGB) pil_image Image.fromarray(rgb) decoded_objects decode(pil_image) if not decoded_objects: return None, None first decoded_objects[0] data first.data.decode(utf-8, errorsignore) barcode_type first.type return data, barcode_type这里有一点需要注意pyzbar 对图片清晰度比较敏感。如果条码在倾斜角度大、光照不均匀的环境下拍出来解码失败率很高。可以在裁剪后做一些预处理比如转灰度、自适应二值化、轻微腐蚀膨胀提高成功率。def preprocess_for_barcode(crop_bgr: np.ndarray): gray cv2.cvtColor(crop_bgr, cv2.COLOR_BGR2GRAY) gray cv2.equalizeHist(gray) _, binary cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY cv2.THRESH_OTSU) kernel cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3)) binary cv2.morphologyEx(binary, cv2.MORPH_CLOSE, kernel) return binary如果实际项目中遇到大量解码失败可以考虑用基于深度学习的条码识别模型替代 pyzbar。4.3 日期区域 OCR 与正则解析日期区域识别相对复杂因为每个商品包装上的日期格式都不一样。常见写法有生产日期2025/01/01生产日期2025年01月01日保质期12个月保质期180天有效期至2026-01-01先用 pytesseract 识别文本再用正则提取关键信息import pytesseract def ocr_date_region(crop_bgr: np.ndarray): rgb cv2.cvtColor(crop_bgr, cv2.COLOR_BGR2RGB) pil_image Image.fromarray(rgb) text pytesseract.image_to_string(pil_image, langchi_simeng) return text如果 OCR 结果里能识别出“生产日期”就用正则匹配日期import re def parse_production_date(text: str): patterns [ r生产日期[:\s]*(\d{4})[年/\-.](\d{1,2})[月/\-.](\d{1,2})日?, r(\d{4})[年/\-.](\d{1,2})[月/\-.](\d{1,2})日?, ] for pattern in patterns: match re.search(pattern, text) if match: year, month, day match.groups() return f{int(year):04d}-{int(month):02d}-{int(day):02d} return None保质期同样用正则解析def parse_shelf_life(text: str): month_match re.search(r保质期[:\s]*(\d)\s*个月, text) if month_match: return int(month_match.group(1)), month day_match re.search(r保质期[:\s]*(\d)\s*天, text) if day_match: return int(day_match.group(1)), day return None, None4.4 到期日计算拿到生产日期和保质期后可以用 Python 的datetime直接计算到期日from datetime import datetime, timedelta from dateutil.relativedelta import relativedelta def calc_expiry_date(production_date: str, value: int, unit: str): if not production_date: return None prod datetime.strptime(production_date, %Y-%m-%d) if unit month: expiry prod relativedelta(monthsvalue) else: expiry prod timedelta(daysvalue) return expiry.strftime(%Y-%m-%d)这里用到python-dateutil库需要安装pip install python-dateutil如果条码包含 GS1 应用标识符比如以]C1开头或者数据中间有(17)、(15)、(10)等标识也可以直接从条码解析有效期。下面是一个简单实现def parse_gs1_expiry(data: str): match re.search(r\(17\)(\d{6}), data) if match: raw match.group(1) year 2000 int(raw[:2]) month int(raw[2:4]) day int(raw[4:6]) return f{year:04d}-{month:02d}-{day:02d} return None不过不同条码类型的数据结构差异较大实际落地时需要拿真实条码验证。5. PySide6 桌面应用开发实战模型训练好之后接下来就要用 PySide6 把识别能力封装成桌面工具。这里采用“主窗口 摄像头识别线程 业务识别模块”的结构避免摄像头采集阻塞界面。5.1 界面布局设计主界面分为左右两个区域左侧图片显示区域用QLabel显示当前帧或上传的图片。右侧识别信息面板用QTextEdit显示条码、类型、生产日期、保质期、到期日等信息。底部放四个按钮打开图片打开摄像头开始识别停止识别5.2 创建摄像头识别线程摄像头实时识别不能在主线程中运行否则界面会卡死。这里用一个QThread子类来读取摄像头帧并通过信号把结果传给主线程。创建camera_thread.pyimport cv2 import numpy as np from PySide6.QtCore import QThread, Signal from detector import Detector class CameraThread(QThread): frame_ready Signal(np.ndarray) result_ready Signal(dict) error_ready Signal(str) def __init__(self, detector: Detector, parentNone): super().__init__(parent) self.detector detector self.running False def run(self): cap cv2.VideoCapture(0) if not cap.isOpened(): self.error_ready.emit(无法打开摄像头) return self.running True while self.running and not self.isInterruptionRequested(): ret, frame cap.read() if not ret: continue self.frame_ready.emit(frame) boxes, classes, confs self.detector.detect(frame) result { barcode: , production_date: , shelf_life: , expiry_date: , } for box, cls, conf in zip(boxes, classes, confs): crop self.detector.crop(frame, box) if cls 0: data, btype decode_barcode(crop) result[barcode] data or result[barcode_type] btype or elif cls 1: text ocr_date_region(crop) production parse_production_date(text) shelf_value, shelf_unit parse_shelf_life(text) if production: result[production_date] production if shelf_value: result[shelf_life] f{shelf_value} {shelf_unit} result[expiry_date] calc_expiry_date( production, shelf_value, shelf_unit ) self.result_ready.emit(result) cap.release() def stop(self): self.running False self.requestInterruption()frame_ready信号用于把视频帧发送到主窗口显示result_ready信号用于把识别结果发送到主窗口刷新信息面板。5.3 主窗口代码创建main.pyimport sys import cv2 from PySide6.QtCore import Qt from PySide6.QtGui import QImage, QPixmap from PySide6.QtWidgets import ( QApplication, QFileDialog, QHBoxLayout, QLabel, QMainWindow, QPushButton, QTextEdit, QVBoxLayout, QWidget, ) from camera_thread import CameraThread from detector import Detector class MainWindow(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle(条形码保质期识别检测系统) self.detector Detector(runs/detect/barcode_expiry/weights/best.pt) self.camera_thread None self.image_label QLabel(图片显示区域) self.image_label.setAlignment(Qt.AlignCenter) self.image_label.setMinimumSize(640, 480) self.image_label.setStyleSheet(border: 1px solid #ccc; background: #f8f8f8;) self.result_area QTextEdit() self.result_area.setReadOnly(True) self.open_image_btn QPushButton(打开图片) self.open_camera_btn QPushButton(打开摄像头) self.detect_btn QPushButton(开始识别) self.stop_btn QPushButton(停止识别) self.open_image_btn.clicked.connect(self.open_image) self.open_camera_btn.clicked.connect(self.open_camera) self.detect_btn.clicked.connect(self.detect_frame) self.stop_btn.clicked.connect(self.stop_camera) right_layout QVBoxLayout() right_layout.addWidget(self.result_area) right_layout.addWidget(self.open_image_btn) right_layout.addWidget(self.open_camera_btn) right_layout.addWidget(self.detect_btn) right_layout.addWidget(self.stop_btn) main_layout QHBoxLayout() main_layout.addWidget(self.image_label, stretch2) main_layout.addLayout(right_layout, stretch1) container QWidget() container.setLayout(main_layout) self.setCentralWidget(container) self.current_frame None def open_image(self): file_path, _ QFileDialog.getOpenFileName( self, 选择图片, , 图片文件 (*.jpg *.jpeg *.png *.bmp) ) if file_path: frame cv2.imread(file_path) self.current_frame frame self.show_frame(frame) def show_frame(self, frame): rgb cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) h, w, ch rgb.shape bytes_per_line ch * w qt_image QImage(rgb.data, w, h, bytes_per_line, QImage.Format_RGB888) self.image_label.setPixmap(QPixmap.fromImage(qt_image).scaled( self.image_label.size(), Qt.KeepAspectRatio, Qt.SmoothTransformation )) def detect_frame(self): if self.current_frame is None: self.result_area.append(请先打开图片) return boxes, classes, confs self.detector.detect(self.current_frame) result self.process_result(boxes, classes, confs, self.current_frame) self.show_result(result) def process_result(self, boxes, classes, confs, frame): result { barcode: , production_date: , shelf_life: , expiry_date: , } for box, cls in zip(boxes, classes): crop self.detector.crop(frame, box) if cls 0: data, btype decode_barcode(crop) result[barcode] data or elif cls 1: text ocr_date_region(crop) production parse_production_date(text) shelf_value, shelf_unit parse_shelf_life(text) if production: result[production_date] production if shelf_value: result[shelf_life] f{shelf_value} {shelf_unit} result[expiry_date] calc_expiry_date(production, shelf_value, shelf_unit) return result def show_result(self, result): self.result_area.clear() self.result_area.append(f条码: {result.get(barcode, )}) self.result_area.append(f生产日期: {result.get(production_date, )}) self.result_area.append(f保质期: {result.get(shelf_life, )}) self.result_area.append(f到期日: {result.get(expiry_date, )}) def open_camera(self): if self.camera_thread and self.camera_thread.isRunning(): return self.camera_thread CameraThread(self.detector, self) self.camera_thread.frame_ready.connect(self.show_frame) self.camera_thread.result_ready.connect(self.show_result) self.camera_thread.error_ready.connect(lambda msg: self.result_area.append(msg)) self.camera_thread.start() def stop_camera(self): if self.camera_thread: self.camera_thread.stop() self.camera_thread.wait() if __name__ __main__: app QApplication(sys.argv) window MainWindow() window.resize(1100, 600) window.show() sys.exit(app.exec())这段代码里需要引入前面utils.py中的函数这里为了演示方便直接写在主文件中。实际项目建议把解码、OCR、正则解析放到独立模块比如utils.py主窗口只负责界面交互。5.4 摄像头实时识别优化上面的实现是每一帧都做一次完整检测和识别。实际中如果算力有限可以降低检测频率比如每 5 帧检测一次frame_count 0 while True: ret, frame cap.read() frame_count 1 if frame_count % 5 ! 0: self.frame_ready.emit(frame) continue boxes, classes, confs self.detector.detect(frame)这样界面显示依然是流畅的识别结果每隔一段时间更新一次比较适合摄像头实时场景。6. 运行验证与界面交互演示6.1 启动系统在项目根目录执行python main.py启动后出现主窗口左侧是图片显示区右侧是识别结果面板。6.2 图片识别流程点击“打开图片”选择一张包含条码和日期的商品包装图。再点击“开始识别”系统会输出类似下面的结果条码: 6901234567892 生产日期: 2025-01-01 保质期: 12 month 到期日: 2026-01-01如果图片中的条码区域日期区域已经被 YOLO 准确框出那么整个识别过程大约 1 到 2 秒。6.3 摄像头实时识别点击“打开摄像头”系统会弹出摄像头画面。把商品包装放到画面中系统会自动检测条码和日期区域并持续刷新识别结果。因为加入了多线程界面不会卡顿摄像头画面基本能保持实时。如果摄像头画面中没有检测到目标优先检查光照是否充足、条码是否过小、模型置信度阈值是否设置得过高。7. 常见问题与排查清单在实际使用过程中很容易遇到下面这些问题这里整理了一份排查表。问题现象常见原因解决思路PySide6 安装失败Python 版本过低或网络问题使用 Python 3.9换国内镜像源安装摄像头打不开摄像头被其他程序占用关闭其他摄像头应用更换 index 测试pyzbar 导入报错Windows 缺少 VC 运行库 / Linux 缺少 libzbar安装对应运行库pyzbar 解码失败条码模糊、倾斜、光照差预处理增强、提高分辨率、增加训练样本pytesseract 报错找不到 tesseract未安装 Tesseract 引擎或路径未配置安装 Tesseract配置tesseract_cmd训练时 CUDA out of memory显存不足调小 batch使用 yolov8n 或 CPU 训练检测不到小目标条码训练集中小目标样本少增加小目标样本调大imgsz使用高分辨率图片识别界面卡顿主线程做检测/识别使用 QThread 或 QTimer 异步处理epoch 训练很久没提升学习率不合适或数据量太少数据增强、使用预训练权重增量训练遇到问题时最好的排查方式是“分段验证”。先用单独脚本测试条码解码再测试 OCR 解析最后再测试 YOLO 检测框是否正确。这样能快速定位是哪一段出了问题。8. 工程化与发布建议8.1 多线程与界面架构优化生产环境建议把识别逻辑设计成独立的服务层界面层只负责展示和用户交互。这样以后如果要把系统改成 Web 服务或者接入 API不需要改动识别核心代码。另外需要注意OpenCV 的VideoCapture对象不能跨线程随便释放否则容易出现崩溃。正确做法是在摄像头线程内部创建和释放VideoCapture不要让主界面直接操作。8.2 模型性能与精度取舍对实时性要求高的场景优先选择 YOLOv8n 或 YOLOv8s。对精度要求高但帧率要求不高的场景可以选择 YOLOv8m 或更大模型。如果条码区域经常很小可以单独训练一个专门检测条码的模型或者增大输入尺寸。推理时尽量用半精度或 ONNX Runtime能明显提升速度。如果想把模型进一步优化可以考虑 YOLOv8 的改进思路引入多头注意力机制 MHSA、替换主干网络为 ConvNeXt V2、增加小目标检测头等。但这些方案会增加训练复杂度建议先跑通基础版本再逐步升级。8.3 PyInstaller 打包发布桌面应用开发完成后可以用 PyInstaller 打包成 exepip install pyinstaller pyinstaller -w main.py --collect-all ultralytics --collect-all PySide6打包时需要把模型权重文件一起复制到目录中。由于模型文件和 PySide6 本身较大打包后的 exe 可能达到几百 MB这是正常现象。如果希望减小体积可以在打包前先导出 ONNX 模型再把推理后端切换到 ONNX Runtime这样可以减少携带整个 PyTorch 环境的体积。8.4 数据安全与合规提醒在真实的仓库、商超场景中使用条码识别系统要注意几个方面只能扫描有授权来源的商品条码不能利用条码解码功能获取或破解未授权信息。日期识别结果涉及食品、药品安全建议在界面上增加“人工确认”按钮识别结果仅供参考。涉及数据库写入、库存变更等操作必须先做权限校验和数据备份。这部分虽然和代码关系不大但在项目落地时非常关键提前设计好能少走很多弯路。9. 经验复盘与后续优化方向整个系统真正难的不是某个技术点而是把目标检测、条码解码、OCR 文本解析、GUI 多线程串成一条完整流水线。每个环节单独看都有成熟方案但拼接起来就会出现很多边界情况比如条码检测到了但解码失败日期识别出来了但格式不是预期摄像头画面里多个商品叠加等。后续可以做的优化方向很多把 OCR 换成 PaddleOCR 或深度学习文本检测模型提高日期识别鲁棒性引入条码数据库查询识别到条码后自动关联商品名称、规格、批次信息把到期日信息和库存表联动实现自动预警将模型导出为 ONNX 或 TensorRT部署到边缘设备。如果打算接入 YOLO11也可以用它替代 YOLOv8 重新训练因为数据格式和接口基本兼容值得对比一下效果。建议先把本文的流程完整跑通生成一批自己的条码和日期数据再根据实际业务不断迭代。识别类的系统数据质量和场景覆盖往往比模型结构更重要。希望这套实战方案能帮你减少踩坑快速做出第一版可用原型。