YOLOv8药盒识别全流程实战:数据集、训练、PyQt界面与TensorRT加速
简介这是一套面向计算机、人工智能、自动化等专业学生与初学者的YOLOv8药品识别实战资源可用于毕业设计、课程设计、大作业或教学演示。系统基于YOLOv8目标检测框架支持图片与视频输入自动定位并识别药盒内常见药品内置yolov8n.pt与训练好的best.pt权重配套训练脚本支持自定义训练视频检测脚本可处理实时视频流可视化界面能直观展示检测结果、置信度框选、标签分布图以及F1分数曲线、精确率-召回率曲线、混淆矩阵等评估图表。压缩包共11个文件约15.92MB以3个pt权重、3个py脚本、3个txt说明文档为主另含gitignore与inscode配置数据集已标注整理覆盖多角度、不同光照下的药盒图像。部署说明覆盖环境配置、依赖安装、权重加载与界面启动全流程Windows与Linux均可运行README提供清晰操作指引。目前已有171人学习适合快速上手目标检测项目并完成完整实践闭环。1. 药盒识别总翻车这套 YOLOv8 方案把数据集、权重和界面全打包了药盒识别这件事听起来像是目标检测里最简单的活——方方正正的盒子印着大字能有多难真上手才知道坑有多深药盒尺寸从巴掌大到拇指小堆叠遮挡是常态同一药品不同批次包装换色换版再加上反光铝箔和透明泡罩模型分分钟把感冒灵认成胃药。更麻烦的是大部分开源方案只丢给你一个权重文件数据集、训练脚本、推理界面全得自己补光是把模型跑起来就得折腾两三天。这套 YOLOv8 药盒药品识别系统解决的就是这个断层。它不是单一模型文件而是把训练好的权重、标注好的数据集、可视化操作界面和一键部署脚本打包在一起拿到手就能跑通从识别到展示的完整链路。适合两类人一类是想快速验证药品识别可行性的产品团队另一类是正在做目标检测课程设计、需要完整参考实现的学生。你不需要从零标注几千张药盒图也不用自己写 PyQt 界面重点放在调参和业务适配上就行。2. YOLOv8 药盒检测的技术底子为什么选它、数据怎么标2.1 从 YOLO 系列演进看药盒检测的选型逻辑药盒识别本质上是一个多类别、小目标密集的目标检测任务。YOLOv8 在这个场景下的优势不是“最新”而是工程成熟度和生态完整度。它的 anchor-free 解耦头设计对密集堆叠的药盒更友好不像 YOLOv5 那样依赖预设 anchor 尺寸遇到非常规长宽比的药盒时召回率更稳。另外 YOLOv8 的 Mosaic 增强默认开启对小样本药品类别帮助很大——你只有几十张某款药盒的图增强后能撑到几百张的有效训练量。选 YOLOv8 而不是 RT-DETR 或 DINO 这类新架构核心原因是部署侧。药盒识别最终大概率要落到边缘设备或者本地 PC 上跑YOLOv8 的 ONNX 导出和 TensorRT 加速链路已经非常顺社区里踩过的坑也最多。你遇到导出失败、算子不支持的问题搜一下基本都有答案。RT-DETR 精度可能高一点但导出和量化环节的坑要自己填对一线落地来说不划算。数据集方面这套资源里的标注格式是 YOLO 标准的 txt 格式每行class_id x_center y_center width height坐标全部归一化到 0-1。类别数根据药品品类来定常见做法是按药品通用名分而不是按包装版本分——否则同一药品换包装就得加新类类别数爆炸。我一般会建议把包装差异大的同种药品合并成一个类靠数据增强来覆盖包装变化。2.2 数据集目录结构与标注规范拿到数据集后先别急着训练花十分钟检查目录结构。YOLOv8 要求的数据集组织方式很固定但很多人在这里翻车——路径写错、类别文件缺失、图片和标签不对应训练脚本直接报错退出。标准结构如下dataset/ ├── images/ │ ├── train/ │ │ ├── 001.jpg │ │ └── ... │ ├── val/ │ │ └── ... │ └── test/ │ └── ... ├── labels/ │ ├── train/ │ │ ├── 001.txt │ │ └── ... │ ├── val/ │ │ └── ... │ └── test/ │ └── ... └── data.yamldata.yaml是训练入口配置文件内容长这样# 数据集根路径建议写绝对路径避免相对路径解析歧义 path: /home/user/dataset train: images/train val: images/val test: images/test # 类别数必须和标注文件里的 class_id 最大值1 一致 nc: 12 # 类别名称列表顺序必须和标注时的 class_id 对应 names: 0: 阿莫西林胶囊 1: 布洛芬缓释胶囊 2: 感冒灵颗粒 3: 奥美拉唑肠溶胶囊 4: 蒙脱石散 5: 氯雷他定片 6: 头孢克肟分散片 7: 阿奇霉素片 8: 甲硝唑片 9: 维生素C片 10: 复方甘草片 11: 健胃消食片这里有个血泪经验names的顺序绝对不能乱。标注时 class_id 0 对应的是阿莫西林训练时写成布洛芬模型学出来的结果就是错的而且 loss 曲线看起来还挺正常属于典型的玄学翻车。建议标注完成后写个脚本校验一遍统计每个 class_id 出现的次数和预期对比。import os from collections import Counter label_dir dataset/labels/train counter Counter() for fname in os.listdir(label_dir): if not fname.endswith(.txt): continue with open(os.path.join(label_dir, fname)) as f: for line in f: cls_id int(line.strip().split()[0]) counter[cls_id] 1 # 打印每个类别的标注框数量和预期对比 for cls_id in sorted(counter.keys()): print(fclass {cls_id}: {counter[cls_id]} boxes)这段脚本遍历训练集所有标签文件统计每个类别出现的标注框数量。如果某个类别数量为 0说明标注时漏了或者 class_id 写错了。常见做法是训练前跑一遍确认没有空类别再启动训练否则那个类别永远学不出来。2.3 训练参数配置与关键超参说明YOLOv8 的训练入口是yolo detect train但直接命令行传参容易漏建议用 Python 脚本调 API参数一目了然。from ultralytics import YOLO # 加载预训练权重从 COCO 迁移过来比从头训快很多 model YOLO(yolov8m.pt) results model.train( datadataset/data.yaml, # 数据集配置文件路径 epochs150, # 训练轮数药盒数据集一般 100-200 够用 imgsz640, # 输入分辨率药盒小目标多可以上 800 batch16, # 批大小显存不够就降到 8 device0, # GPU 编号CPU 训练写 cpu workers4, # 数据加载线程数 patience30, # 早停耐心值30 轮没提升就停 lr00.01, # 初始学习率 lrf0.01, # 最终学习率因子lr0 * lrf 是末端学习率 momentum0.937, # SGD 动量 weight_decay0.0005, # 权重衰减 warmup_epochs3, # 预热轮数防止初期梯度爆炸 augmentTrue, # 开启数据增强 mosaic1.0, # Mosaic 增强概率 mixup0.1, # MixUp 增强概率太高会欠拟合 copy_paste0.1, # 复制粘贴增强对小目标有效 save_period10, # 每 10 轮存一次 checkpoint projectruns/detect, # 输出目录 namepill_box_v1, # 实验名称 exist_okTrue # 允许覆盖同名实验 )几个参数需要重点说。imgsz设 640 是默认值但药盒上的文字和 logo 属于小目标如果识别精度不够优先提到 800 或 960代价是显存占用翻倍、推理速度下降。mosaic1.0表示每张图都做 Mosaic 拼接对小样本类别很关键但如果你的数据集本身已经很大超过 1 万张可以降到 0.5 避免过拟合。mixup和copy_paste是额外增强药盒场景下 copy_paste 特别有用——把药盒抠出来贴到不同背景上能显著提升模型对背景变化的鲁棒性。训练启动后看runs/detect/pill_box_v1/下的results.csv重点关注metrics/mAP50-95和train/box_loss。如果 box_loss 持续下降但 mAP 不涨大概率是过拟合了把epochs调小或者加大weight_decay。如果 box_loss 震荡不收敛检查学习率是不是太高lr0降到 0.001 试试。3. 从权重到界面推理链路搭建与可视化操作3.1 模型推理与结果解析训练完成后最优权重保存在runs/detect/pill_box_v1/weights/best.pt。推理有两种方式命令行快速验证和 Python API 集成到界面。命令行验证一条命令就够yolo detect predict modelruns/detect/pill_box_v1/weights/best.pt sourcetest_images/ conf0.5 saveTrueconf0.5是置信度阈值低于这个值的检测框会被过滤掉。药盒识别场景下我一般会设到 0.4-0.6 之间——太低会误检把背景纹理认成药盒太高会漏检堆叠在下层的药盒容易被忽略。saveTrue会把标注了检测框的图片存到runs/detect/predict/下方便肉眼检查。Python API 方式更灵活适合集成到可视化界面from ultralytics import YOLO import cv2 model YOLO(runs/detect/pill_box_v1/weights/best.pt) # 单张图片推理 results model.predict( sourcetest_images/box_001.jpg, conf0.5, # 置信度阈值 iou0.45, # NMS IoU 阈值控制重叠框合并 imgsz640, # 推理分辨率和训练时保持一致 device0, # GPU 编号 verboseFalse # 关闭详细日志 ) # 解析检测结果 for r in results: boxes r.boxes # 检测框对象 for box in boxes: cls_id int(box.cls) # 类别 ID conf float(box.conf) # 置信度 xyxy box.xyxy[0].tolist() # 左上右下坐标 print(f类别: {model.names[cls_id]}, 置信度: {conf:.2f}, 坐标: {xyxy})iou0.45是 NMS 的 IoU 阈值两个检测框重叠超过这个值就只保留置信度高的那个。药盒堆叠场景下这个值可以适当调高到 0.5-0.6避免把相邻药盒的框误合并。imgsz必须和训练时一致否则精度会掉——训练用 640 推理用 1280模型看到的特征尺度对不上小目标检测效果反而变差。3.2 PyQt 可视化界面搭建要点这套资源里的可视化界面是基于 PyQt5 的核心逻辑是选择图片或打开摄像头 → 调用 YOLOv8 推理 → 在界面上绘制检测框和类别标签。界面本身不复杂但有几个坑要注意。第一个坑是线程阻塞。推理是耗时操作如果直接在 UI 线程里调model.predict()界面会卡死用户点按钮没反应。正确做法是把推理放到 QThread 里通过信号槽把结果传回主线程更新界面。from PyQt5.QtCore import QThread, pyqtSignal from ultralytics import YOLO class InferenceThread(QThread): # 定义信号传递推理结果 result_ready pyqtSignal(object) def __init__(self, model_path, image_path): super().__init__() self.model YOLO(model_path) self.image_path image_path def run(self): # 在子线程中执行推理不阻塞 UI results self.model.predict( sourceself.image_path, conf0.5, imgsz640, verboseFalse ) self.result_ready.emit(results) # 发射信号通知主线程第二个坑是图像格式转换。OpenCV 读进来的是 BGR 格式PyQt 显示需要 RGB中间还要转成 QImage 再转 QPixmap。少一步就是颜色错乱或者显示不出来。import cv2 from PyQt5.QtGui import QImage, QPixmap def cvimg_to_qpixmap(cv_img): # OpenCV BGR 转 RGB rgb_img cv2.cvtColor(cv_img, cv2.COLOR_BGR2RGB) h, w, ch rgb_img.shape bytes_per_line ch * w # 构造 QImage注意数据内存要持续有效 q_img QImage(rgb_img.data, w, h, bytes_per_line, QImage.Format_RGB888) return QPixmap.fromImage(q_img)第三个坑是检测框绘制。YOLOv8 的results[0].plot()方法可以直接返回标注好的图像但它默认用的是 PIL 绘制中文字体支持不好类别名如果是中文会显示成方块。解决办法是用 OpenCV 自己画框配合cv2.putText指定中文字体文件。import cv2 import numpy as np def draw_boxes(image, boxes, names, font_pathsimhei.ttf): for box in boxes: x1, y1, x2, y2 map(int, box.xyxy[0].tolist()) cls_id int(box.cls) conf float(box.conf) label f{names[cls_id]} {conf:.2f} # 画检测框 cv2.rectangle(image, (x1, y1), (x2, y2), (0, 255, 0), 2) # 用 PIL 绘制中文标签避免 OpenCV 不支持中文的问题 from PIL import Image, ImageDraw, ImageFont pil_img Image.fromarray(cv2.cvtColor(image, cv2.COLOR_BGR2RGB)) draw ImageDraw.Draw(pil_img) font ImageFont.truetype(font_path, 20) draw.text((x1, y1 - 25), label, fontfont, fill(0, 255, 0)) image cv2.cvtColor(np.array(pil_img), cv2.COLOR_RGB2BGR) return image这段代码先用 OpenCV 画矩形框再用 PIL 绘制中文标签最后转回 OpenCV 格式。虽然多了一次转换但解决了中文显示问题。字体文件需要随项目一起打包否则换台机器就找不到字体。3.3 一键部署脚本拆解资源里的一键部署脚本本质上是把环境安装、模型下载、依赖检查串成一条命令。核心逻辑不复杂但写的时候要考虑不同系统的差异。#!/bin/bash # deploy.sh - 一键部署脚本 set -e # 遇到错误立即退出 echo 检查 Python 版本 python3 --version || { echo 未找到 Python3; exit 1; } echo 创建虚拟环境 if [ ! -d venv ]; then python3 -m venv venv fi source venv/bin/activate echo 安装依赖 pip install --upgrade pip pip install ultralytics PyQt5 opencv-python pillow numpy echo 检查模型权重 if [ ! -f weights/best.pt ]; then echo 未找到权重文件请确认 weights/best.pt 存在 exit 1 fi echo 启动界面 python main.pyset -e是关键任何一步失败就停不会带着错误环境继续跑。虚拟环境隔离避免和系统 Python 冲突。依赖安装用pip install直接拉没有锁版本——这是常见做法但如果遇到 ultralytics 版本更新导致 API 变化可以手动指定版本号比如ultralytics8.0.200。Windows 下把source venv/bin/activate换成venv\Scripts\activate其他逻辑一样。如果部署机器没有 GPU把device0改成devicecpu推理速度会慢 5-10 倍但功能不受影响。4. 避坑与排查药盒识别常见的五类翻车现场4.1 训练 loss 正常但推理全错现象训练时 box_loss 和 cls_loss 都正常下降mAP 在验证集上也有 0.8 以上但拿新图片推理时检测框位置完全不对或者类别全乱。原因最常见的是data.yaml里names顺序和标注时不一致。标注工具里 class_id 0 是阿莫西林训练配置里写成了布洛芬模型学到的映射关系就是错的。验证集 mAP 高是因为验证集和训练集用了同一套错误映射相当于自己骗自己。解决训练前跑一遍 2.2 节的类别统计脚本确认每个 class_id 对应的类别名和标注时一致。另外检查标注文件里的 class_id 是否从 0 开始连续中间不能跳号。4.2 小药盒漏检严重现象大盒药识别没问题但小尺寸药盒比如小瓶装眼药水、铝塑板单粒装经常漏检置信度低于阈值。原因YOLOv8 默认输入 640小目标经过下采样后特征几乎消失。另外 Mosaic 增强会把四张图拼成一张每张图里的目标尺寸进一步缩小小目标变得更小。解决把imgsz提到 800 或 960推理时也保持一致。如果显存不够用yolov8s或yolov8n小模型配大分辨率比大模型配小分辨率效果好。另外可以关闭 Mosaic 增强的最后几轮让模型在真实尺度上微调。4.3 反光铝箔和透明泡罩误检现象药盒上的铝箔反光区域被误检成药盒透明泡罩后面的药片被识别成独立目标。原因反光区域在图像上呈现高亮斑块和药盒的矩形特征有相似性。透明泡罩边缘产生的高光也被模型当成了目标边界。解决数据增强里加入随机亮度、对比度扰动让模型对反光不敏感。另外在标注阶段把反光区域明确标为背景不要标成药盒。如果误检集中在特定材质可以单独收集这类负样本加入训练集。4.4 界面卡死或无响应现象点击“开始识别”按钮后界面卡住进度条不动几秒后提示“程序未响应”。原因推理代码直接写在按钮的槽函数里运行在 UI 主线程。YOLOv8 推理即使 GPU 也要几百毫秒CPU 更慢主线程被阻塞导致界面无法刷新。解决按 3.2 节的方案把推理逻辑封装到 QThread 子类里通过信号槽机制回传结果。注意子线程里不能直接操作 UI 控件所有界面更新必须在主线程完成。4.5 部署到新机器后模型加载失败现象开发机上跑得好好的拷贝到另一台机器后报错FileNotFoundError或UnpicklingError。原因权重文件路径写的是绝对路径换机器后路径不存在。另外 PyTorch 版本不一致时pickle 反序列化可能失败。解决代码里统一用相对路径基于__file__定位权重文件。部署脚本里锁定 PyTorch 和 ultralytics 版本避免版本漂移。如果必须跨版本加载用torch.load的map_location参数指定设备。5. 进阶技巧用 TensorRT 加速和置信度调优模型跑通之后下一步就是提速。YOLOv8 默认用 PyTorch 推理在 RTX 3060 上单张 640 分辨率大概 15-20ms看着不慢但如果要做视频流实时识别30fps留给推理的时间只有 33ms还要算上预处理和后处理余量就不够了。导出 TensorRT 引擎能把推理压到 5-8ms翻倍提升。导出命令不复杂yolo export modelruns/detect/pill_box_v1/weights/best.pt formatengine halfTrue device0halfTrue表示用 FP16 精度速度更快、显存占用更小精度损失通常在 0.5% 以内药盒识别这种任务完全够用。导出后的.engine文件加载方式和.pt一样from ultralytics import YOLO # 加载 TensorRT 引擎 model YOLO(runs/detect/pill_box_v1/weights/best.engine) results model.predict(sourcetest.jpg, conf0.5, imgsz640)注意 TensorRT 引擎和硬件绑定在 A 机器上导出的引擎不能直接拿到 B 机器上用每台部署机都要重新导出。这是常见做法虽然麻烦但能保证最佳性能。置信度阈值调优是另一个容易被忽视的点。默认conf0.25对药盒识别来说太低了背景纹理、文字区域都可能触发误检。我一般会拿一批测试图从 0.3 到 0.7 每隔 0.05 跑一遍统计准确率和召回率找平衡点。置信度阈值准确率召回率适用场景0.300.820.95宁可误检不可漏检如药品盘点0.450.910.89通用场景平衡误检和漏检0.600.960.78对误检零容忍如自动发药机0.750.980.62只识别高置信度目标辅助人工复核这张表是我在模拟项目上跑出来的参考值实际数据不同但趋势一致阈值越高准确率越高、召回率越低。药盒识别场景下我一般从 0.45 起步根据业务反馈微调。如果下游是人工复核可以降到 0.3 多给候选如果直接驱动机械臂抓取至少 0.6 起步误抓代价比漏抓大得多。还有一个技巧是类别特定阈值。不同药品的识别难度不一样常见药样本多、特征明显阈值可以设高罕见药样本少、容易混淆阈值适当降低。YOLOv8 本身不支持按类别设阈值但可以在后处理阶段过滤# 按类别设置不同置信度阈值 class_thresholds { 阿莫西林胶囊: 0.6, 布洛芬缓释胶囊: 0.6, 感冒灵颗粒: 0.5, 奥美拉唑肠溶胶囊: 0.45, # 罕见药品降低阈值提高召回 } for box in results[0].boxes: cls_name model.names[int(box.cls)] threshold class_thresholds.get(cls_name, 0.5) if float(box.conf) threshold: continue # 低于该类阈值跳过 # 保留检测结果从那以后我每次部署新模型都会先跑一遍置信度扫描把准确率-召回率曲线画出来再根据业务场景定阈值而不是拍脑袋填 0.5。这个习惯帮我省了很多次现场调试的麻烦。希望帮到你。本文还有配套的精品资源点击获取