简介面向目标检测入门与药品包装识别场景的板蓝根颗粒检测数据集提供了111张袋装药品实拍图并已按Pascal VOC与YOLO双格式完成标注。包内共335个文件以jpg原图、xml标注和txt标注为主其中111张图片对应111个xml文件与111个yolo标签压缩包仅3.71MB下载和预处理都较为轻量。数据集包含两类目标999感冒灵49框与板蓝根85框总框数134个均使用labelImg工具标注类别与框数信息可直接用于模型评估与指标统计。已有121人学习下载适合需要真实小样本数据集进行目标检测模型测试、教学演示、算法练手或数据格式转换验证的开发者也可作为数据集扩充与标注质量复核的参考样本。1. 药品板蓝根颗粒检测数据集110张VOCYOLO格式小样本目标检测的典型标本拿到这个标题第一反应不是“图少”而是“这个数据形态太常见了”。做工业质检和药品视觉检测的同行都知道产线上真正能用的缺陷样本、包装样本往往就几十到几百张110张板蓝根颗粒图片反而是最贴近真实项目的规模。VOC和YOLO双格式标注意味着你不需要纠结转换脚本解压就能同时喂给两套训练管线。这个数据集的定位不是让模型刷榜而是让你在最短时间内跑通一个完整闭环——从数据检查、标注核对、格式转换到训练策略验证每一步都能暴露真实问题。适合三类人刚接触目标检测的初学者想验证小样本训练流程的算法工程师以及需要在药品包装场景快速出效果的落地工程师。这篇笔记不吹不黑把这份数据从解压到训练、再到判断模型会不会翻车的完整路径拆开讲。2. 数据集先拆清楚110张图里藏着哪些容易忽略的细节2.1 zip压缩包的结构和VOC/YOLO双格式的对应关系拿到一个数据集压缩包先别急着解压训练第一步一定是看目录结构。常见做法是用unzip -l直接列出zip内容不实际解压快速确认文件组织方式。unzip -l 药品板蓝根颗粒检测数据集110张VOCYOLO格式.zip如果系统没有unzip用python3 -m zipfile -l也可以。注意一点如果你在Windows上操作压缩包的编码可能是GBK直接解压会出现中文乱码推荐用7-Zip或Bandizip它们对中文文件名兼容性更好。Linux下可以用unzip -O GBK指定编码但前提是你的unzip版本支持这个参数。正常情况下你会看到两类顶层目录——VOC和YOLO。VOC侧通常包含JPEGImages、Annotations和ImageSets三个子目录JPEGImages放原图Annotations放.xml标注文件ImageSets里则是train.txt、val.txt之类划分文件。YOLO侧就是images和labels每张图对应一个同名.txt标签文件。2.2 类别定义永远是第一个要核对的东西板蓝根颗粒检测的标注类别可能只有一类bavdroot_granule也可能拆成package_front、package_back、defect等多类这直接决定你后面的训练配置。最快的方式是扫描所有标签文件统计类别索引。import os from collections import Counter label_dir YOLO/labels classes Counter() for f in os.listdir(label_dir): if not f.endswith(.txt): continue with open(os.path.join(label_dir, f), r, encodingutf-8) as fp: for line in fp: line line.strip() if not line: continue cls_id line.split()[0] classes[cls_id] 1 print(classes)这段代码遍历所有YOLO格式的标注文件统计每个类别出现的次数。注意两点一是要跳过空文件因为有些图可能没有标注对象二是对文件名的解析要和实际目录结构匹配不要在路径拼接上报错。此外还应检查.txt文件是否以空格分隔、是否有多余逗号IDE自动换行符也可能混进来。统计结果如果类别数量和你预期不符宁可停下来核对VOC的.xml也不要带着错误标注继续训练。2.3 图像尺寸、通道和文件名对不上的问题110张图里如果混入不同分辨率的图片对训练影响不大——YOLO训练时本身会做letterbox缩放。但如果混入了灰度图、RGBA图或损坏的图片训练到一半就可能报OpenCV(4.x) ... error: Assertion failed。所以解压后先做一轮批量检查。python3 - EOF import os from PIL import Image img_dir YOLO/images for root, dirs, files in os.walk(img_dir): for name in files: path os.path.join(root, name) try: img Image.open(path) img.verify() except Exception as e: print(f损坏文件: {path} - {e}) EOF这段脚本的好处是只“验证”不加载不会把大图全读进内存。如果输出为空说明图片文件本身没有明显损坏但别忘了看一眼通道数和位深比如16位PNG在OpenCV下读取方式不同会在训练时报奇怪的维度错误。还有一个细节VOC的JPEGImages和YOLO的images里图片要完全一致如果两边数量不同说明数据集作者导出时漏了文件训练前需要手动补齐或调整划分脚本。3. 标签合法性检查VOC标注里四类高频问题3.1 越界坐标、空标签和错位标注的批量扫描VOC格式的.xml标注最容易出三个问题坐标越界xmin小于0或xmax大于图像宽度、空标签没有object节点、以及标注框完全重叠。这些在人工标注时很难发现特别是只有110张图的情况下一个坏标注就占数据集近1%的权重。import xml.etree.ElementTree as ET import os from PIL import Image ann_dir VOC/Annotations img_dir VOC/JPEGImages for ann_file in os.listdir(ann_dir): if not ann_file.endswith(.xml): continue tree ET.parse(os.path.join(ann_dir, ann_file)) root tree.getroot() img_name root.findtext(filename) img_path os.path.join(img_dir, img_name) if not os.path.exists(img_path): print(f缺少图片: {img_name}) continue w int(root.findtext(size/width)) h int(root.findtext(size/height)) for obj in root.iter(object): xmin int(float(obj.findtext(bndbox/xmin))) ymin int(float(obj.findtext(bndbox/ymin))) xmax int(float(obj.findtext(bndbox/xmax))) ymax int(float(obj.findtext(bndbox/ymax))) if xmin 0 or ymin 0 or xmax w or ymax h: print(f越界: {ann_file}, box({xmin},{ymin},{xmax},{ymax}), img({w},{h})) if xmax xmin or ymax ymin: print(f无效框: {ann_file}, box({xmin},{ymin},{xmax},{ymax}))这段脚本逐条检查坐标合法性和图片引用完整性。root.findtext(size/width)用了XPath路径能直接取到嵌套字段比一层层find更简洁。int(float(...))是为了兼容带小数点的坐标某些标注工具导出时会写浮点数。如果扫描结果报出一堆问题不要抱着侥幸心理用脚本自动裁剪坐标先人工抽查几十张确认标注风格是整体偏移还是个别异常。3.2 小目标标注的独特性板蓝根颗粒为什么容易被漏检板蓝根颗粒的包装通常是一个长方体盒子正面有图案、背面有文字说明。如果标注的是一盒完整药品目标尺寸不小如果标注的是颗粒冲剂的小包装或颗粒本身那就属于典型的小目标检测——目标像素可能只有32x32甚至更小。这类样本在110张图里可能只占少数训练时会被大目标主导损失函数。所以检查标注时除了坐标合法性还要统计目标尺寸分布。import os import numpy as np import xml.etree.ElementTree as ET ann_dir VOC/Annotations sizes [] for ann_file in os.listdir(ann_dir): tree ET.parse(os.path.join(ann_dir, ann_file)) root tree.getroot() img_w int(root.findtext(size/width)) img_h int(root.findtext(size/height)) for obj in root.iter(object): xmin float(obj.findtext(bndbox/xmin)) ymin float(obj.findtext(bndbox/ymin)) xmax float(obj.findtext(bndbox/xmax)) ymax float(obj.findtext(bndbox/ymax)) w xmax - xmin h ymax - ymin sizes.append((w / img_w, h / img_h)) # 归一化尺寸 sizes np.array(sizes) print(f目标总数: {len(sizes)}) print(f宽占比: mean{sizes[:,0].mean():.3f}, min{sizes[:,0].min():.3f}, max{sizes[:,0].max():.3f}) print(f高占比: mean{sizes[:,1].mean():.3f}, min{sizes[:,1].min():.3f}, max{sizes[:,1].max():.3f})这个统计结果的解读逻辑如果归一化宽度和高度的均值都小于0.1就属于小目标密集场景训练时建议开启YOLO的small检测头或者用SAHI切片推理。如果目标尺寸分布极不均匀比如大部分目标占图幅50%以上、只有个别是5%那就要考虑针对性采样。另一个统计维度是每张图的平均目标数量如果差异很大有的图1个目标、有的图20个目标训练时数据采样会偏向大目标多的图影响小目标图的收敛速度。4. 把VOC转成YOLO格式并划分数据集脚本与三个必调参数4.1 转换脚本从xml到txt的完整流程很多数据集同时提供两种格式但两份标注之间可能存在细微差异。稳妥操作是以VOC的.xml为基准自己转一份YOLO格式而不是直接信任YOLO目录下的.txt。转换逻辑本身不难难的是处理边界情况。import xml.etree.ElementTree as ET import os CLASSES [bavdroot_granule] # 按你自己的类别表修改 def convert_voc_to_yolo(xml_path, out_dir): tree ET.parse(xml_path) root tree.getroot() img_w int(root.findtext(size/width)) img_h int(root.findtext(size/height)) txt_name os.path.splitext(os.path.basename(xml_path))[0] .txt lines [] for obj in root.iter(object): cls_name obj.findtext(name) if cls_name not in CLASSES: print(f未知类别 {cls_name} in {xml_path}) continue cls_id CLASSES.index(cls_name) xmin float(obj.findtext(bndbox/xmin)) ymin float(obj.findtext(bndbox/ymin)) xmax float(obj.findtext(bndbox/xmax)) ymax float(obj.findtext(bndbox/ymax)) x_center ((xmin xmax) / 2) / img_w y_center ((ymin ymax) / 2) / img_h w (xmax - xmin) / img_w h (ymax - ymin) / img_h # 坐标越界截断 x_center max(0, min(1, x_center)) y_center max(0, min(1, y_center)) w max(0, min(1, w)) h max(0, min(1, h)) lines.append(f{cls_id} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}) with open(os.path.join(out_dir, txt_name), w) as fp: fp.write(\n.join(lines)) # 遍历所有xml并转换 xml_dir VOC/Annotations out_dir VOC_yolo_labels os.makedirs(out_dir, exist_okTrue) for f in os.listdir(xml_dir): if f.endswith(.xml): convert_voc_to_yolo(os.path.join(xml_dir, f), out_dir)第一个关键参数是CLASSES列表的命名必须和VOC标注中的name字段完全一致包括大小写和空格。第二个关键参数是坐标归一化用的是图像宽高不是letterbox后的尺寸这个搞错了框会整体偏移。第三个关键参数是越界截断逻辑我用max(0, min(1, x))把所有坐标限制到[0,1]区间但要注意截断本身会掩盖标注错误所以这个脚本跑完后回头看一眼警告信息。4.2 数据划分固定随机种子别让验证集“作弊”110张图按照常见比例划分大约是训练88张、验证11张、测试11张。但这里有个小样本划分的坑如果原图中有多个相似批次的生产照片随机划分会导致同一产品出现在训练集和验证集验证分数虚高换新图片就翻车。import random import os random.seed(42) # 固定种子保证可复现 all_files [f for f in os.listdir(VOC/JPEGImages) if f.endswith(.jpg)] random.shuffle(all_files) train_ratio 0.8 val_ratio 0.1 # 剩余0.1为测试集 n_train int(len(all_files) * train_ratio) n_val int(len(all_files) * val_ratio) train_files all_files[:n_train] val_files all_files[n_train:n_train n_val] test_files all_files[n_train n_val:] def write_set(file_list, output_path): with open(output_path, w) as fp: for f in file_list: fp.write(os.path.splitext(f)[0] \n) os.makedirs(VOC/ImageSets/Main, exist_okTrue) write_set(train_files, VOC/ImageSets/Main/train.txt) write_set(val_files, VOC/ImageSets/Main/val.txt) write_set(test_files, VOC/ImageSets/Main/test.txt)这段代码的核心是random.seed(42)不设固定随机种子的划分结果每次跑都不一样你自己能复现也没法对比实验。.txt文件里每一行是图片的不带扩展名的文件名YOLO训练时会根据这个名称去拼接图片路径和标签路径。如果原图是.png或.jpeg后缀os.path.splitext(f)[0]同样适用但后面训练配置里的img_path拼接逻辑也要同步修改。另一个细节是write_set函数里加了\n文件末尾不留空行避免某些解析器读到空字符串报错。4.3 划分后的验证脚本看看每张图的标注有没有真正跟上划分只是写几个txt真正要验证的是YOLO训练时能否根据这些txt找到对应的图片和标签。写一个快速检查脚本import os img_dir YOLO/images label_dir YOLO/labels with open(train.txt) as fp: names [line.strip() for line in fp if line.strip()] missing_img [n for n in names if not os.path.exists(os.path.join(img_dir, f{n}.jpg))] missing_lbl [n for n in names if not os.path.exists(os.path.join(label_dir, f{n}.txt))] if missing_img: print(f缺少图片: {missing_img[:5]}) if missing_lbl: print(f缺少标注: {missing_lbl[:5]}) if not missing_img and not missing_lbl: print(fOK: {len(names)} 张图图片和标注全部可定位)这里需要注意的是图片扩展名可能不统一有的jpg、有的png、有的JPEG所以更稳妥的方式是把图片目录里的文件名做成集合再去判断。import os img_dir YOLO/images imgs_available set(os.listdir(img_dir)) labels_available set(os.listdir(YOLO/labels)) def find_image(name, imgs): for ext in [.jpg, .png, .jpeg, .JPG, .PNG]: if name ext in imgs: return name ext return None with open(train.txt) as fp: names [line.strip() for line in fp if line.strip()] missing [] for n in names: img_file find_image(n, imgs_available) if img_file is None: missing.append(n) continue lbl_file n .txt if lbl_file not in labels_available: missing.append(n) if missing: print(f有问题: {missing}) else: print(全部通过)小样本数据集最怕这种“看起来能训练”的错觉。如果图片扩展名不统一VOC的JPEGImages目录里可能混着jpg和pngYOLO的images目录导出时可能统一成jpg转换后标签名又用的原名结果半数标签找不到对应图片。5. 避坑笔记小样本药品检测训练时最容易翻车的五个环节5.1 现象loss降到0.2但验证集mAP只有0.3原因过拟合。110张图跑50个epoch模型把训练集的背景纹理都背下来了。解决第一加深数据增强强度特别是hsv_h、hsv_s、fliplr这些不影响语义的增强第二把dropout从默认的0调到0.1第三早停机制打开patience设10个epoch。如果还不行直接上迁移学习用COCO预训练权重。5.2 现象训练时loss出现NaN原因学习率过大或标注里出现了极端坐标值。110张小数据集初始学习率建议从0.001开始不要用YOLO默认的0.01。坐标值如果是归一化后大于1的异常框会导致损失函数发散需要回到第2章的越界检查逻辑重新扫一遍标注。解决把lr0调到0.001同时把batch降到8或4小样本下大batch更容易震荡。5.3 现象训练正常但推理时对药盒的检测框忽大忽小原因训练和推理时的输入尺寸不一致。训练时开了rectTrue按比例自适应但推理时用了固定640x640导致letterbox的填充比例不同框回来时就偏移了。解决统一训练和推理的imgsz参数要么都640要么都416不要混用。另一个常见原因是验证集的标注坐标没归一化好在训练时被当成异常框跳过推理时模型学到的框回归模式就不稳定。5.4 现象类别不平衡导致多数类把少数类压死板蓝根颗粒数据里正面包装图可能有80张、侧面图只有10张、缺陷样本只有5张。模型会倾向于把侧面图预测成正面。解决用cls损失权重把少数类的损失权重上调2~3倍。YOLOv8的cls参数可以单独设置或者采用Focal Loss变体fl_gamma调到1.5~2.0。5.5 现象标注框数量太少模型完全不收敛这是小样本数据集的经典痛点——110张图只有200个目标平均每张不足2个大量背景区域没有正样本。解决开启mosaic增强默认就是开启的它会把4张图拼成一张变相增加了每张训练图的目标密度。但要注意小样本下mosaic会放大标注误差一个标注偏移的框在拼图后更难被发现所以每50轮保存一次权重在验证集上看效果而不是只看训练loss。6. 用YOLOv8在110张图上跑通训练最小命令和三个必调参数6.1 环境准备与最小训练命令用YOLOv8是当前最稳的选择它的ultralytics库封装了训练、验证、导出全流程。先装依赖再准备数据集配置文件。pip install ultralytics这句装完就够不需要额外装PyTorch——如果之前装过带CUDA的PyTorch就用GPU训练没装过的话先装CPU版跑通流程再说。接下来构建数据配置文件data.yamltrain: /path/to/dataset/YOLO/images/train val: /path/to/dataset/YOLO/images/val test: /path/to/dataset/YOLO/images/test nc: 1 names: [bavdroot_granule]三个关键点train和val路径指向图片目录而不是txt文件YOLOv8会自动查找同级labels目录下的标注文件nc必须和names列表长度一致路径用绝对路径避免相对路径在切换工作目录时报错。执行训练yolo train datadata.yaml modelyolov8n.pt epochs50 imgsz640 batch8 device0modelyolov8n.pt是自动下载COCO预训练权重这是小样本训练能收敛的关键之一。如果网络环境不允许自动下载可以手动下载后改成model/path/to/yolov8n.pt。epochs50在110张图上已经够多再多就过拟合了。batch8是显存不够时的保守选择8G显存完全能跑nano模型。device0指定第一块GPU没有GPU就写devicecpu但训练时间会翻几倍。6.2 三个必调参数imgsz、patience和augment第一个是imgsz640这个值不能随便改。目标尺寸如果本身就小于32x32640输入下小目标特征图只有10x10像素左右检测头很难学到有效特征。可以试试imgsz960或imgsz1280显存够就往上调特别是板蓝根包装盒上的小字、小图标这类细节目标。但注意验证时也要用同样尺寸。第二个是patience15这是早停参数含义是连续15个epoch验证集mAP没有提升就停止训练。小样本数据集50个epoch内经常在第20~35轮达到最优之后就开始过拟合开着早停能省时间也能自动保存最优权重。第三个是augmentTrue显式开启增强。YOLOv8默认开启增强但小样本场景可以额外调整几个子参数。yolo train datadata.yaml modelyolov8n.pt epochs50 imgsz640 batch8 device0 augmentTrue hsv_h0.02 hsv_s0.6 fliplr0.5hsv_h0.02是色相偏移幅度药盒颜色通常比较鲜艳偏移太大会让模型学到错误的颜色关联0.02足够。hsv_s0.6是饱和度增强这个值可以适当加大因为光照变化对药品包装拍摄的影响主要体现在饱和度上。fliplr0.5是水平翻转概率如果你的数据里没有大量带文字的正反面包装这个增强是安全的但如果药盒正面有“板蓝根”三个大字翻转后文字方向变了模型可能学到奇怪的特征谨慎开启。6.3 用训练日志和权重挑选把握模型状态训练完成后runs/detect/train目录下会生成多个文件。results.png是训练曲线图你需要重点看两条曲线val/box_loss是否持续下降后再上升过拟合信号以及metrics/mAP50是否在某个epoch后突然飙升标注错误导致的假收敛。weights/best.pt是验证集上mAP最高的权重last.pt是最后一轮的权重小样本场景下两者差异可能很大优先用best.pt。验证模型性能yolo val modelruns/detect/train/weights/best.pt datadata.yaml输出会包含mAP50、mAP50-95、precision、recall四项指标。110张图、单类别、小目标的前提下mAP50能到0.8以上就算合格不用死磕mAP50-95。看到mAP50偏低不要急着调模型结构先回到标注检查——这类问题八成是标注框画偏了而不是模型不行。6.4 进阶测试换个角度验证泛化能力把训练好的模型拿到没有见过的场景做推理判断是否真的能用python3 - EOF from ultralytics import YOLO model YOLO(runs/detect/train/weights/best.pt) results model.predict(test_images/, saveTrue, conf0.25, imgsz640) for r in results: print(f{r.path}: {len(r.boxes)} 个目标) for box in r.boxes: cls_id int(box.cls[0]) conf float(box.conf[0]) xyxy box.xyxy[0].tolist() print(f class{model.names[cls_id]}, conf{conf:.2f}, box{[int(v) for v in xyxy]}) EOF这段代码里conf0.25是置信度阈值药品检测场景建议调到0.3~0.4因为漏检比误检更危险——漏检可能导致不合格药品流入下一工序。imgsz640要和训练尺寸一致这个前面强调过了。输出内容逐行显示每个检测框的坐标和置信度方便你对照原图看定位准不准。最终来看这110张图的价值不在于让你训练出完美的生产级模型而在于用最小成本走通目标检测全链路。我自己的习惯是每换一个数据集就先跑一遍上面第2章的合法性和尺寸分布检查再决定要不要补数据、调增强、换模型尺寸。如果有条件拿到数据集后再拍一些不同光照、不同背景的补充图小样本模型泛化能力才能看到真实边界。希望帮到你。本文还有配套的精品资源点击获取
企业数字化 ERP 产品动态
相关推荐
MMSE均衡原理与FPGA工程落地实战指南 简介:本资源是一份面向通信工程与数字信号处理初学者的MATLAB实践教学材料,聚焦多径信道下符号间干扰(ISI)的抑制问题,系统实现最小均方差(MMSE)均衡算法。资源包含2个核心MATLAB脚本文件&#… · 2026/9/23 22:41:28
基于PyQt5与OpenPose的姿态识别可视化:从骨架提取到关节角度分析 简介:基于PyQt5与OpenPose实现的太极拳姿态识别可视化系统,是一份完整可运行的毕业设计项目源码包,面向计算机相关专业正在准备毕设、课程设计或期末大作业的学生,也适合需要项目实战练习的Python学习者,可作为课程模板… · 2026/9/23 22:41:28
IEEE802.15.3a UWB信道模型:从参数解析到链路仿真避坑指南 简介:这份资源面向超宽带(UWB)通信方向的研究生、工程师与仿真开发者,聚焦IEEE 802.15.3a标准下的信道建模与冲击响应分析,帮助读者理解多径环境中的信号传播规律,为系统仿真、均衡同步与解调算法设计提供参… · 2026/9/23 22:41:22
Word页眉页脚删除失败?真正原因是节与链接机制 1. 项目概述:为什么一个看似简单的操作,却让90%的Word用户反复踩坑?“删除Word文档中的页眉页脚”——这行字看起来平平无奇,甚至有点过时。但如果你最近处理过一份从PDF转来的投标书、一份带单位红头的公文扫描件、或者一份学生交… · 2026/9/23 23:23:13
AI PLC落地指南:存量设备改造与智能升级路径解析 前两个月,我在现场给一条老产线做节拍优化,甲方仪表工程师拿着七八个功能块问我这几个PID回路能不能联动降波动。我看了一眼,还是老掉牙的梯形图,注释早就丢光了。换在以前,我得抱着几百页程序慢慢捋寄存器地址&#x… · 2026/9/23 23:23:13
在 Laradock 上使用 Docker 运行 Kanboard:从本地到生产的完整指南 在 Laradock 上使用 Docker 运行 Kanboard:从本地到生产的完整指南 【免费下载链接】laradock Full PHP development environment for Docker. Run Laravel, Symfony, CodeIgniter, Phalcon, WordPress, Drupal, Magento, Moodle, or any PHP project with 70 pre-c… · 2026/9/23 23:22:48
Python大熊猫互动拍照系统:姿态估计与图像融合技术实战解析 简介:这是一套面向毕业设计及AI图像处理学习的Python大熊猫主题互动拍照系统源码。项目围绕人工智能视觉技术,实现了动作识别、人像动漫化、风格迁移、熊猫贴纸合成、视频融合及定时拍照等完整功能,适合需要完成课程设计、毕业设计或希望实战… · 2026/9/23 23:22:35
3招搞定手机怎么下载微信面试难题实战项目解析 3招搞定手机怎么下载微信面试难题实战项目解析 面试被问“手机怎么下载微信”背后的原理,90%的人答不上来。别笑,这看似弱智的问题,实则是考察你对移动应用分发机制、安全校验及网络协议理解的试金石。我带过不少校招新人,他们背了八股文,却连一个A… · 2026/9/23 0:00:03
你有新短消息请注意查收:3个新手避坑指南搞定消息系统选型 你有新短消息请注意查收:3个新手避坑指南搞定消息系统选型 面试被问“高并发下如何保证消息不丢失”,你张口就是“用Redis”,结果面试官追问“如果Redis宕机了怎么办”,你瞬间卡壳。这种场景太常见了,很多新手在背八股文时,只记住了技术名词… · 2026/9/23 0:00:29