
简介本资源是一套面向计算机视觉初学者与算法工程师的药品目标检测专用数据集聚焦感冒类药品图像识别任务适用于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人工框出药名区域且同时提供VOCxml和YOLOtxt两种格式——意味着你解压后不用改一行代码就能直接塞进Ultralytics、Darknet或MMDetection的训练流水线。它不解决“识别精度99%”这种玄学问题但能帮你把baseline模型训出来验证pipeline是否通畅再用它做迁移学习的起点。适合正在做药店智能盘点、家庭药箱管理App、或医药AI初学者项目的人——尤其当你发现标注类别里那个孤零零的drugs框全集仅1个就知道这数据集的真实感有多硬核现实世界里真有那种连药名都糊掉、只能标成“drugs”的过期药盒。2. 数据结构与格式解析为什么VOCYOLO双格式不是噱头而是落地刚需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 folderJPEGImages/folder filenamebuluofen_xyxr_328.jpg/filename size width1280/width height720/height depth3/depth /size object namebuluofen/name bndbox xmin421/xmin ymin215/ymin xmax876/xmax ymax532/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_width1280,image_height720代入xmin421,ymin215,xmax876,ymax532可得center_x (421876)/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: drugs1框drugs这个类别看似冗余实则是数据集设计者埋的“现实锚点”它代表无法辨识具体药名的药盒如褪色、破损、拍摄角度极端。在YOLO训练中若你强行删除该类别会导致类别ID错位原class_id3的样本消失后续class_id2布洛芬的样本在txt中仍写2但类别总数变为3Ultralytics会报IndexError: index 3 is out of bounds for dimension 0 with size 3。更严重的是它迫使你思考长尾分布问题——当测试集出现未知药盒时模型是否该输出drugs而非强行归为buluofen这个单样本本质是给你留的OODOut-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.pyimport 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(parentsTrue, exist_okTrue) (dst_base / split / labels).mkdir(parentsTrue, exist_okTrue) # 获取所有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_trainn_val], test: all_files[n_trainn_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(fSplit 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.0pip install ultralytics启动训练GPU可用时自动启用yolo detect train \ datacold_drug_dataset/data.yaml \ modelyolov8n.pt \ # 使用nano版预训练权重快且省内存 epochs50 \ imgsz640 \ batch16 \ namecold_drug_yolov8n \ projectruns/detect关键参数说明imgsz640YOLOv8默认输入尺寸本数据集图片多为1280×720缩放后信息损失可控batch16若显存8GB需降至8或4batch4时加--device 0指定GPUname实验名称日志存于runs/detect/cold_drug_yolov8n/modelyolov8n.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 \ datacold_drug_dataset/data.yaml \ modelruns/detect/cold_drug_yolov8n/weights/best.pt \ splitval关注输出中的metrics/mAP50-95(B)0.5~0.95 IoU阈值下的平均精度YOLOv8n在本数据集上合理范围是0.65~0.75。若低于0.5检查val/labels/是否为空常见错误脚本复制时漏了txt文件。对test集做推理yolo detect predict \ modelruns/detect/cold_drug_yolov8n/weights/best.pt \ sourcecold_drug_dataset/test/images \ save_txtTrue \ save_confTrue \ conf0.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 0width/height为0则跳过该样本并报此错。本数据集中drugs类仅1个框其txt文件内容为3 0.5 0.5 0.2 0.2假设但若你用其他工具重生成标签可能因浮点精度丢失导致width0.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(fEmpty label: {txt}) txt.unlink()4.2 现象验证时Recall: 0.000所有框都被漏检原因YOLOv8默认IoU阈值为0.5但本数据集中部分药盒框极小如drugs框仅占画面1%或存在严重遮挡如手捏着药盒一角导致预测框与GT框IoU0.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_id0会被当成布洛芬但实际是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设置过大如imgsz1280。本数据集原始图多为1280×720但YOLOv8n的backboneC2f在1280输入下显存暴涨batch size被迫降至2小batch加剧梯度噪声。解决坚持imgsz640或改用yolov8s.ptsmall版配imgsz640,batch32。4.5 现象test集推理结果全为drugs类原因conf0.25过低导致低置信度框被保留而drugs类因样本少模型对其学习不足输出置信度常在0.2~0.3之间恰好被截断。解决提高置信度阈值至conf0.5或对drugs类单独设阈值需修改predict.py源码更合理做法是在后处理中若最高置信度0.6强制标为drugs——这符合“拒识”设计初衷。5. VOC转YOLO的深度定制当你要加新药类别或修错标时5.1 手动修正XML标注用labelImg复现原始标注流程若发现某张图的框不准如999ganmaoling框住了旁边板蓝根盒子需用labelImg修正。关键步骤下载labelImgpip 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_okTrue) 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.yamlnc: 5names追加acetaminophen重标新图用labelImg添加新类别保存为XML更新YOLO txt运行上述xml_to_yolo.py它会自动将acetaminophen映射为class_id4调整训练配置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])验证。希望帮到你。本文还有配套的精品资源点击获取