简介:这是一套面向计算机、人工智能、自动化等专业学生与初学者的YOLOv8药品识别实战资源,可用于毕业设计、课程设计、大作业或教学演示。系统基于YOLOv8目标检测框架,支持图片与视频输入,自动定位并识别药盒内常见药品,内置yolov8n.pt与训练好的best.pt权重,配套训练脚本与实时视频流检测脚本,可视化界面可展示检测结果、置信度框选、标签分布图,以及F1分数曲线、精确率-召回率曲线、混淆矩阵等评估图表。资源包共11个文件,以3个pt权重、3个py脚本、3个txt说明文档为主,另含gitignore与inscode配置,压缩包约15.92MB,数据集已标注整理,覆盖多角度、不同光照下的药盒图像。部署说明覆盖环境配置、依赖安装、权重加载与界面启动全流程,Windows与Linux均可运行,README提供清晰操作指引。目前已有171人学习,适合快速上手目标检测项目并完成完整实践闭环。
1. 药盒识别为什么总在真实货架上翻车
做过药盒识别的人都遇到过同一个反直觉现象:模型在验证集上 mAP 能到 0.95,搬到药店货架前拍一张,识别率直接掉到六成。原因不在模型本身,而在数据分布——训练集里药盒是摆正的、光照均匀的、单盒居中的,真实场景里药盒是斜放的、反光的、成排堆叠的,还有相似包装的干扰。YOLOv8 药盒药品识别系统要解决的就是这个落差:用一套可复现的流程,把数据采集、标注、训练、可视化界面和一键部署串起来,让识别结果在真实货架上站得住。这套方案适合两类人:一类是想把目标检测落地到医药零售、仓储盘点、家庭用药管理的开发者;另一类是想拿一个完整项目练手 YOLOv8 全流程的工程师。下面按我实际做过的路径拆开讲,参数和坑都写清楚。
2. 数据集怎么建:从采集到 YOLO 格式的完整链路
2.1 药盒数据的三个来源与采集参数
药盒识别和通用目标检测最大的区别是类别多、单类样本少。常见做法是三个来源混合:一是自己用手机拍,二是从公开药品图像库筛选,三是用合成方式补稀有类别。手机拍摄我一般固定几个参数:分辨率 1920×1080 以上,焦距锁定避免自动对焦抖动,曝光补偿 -0.3 到 0 之间防止包装反光过曝。每个药盒至少拍 30 张,覆盖正面、侧面 45 度、俯视三个角度,背景要包含白色货架、木质桌面、深色台面三种,否则模型会把背景当特征。
采集时有个血泪经验:不要只拍单个药盒。真实场景里药盒是成排的,所以每个场景至少摆 3 到 5 盒,盒与盒之间留 1 到 2 厘米间隙,让模型学会区分相邻目标。如果只拍单盒,训练出来的模型在密集场景下 NMS 会把相邻框合并,漏检率飙升。
2.2 标注规范与 LabelImg 实操
标注用 LabelImg 或 CVAT 都行,关键是框的边界要统一。药盒是矩形,但拍摄有透视变形,框要贴紧药盒可见边缘,不要框到阴影。类别名用药品通用名拼音缩写,比如阿莫西林写 amoxilin,避免中文类别名在后续训练脚本里编码出错。
# 安装标注工具 pip install labelImg # 启动,指定图片目录和类别文件 labelImg ./images ./classes.txt标注完成后每张图对应一个同名 txt,格式是class_id x_center y_center width height,坐标全部归一化到 0 到 1。这里有个容易翻车的点:如果标注时图片被旋转过,txt 里的坐标不会自动跟着转,训练时框会错位。所以标注前先把图片方向统一,用 EXIF 信息批量旋转。
from PIL import Image, ImageOps import os # 批量根据 EXIF 旋转图片,避免标注坐标错位 def fix_orientation(img_dir): for fname in os.listdir(img_dir): if not fname.lower().endswith(('.jpg', '.png')): continue path = os.path.join(img_dir, fname) img = Image.open(path) img = ImageOps.exif_transpose(img) # 按 EXIF 自动旋转 img.save(path) fix_orientation('./images')这段代码做的是把手机拍摄时记录的旋转信息真正应用到像素上,这样后续标注和训练看到的图方向一致。参数上注意exif_transpose会直接修改原图,建议先备份。
2.3 数据集划分与 data.yaml 配置
标注完按 8:1:1 划分训练、验证、测试。划分时不能随机打散,要按场景分:同一场景的图不能同时出现在训练和验证里,否则验证集精度虚高。我一般按拍摄批次分,比如上午拍的做训练,下午拍的做验证。
# data.yaml path: ./dataset train: images/train val: images/val test: images/test nc: 12 names: ['amoxilin', 'ibuprofen', 'vitamin_c', 'aspirin', 'metformin', 'omeprazole', 'atorvastatin', 'amlodipine', 'losartan', 'gliclazide', 'salbutamol', 'other']nc是类别数,names顺序必须和标注时 class_id 一致,错一个位置整个训练就废了。path用相对路径,方便整个数据集打包迁移。如果类别里有“其他”这类兜底类,样本量要控制在全体的 10% 以内,否则模型会倾向于把所有模糊目标判成 other。
3. YOLOv8 训练:参数怎么设、什么时候该停
3.1 从预训练权重起步的命令与参数含义
训练不要从零开始,用 COCO 预训练的 yolov8s 或 yolov8m 起步。药盒识别类别多但单类样本少,yolov8s 在精度和速度之间比较平衡,显存 8G 就能跑。命令如下:
yolo detect train \ data=./data.yaml \ model=yolov8s.pt \ epochs=150 \ imgsz=640 \ batch=16 \ lr0=0.01 \ lrf=0.01 \ patience=30 \ augment=True \ mosaic=1.0 \ mixup=0.1 \ project=./runs \ name=medicine_v1imgsz=640是输入尺寸,药盒上的文字小字如果识别不清可以提到 800,但显存和速度会成倍增加。lr0初始学习率 0.01 是 YOLOv8 的默认值,如果 loss 在前 10 轮就震荡,降到 0.005。patience=30表示 30 轮验证指标不提升就早停,这个值别设太小,药盒数据少,指标波动大,设 10 容易误停。mosaic=1.0是马赛克增强,对小样本很有效,但最后 20 轮建议关掉,让模型适应真实构图。
3.2 训练过程看什么指标、怎么判断过拟合
训练时重点看三个输出:box_loss、cls_loss、mAP50。box_loss降到 0.5 以下说明框回归基本收敛,cls_loss降到 0.3 以下分类才稳。如果训练 loss 持续降但验证 mAP 在第 60 轮后走平甚至下降,就是过拟合了。药盒数据少的时候过拟合很常见,解决办法不是加数据就是加增强,别硬调学习率。
from ultralytics import YOLO # 加载训练好的权重,在验证集上跑一遍 model = YOLO('./runs/medicine_v1/weights/best.pt') metrics = model.val(data='./data.yaml', imgsz=640, batch=16) print(f"mAP50: {metrics.box.map50:.4f}") print(f"mAP50-95: {metrics.box.map:.4f}")这段代码用来在训练结束后独立验证,注意val用的是 best.pt 而不是 last.pt,best.pt 是验证指标最好的那一轮权重。如果 mAP50 和 mAP50-95 差距超过 0.2,说明框的位置还不够准,可以适当提高box损失权重。
3.3 推理速度与精度的权衡:模型选型对比
| 模型 | 参数量 | mAP50 参考 | 单张推理耗时 | 显存占用 |
|---|---|---|---|---|
| yolov8n | 3.2M | 0.82 | 8ms | 2G |
| yolov8s | 11.2M | 0.91 | 15ms | 4G |
| yolov8m | 25.9M | 0.94 | 28ms | 8G |
| yolov8l | 43.7M | 0.95 | 45ms | 12G |
药盒识别我一般选 yolov8s,精度够用,部署到边缘设备也不吃力。如果类别超过 50 种,建议上 yolov8m,小模型对多类别的区分能力会明显下降。推理耗时是在 RTX 3060 上测的,CPU 上会慢 5 到 8 倍,部署前要确认目标硬件。
4. 可视化界面:从检测结果到可用的交互工具
4.1 用 Gradio 搭一个最小可用界面
训练完模型只是半成品,要让非技术用户能用,得有个界面。Gradio 是最快的方式,几十行代码就能跑起来。界面功能包括上传图片、显示检测框、列出识别到的药品名和置信度。
import gradio as gr from ultralytics import YOLO from PIL import Image import numpy as np model = YOLO('./runs/medicine_v1/weights/best.pt') def detect(image): results = model(image, conf=0.4, iou=0.5) # 在原图上画框 annotated = results[0].plot() # 提取类别和置信度 names = results[0].names boxes = results[0].boxes labels = [] for cls_id, conf in zip(boxes.cls.tolist(), boxes.conf.tolist()): labels.append(f"{names[int(cls_id)]}: {conf:.2f}") return annotated, "\n".join(labels) if labels else "未识别到药品" demo = gr.Interface( fn=detect, inputs=gr.Image(type="pil"), outputs=[gr.Image(type="numpy"), gr.Textbox(label="识别结果")], title="药盒药品识别", description="上传药盒图片,自动识别药品名称和位置" ) demo.launch(server_name="0.0.0.0", server_port=7860)conf=0.4是置信度阈值,低于这个值的框不显示。药盒场景建议设 0.35 到 0.45,太低会出很多误检,太高会漏掉被遮挡的药盒。iou=0.5是 NMS 的 IoU 阈值,密集摆放时如果相邻药盒框被合并,把这个值降到 0.4。server_name="0.0.0.0"让局域网内其他设备也能访问,方便在手机上测试。
4.2 界面响应速度优化与批量处理
单张推理在 CPU 上可能要 200ms 以上,界面会卡。优化方式有两个:一是把模型转成 ONNX 或 TensorRT,二是加一个简单的队列。ONNX 转换命令如下:
yolo export model=./runs/medicine_v1/weights/best.pt format=onnx imgsz=640 half=Truehalf=True表示用 FP16 精度,速度能提升 30% 左右,精度损失很小。导出后在 Gradio 里用 onnxruntime 加载,CPU 推理也能压到 80ms 以内。如果要做批量处理,把fn改成接收文件夹路径,循环推理后打包结果返回,注意加进度条提示,否则用户以为卡死了。
4.3 识别结果的可信度展示与人工复核
药盒识别涉及用药安全,不能只给一个框就完事。界面上要同时显示置信度,低于 0.6 的结果标黄提示“建议人工确认”。如果同一张图里识别到多个相同药品,要合并计数而不是重复列出。这些细节不做,用户对系统的信任度会很低,实际落地时会被要求返工。
5. 一键部署:把训练、推理、界面打包成可交付方案
5.1 用 Docker 封装完整环境
一键部署的核心是把 Python 版本、CUDA、依赖库全部锁死。Dockerfile 写法如下:
FROM pytorch/pytorch:2.1.0-cuda11.8-cudnn8-runtime WORKDIR /app COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt COPY . . EXPOSE 7860 CMD ["python", "app.py"]基础镜像选 PyTorch 官方带 CUDA 的版本,省去自己装驱动的麻烦。requirements.txt里锁版本,比如ultralytics==8.1.0、gradio==4.8.0,不锁版本今天能跑明天就报错。构建命令docker build -t medicine-detect:v1 .,运行命令docker run --gpus all -p 7860:7860 medicine-detect:v1。--gpus all让容器能用宿主机的 GPU,没有 GPU 就去掉这个参数,用 CPU 跑。
5.2 部署后的健康检查与日志
容器跑起来不代表服务正常。加一个健康检查接口,返回模型加载状态和显存占用。日志要输出到文件并限制大小,否则跑一周磁盘就满了。我一般用 Python 的 logging 模块,按天切分,保留 7 天。如果部署在边缘设备上,还要加一个看门狗脚本,检测到进程退出自动重启。
5.3 模型更新与版本回滚
药品包装会换版,模型需要定期更新。部署方案里要留一个模型目录,用版本号命名,比如weights/v1、weights/v2。更新时把新权重放进去,改一下配置文件的路径,重启服务即可。回滚就是把路径改回旧版本。这个机制不做,每次更新都要重新构建镜像,运维成本很高。
6. 避坑与排查:药盒识别落地时最容易翻车的五件事
6.1 识别框重叠导致漏检
现象:成排药盒只识别出第一个,后面的框被 NMS 合并。原因:相邻药盒 IoU 超过阈值,被当成同一个目标。解决:把推理时的iou从默认 0.7 降到 0.4 到 0.5,同时在训练数据里增加密集摆放的样本。如果还不行,换用 soft-NMS,但 YOLOv8 默认不支持,需要自己改后处理。
6.2 反光包装导致置信度骤降
现象:同一药盒换个角度拍,置信度从 0.9 掉到 0.3。原因:包装覆膜反光,模型学到的特征被高光覆盖。解决:采集时故意拍一些反光样本,训练时开hsv_v增强,把亮度扰动范围调大。推理时如果还是不行,加一个偏振镜再拍,这是硬件层面的解法。
6.3 类别名编码错误导致训练中断
现象:训练启动报UnicodeDecodeError或类别全变成乱码。原因:data.yaml里用了中文类别名,而某些环境默认编码不是 UTF-8。解决:类别名统一用英文或拼音,yaml 文件保存时选 UTF-8 无 BOM 格式。已经标了中文的,写个脚本批量替换标注文件里的 class_id 映射。
6.4 显存不足导致训练崩溃
现象:训练到一半报CUDA out of memory。原因:batch设太大,或者imgsz提到 800 以上。解决:先把batch降到 8,再不行降到 4,同时开amp=True混合精度。如果还崩,检查是不是mosaic增强把四张图拼一起导致单批显存翻倍,把mosaic关掉试试。
6.5 部署后接口超时
现象:界面点上传后一直转圈,最后报超时。原因:模型在 CPU 上推理太慢,或者图片没做尺寸限制,用户传了一张 8000×6000 的图。解决:在推理前加一步 resize,把长边限制到 1280,同时设置 Gradio 的timeout参数。如果还是慢,转 ONNX 或 TensorRT,这是最有效的提速手段。
7. 进阶技巧:用测试集反推数据短板
模型上线后别急着庆祝,拿测试集跑一遍混淆矩阵,看哪些类别互相误判。我一般会导出混淆矩阵和 PR 曲线,重点看两类:一类是召回率低于 0.7 的,说明漏检多,要补这类样本;另一类是精确率低于 0.6 的,说明误检多,要检查是不是背景干扰。这个分析做完,下一轮数据采集就有方向了,比盲目加数据效率高得多。
from ultralytics import YOLO import matplotlib.pyplot as plt model = YOLO('./runs/medicine_v1/weights/best.pt') metrics = model.val(data='./data.yaml', plots=True, save_json=True) # 混淆矩阵会保存到 runs 目录,直接看哪些类别混淆严重 # 如果某两类互相误判超过 20%,考虑合并类别或补特征差异plots=True会生成混淆矩阵、PR 曲线等图,save_json=True把指标存成 json 方便对比不同版本。我习惯每训练一版就把这些图存到一个独立目录,版本多了之后回头看,能清楚看到哪次数据补充真正起了作用。
最后说个我自己的习惯:每次训练前先拿 10 张测试图跑一遍当前模型,记录识别结果,训练后再跑同样 10 张,对比变化。这比看 mAP 数字直观得多,能发现指标掩盖的问题。药盒识别这行,数据质量比模型结构重要,参数调优比换模型重要,测试集分析比训练轮数重要。希望帮到你。
本文还有配套的精品资源,点击获取