尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

药品感冒药999感冒灵检测数据集实战:VOC转YOLO与训练避坑指南

药品感冒药999感冒灵检测数据集实战:VOC转YOLO与训练避坑指南 简介这是一份面向计算机视觉学习者、算法工程师和药品零售场景开发者的目标检测数据集主题为“999感冒灵”外包装识别。数据集共提供372张jpg原图以及一一对应的VOC格式xml标注文件和YOLO格式txt标注文件由labelImg工具标注完成标注类别为999ganmaoling标注框总数573个每个类别框数明确。压缩包内文件总数为1119个其中jpg、xml、txt三类文件数量基本对应便于按图像-标签一一对应的方式完成训练集和验证集划分资源包整体约20.88MB适合快速下载后直接用于YOLO系列、Faster R-CNN、MMDetection等常见检测框架的训练、迁移学习与效果验证。目前已有249人学习下载可作为药品外包装检测、零售货架识别、目标检测课程作业等场景的入门或补充数据。由于数据已标明图片数、标注框数和类别名使用者能够方便评估数据规模、判断样本均衡性并进一步做数据增强、难例挖掘或格式转换减少前期数据整理成本。1. 药品感冒药999感冒灵检测数据集372张图能做什么、不能做什么「药品感冒药999感冒灵检测数据集」这个名字乍看普通却是目标检测里最典型的“小而专”样本372张图片、VOC与YOLO两套标注格式场景几乎全是药店货架、桌面、手持包装盒这类近景拍摄。它的价值不在规模而在干净——类别少、背景相对固定、光照和角度还算可控正好用来验证迁移学习在小样本上的真实下限。对刚入门目标检测的开发者它是练习数据标注规范、跑通训练全流程的练手样本对做药品识别或药店库存盘点的从业者它是低成本验证方案可行性、再决定是否扩采数据的起点。这一篇我就按自己拿到这种小数据集后的完整套路来讲先体检数据再统一格式然后训练调参最后教你避开小数据集最常翻车的几个坑。2. 拆开药品感冒药999感冒灵检测数据集目录结构、标注内容与体检方法2.1 VOC与YOLO两套标注并存的目录长什么样解压zip之后先把目录结构摸清楚。一个同时提供VOC和YOLO格式的数据集常见做法是AnnotationsXML标注、JPEGImages原图、labelsYOLO的txt标注三个目录平行存在或者按images/train与labels/train这种YOLO惯例拆分。区别只在于组织方式标注内容等价。路径常见布局格式内容作用JPEGImages/.jpg原始图片模型输入Annotations/.xmlVOC标注对象名、bndbox坐标、图片尺寸人类可读、便于画框检查labels/.txtYOLO格式类别ID 归一化中心坐标 宽高直接喂给YOLO训练拿到手先别急着训练我一般会先做两件事确认每个XML在labels里有对应txt确认每个txt里没有空行和越界坐标。这种小数据集通常是人工或半自动标注的最怕的是标注者用不同工具补标过几批导致格式不统一尤其是类别名大小写混用、某一两张图漏标。2.2 372张图的样本结构与“小数据集”的真实含义372张图对目标检测来说非常小。以YOLOv8默认的COCO预训练权重为起点做迁移学习勉强够用但要想让模型对“不同光照下的同款药盒”都稳定这个量级必然吃紧。我的判断标准是单类别检测每类至少100~300个正样本框且每个框在尺寸、角度、遮挡状态上要有差异。999感冒灵的包装盒外观相对统一372张图里如果大多数是正面平拍模型的泛化性会很差如果混入了货架斜拍、多盒堆叠、部分遮挡反而更有训练价值。所以拿到数据的第一件事不是配环境而是统计。统计每个类别到底有多少框、每张图平均几个框、图片尺寸分布如何、有没有灰图或损坏文件。这些决定了后面要不要做数据增强、用多大的输入分辨率、验证集怎么划分。2.3 训练前必做的数据体检一张图对应几个框、框是否越界这里给你一个可以直接跑的统计脚本把JPEGImages和Annotations丢进去它会输出图片数量、XML数量、类别分布、图片尺寸范围和异常文件省得一张张翻文件夹。import os import xml.etree.ElementTree as ET from collections import Counter from PIL import Image img_dir JPEGImages ann_dir Annotations imgs [f for f in os.listdir(img_dir) if f.endswith(.jpg)] xmls [f for f in os.listdir(ann_dir) if f.endswith(.xml)] print(图片数:, len(imgs), 标注数:, len(xmls)) name_counter Counter() # 类别名统计 size_set set() # 图片尺寸集合 missing_img [] # 有xml但缺jpg的 for xml_name in xmls: tree ET.parse(os.path.join(ann_dir, xml_name)) root tree.getroot() # 检查对应图片是否存在 img_name root.find(filename).text if img_name not in imgs: missing_img.append(img_name) width int(root.find(size/width).text) height int(root.find(size/height).text) size_set.add((width, height)) for obj in root.iter(object): name obj.find(name).text name_counter[name] 1 print(类别统计:, name_counter) print(出现过的图片尺寸:, size_set) print(缺失图片的xml:, missing_img[:10])这段脚本的逻辑很简单遍历所有XML记录类别名和图片尺寸同时检查filename字段引用的图片是否真的存在于JPEGImages目录。缺失图片、类别名不一致、尺寸混乱这三类问题都会在后续转换或训练时暴雷。参数上要注意root.find(size/width)这一层路径不同标注工具生成的XML结构略有差异有些把size节点放在object之前有些把difficult、truncated等字段加在object里脚本都能兼容但如果遇到字段缺失会抛AttributeError到时再把对应XML单独打出来看就行。3. 把VOC转成YOLO标注一个脚本和四条边界规则3.1 为什么必须转YOLO只认txt不认XMLVOC标注是把“框的左上角和右下角坐标”直接写进XML而YOLO训练要求每张图对应一个txt每一行是“类别ID、归一化中心x、归一化中心y、归一化宽、归一化高”。两者描述的是同一个框但坐标系不同。如果你的zip里只有Annotations和JPEGImages训练前就必须自己做转换如果labels目录已经存在你也最好抽样验证一下转换逻辑是否正确因为很多数据集发布者用的转换脚本不一定处理了越界坐标。转换的核心公式就两行x_center (xmin xmax) / 2 / img_width y_center (ymin ymax) / 2 / img_height box_width (xmax - xmin) / img_width box_height (ymax - ymin) / img_height这四个值全部归一到0~1之间。注意归一化的分母是图片宽高不是标注框的宽高新手在这里容易写反。3.2 一个能直接用的VOC转YOLO脚本下面这段是数据转换脚本兼容单个XML和整个Annotations目录批量处理。import os import xml.etree.ElementTree as ET from pathlib import Path def voc_to_yolo(xml_path, class_map, out_dir): 把单个VOC XML转成YOLO txtclass_map是类别名到ID的映射 tree ET.parse(xml_path) root tree.getroot() img_w int(root.find(size/width).text) img_h int(root.find(size/height).text) lines [] for obj in root.iter(object): # difficult1的对象建议跳过避免干扰训练 if obj.find(difficult) is not None and int(obj.find(difficult).text) 1: continue name obj.find(name).text if name not in class_map: print(f跳过未映射类别: {name} 在 {xml_path.name}) continue cls_id class_map[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) # 边界裁剪防止坐标越界 xmin max(0, min(xmin, img_w - 1)) ymin max(0, min(ymin, img_h - 1)) xmax max(0, min(xmax, img_w - 1)) ymax max(0, min(ymax, img_h - 1)) if xmax xmin or ymax ymin: print(f非法框: {xml_path.name} {name}) continue x_center ((xmin xmax) / 2) / img_w y_center ((ymin ymax) / 2) / img_h w (xmax - xmin) / img_w h (ymax - ymin) / img_h lines.append(f{cls_id} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}) if lines: out_name Path(xml_path).stem .txt with open(os.path.join(out_dir, out_name), w) as f: f.write(\n.join(lines)) # 示例调用把Annotations下所有xml转换到labels目录 class_map {ganmaoling: 0} # 类别名-ID按你的实际类别改 os.makedirs(labels, exist_okTrue) for xml_file in Path(Annotations).glob(*.xml): voc_to_yolo(xml_file, class_map, labels)逻辑说明先解析XML拿到图片宽高再遍历object节点提取每个目标的类别和bndbox四角坐标。中间做了两步防御一是跳过difficult标记的目标二是把框坐标裁剪到图片边界内避免标注时手滑标出图的负坐标。最后按YOLO五列格式写入txt类别ID由class_map映射。参数上你需要改动的只有class_map。药品数据集的类别名可能是999、ganmaoling、感冒灵这样五花八门的写法务必先跑一遍第2章的统计脚本确认类别名的真实写法再填进映射。另外输出小数精度保留6位足够文件路径用Path对象拼接比字符串加号更稳妥Windows和Linux下都能跑。3.3 转换后的四个边界坑类别名不一致、空txt、重复框、图片尺寸不匹配第一坑类别名大小写不一致。同一个感冒灵有的XML写ganmaoling有的写Ganmaoling直接导致转换时后者被当成未映射类别跳过标注框莫名丢失。解决方法是统计脚本里先对类别名做Counter发现大小写混用就用一个规范化字典统一映射。第二坑转换后有些txt是空的。常见原因是该XML里所有对象的类别都没进class_map或者全部被difficult过滤。空txt在训练时会报label格式错误或直接忽略你根本不知道那张图没参与训练。转换完要跑一行检查打印所有字节数为0的txt再回去看对应XML。第三坑重复标注框。同一个人在标注时可能把同一个药盒框了两次位置几乎重叠。YOLO训练对这种重复框不报错但会重复计算损失让模型对那个位置的响应过强。最简单的方法是转换时按IoU去重两个框IoU大于0.9就只保留置信度高的那个。第四坑图片尺寸与XML里的size不一致。有的数据集图片被统一压缩过但XML里的width/height没跟着改归一化后的坐标全是错的。验证方法很直接用PIL读图片实际尺寸在程序里加一个断言不一致时打印警告并优先使用图片真实尺寸做归一化分母。4. 用YOLOv8训练这个小数据集最小配置、损失函数与参数调法4.1 数据YAML怎么写路径、类别名、train/val划分把数据准备好之后YOLO训练前要写一个数据描述文件。这个YAML是Ultralytics框架的入口路径写错或者类别名对不上会直接在训练开始时报错。# ganmaoling.yaml path: ./datasets/ganmaoling # 数据集根目录绝对或相对当前工作目录 train: images/train # 训练图片目录 val: images/val # 验证图片目录 names: 0: ganmaoling # 类ID从0开始与txt标注一致数据集的图片要按train和val分好而不是让YOLO自动划分。372张图这样的小数据集自动划分容易造成验证集里混入与训练集高度相似的图片导致评估虚高。我一般按9:1或8:2人工划分同时保证同一盒药的不同角度照片只出现在train或只出现在val否则模型在验证集上的成绩只能算记忆测试。训练命令是yolo detect train dataganmaoling.yaml modelyolov8n.pt epochs100 batch16 imgsz640 patience20model用的是yolov8n.ptnano版本对这个任务足够轻。如果你的显卡只有4GB显存batch降到8或4imgsz降到480或320不要硬扛。输出目录默认在runs/detect/train训练结束后weights目录下会有best.pt和last.pt验证和推理都用best.pt。4.2 小数据集下损失函数的表现怎么看YOLOv8训练时终端会实时打印三个损失box_loss、cls_loss、dfl_loss。新手经常只看总loss往下掉就觉得没问题其实拆分看才有效果。box_loss衡量预测框与真实框的位置差距cls_loss衡量分类是否正确dfl_loss是Distribution Focal Loss负责让框的边界回归更精准。小数据集上最常见的现象是cls_loss快速降到很低但box_loss和dfl_loss震荡甚至上升。原因是372张图里外观差异太小模型很快记住了“这个类别是什么”但框的位置边界始终学不扎实。遇到这种情况不要盲目加epochs先把imgsz提到640让模型看到更多像素级细节再检查标注框是不是普遍过大或过小如果全是紧贴盒身的框dfl_loss可能因为边界锐利而波动剧烈这是正常现象重点看验证集mAP是否同步上升。4.3 372张图的训练策略迁移学习、数据增强和早停我训练这种小数据集的标准动作是三步。第一步基于COCO预训练权重yolov8n.pt开始训练而不是从零随机初始化迁移学习能省掉大量“学边缘、学纹理”的epoch。第二步打开数据增强ultralytics默认的augmentation对它来说偏保守你可以在训练命令里手动调大yolo detect train dataganmaoling.yaml modelyolov8n.pt epochs100 batch16 imgsz640 \ hsv_h0.02 hsv_s0.8 hsv_v0.6 flipud0.2 translate0.1 scale0.5hsv_h是色调扰动药品包装盒的颜色是特征之一不要调太大否则药盒颜色被改得不像药盒scale0.5允许图片缩放范围更大模拟不同拍摄距离。第三步设patience20连续20个epoch验证集mAP不涨就早停小数据集过拟合极快最后二十个epoch基本都在反复横跳早停能帮你留下成绩最好的权重而不是最后一个epoch的权重。这三个参数我几乎每次都要调batch、imgsz、patience。很多人只看epochs其实在小数据集上patience比epochs重要得多372张图训练100个epoch大概几分钟但第40个epoch可能就已经是最优点了后面全是过拟合。5. 药品检测数据集训练的五个坑过拟合、标注泄漏与格式陷阱5.1 训练loss很好看验证集mAP却很低现象终端打印的loss一路下降cls_loss趋近于0但epoch结束后验证集mAP50只有0.2左右。原因过拟合。372张图片对模型来说太少网络直接把训练图的背景纹理、光照条件记住了压根没有学到“感冒灵包装盒”这个类别本身。尤其是数据集中如果同一张图多次出现重复样本模型学到的就只剩记忆。解决先查重复图片用图片的MD5值去重然后降低模型复杂度从yolov8m换到yolov8n再拉大数据增强强度重点加scale和fliplr最后减少训练轮数把patience从20降到10让训练提前终止在泛化最好的位置。5.2 类别名大小写不一致转换时静默丢框现象训练前统计txt里总框数发现比XML里所有object节点数少了一截但没有任何报错。原因第3.2节脚本里对未映射类别是打印一条提示后continue如果类名Ganmaoling和ganmaoling并存后者会被跳过。数据集如果是多人标注合并而成这种问题几乎必现。解决转换前先跑第2.3节统计脚本把Counter打印出的所有类别名列出来逐一核对大小写和空格。规范做法是在脚本里加一行name name.strip().lower()统一转小写再做映射一劳永逸。5.3 标注框在图片边缘越界坐标把训练搞崩现象训练刚开始就报ValueError: All bounding boxes should have positive height and width或者某个epoch中途loss变成NaN。原因标注工具允许框拖出图片边界xmin或ymin可能是负值xmax或ymax大于图片宽高。YOLO在计算IoU时负值和越界坐标会产生非法宽高。解决转换脚本里的边界裁剪不能省xmin max(0, min(xmin, img_w - 1))这一行就是后悔药。针对已有txt的情况写一段脚本遍历labels把越界值全部裁剪并重新归一化。裁剪后如果出现xmax xmin的退化框直接删掉这一行。5.4 图片文件损坏训练中途读图失败现象训练跑到第七八个epoch突然抛PIL.UnidentifiedImageError或者Image file is truncated程序终止。原因数据在打包zip、拷贝、解压过程中图片文件损坏或者某张jpg本来就是截断的。这类问题在372张的小数据集中随机出现一张就能让你的训练中断而且每次中断的epoch可能都不同看起来非常像玄学。解决训练前对JPEGImages做一次全量完整性检查。用PIL逐个Image.open(img).load()读取失败的直接移动到corrupted目录。不要删原文件移动出去方便后面人工确认是否可修复。from PIL import Image from pathlib import Path for img_path in Path(JPEGImages).glob(*.jpg): try: img Image.open(img_path) img.load() except Exception as e: print(f损坏图片: {img_path} - {e}) img_path.rename(Path(corrupted) / img_path.name)这段脚本的PIL部分没有多余参数关键是load()方法必须在open之后手动调用否则拖延到训练时才真正读数据问题就后置了。5.5 划分验证集时泄漏同一盒药的两个角度分别进了train和val现象验证集mAP高达0.95部署到现场拍新的药盒检测效果一塌糊涂。原因数据划分时用的是随机划分没有考虑样本来源。同一盒感冒灵在不同角度、不同光线下的多张照片如果一张进train一张进val模型在验证时等于直接看到了训练过的同一个物体评估成绩虚高。解决按“物体实例”而非“图片”划分。看文件名是否包含药品编号或拍摄批号比如ganmaoling_01_a.jpg和ganmaoling_01_b.jpg属于同一个药盒就应整组划入同一侧。实在没有编号信息就用聚类思路把所有图片按拍摄批次分组批次为单位划分train/val保证批次不交叉。6. 模型落地的验证套路先过混淆矩阵再过手机实拍最后一个阶段是验证模型是不是真的能用。我自己的验收套路分三层第一层看训练输出里的混淆矩阵和PR曲线确认这个类别没有系统性误检第二层拿训练时没见过的现场照片做批量推理看漏检率和误检率第三层是拍一段货架视频跑实时推理看帧率和小角度偏移下的稳定性。混淆矩阵关注两处一是背景被误判为感冒灵的比例高不高二是感冒灵被漏判的比例高不高。药品包装盒之间颜色相近如果矩阵里背景行有明显响应就需要在推理时提高置信度阈值默认0.25太宽容建议调到0.4甚至0.5。视频推理用yolo predict modelbest.pt sourcetest_video.mp4 conf0.4如果检测框在连续帧间跳来跳去优先检查imgsz是不是太小640下小包装盒的边缘特征不够换成960或1280通常能稳定很多代价是帧率下降。我的习惯是训练结束后把best.pt和那一批corrupted图片名单一起存档既留模型也留数据问题记录方便下次扩采数据时避开同样的坑。372张图训练出的模型承担不了“所有光照下都检测准确”的预期但它足够让你看清这个方向上线的成本和效果差距。希望帮到你。本文还有配套的精品资源点击获取
返回列表