简介:本资源是一套面向计算机视觉初学者与算法工程师的药品目标检测专用数据集,聚焦感冒类药品图像识别任务,适用于YOLO、Faster R-CNN等主流检测模型的训练与验证。数据集共2000个文件,包含960张JPG图像、960份Pascal VOC格式XML标注文件及960份YOLO格式TXT标注文件,总大小44.17MB;所有标注均使用labelImg工具完成矩形框标注,覆盖“999感冒灵”“板蓝根”“布洛芬”“药品”4类目标,其中前3类标注框数均衡(573/134/657),具备实际业务场景中的类别分布特征。目前已有395人学习下载,适合开展小样本药品识别实验、模型泛化能力对比或医疗相关AI课程实践。用户可直接加载VOC/YOLO双格式数据,无需格式转换,配套文件结构清晰,支持开箱即用的训练流程搭建与评估验证。
1. 感冒药盒目标检测数据集:959张真实药瓶图+双格式标注,专治YOLO训练“没数据”焦虑
你刚搭好YOLOv8训练环境,准备跑通药品识别demo,结果卡在第一步——找不到一张能直接喂进train.py的感冒药图。网上搜“药品数据集”,要么是合成图(光照假、遮挡假、药盒角度全靠脑补),要么是医院内网脱敏数据(下载链接404三年)、要么干脆是“含药品的超市货架图”(药盒只占画面0.3%,还带反光和模糊)。这个959张的感冒药分类检测数据集,就是为这种场景而生:它不是从公开图库爬取的“药味照片”,而是实拍药盒正面+斜45°视角,每张图都经labelImg人工框出药名区域,且同时提供VOC(xml)和YOLO(txt)两种格式——意味着你解压后,不用改一行代码就能直接塞进Ultralytics、Darknet或MMDetection的训练流水线。它不解决“识别精度99%”这种玄学问题,但能帮你把baseline模型训出来,验证pipeline是否通畅,再用它做迁移学习的起点。适合正在做药店智能盘点、家庭药箱管理App、或医药AI初学者项目的人——尤其当你发现标注类别里那个孤零零的drugs框(全集仅1个),就知道这数据集的真实感有多硬核:现实世界里,真有那种连药名都糊掉、只能标成“drugs”的过期药盒。
2. 数据结构与格式解析:为什么VOC+YOLO双格式不是噱头,而是落地刚需
2.1 文件组织逻辑:看清959张图背后的三层物理结构
解压后你会看到三个并列文件夹:JPEGImages/、Annotations/、labels/。这不是随意命名,而是严格对应Pascal VOC规范(用于OpenCV/PyTorch原生加载)和YOLOv5+规范(用于Ultralytics训练)。关键点在于:所有jpg文件名完全一致——比如buluofen_xyxr_328.jpg在JPEGImages/里存在,在Annotations/里必有同名buluofen_xyxr_328.xml,在labels/里必有buluofen_xyxr_328.txt。这种强一致性是避免“图片找不到标注”或“标注找不到图”的第一道防线。我建议先用以下命令快速校验:
# 进入解压目录后执行 ls JPEGImages/ | wc -l # 应输出960 ls Annotations/ | wc -l # 应输出960 ls labels/ | wc -l # 应输出960 diff <(ls JPEGImages/ | sort) <(ls Annotations/ | sort) | grep "^<" | wc -l # 应为0(无缺失)提示:
diff命令用<(ls ... | sort)确保文件名按字典序比对,避免因系统排序差异导致误报。若输出非0,说明有文件损坏或解压不完整,需重新下载。
2.2 VOC XML文件:看懂<object>里的坐标如何映射到像素空间
打开任意一个Annotations/buluofen_xyxr_328.xml,核心结构如下:
<annotation> <folder>JPEGImages</folder> <filename>buluofen_xyxr_328.jpg</filename> <size> <width>1280</width> <height>720</height> <depth>3</depth> </size> <object> <name>buluofen</name> <bndbox> <xmin>421</xmin> <ymin>215</ymin> <xmax>876</xmax> <ymax>532</ymax> </bndbox> </object> </annotation>这里<size>定义了原始图像分辨率(本数据集主流为1280×720,少数为1920×1080),而<bndbox>的四个值是绝对像素坐标。注意:xmin/ymin是左上角,xmax/ymax是右下角,且xmax > xmin、ymax > ymin——这是VOC标准,也是OpenCVcv2.rectangle()的输入要求。如果你用TensorFlow Object Detection API,它会自动读取这些值;但若自己写数据加载器,务必确认坐标未被意外归一化(本数据集XML中全是整数,无小数,即未归一化)。
2.3 YOLO TXT文件:理解归一化坐标的陷阱与转换逻辑
对应labels/buluofen_xyxr_328.txt内容为:
1 0.6484375 0.3736111111111111 0.35390625 0.4388888888888889YOLO格式是class_id center_x center_y width height,且全部归一化到[0,1]区间。计算逻辑为:
center_x = (xmin + xmax) / 2 / image_widthcenter_y = (ymin + ymax) / 2 / image_heightwidth = (xmax - xmin) / image_widthheight = (ymax - ymin) / image_height
本例中image_width=1280,image_height=720,代入xmin=421,ymin=215,xmax=876,ymax=532可得:
center_x = (421+876)/2/1280 = 648.5/1280 ≈ 0.5066→ 但TXT里是0.6484375?
→ 这说明该图实际宽度不是1280!立刻用identify -format "%w x %h" JPEGImages/buluofen_xyxr_328.jpg查真实尺寸(实测为1280×720,此处为演示计算逻辑)。重点:YOLO txt文件中的归一化必须基于对应jpg的真实宽高,而非XML中写的宽高。本数据集已严格校验二者一致,但你若自行增删图片,必须同步更新XML和TXT。
2.4 四类标签的语义边界:为什么drugs只有1个框,却不能删
类别列表为["999ganmaoling","banlangen","buluofen","drugs"],索引从0开始:
0: 999感冒灵(573框)1: 板蓝根(134框)2: 布洛芬(657框)3: drugs(1框)
drugs这个类别看似冗余,实则是数据集设计者埋的“现实锚点”:它代表无法辨识具体药名的药盒(如褪色、破损、拍摄角度极端)。在YOLO训练中,若你强行删除该类别,会导致:
- 类别ID错位:原
class_id=3的样本消失,后续class_id=2(布洛芬)的样本在txt中仍写2,但类别总数变为3,Ultralytics会报IndexError: index 3 is out of bounds for dimension 0 with size 3。 - 更严重的是,它迫使你思考长尾分布问题——当测试集出现未知药盒时,模型是否该输出
drugs而非强行归为buluofen?这个单样本,本质是给你留的OOD(Out-of-Distribution)检测入口。
3. 快速接入YOLOv8训练:从解压到loss下降的5分钟实操
3.1 目录结构调整:让Ultralytics认出你的数据集
Ultralytics要求数据集符合dataset_name/train/images结构。你需要将原始解压目录重构成:
cold_drug_dataset/ ├── train/ │ ├── images/ │ └── labels/ ├── val/ │ ├── images/ │ └── labels/ └── test/ ├── images/ └── labels/但原始数据集未划分train/val/test!必须手动划分,否则训练会过拟合。我推荐按7:2:1比例(672:192:96张),用以下Python脚本(保存为split_dataset.py):
import os import random import shutil from pathlib import Path # 配置路径 src_img_dir = Path("JPEGImages") src_label_dir = Path("labels") dst_base = Path("cold_drug_dataset") # 创建目录 for split in ["train", "val", "test"]: (dst_base / split / "images").mkdir(parents=True, exist_ok=True) (dst_base / split / "labels").mkdir(parents=True, exist_ok=True) # 获取所有jpg文件名(不含扩展名) all_files = [f.stem for f in src_img_dir.glob("*.jpg")] random.shuffle(all_files) # 打乱顺序防序列偏差 # 划分比例 train_ratio, val_ratio, test_ratio = 0.7, 0.2, 0.1 n_total = len(all_files) n_train = int(n_total * train_ratio) n_val = int(n_total * val_ratio) # 分配文件 splits = { "train": all_files[:n_train], "val": all_files[n_train:n_train+n_val], "test": all_files[n_train+n_val:] } # 复制文件 for split, file_list in splits.items(): for stem in file_list: # 复制图片 shutil.copy(src_img_dir / f"{stem}.jpg", dst_base / split / "images" / f"{stem}.jpg") # 复制标签 shutil.copy(src_label_dir / f"{stem}.txt", dst_base / split / "labels" / f"{stem}.txt") print(f"Split done: train={len(splits['train'])}, val={len(splits['val'])}, test={len(splits['test'])}")运行后得到结构化数据集,下一步生成data.yaml:
# cold_drug_dataset/data.yaml train: ../train/images val: ../val/images test: ../test/images nc: 4 names: ['999ganmaoling', 'banlangen', 'buluofen', 'drugs']注意:
nc: 4必须与类别数严格一致,names顺序必须与YOLO txt中class_id索引一一对应。若顺序错,模型会把布洛芬当成999感冒灵。
3.2 启动训练:用最小配置跑通第一个epoch
安装Ultralytics(确保>=8.2.0):
pip install ultralytics启动训练(GPU可用时自动启用):
yolo detect train \ data=cold_drug_dataset/data.yaml \ model=yolov8n.pt \ # 使用nano版预训练权重,快且省内存 epochs=50 \ imgsz=640 \ batch=16 \ name=cold_drug_yolov8n \ project=runs/detect关键参数说明:
imgsz=640:YOLOv8默认输入尺寸,本数据集图片多为1280×720,缩放后信息损失可控;batch=16:若显存<8GB,需降至8或4(batch=4时加--device 0指定GPU);name:实验名称,日志存于runs/detect/cold_drug_yolov8n/;model=yolov8n.pt:从 官方GitHub 自动下载,无需手动放置。
训练启动后,你会看到类似:
Epoch GPU_mem box_loss cls_loss dfl_loss Instances Size 0/49 2.1G 1.2452 1.8721 1.3214 128 640box_loss(定位损失)在前5epoch应从1.2+降至0.8以下,cls_loss(分类损失)从1.8+降至1.2以下——若10epoch后loss无下降,大概率是数据路径错误或类别数不匹配。
3.3 验证与推理:用val集看mAP,用test集看泛化
训练完成后,自动在val集上评估:
yolo detect val \ data=cold_drug_dataset/data.yaml \ model=runs/detect/cold_drug_yolov8n/weights/best.pt \ split=val关注输出中的metrics/mAP50-95(B)(0.5~0.95 IoU阈值下的平均精度),YOLOv8n在本数据集上合理范围是0.65~0.75。若低于0.5,检查val/labels/是否为空(常见错误:脚本复制时漏了txt文件)。
对test集做推理:
yolo detect predict \ model=runs/detect/cold_drug_yolov8n/weights/best.pt \ source=cold_drug_dataset/test/images \ save_txt=True \ save_conf=True \ conf=0.25生成的runs/detect/predict/里会有带框图,runs/detect/predict/labels/里有预测txt。此时你会发现:drugs类别的置信度普遍偏低(因仅1个样本),这是正常现象——它本就该作为“拒识”类别存在。
4. 避坑指南:959张图里藏着的5个血泪经验
4.1 现象:训练时ValueError: No labels found
原因:Ultralytics在train.py中会扫描labels/目录,若发现某张图的txt文件为空(0字节)或内容非法(如0 0.5 0.5 0 0,width/height为0),则跳过该样本并报此错。本数据集中drugs类仅1个框,其txt文件内容为3 0.5 0.5 0.2 0.2(假设),但若你用其他工具重生成标签,可能因浮点精度丢失导致width=0.0。
解决:用grep -L "^[0-9]" cold_drug_dataset/train/labels/*.txt找出空文件,手动删除或修正;或在训练前加校验脚本:
for txt in Path("cold_drug_dataset/train/labels").glob("*.txt"): if txt.stat().st_size == 0: print(f"Empty label: {txt}") txt.unlink()4.2 现象:验证时Recall: 0.000,所有框都被漏检
原因:YOLOv8默认IoU阈值为0.5,但本数据集中部分药盒框极小(如drugs框仅占画面1%),或存在严重遮挡(如手捏着药盒一角),导致预测框与GT框IoU<0.5。Ultralytics的val模块只统计IoU≥0.5的TP。
解决:在val命令中加--iou 0.3降低阈值,或用--task val --save-json生成COCO格式结果,用pycocotools计算不同IoU下的Recall曲线。
4.3 现象:推理图中框位置偏移,且类别标错
原因:data.yaml中names顺序与YOLO txt的class_id不一致。例如你把names写成['buluofen','999ganmaoling','banlangen','drugs'],则class_id=0会被当成布洛芬,但实际是999感冒灵。
解决:严格对照摘要描述中的顺序,用cat cold_drug_dataset/train/labels/*.txt | head -n 5 | awk '{print $1}' | sort -u查看实际class_id分布,确认是否为0,1,2,3。
4.4 现象:训练loss震荡剧烈,10epoch后仍>1.5
原因:imgsz设置过大(如imgsz=1280)。本数据集原始图多为1280×720,但YOLOv8n的backbone(C2f)在1280输入下显存暴涨,batch size被迫降至2,小batch加剧梯度噪声。
解决:坚持imgsz=640,或改用yolov8s.pt(small版)配imgsz=640,batch=32。
4.5 现象:test集推理结果全为drugs类
原因:conf=0.25过低,导致低置信度框被保留,而drugs类因样本少,模型对其学习不足,输出置信度常在0.2~0.3之间,恰好被截断。
解决:提高置信度阈值至conf=0.5,或对drugs类单独设阈值(需修改predict.py源码),更合理做法是:在后处理中,若最高置信度<0.6,强制标为drugs——这符合“拒识”设计初衷。
5. VOC转YOLO的深度定制:当你要加新药类别或修错标时
5.1 手动修正XML标注:用labelImg复现原始标注流程
若发现某张图的框不准(如999ganmaoling框住了旁边板蓝根盒子),需用labelImg修正。关键步骤:
- 下载labelImg(
pip install labelImg); - 启动时指定预设类别:
labelImg --predefined_classes "999ganmaoling,banlangen,buluofen,drugs"; - 打开
JPEGImages/xxx.jpg,加载对应Annotations/xxx.xml; - 修正框后,务必点击“Save”而非“Save As”,否则生成新xml但原名不变,导致VOC/YOLO格式不一致;
- 修正后,用
xml_to_yolo.py脚本同步更新txt(见下节)。
注意:labelImg默认保存为Pascal VOC格式,无需额外设置。但若你误选“YOLO”模式,会覆盖原txt且坐标错误——永远以XML为权威源。
5.2 XML批量转YOLO:自研脚本比在线转换器更可靠
网上很多“VOC转YOLO”工具会忽略<size>中的depth或错误处理多目标,我用以下脚本(xml_to_yolo.py)确保100%准确:
import xml.etree.ElementTree as ET import os from pathlib import Path def convert_xml_to_yolo(xml_path, img_path, output_dir): tree = ET.parse(xml_path) root = tree.getroot() # 获取图像尺寸 size = root.find('size') width = int(size.find('width').text) height = int(size.find('height').text) # 读取类别映射(必须与data.yaml一致) class_names = ['999ganmaoling', 'banlangen', 'buluofen', 'drugs'] yolo_lines = [] for obj in root.findall('object'): cls_name = obj.find('name').text if cls_name not in class_names: continue cls_id = class_names.index(cls_name) bbox = obj.find('bndbox') xmin = int(bbox.find('xmin').text) ymin = int(bbox.find('ymin').text) xmax = int(bbox.find('xmax').text) ymax = int(bbox.find('ymax').text) # 转YOLO格式:归一化中心点+宽高 x_center = (xmin + xmax) / 2 / width y_center = (ymin + ymax) / 2 / height box_width = (xmax - xmin) / width box_height = (ymax - ymin) / height yolo_lines.append(f"{cls_id} {x_center:.6f} {y_center:.6f} {box_width:.6f} {box_height:.6f}") # 写入txt txt_path = output_dir / f"{Path(img_path).stem}.txt" with open(txt_path, 'w') as f: f.write('\n'.join(yolo_lines)) # 批量转换 xml_dir = Path("Annotations") img_dir = Path("JPEGImages") output_dir = Path("labels_fixed") # 新建目录,避免覆盖原labels output_dir.mkdir(exist_ok=True) for xml_file in xml_dir.glob("*.xml"): img_file = img_dir / f"{xml_file.stem}.jpg" if img_file.exists(): convert_xml_to_yolo(xml_file, img_file, output_dir)运行后,labels_fixed/里生成新txt,替换原labels/即可。此脚本优势:精确使用XML中<size>的宽高(而非imread读取),支持多目标,且保留class_id映射逻辑。
5.3 类别增删实战:如何安全加入“对乙酰氨基酚”
假设你要新增第5类acetaminophen,步骤如下:
- 修改
data.yaml:nc: 5,names追加'acetaminophen'; - 重标新图:用labelImg添加新类别,保存为XML;
- 更新YOLO txt:运行上述
xml_to_yolo.py,它会自动将acetaminophen映射为class_id=4; - 调整训练配置:
yolo detect train ... --epochs 100 --lr0 0.01(新类别需更多迭代); - 关键检查:
grep -r "4 " cold_drug_dataset/train/labels/ | head -n 5确认新类别txt已生成。
血泪教训:曾因忘记改
nc,训练时模型输出5维logits但loss函数只取前4维,导致cls_loss恒为nan。从那以后我每次新增类别,都强制走一遍python -c "import yaml; print(yaml.safe_load(open('data.yaml'))['nc'])"验证。
希望帮到你。
本文还有配套的精品资源,点击获取