简介:这是一份面向目标检测初学者与药品识别应用开发者的感冒药品分类检测数据集,可用于训练和验证药品包装检测模型,适用于零售药房自动盘点、智能货架识别等场景。压缩包共约2000个文件,以960个VOC格式xml标注、962个YOLO格式txt标注和960张jpg图片为主,xml与txt分别对应两种主流标注格式,图片为原始样本,整体约44.17MB,解压后可直接接入YOLO或VOC训练流程。数据集共标注4类,包括999ganmaoling、banlangen、buluofen及drugs,总框数1365,其中buluofen与999ganmaoling样本较多,drugs仅1框,类别分布差异明显,使用labelImg按矩形框规则标注。目前已有395人学习下载,读者可借此快速搭建药品检测基线、验证数据增强与类别不平衡处理策略,并对照标注文件理解VOC与YOLO格式的转换关系。
1. 959 张药品盒检测数据集:从文件名到标注框,这份资源到底能跑什么
拿到一个目标检测数据集,第一件事不是急着train.py,而是先搞清楚它能不能对上你的业务。这份「感冒药品分类检测数据集」给的是 959 张 jpg 图片,配套 Pascal VOC 的 xml 和 YOLO 的 txt 各 960 份,标注工具是 labelImg,类别四个:999ganmaoling、banlangen、buluofen、drugs。框数分布很不均——buluofen657 框、999ganmaoling573 框、banlangen134 框,而drugs只有 1 框。这个分布本身就是信号:前三类是真实可训的药品盒目标,drugs更像一个占位或兜底类,训练时基本可以忽略。它适合谁?做零售货架盘点、药房库存视觉识别、药品分拣机器人视觉模块的团队,拿它当冷启动数据最合适;想直接上产线的,得先接受它只有几百张、单类样本偏少的现实。下面按「先看懂格式 → 再跑通训练 → 再避坑 → 最后做增强」的顺序拆。
2. VOC 与 YOLO 双格式拆解:xml 和 txt 到底怎么对应
2.1 两种格式的字段映射关系
VOC 的 xml 是「一图一文件」,根节点<annotation>下挂<filename>、<size>(宽高通道)、以及若干个<object>,每个 object 里有<name>和<bndbox>(xmin/ymin/xmax/ymax,绝对像素坐标)。YOLO 的 txt 是「一图一文件」,每行一个目标,格式是class_id cx cy w h,全部归一化到 0~1。两者描述的是同一批框,只是坐标系和类别表达方式不同。这份数据集两种都给全了,意味着你可以直接喂给 YOLO 系列,也可以先用 VOC 工具链做可视化核对。
| 维度 | VOC xml | YOLO txt |
|---|---|---|
| 坐标 | 绝对像素 xmin/ymin/xmax/ymax | 归一化 cx/cy/w/h |
| 类别 | 字符串 name | 整数 class_id |
| 一图多目标 | 多个 object 节点 | 多行 |
| 常用工具 | labelImg、labelme | labelImg、多数训练框架 |
2.2 类别名到 id 的映射必须自己定死
YOLO 的 txt 里只有数字,没有类别名。这份数据集的四个类999ganmaoling、banlangen、buluofen、drugs对应哪个 id,取决于生成 txt 时的顺序。如果你直接拿 txt 训练却不核对映射,模型学出来的「0 号类」可能根本不是你以为的感冒灵。常见做法是写一个脚本,从 xml 里反查每个 id 对应的 name,生成一份classes.txt或data.yaml。
import os import xml.etree.ElementTree as ET from collections import defaultdict xml_dir = "annotations" # VOC xml 目录 id2name = defaultdict(set) for f in os.listdir(xml_dir): if not f.endswith(".xml"): continue tree = ET.parse(os.path.join(xml_dir, f)) root = tree.getroot() for obj in root.iter("object"): name = obj.find("name").text.strip() # 这里假设 txt 的 id 顺序与 xml 中类别首次出现顺序一致 # 实际应以生成 txt 的脚本为准,此处仅做反查校验 id2name[name].add(f) for name, files in id2name.items(): print(f"{name}: {len(files)} 张图包含该类")这段脚本不直接产出 id,而是统计每个类别出现在多少张图里。跑完你会看到drugs只出现在 1 张图,banlangen出现在约 130 张左右,和框数对得上。逻辑说明:ET.parse逐文件解析,root.iter("object")遍历所有目标框,name.text取类别字符串。参数上唯一要改的是xml_dir路径。跑完这一步,你心里对类别分布就有底了,再决定要不要把drugs合并或剔除。
2.3 用一条命令把 VOC 转成 YOLO 做交叉验证
虽然数据集已经给了 YOLO txt,但自己转一遍能验证两份标注是否一致。常见做法是用voc2yolo类脚本,核心是把绝对坐标归一化:
import xml.etree.ElementTree as ET def voc_to_yolo(xml_path, classes): tree = ET.parse(xml_path) root = tree.getroot() size = root.find("size") w = int(size.find("width").text) h = int(size.find("height").text) lines = [] for obj in root.iter("object"): name = obj.find("name").text.strip() if name not in classes: continue cls_id = classes.index(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.0 / w cy = (ymin + ymax) / 2.0 / h bw = (xmax - xmin) / w bh = (ymax - ymin) / h lines.append(f"{cls_id} {cx:.6f} {cy:.6f} {bw:.6f} {bh:.6f}") return lines classes = ["999ganmaoling", "banlangen", "buluofen", "drugs"] print(voc_to_yolo("999ganmaoling_xyxr_334.xml", classes))逻辑说明:先读图片宽高,再把每个框的中心点和宽高除以宽高做归一化,保留 6 位小数足够。参数说明:classes列表的顺序就是最终 id 顺序,必须和训练时的data.yaml完全一致,否则类别全乱。转完拿自己生成的 txt 和数据集自带的 txt 做 diff,如果行数和数值基本一致,说明标注可信;如果差很多,优先信 xml,因为 xml 里类别名是明文,不容易被 id 映射坑到。
3. 从 959 张图到可训练集:划分、配置与首轮训练
3.1 训练集验证集划分要按类别分层
959 张图直接随机 8:2 划分,drugs那一张大概率进验证集或训练集之一,导致某一侧完全没有该类。更稳的做法是按类别分层抽样,保证每个 split 里前三类都有。常见做法是用sklearn.model_selection.train_test_split的stratify参数,但多标签场景要自己构造分层键。简单点:以「图片包含的主要类别」为键做分层。
import os import random from collections import defaultdict img_dir = "images" all_imgs = [f for f in os.listdir(img_dir) if f.endswith(".jpg")] # 按文件名前缀粗分,实际应按标注内容分层 buckets = defaultdict(list) for f in all_imgs: if f.startswith("999ganmaoling"): buckets["999ganmaoling"].append(f) elif f.startswith("banlangen"): buckets["banlangen"].append(f) elif f.startswith("buluofen"): buckets["buluofen"].append(f) else: buckets["other"].append(f) train, val = [], [] for k, files in buckets.items(): random.shuffle(files) split = int(len(files) * 0.8) train += files[:split] val += files[split:] print(f"train: {len(train)}, val: {len(val)}")逻辑说明:按文件名前缀分桶,是因为这份数据集的命名里带了类别线索(如buluofen_xyxr_390.jpg)。参数说明:0.8是训练比例,样本少的类可以调到0.9让训练集多留一点。跑完把文件名写进train.txt和val.txt,YOLO 训练时直接读这两个列表。
3.2 data.yaml 的四个字段别写错
YOLO 系列训练入口通常吃一个 yaml,字段就四个关键项:
path: ./dataset train: train.txt val: val.txt nc: 4 names: ["999ganmaoling", "banlangen", "buluofen", "drugs"]逻辑说明:path是数据集根目录,train/val是相对路径的列表文件,nc是类别数,names顺序必须和 txt 里的 id 一致。参数说明:如果你决定剔除drugs,要把nc改成 3,names删掉最后一项,同时所有 txt 里 id 为 3 的行要么删要么重映射,否则训练时索引越界直接报错。这一步是新手翻车高发区,改类别数不改 txt,报错信息往往只给一个 index 越界,看不出根因。
3.3 首轮训练命令与关键超参
以常见的 YOLO 训练脚本为例,首轮建议小 epoch 跑通再放大:
python train.py \ --data data.yaml \ --img 640 \ --batch 16 \ --epochs 100 \ --weights yolov8n.pt \ --device 0逻辑说明:--img 640是输入分辨率,药品盒目标通常不大,640 够用;--batch 16在 8G 显存上比较稳;--epochs 100是首轮试探,看 loss 曲线再决定要不要加到 300;--weights用预训练权重能明显加快收敛。参数说明:如果显存不够,把 batch 降到 8 并开--amp;如果banlangen这类小样本类 recall 一直上不去,优先考虑过采样或 mosaic 增强,而不是盲目加 epoch。跑完看results.csv里的mAP50和每类instances,drugs那 1 个框基本不会贡献有效指标,别被它拉低整体判断。
4. 避坑与排查:这份数据集最容易翻车的五个点
4.1 现象:训练 loss 正常但验证 mAP 为 0
原因:data.yaml里names顺序和 txt 里的 id 对不上,模型学的是「错位类别」,验证时按正确名字算指标自然全错。解决:用第 2.2 节的脚本反查每个 id 对应的 xml 类别名,确认顺序一致后再训。这个坑最隐蔽,因为 loss 会正常下降,只有看混淆矩阵才发现类别全串了。
4.2 现象:报错IndexError: index 3 is out of bounds
原因:剔除了drugs类但没改 txt,或者nc写成了 3 而 txt 里还有 id=3 的行。解决:全局搜一遍所有 txt,把 id 大于等于nc的行删掉或重映射,再确认data.yaml的nc和names长度一致。血泪经验是改类别一定要「yaml + txt + 可视化」三处同步。
4.3 现象:banlangen类几乎检不出
原因:该类只有 134 框,且可能集中在少数图片里,模型见得太少。解决:先统计该类出现在多少张图,如果少于 100 张,考虑对含该类的图做复制过采样,或调低该类在损失里的权重惩罚。不要直接调高学习率,小样本类调学习率往往更糟。
4.4 现象:图片能读但标注框整体偏移
原因:VOC 的 xmin/ymin 是左上角,YOLO 的 cx/cy 是中心点,转换时如果忘了除以 2 或忘了归一化,框会整体偏移甚至超出 1。解决:转完抽 5 张图用可视化脚本画框,肉眼看框是否贴合药品盒。常见做法是用PIL画矩形,比对着原图看。
4.5 现象:训练到一半显存爆掉
原因:batch设太大,或 mosaic 增强把 4 张图拼成一张导致等效分辨率翻倍。解决:先把 batch 降到 8,再关掉 mosaic 试一轮。如果还爆,把--img从 640 降到 512。药品盒检测对分辨率没那么敏感,512 通常够用,别为了 640 硬撑爆显存。
5. 小样本药品检测的增强技巧:从 959 张里榨出更多有效样本
959 张图、1365 个框,放在目标检测里属于小数据集。想让它训出能用的模型,增强策略比模型结构更关键。我一般会按「先保类别平衡,再保尺度多样,最后保背景真实」的顺序做。
第一,针对banlangen只有 134 框的问题,做类别感知的过采样。不是简单复制图片,而是把含banlangen的图在训练列表里重复 2~3 次,同时配合 mosaic 让它们和其他类拼在一起,增加共现场景。这样模型不会因为该类样本少而直接忽略。
第二,尺度增强要克制。药品盒在货架上通常占图比例中等,mosaic 的scale参数别开太大,常见做法是scale=0.5,避免把盒子缩得太小导致标注框和实际目标错位。如果你发现验证时小目标 recall 明显低,优先检查是不是增强把目标缩过头了。
第三,背景要真实。这份数据集的图片命名带xyxr后缀,推测是同一批采集场景。如果全是白底或单一货架,模型换到真实药房会崩。常见做法是加mixup或随机裁剪,但 mixup 对小数据集容易产生不真实叠加,我一般只在 epoch 后 30% 开启,前期先让模型学干净特征。
第四,验证阶段别只看 mAP。小数据集上 mAP 波动大,建议同时看每类的 precision 和 recall,尤其是banlangen的 recall。如果 recall 长期低于 0.5,说明该类样本量或增强还不够,这时候加 epoch 没用,得回去补数据或调采样。
最后说个习惯:我每次拿到新数据集,都会先跑一遍「可视化 20 张 + 统计类别分布 + 核对 id 映射」这三件事,再动训练脚本。这份数据集的双格式给了很大便利,xml 用来核对,txt 用来训练,两边对不上就以 xml 为准。从那以后我每次接新数据集都强制走一遍这个流程,省下的返工时间远比那十分钟多。希望帮到你。
本文还有配套的精品资源,点击获取