简介:面向药品检测与目标检测模型训练场景,这份感冒药品分类检测数据集提供960张标注图片,覆盖999感冒灵、板蓝根、布洛芬、drugs四个类别,累计1365个矩形标注框,可用于药品识别、零售货架巡检、药品包装检测等实际任务。数据集同时提供Pascal VOC和YOLO两种格式标注,xml与txt文件按图片命名对应,适配LabelImg标注工具,可直接接入YOLO系列、Faster R-CNN等主流检测框架,省去格式转换环节。压缩包共2000个文件,以txt标签文件、xml标注文件和jpg图像文件为主,整体大小44.17MB,文件结构规整,便于批量读取和训练。目前已有394人学习/下载,适合目标检测初学者或药品行业开发者作为标注数据补充,既能用于教学实践,也可为医疗、零售场景的检测模型提供高质量的标注样本,节省人工采集与标注时间,提升项目迭代效率。
1. 目标检测数据集到手,先别急着训练
拿到「感冒药品分类检测数据集 959 张 3 类别 VOC+YOLO 格式的 zip 压缩包」这一类的目标检测数据集,多数人第一反应是解压出来直接丢进 YOLO 训练脚本里。然后踩坑:loss 异常、mAP 上不去、类别全丢。问题不在模型,在于你还没弄清这批数据到底长什么样、标注是不是干净、VOC 和 YOLO 两种格式之间到底能不能直接互相替代。这类小规模医学/药品类目标检测数据集通常只有几百到一千张图,3 个类别意味着每个类别的正样本数量非常有限,任何标注错位、类别映射错误都会在训练和评估时被无限放大。这篇文章就是把目标检测数据集的完整处理链路讲清楚:从解压 zip 开始,到目录识别、格式校验、可视化,再到用 YOLO 流程完成训练和验证,最后给出让这个小数据集发挥最大效用的几个实用建议。
2. 从 zip 里看清 VOC 与 YOLO 数据集的标准布局
2.1 解压结构里藏着两套标注体系
压缩包叫「感冒药品分类检测数据集 959 张 3 类别 VOC+YOLO 格式.zip」,绝大多数此类数据集解压后都是这样的目录骨架:
unzip "感冒药品分类检测数据集959张3类别VOC+YOLO格式.zip" -d ./medicine_dataset cd ./medicine_dataset find . -maxdepth 3 -type d | sort常见的目录布局要么是VOCdevkit风格,要么是images + labels的 YOLO 风格,有些打包者会把两种格式平铺成两个根目录,比如VOC/和YOLO/。如果是前者,里面通常是JPEGImages、Annotations、ImageSets/Main三分天下;如果是后者,images/train、labels/train一一对应。这是阅读数据的第一个关键点:目标检测数据集的可用性并不在于图片数量,而在于标注与图片的文件名是否完全咬合,一旦错位,模型读到的是错配的框,训练效率直接归零。
我一般会先做一个全量文件清单,确认图片数量和 xml/txt 数量是否对齐:
echo "图片文件数: $(find . -name '*.jpg' -o -name '*.jpeg' -o -name '*.png' | wc -l)" echo "VOC标注数: $(find . -name '*.xml' | wc -l)" echo "YOLO标注数: $(find . -name '*.txt' | wc -l)"配合数量检查,还需要看每个类别下的正样本图片数。3 类别通常对应「感冒颗粒 / 胶囊 / 口服液」或「颗粒 / 片剂 / 胶囊」一类划分,具体类别名需要看 labels 目录的.txt文件与配置文件obj.names或data.yaml来确认。常见坑是:txt 里的类别编号从 0 开始,而 xml 里的类别名是字符串,二者如果靠人工去写对应关系,漏一个就全乱。
2.2 VOC 的 XML 字段与 YOLO 的 TXT 字段到底差在哪
VOC 格式全称 PASCAL VOC,标注以 XML 描述图片中每个目标的<object>节点:
<annotation> <folder>JPEGImages</folder> <filename>IMG_0001.jpg</filename> <size> <width>640</width> <height>480</height> <depth>3</depth> </size> <object> <name>capsule</name> <bndbox> <xmin>100</xmin> <ymin>120</ymin> <xmax>260</xmax> <ymax>300</ymax> </bndbox> </object> </annotation>YOLO 格式则完全不同,每个 txt 文件与图片同名,每一行表示一个目标,五个字段依次是class x_center y_center width height,全部是相对图片宽高的归一化小数:
0 0.500000 0.466667 0.240625 0.325000VOC 保存的是左上角和右下角的绝对像素坐标,YOLO 保存的是中心点加宽高的相对坐标。理解这个差异是后续所有脚本和训练排错的地基,常见的「坐标全跑到图外、loss 不收敛」都源于换算公式用反。
| 对比维度 | VOC(XML) | YOLO(TXT) |
|---|---|---|
| 坐标形式 | 绝对值(像素) | 归一化小数(0~1) |
| 框的表示 | xmin, ymin, xmax, ymax | x_center, y_center, width, height |
| 类别表示 | 字符串标签名 | 数字编号(需配合 names 文件) |
| 每图文件数 | 1 个 xml 标注整张图 | 1 个 txt 标注整张图 |
| 常见配套目录 | JPEGImages + Annotations | images + labels |
从实际使用角度说,YOLO 系列训练脚本默认只吃 txt,VOC 格式一般用于学术对比、检测模型迁移和部分老工具链。拿到数据集以后,我建议以「原始格式识别 + 统一转换到 YOLO」为第一优先级,因为 YOLO 系的目标检测流程(无论 v5、v8 还是其他)全部围绕 images/labels 结构展开,统一后能免去几乎所有的格式兼容性问题。
3. 用脚本把感冒药品检测数据集从 VOC 转到 YOLO 并完成校验
3.1 数据集格式转换的最小实现
常见做法是写一个 Python 脚本,遍历Annotations下的 xml,用xml.etree.ElementTree解析出每个目标的类别和坐标,再做归一化映射。以感冒药品 3 类别数据集为例,先建立类别映射表,再用一个循环把所有标注转成 YOLO txt。这里给出一个可以直接复制的实现:
import os import xml.etree.ElementTree as ET # 类别映射:以数据集中真实类别名为准,这里仅示例 class_mapping = { 'capsule': 0, 'granule': 1, 'oral_liquid': 2, } voc_ann_dir = 'VOC/Annotations' yolo_label_dir = 'YOLO/labels' os.makedirs(yolo_label_dir, exist_ok=True) for xml_file in os.listdir(voc_ann_dir): if not xml_file.endswith('.xml'): continue tree = ET.parse(os.path.join(voc_ann_dir, xml_file)) root = tree.getroot() img_w = int(root.find('size/width').text) img_h = int(root.find('size/height').text) txt_name = xml_file.replace('.xml', '.txt') lines = [] for obj in root.findall('object'): name = obj.find('name').text if name not in class_mapping: print(f'警告:未知类别 {name} 出现在 {xml_file}') continue class_id = class_mapping[name] bndbox = obj.find('bndbox') xmin = float(bndbox.find('xmin').text) ymin = float(bndbox.find('ymin').text) xmax = float(bndbox.find('xmax').text) ymax = float(bndbox.find('ymax').text) # 关键:确保边界坐标不越界 xmin = max(0, min(xmin, img_w - 1)) xmax = max(0, min(xmax, img_w - 1)) ymin = max(0, min(ymin, img_h - 1)) ymax = max(0, min(ymax, img_h - 1)) x_center = (xmin + xmax) / 2.0 / img_w y_center = (ymin + ymax) / 2.0 / img_h w = (xmax - xmin) / img_w h = (ymax - ymin) / img_h # 过滤掉无效小框或全零框 if w <= 0 or h <= 0: print(f'跳过无效框:{xml_file} 中 {name}') continue lines.append(f'{class_id} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}') with open(os.path.join(yolo_label_dir, txt_name), 'w') as f: f.write('\n'.join(lines))脚本逻辑并不复杂,但有三处细节值得单独强调。第一是坐标裁剪,XML 里偶尔会有手工标注越界的点,比如 xmax 超过图片宽度 2 个像素,这类边界数据在训练时会导致 anchor 匹配异常,最好在转换阶段统一处理掉。第二是归一化的顺序,必须先算中心点再除以宽高,如果用原始宽高表达式(xmax - xmin) / 2 + xmin再归一化,同样结果没问题,但要避免把两个端点直接当成中心点用。第三是过滤w <= 0的退化框,这种框在 VOC 工具链里能存在,在 YOLO 输入管线里则会成为 NaN loss 的来源。
3.2 转换后必须做的标注完整性检查
转换完成不等于数据可用,还要做一轮自动校验。这一步是新手最容易跳过但老手绝不省略的环节。主要检查三件事:txt 文件与图片文件是否同名存在、txt 里每行的类别编号是否存在于 0~2 之间、归一化坐标是否都落在 0~1 区间内。
import os image_dir = 'YOLO/images' label_dir = 'YOLO/labels' valid_classes = {0, 1, 2} image_names = {os.path.splitext(f)[0] for f in os.listdir(image_dir)} label_names = {os.path.splitext(f)[0] for f in os.listdir(label_dir)} missing_labels = image_names - label_names missing_images = label_names - image_names print(f'缺标注的图片数量: {len(missing_labels)}') print(f'缺图片的标注数量: {len(missing_images)}') for f in sorted(os.listdir(label_dir)): if not f.endswith('.txt'): continue with open(os.path.join(label_dir, f)) as fh: for line in fh: parts = line.strip().split() if len(parts) != 5: print(f'字段数错误: {f}') continue cls = int(parts[0]) coords = list(map(float, parts[1:])) if cls not in valid_classes: print(f'类别编号越界: {f}') if any(c < 0 or c > 1 for c in coords): print(f'坐标越界: {f}')像感冒药品这种小目标检测场景,图像里一个胶囊或一袋颗粒只占画面的百分之几,坐标转换差一点点就可能导致目标框偏移到背景上。因此校验脚本建议在每次处理数据集后都跑一遍,不要只跑一次就对自己说「没问题」。我也习惯顺手统计每个类别的目标框数量,确认三个类别是否均衡。类别不均衡严重时,即便训练 mAP 数值看起来还行,实际推理对少数类别的召回率会非常难看,这种情况在自带小样本特性的药品分类数据上尤其常见。
4. 用拆分后的照片跑通 YOLO 目标检测流程并调参
4.1 构建训练集与验证集的数据集 YAML
YOLO 系训练框架统一通过 YAML 描述数据集路径与类别数。把 959 张感冒药品图片按 8:1:1 或 8:2 拆成训练集和验证集之后,配置文件长这样:
path: /home/user/medicine_dataset/YOLO train: images/train val: images/val test: images/test nc: 3 names: 0: capsule 1: granule 2: oral_liquid如果你的数据集没有预拆分,用一行 Python 按比例随机划分即可:
import os import random import shutil src_images = 'YOLO/images' src_labels = 'YOLO/labels' out_base = 'YOLO' random.seed(42) for split in ['train', 'val', 'test']: os.makedirs(f'{out_base}/images/{split}', exist_ok=True) os.makedirs(f'{out_base}/labels/{split}', exist_ok=True) all_files = [f for f in os.listdir(src_images) if f.endswith('.jpg')] random.shuffle(all_files) n = len(all_files) n_train = int(n * 0.8) n_val = int(n * 0.1) splits = { 'train': all_files[:n_train], 'val': all_files[n_train:n_train + n_val], 'test': all_files[n_train + n_val:], } for split, files in splits.items(): for f in files: shutil.move(os.path.join(src_images, f), f'{out_base}/images/{split}/{f}') label_file = f.replace('.jpg', '.txt') shutil.move(os.path.join(src_labels, label_file), f'{out_base}/labels/{split}/{label_file}')YAML 里的path建议写成绝对路径,避免不同系统相对路径歧义;nc必须与类别映射表的数量保持一致,否则训练进程直接报错。拆分的随机种子固定住,保证每次实验结果可复现,这一点在反复调参时尤其有用。接下来就是真正把数据送进 YOLO 目标检测流程里跑起来。
4.2 用预训练权重训练感冒药品检测模型
训练命令要能直接看出输入输出的位置,以 YOLOv8 为例,最直接的训练命令是:
cd /home/user/medicine_dataset yolo detect train \ data=medicine.yaml \ model=yolov8s.pt \ epochs=100 \ imgsz=640 \ batch=16 \ lr0=0.01 \ patience=20 \ project=runs/detect \ name=gansmao_exp如果你的环境里是 YOLOv5,对应的训练命令稍有不同:
python train.py \ --data medicine.yaml \ --weights yolov5s.pt \ --img 640 \ --epochs 100 \ --batch 16 \ --project runs/train \ --name gansmao_exp959 张的规模决定了模型不应从随机权重从头训练,预训练权重提供的底层特征(边缘、纹理、局部形状)对药品这类小物体极其宝贵。常见参数配置逻辑是:yolov8s或yolov5s足够,没必要上l或x,因为数据量只有千张级别,大模型在这个规模上更容易过拟合;imgsz=640是速度和精度之间的常见默认值;batch大小根据显存调整,显存不足就降到 8 或 4;patience做早停,小数据集训练很容易在 60~80 轮后开始震荡,早停可以帮你保住最佳权重。
| 参数名 | 推荐值 | 说明 |
|---|---|---|
| model | yolov8s.pt | 小模型在千级数据集上泛化性更好 |
| imgsz | 640 | 标准输入分辨率,兼顾速度与精度 |
| batch | 4~16 | 由显存决定,越稳定越好 |
| lr0 | 0.005~0.01 | 小数据集可适当降低初始学习率 |
| patience | 20~30 | 验证集分数停滞时提前终止 |
| cache | True | 小数据集可缓存到内存,显著提速 |
AMD 显卡用户需要注意,原生 YOLO 环境大多面向 NVIDIA CUDA,换到 AMD 平台需要配好对应推理环境或改用 CPU 训练。这类环境问题不是模型问题,尽可能先用云 GPU 或本地 NVIDIA 卡跑通一小轮(比如 10 个 epoch)确认数据链路没问题,再开完整训练。第一个 epoch 的 loss 曲线如果迅速下降然后趋于平缓,说明数据和模型整体健康;如果 loss 直接跳动甚至出现 NaN,优先排查标注文件而不是瞎调学习率。
5. 验证检测效果时重点看每类别的各项指标而不是总体精度
5.1 用 confusion matrix 和 per-class AP 定位类别混淆
训练结束后,YOLO 输出目录里会有confusion_matrix.png和results.csv。先别急着看 mAP,把三个类别的 precision、recall 和 AP 分别列出来,看哪个类别拖后腿。感冒药品的三个类别在视觉上极易混淆:胶囊壳有半透明反光,颗粒冲剂形态不规则,口服液玻璃瓶反光强烈,这些都会让检测模型把背景纹理误判为目标边缘。下边这个命令直接读取训练输出中的验证结果,逐类汇报检测效果:
cat runs/detect/gansmao_exp/results.csv | column -t -s,重点看指标为metrics/precision(B)、metrics/recall(B)、metrics/mAP50(B)和metrics/mAP50-95(B)的行。如果你的识别任务中口服液类别 recall 偏低,后续改进方向是:针对小目标检测调整 anchor 或增大输入分辨率,或者在数据增强中增加 HSV 扰动强度来模拟不同光照下的玻璃反光。如果 confusion matrix 显示颗粒经常被误判为胶囊,就该回看训练图片里这两类的朝向和尺度分布,而不是盲目增加训练轮数。
5.2 用推理图检查感冒药品解的框是否贴合目标轮廓
跑一轮真实推理,把预测结果画回原图,是比任何数值都直观的验证方式。YOLO 提供了直接推理并输出标注图的命令:
yolo predict \ model=runs/detect/gansmao_exp/weights/best.pt \ source=YOLO/images/val \ save=True \ conf=0.25 \ iou=0.45 \ project=runs/val_predict \ name=visual_check检查推理图时看三个细节:目标框边缘是否明显大于或小于药品实体边界(说明标注不精准)、同一目标是否出现多个重叠框(NMS 阈值偏低)、小目标是否被漏检。如果推理结果里框总是偏大,说明验证集标注本身存在误差,需要回炉修正;如果偏小,通常不是标注问题,而是模型把药品局部纹理当成完整物体,这时需要检查训练集中的标注是否遗漏了部分目标。
5.3 小数据集上让检测精度再上一个台阶的实践技巧
959 张图做 3 类别检测,模型很快就能达到「能看但不稳」的水平。要继续提升,常见的方向是按顺序做这几件事:第一,把图片分辨率从 640 提到 960,对感冒药品这类小目标检测数据集来说,分辨率提升的收益往往比换模型架构更大,代价是训练显存和推理耗时增加;第二,用 YOLO 自带的数据增强参数,比如hsv_h=0.015、hsv_s=0.7、hsv_v=0.4、degrees=10,让药品在不同光照和轻微旋转下依然能被检出,注意幅度不能太大,否则颗粒类目标会形变到不可识别的程度;第三,用已经训练好的权重做第二轮微调,把epochs降到 50、学习率降到 0.001,让模型在专用数据上精修而不是从头学一遍。
再进阶一点的做法是用mosaic=0.5替代默认的 1.0。mosaic 增强在这个规模的数据集中容易制造大量包含多个药品目标但不含标注信息的小块,过强的 mosaic 反而干扰小目标学习。另外可以尝试训练时开启cos_lr=True,让学习率余弦衰减,通常能比固定衰减多稳定最后一小段训练。最后一个建议是,如果这种药品分类检测的标注质量一般,优先用置信度阈值 0.35 而不是 0.25 做推理,避免把药品包装上的文字区域也框成目标。
本文还有配套的精品资源,点击获取