
简介本资源为药品布洛芬目标检测数据集面向从事药品识别、智能零售与医药分拣等方向的算法工程师、学生及研究者可用于训练和验证单类别目标检测模型。压缩包共1430个文件包含476张jpg图片、476个VOC格式xml标注文件、476个YOLO格式txt标注文件及少量说明文件整体约19.62MBVOC与YOLO双格式并存便于直接接入YOLO系列或Faster R-CNN等主流检测框架。标注采用labelImg完成类别为buluofen共657个矩形框标注规范统一。目前已有175人学习下载。数据集覆盖多种拍摄角度与背景适合作为药品检测赛题或课程实验的基准数据帮助读者快速完成数据加载、模型训练与精度评估省去自行采集与标注的成本。1. 476张药品布洛芬检测数据集小样本目标检测到底能不能打药品包装检测这个场景很多人第一反应是「工业质检」但真正做过药房自动化、药品分拣或者智能药柜的工程师知道布洛芬这类常见OTC药品的视觉识别是个典型的细粒度小样本问题。同一货架上布洛芬缓释胶囊、布洛芬混悬液、布洛芬颗粒包装配色和版式高度相似类别间差异极小而单类样本量往往只有几百张。这份476张的布洛芬检测数据集提供VOC和YOLO两种标注格式正好卡在「够跑通一个baseline、但不够直接上产线」的区间。它适合谁适合想验证药品检测pipeline可行性的算法工程师、需要快速搭一个药品识别demo的嵌入式开发者以及正在做小样本目标检测研究、需要一个真实细粒度场景做对比实验的人。476张不是缺陷是约束条件——你得先搞清楚在这个约束下模型能到什么水平再决定要不要扩数据。2. 布洛芬数据集拆开看VOC与YOLO双格式的目录结构与标注逻辑2.1 476张图片背后的类别分布与场景假设拿到一个目标检测数据集第一件事不是急着训练而是先搞清楚它的分布。476张图片如果只有「布洛芬」一个类别那平均每张图大约1到2个目标框这个密度偏低意味着模型学到的上下文信息有限。如果包含多个类别比如不同剂型或不同品牌那每类可能只有100到200张属于典型的小样本。常见做法是先用脚本统计标注文件里的类别名和框数量别靠猜。下面这段Python代码遍历VOC格式的XML标注输出类别分布和每类框数import os import xml.etree.ElementTree as ET from collections import Counter # 指向VOC格式的Annotations目录 anno_dir ./VOCdevkit/VOC2007/Annotations class_counter Counter() boxes_per_image [] for xml_file in os.listdir(anno_dir): if not xml_file.endswith(.xml): continue tree ET.parse(os.path.join(anno_dir, xml_file)) root tree.getroot() objs root.findall(object) boxes_per_image.append(len(objs)) for obj in objs: name obj.find(name).text.strip() class_counter[name] 1 print(类别分布:, dict(class_counter)) print(每图平均框数:, sum(boxes_per_image) / len(boxes_per_image)) print(最大框数:, max(boxes_per_image), 最小框数:, min(boxes_per_image))逻辑说明这段代码不依赖任何深度学习框架纯标准库就能跑。class_counter统计每个类别出现的总次数boxes_per_image记录每张图的框数。参数上你只需要改anno_dir指向实际解压后的Annotations目录。跑完之后如果发现某个类别只有几十个框那训练时就要考虑类别不平衡的问题比如用focal loss或者对少数类做过采样。这一步的产出直接决定后续策略单类且框数少优先考虑数据增强和迁移学习多类且分布不均先解决长尾问题再谈精度。2.2 VOC转YOLO转换脚本与四个边界坑数据集同时提供VOC和YOLO两种格式但很多人拿到VOC格式后还是得自己转YOLO因为YOLOv5/v8的训练管线默认吃txt标注。转换本身不复杂但边界条件容易翻车。VOC的标注是xmin, ymin, xmax, ymax绝对像素坐标YOLO需要class_id, x_center, y_center, width, height归一化到0到1。转换公式x_center (xmin xmax) / 2 / img_width y_center (ymin ymax) / 2 / img_height width (xmax - xmin) / img_width height (ymax - ymin) / img_height下面是一个完整的转换脚本import os import xml.etree.ElementTree as ET from PIL import Image voc_anno_dir ./VOCdevkit/VOC2007/Annotations voc_jpeg_dir ./VOCdevkit/VOC2007/JPEGImages yolo_label_dir ./labels os.makedirs(yolo_label_dir, exist_okTrue) # 类别名到id的映射按你的实际类别顺序改 classes [ibuprofen] class_to_id {name: i for i, name in enumerate(classes)} for xml_file in os.listdir(voc_anno_dir): if not xml_file.endswith(.xml): continue tree ET.parse(os.path.join(voc_anno_dir, xml_file)) root tree.getroot() img_name root.find(filename).text img_path os.path.join(voc_jpeg_dir, img_name) if not os.path.exists(img_path): print(图片缺失跳过:, img_name) continue with Image.open(img_path) as im: w, h im.size lines [] for obj in root.findall(object): name obj.find(name).text.strip() if name not in class_to_id: continue bbox obj.find(bndbox) xmin float(bbox.find(xmin).text) ymin float(bbox.find(ymin).text) xmax float(bbox.find(xmax).text) ymax float(bbox.find(ymax).text) # 边界裁剪防止标注越界 xmin max(0, min(xmin, w - 1)) ymin max(0, min(ymin, h - 1)) xmax max(0, min(xmax, w - 1)) ymax max(0, min(ymax, h - 1)) if xmax xmin or ymax ymin: print(无效框跳过:, xml_file) continue xc (xmin xmax) / 2.0 / w yc (ymin ymax) / 2.0 / h bw (xmax - xmin) / w bh (ymax - ymin) / h lines.append(f{class_to_id[name]} {xc:.6f} {yc:.6f} {bw:.6f} {bh:.6f}) txt_name os.path.splitext(xml_file)[0] .txt with open(os.path.join(yolo_label_dir, txt_name), w) as f: f.write(\n.join(lines))逻辑说明脚本先读XML拿到图片文件名再用PIL打开原图获取宽高然后逐框转换。classes列表必须和你的实际类别一致顺序决定class_id。四个边界坑分别是第一标注框可能超出图片边界不做裁剪会导致归一化坐标大于1YOLO训练时直接报错第二xmax xmin的无效框要跳过否则宽高为负第三图片文件名和XML里的filename字段可能不一致要以实际文件为准做校验第四类别名如果有空格或大小写差异映射会丢框建议先统一strip和lower。转换完成后建议随机抽10张图用可视化脚本画框验证别直接开训。我一般会写一个简单的matplotlib脚本把YOLO格式的框还原到图上肉眼确认一遍再往下走。3. 用YOLOv8跑通布洛芬检测从环境配置到训练命令的完整链路3.1 Anaconda环境配置与ultralytics安装的版本选择YOLOv8的训练依赖ultralytics包环境配置本身不复杂但版本兼容性是个高频翻车点。截至我写这篇的时候ultralytics已经迭代到8.x系列Python要求3.8以上PyTorch要求1.8以上。如果你用的是较新的CUDA比如12.x建议直接装最新版PyTorch和ultralytics别去锁老版本否则编译依赖会让你怀疑人生。常见做法是用conda建一个独立环境conda create -n ibuprofen_yolo python3.10 -y conda activate ibuprofen_yolo pip install torch torchvision --index-url https://download.pytorch.org/whl/cu121 pip install ultralytics参数说明python3.10是个稳妥选择3.11和3.12在部分CUDA版本下会有wheel缺失。cu121对应CUDA 12.1如果你的是CUDA 11.8就换成cu118。装完之后用yolo checks命令验证环境它会输出PyTorch版本、CUDA是否可用、以及ultralytics版本。如果CUDA显示不可用先检查驱动版本别急着重装。这里有个血泪经验不要在base环境里直接pip install ultralytics它会把numpy、opencv等依赖升到最新可能破坏你其他项目的环境。独立环境是底线。3.2 数据集yaml配置与训练参数怎么设YOLOv8的数据集配置用一个yaml文件描述结构很简单但路径写错就找不到图。假设你的目录结构是ibuprofen_dataset/ images/ train/ val/ labels/ train/ val/对应的yaml文件path: ./ibuprofen_dataset train: images/train val: images/val nc: 1 names: [ibuprofen]path是数据集根目录train和val是相对路径。nc是类别数names是类别名列表顺序必须和转换脚本里的classes一致。如果只有单类nc: 1names里就一个元素。训练命令yolo detect train \ dataibuprofen.yaml \ modelyolov8n.pt \ epochs100 \ imgsz640 \ batch16 \ lr00.01 \ patience20 \ projectruns/ibuprofen \ nameexp1参数逐个说modelyolov8n.pt选的是nano版本476张图用大模型容易过拟合nano或small足够。epochs100配合patience20意思是20轮验证集指标不提升就早停省时间。imgsz640是默认输入尺寸如果你的图片分辨率远大于640可以适当调大但显存占用会涨。batch16在8GB显存下比较稳显存小就降到8。lr00.01是初始学习率小数据集不建议设太大否则loss震荡。训练过程中重点看两个指标mAP50和mAP50-95。476张图如果验证集划分合理比如8:2mAP50能到0.85以上就算这个数据集本身质量不错。如果低于0.6先别怀疑模型去检查标注有没有漏框或错框。3.3 训练结果解读mAP、混淆矩阵与置信度门限调整训练结束后runs/ibuprofen/exp1/目录下会生成一堆文件别只看results.csv里的最终mAP。混淆矩阵confusion_matrix.png能告诉你模型把布洛芬误判成什么如果背景被大量误检说明负样本不够或者标注里有漏标。val_batch0_pred.jpg是验证集第一批的预测可视化肉眼扫一遍比看数字快。置信度门限conf threshold的调整是个实操技巧。默认推理时conf0.25但在药品检测场景漏检比误检代价高我一般会把门限降到0.15到0.2之间先保召回。下面这段代码用训练好的模型做推理并调整门限from ultralytics import YOLO model YOLO(runs/ibuprofen/exp1/weights/best.pt) results model.predict( source./test_images, conf0.15, # 降低置信度门限提高召回 iou0.5, # NMS的IoU阈值 saveTrue, save_txtTrue ) for r in results: print(r.boxes.conf, r.boxes.cls)逻辑说明conf0.15让更多低置信度框进入NMSiou0.5控制重叠框的合并力度。save_txtTrue会把预测结果存成YOLO格式的txt方便后续做误差分析。如果发现误检太多再把conf往上调这个参数没有标准答案取决于你的业务容忍度。4. 小样本药品检测的避坑清单从标注到部署的五个翻车点4.1 标注框贴边导致训练loss异常现象训练前几个epoch loss就变成nan或者mAP一直是0。原因VOC转换YOLO时部分标注框的xmax等于图片宽度归一化后x_center width/2刚好等于1.0某些版本的YOLO在计算loss时会出现数值问题。解决转换时把坐标裁剪到[0, w-1]和[0, h-1]确保归一化坐标严格小于1。上面转换脚本里的max(0, min(xmax, w-1))就是干这个的。4.2 训练集和验证集图片重复导致指标虚高现象验证集mAP高得离谱但拿新图片测试效果很差。原因476张图本身就不多如果随机划分时没有去重同一张图可能同时出现在训练集和验证集。解决划分前先对图片做哈希去重用md5或感知哈希都行。我一般会写个脚本按文件名排序后按比例切分而不是用随机数这样可复现。4.3 类别名大小写不一致导致丢框现象转换后的txt文件里某些图是空的但XML里明明有标注。原因XML里类别名可能是Ibuprofen、ibuprofen、IBUPROFEN混着写而你的class_to_id只认一种。解决在转换脚本里对name做strip().lower()统一处理同时打印出所有未匹配的类别名人工确认一遍。4.4 图片格式不是RGB导致PIL读取通道数异常现象转换脚本报错cannot identify image file或者宽高读出来是0。原因部分图片是灰度图或RGBA四通道PIL打开后size正常但后续处理可能出问题。解决在Image.open之后加一句im im.convert(RGB)强制转三通道。这个操作不影响标注坐标因为宽高不变。4.5 推理时忘记切回eval模式导致结果随机现象用训练好的模型推理同一张图每次结果都不一样。原因如果手动加载模型后没有调用model.eval()BatchNorm和Dropout层还在训练模式。解决ultralytics的YOLO.predict内部会自动切eval但如果你自己写推理脚本加载best.pt记得加model.eval()和torch.no_grad()。这个坑在自定义部署时特别常见。5. 把476张用到极致小样本增强策略与模型微调技巧476张图要跑出可用的模型核心思路不是堆epoch而是让每张图产生更多有效梯度。我一般会从三个层面下手数据增强、迁移学习策略、以及推理阶段的TTA。数据增强方面YOLOv8默认开了mosaic、HSV抖动和随机翻转。但在药品检测场景mosaic会把四张图拼在一起可能让模型学到不相关的上下文。我的习惯是先把mosaic关掉mosaic0.0跑一版baseline再开mosaic跑一版对比。如果开mosaic后mAP反而降了说明这个场景不适合强拼接增强。另外药品包装的文字和logo对旋转敏感随机旋转角度别超过15度否则文字变形太严重模型学不到有效特征。迁移学习策略上yolov8n.pt是在COCO上预训练的COCO里有大量日常物品但没有药品。直接fine-tune所有层在小数据集上容易过拟合。一个实用技巧是冻结backbone的前几层只训练neck和head。ultralytics支持通过freeze参数指定冻结层数yolo detect train \ dataibuprofen.yaml \ modelyolov8n.pt \ epochs150 \ imgsz640 \ batch16 \ freeze10 \ lr00.005 \ projectruns/ibuprofen \ nameexp_freezefreeze10表示冻结前10层这些层学的是通用边缘和纹理特征不需要在476张图上重新学。lr00.005比默认的0.01更小因为可训练参数少了学习率太大会破坏预训练权重。跑完之后对比exp1和exp_freeze的mAP曲线如果冻结版收敛更快且最终mAP不低说明小数据集上冻结策略有效。推理阶段的TTATest Time Augmentation是另一个提点手段。ultralytics的predict支持augmentTrue它会对每张图做多尺度翻转推理再合并结果。代价是推理速度慢2到3倍但在离线质检场景可以接受。我实测在476张训练集上TTA能把mAP50提升1到3个百分点具体取决于验证集难度。最后说一个验证方法别只看mAP拿20张完全没参与训练的布洛芬图片可以从网上找或者自己拍跑一遍推理统计漏检和误检。如果漏检集中在某个角度或光照条件下说明数据增强没覆盖到那个分布针对性补数据比调模型更有效。476张是起点不是终点。我自己的习惯是每跑完一版模型就把bad case截图存到一个文件夹攒够50张就分析一次看是标注问题还是模型问题。这个习惯帮我省了很多盲目调参的时间。希望帮到你。本文还有配套的精品资源点击获取