板蓝根颗粒目标检测数据集:111图134框,VOC/YOLO双格式训练全流程
简介面向药品包装检测与目标检测算法练习场景这份数据集收录了板蓝根颗粒袋装药品的实拍图像及对应标注适合正在学习YOLO、Faster R-CNN等检测框架的开发者、学生以及需要快速验证药品识别模型的算法工程师。压缩包共包含335个文件以111张jpg原图为核心配合111个Pascal VOC格式xml标注文件与113个YOLO格式txt标注文件整体仅3.71MB几乎可以秒级下载便于直接接入现有训练流程。标注内容覆盖999ganmaoling与banlangen两个类别总框数134个数据已通过labelImg人工标注类别边界与目标框位置可直接用于模型训练由于图片数量适中既可作为目标检测入门练习集也可作为小规模药品外观识别实验的验证集。目前已有121人学习下载资源量小而精对需要快速搭建检测基线或演示项目的人员尤其友好。1. 板蓝根颗粒目标检测数据集111 张图、134 个框VOC 和 YOLO 双格式拿来就能训药品颗粒袋检测这类目标检测需求很具体产线上要识别板蓝根颗粒的袋装包装药房盘点要把板蓝根和感冒灵这类外观接近的颗粒袋区分开。公开数据集里几乎找不到这种细分品类所以这份板蓝根颗粒检测数据集的价值在于把特定场景的样本直接给你。它包含 111 张 jpg 图片、111 个 Pascal VOC 格式的 xml 标注和 111 个 YOLO 格式的 txt 标注类别有 999ganmaoling 和 banlangen 两个总框数 134 个标注工具是 labelImg。适合从公开数据集过渡到自己项目数据的算法新手做毕业设计或小规模验证也适合想快速跑通 YOLO 训练流程的工程师解压、配置、训练三步就能起来。2. 先拆压包VOC 与 YOLO 双格式的文件结构、同名规则和坐标换算拿到压缩包第一件事不是急着配环境而是把三个文件类型之间的对应关系理清楚。111 张照片对应 111 个 xml 和 111 个 txt文件名是 banlangen_xyxr_编号 的规律比如 banlangen_xyxr_3.jpg、banlangen_xyxr_45.jpg。这份数据集的标注用 labelImg 完成labelImg 导出 VOC 后可以再转成 YOLO 格式所以两类格式并存并不是冗余而是为了适配不同训练框架和工具链。2.1 三类文件之间到底怎么对应同名规则与「没有分割 txt」的含义压缩包里的文件结构很干净没有嵌套一堆子目录主体就是三种文件。图片是训练输入xml 是 labelImg 直接导出的 Pascal VOC 标注txt 是 YOLO 训练实际读取的标注。它们靠文件名关联banlangen_xyxr_3.jpg 对应 banlangen_xyxr_3.xml 和 banlangen_xyxr_3.txt同名不同后缀。这个规则是 YOLO 系框架的硬约定改任何一边的名字都会导致训练时找不到标注。文件类型数量格式说明训练中的作用jpg 图片111RGB 图像模型输入xml 标注111Pascal VOC存绝对坐标与类别名可视化与格式转换txt 标注111YOLO 格式存归一化坐标与类别 ID训练时直接读取注意摘要里那句「不包含分割路径的 txt 文件」意思是压缩包里没有 train.txt、val.txt 这种列出图片路径的清单文件只给了图片和标注本身。这有好处也有代价。好处是目录结构简单YOLOv8 这类新框架不需要路径清单靠目录结构就能划分数据集代价是如果你要跑 darknet 或老版 YOLOv5 的某些分支train.txt、val.txt 得自己生成后面第 4 章会给生成命令。2.2 同一张图的两套坐标xml 的绝对坐标如何换算成 txt 的归一化坐标VOC 格式和 YOLO 格式标注的是同一个目标只是坐标系不同。VOC 存绝对像素坐标YOLO 存相对原图宽高的归一化值。看一个典型的 xml 标注结构由 labelImg 导出annotation folderbanlangen_xyxr/folder filenamebanlangen_xyxr_3.jpg/filename size width640/width height480/height depth3/depth /size object namebanlangen/name bndbox xmin120/xmin ymin80/ymin xmax300/xmax ymax200/ymax /bndbox /object /annotation一个 object 节点就是一个标注框多个 object 就是多目标。name 存类别字符串bndbox 存左上角和右下角的绝对坐标。xml 的 size 节点是关键坐标换算时要用它做分母。把上面的框转成 YOLO 格式就是下面这行0 0.328125 0.291667 0.281250 0.250000五个数字分别是类别 ID、中心点 x 归一化、中心点 y 归一化、框宽归一化、框高归一化。换算公式是# 假设原图宽高为 width, height cx (xmin xmax) / 2 / width # 中心点 x 归一化 cy (ymin ymax) / 2 / height # 中心点 y 归一化 w (xmax - xmin) / width # 框宽归一化 h (ymax - ymin) / height # 框高归一化这套换算里最容易翻车的点分母用的必须是原图分辨率也就是 xml size 节点里的 width 和 height不是训练时的 imgsz。很多人在这一步把分母写成 640 或 416结果训练出来的框整体偏移后面第 5 章会单独讲这个坑。txt 里的第一个数字是类别 IDxml 里 name 是字符串两者靠类别列表对应这份数据集的类别列表只有两个对比项VOC xmlYOLO txt坐标形式xmin, ymin, xmax, ymax 绝对像素cx, cy, w, h 归一化浮点类别表示字符串 name整数类别 ID文件读取方labelImg、darknet、各类转换脚本ultralytics 训练直接读取提示如果 xml 的 size 和实际图片分辨率不一致txt 的归一化坐标就是错的训练时框的位置会直接偏移。拿到数据先随机挑两张图把 xml 里的框画在原图上确认标注是贴合的再往下走。3. 摸清两个类别的底细134 个框怎么分布直接决定训练策略训练之前先回答一个问题这 134 个框到底是怎么分布的数据集的形态决定了你后面要用什么策略盲目套标准流程容易浪费大量时间。这一章把类别含义和样本分布拆开讲顺便给一个统计脚本让你用自己的眼睛确认数据而不是靠压缩包描述猜。3.1 类别名的背后999ganmaoling 与 banlangen 分别是什么目标从命名习惯看banlangen 就是板蓝根颗粒的袋装包装999ganmaoling 是 999 感冒灵颗粒的拼音缩写两者都是药品颗粒袋装外观差异主要体现在包装配色和文字上。这份数据集的标注工具是 labelImg它画出来的是外接矩形 bndbox矩形内会带一部分背景或相邻物体这是正常现象不需要刻意清理干净保留这些边界噪声反而能让模型在真实场景里更稳。两个类别在语义上很接近都是小尺寸的袋装目标检测器的难点在于区分包装而不是找目标。如果你要把类别换成自己的业务名称比如 banlangen_v3、999ganmaoling_new只需要同步修改 xml 里的 name 字符串和 txt 里的类别 ID但两边必须保持一致否则可视化时会出现框和类别对不上的情况。我的习惯是先用脚本批量替换再随机抽几张图做可视化检查不手动改文件。3.2 49 框 vs 85 框样本不均衡在小数据集里有多致命先看总量111 张图、134 个框平均每张 1.2 个目标。这意味着不少图里同时出现两袋颗粒而不是每张图只有一个目标。如果你按每张图最多两个目标来推至少有 23 张图是双目标的实际存在三目标图的话多目标图的数量只会更多。所以训练时要注意模型不仅要学「框住目标」还要学「一张图里有多个同类的响应」。再看类别分布999ganmaoling 框数 49banlangen 框数 85后者约是前者的 1.7 倍。小数据集里这种不均衡是致命的因为模型会天然偏向多数类导致 999ganmaoling 的召回率偏低。常见的处理办法有三个一是对少数类做 Copy-Paste 增强把 999ganmaoling 的框贴到空白背景上合成新样本二是调整损失权重让少数类的分类损失权重更高三是先不追求均衡跑一版看 Per-class 的 PR 曲线确认少数类到底掉在哪再动手。我一般会先跑基线用混淆矩阵说话不凭感觉调参。3.3 用脚本统计框数确认类别分布而不是凭感觉写一个简单的统计脚本读 YOLO txt 就能拿到类别分布、每张图框数和空图数量。这段脚本在任何数据集上都能用训练前跑一遍不亏。from pathlib import Path from collections import Counter label_dir Path(labels) # txt 所在目录 num_boxes Counter() # 每张图的框数分布 class_counts Counter() # 每个类别的框数 empty_files 0 # 空标注文件数量 for txt in label_dir.glob(*.txt): n 0 for line in txt.read_text().strip().splitlines(): parts line.split() if len(parts) 5: continue # 跳过格式不完整的行 class_counts[int(parts[0])] 1 n 1 num_boxes[n] 1 if n 0: empty_files 1 print(类别 ID 与框数:, dict(class_counts)) print(每张图框数分布:, dict(sorted(num_boxes.items()))) print(空标注文件数:, empty_files)统计结果直接告诉你三件事类别 ID 到底有几个、是不是和 yaml 里的 names 对得上、空图多不多。如果空标注文件多划分验证集时要特意保留一部分让评估场景贴近真实拍照环境。这一跑通常用不了几秒钟却能把后面几小时训练的方向定下来。4. 把数据集跑成模型目录组织、dataset.yaml 与 YOLOv8 训练参数数据摸清了这一章进入实操。以 YOLOv8 为例因为它是现在跑自定义数据集最常用的框架之一对目录结构的要求简单而且这份数据集的 YOLO txt 可以直接被 ultralytics 读取。从目录整理开始到配置文件、训练命令、结果判读一条线走完。4.1 目录规范images 与 labels 分层train/val 按 8:2 划分ultralytics 的目录约定是 images 和 labels 同级各自下面分 train、val 子目录txt 与 jpg 严格同名。原始压缩包里图片和标注大概率是混在一起的先建目录再按比例划分。下面这段脚本用 8:2 划分并固定随机种子保证每次结果一致import random from pathlib import Path import shutil random.seed(42) # 固定种子保证可复现 src_images Path(jpg) # 原始图片目录 src_labels Path(labels) # 原始 txt 目录 split 0.8 # 训练集比例 images sorted(src_images.glob(*.jpg)) random.shuffle(images) for i, img in enumerate(images): role train if i int(len(images) * split) else val name img.stem # 不含后缀的文件名 dst_img Path(images) / role / img.name dst_lbl Path(labels) / role / f{name}.txt shutil.copy2(img, dst_img) shutil.copy2(src_labels / f{name}.txt, dst_lbl)逻辑说明脚本先按文件名排序再打乱避免文件系统顺序带来的分组偏差train 和 val 的图片在 src 里只复制不移动原目录原样保留方便后续重新划分。参数 split 就是训练集比例想改成 9:1 直接换数字。如果你的验证集里某个类别一个框都没有说明随机划分不够均匀需要按类别分层先统计哪些文件包含 999ganmaoling 框再分别从两类里抽验证集。如果你是 darknet 用户还需要生成路径清单文件用 find 命令即可find $(pwd)/images/train -name *.jpg train.txt find $(pwd)/images/val -name *.jpg val.txtYOLOv8 不需要这两行darknet 才需要。路径要写绝对路径否则 darknet 在解析图片时容易因为相对路径报错。4.2 dataset.yaml 与训练命令小数据集上的参数怎么设才不白跑目录整理好之后在数据集根目录写一个 dataset.yaml。注意 names 的顺序必须和 txt 里的类别 ID 对应第 3.3 节的统计脚本已经告诉你有几个 ID、各是几直接用那个顺序写path: /path/to/banlangen_xyxr # 改成你的绝对路径 train: images/train val: images/val nc: 2 names: 0: 999ganmaoling 1: banlangen训练命令用 ultralytics 的命令行即可关键在小数据集参数怎么选yolo detect train databanlangen.yaml modelyolov8n.pt \ epochs200 imgsz640 batch8 device0 patience50 \ projectruns/banlangen nameexp1参数设置的逻辑用表说明参数取值理由modelyolov8n.pt111 张数据只能用 nano 级大模型必过拟合epochs200数据量小收敛慢多跑几轮给早停留空间imgsz640默认值显存不够降到 480对小目标影响不大batch8小显存的常见值有更大显卡可以加到 16patience50小数据集 loss 波动大太低会在还没收敛时停掉数据量少的时候增强策略要适度。mosaic 默认是开的但在 111 张图上过强的 mosaic 会把目标裁切得支离破碎反而干扰学习。我一般会把 mosaic 降到 0.7 左右或者干脆关闭用 export 参数控制yolo detect train databanlangen.yaml modelyolov8n.pt epochs200 mosaic0.7另外 yolov8n.pt 第一次运行会自动下载预训练权重离线环境需要提前准备权重文件放到项目目录下训练命令里改成 model/path/to/yolov8n.pt 就行。4.3 训练完看什么混淆矩阵、PR 曲线和 results.png 的读法训练结束后去 runs/banlangen/exp1 目录下看结果重点看三个文件。一是 weights/best.pt 和 last.ptbest 是按验证集指标挑出来的最优权重部署时用 best。二是混淆矩阵图直接看两个类别互相误检的情况如果 999ganmaoling 被大量判成 banlangen说明两类特征区分度不够。三是 PR_curve.png看每个类别的 AP 曲线曲线越靠近右上角越好。results.png 里最值得关注的是 train loss 和 val loss 的分离趋势。val loss 快速上扬而 train loss 继续下降就是过拟合的典型信号。111 张数据很容易过拟合所以遇到这种情况优先想到的是换更小的模型或增强正则而不是继续加 epochs。还要提醒一句小数据集上单次训练的 mAP 波动很大一次 0.85 一次 0.78 都是正常现象不代表模型能力有这么大差距。更可靠的做法是固定训练参数跑 3 个不同随机种子取 mAP 均值作为模型的真实水平。5. 从翻车到避坑111 张数据训练时最容易踩的五个坑与排查顺序这一段是血泪经验整理。小数据集训练报错的表现五花八门但根因高度集中在标注一致性、数据划分和早停这三个方向上。我把最常见的五种现象按「现象→原因→解决」写清楚你照着排错能省下大把时间。5.1 现象训练一开始提示 no labels found或者日志里出现 labels.cache 找不到标注原因YOLO 训练时会在数据目录下生成 labels.cache 缓存文件如果 images/train 和 labels/train 里的文件名对不上或者 labels 目录层级放错缓存就找不到对应的 txt。压缩包里的图片和 txt 是平铺的没整理成标准目录就直接训最容易触发这个问题。解决第一步检查同名文件用一行命令对比ls images/train | sed s/.jpg// | sort a.txt ls labels/train | sed s/.txt// | sort b.txt diff a.txt b.txt有差异就说明命名或文件缺失。修正后删除数据集目录下残留的 labels.cache 文件再重新训练因为旧缓存不会自动刷新。5.2 现象loss 正常下降但验证集 mAP 几乎为 0原因这是最隐蔽的坑。data.yaml 里 names 的书写顺序和 txt 里类别 ID 不一致比如 txt 里 0 是 banlangen而 yaml 里 0 写成了 999ganmaoling。模型学到的类别 0 特征和 ground truth 的类别 0 是两种东西导致分类完全错位但 loss 看起来一切正常。解决用第 3.3 节的统计脚本读一遍 txt 的类别 ID有多少个 ID、各是几然后严格按这个顺序写 yaml 的 names。改动后重新训练。不要凭压缩包描述里的顺序猜训练前跑 3 秒脚本比训练完发现白跑 2 小时划算得多。5.3 现象验证集图片只有二十几张PR 曲线像锯齿某个类别的曲线直接消失原因随机划分数据集时某一类的框全部落进训练集验证集里一个都没有。这样验证集指标缺失模型对少数类的真实能力完全没评估到。解决划分时做分层处理保证验证集里两个类别至少各有 5 个框。更稳妥的方式是 5 折交叉验证每折单独训练并评估把 5 次结果的 mAP 和混淆矩阵取平均这是小数据集评估的标准做法。虽然要多跑 5 次训练但比单次划分的随机结果可信得多。5.4 现象训练能跑通但检测出来的框整体偏移位置像是被缩放过的原因txt 里坐标的归一化分母用的是原图宽高有人却误以为是在 imgsz640 下归一化的训练前对 txt 做了错误的缩放或者 xml 的 size 和实际图片尺寸不一致转 txt 时就标错了。解决用 PIL 读原图宽高把 txt 的归一化坐标反算回绝对坐标from PIL import Image w, h Image.open(banlangen_xyxr_3.jpg).size # txt 中 cx, cy, bw, bh 为归一化值 x1 int((cx - bw / 2) * w) y1 int((cy - bh / 2) * h) x2 int((cx bw / 2) * w) y2 int((cy bh / 2) * h)再画到原图上对比确认标注贴合后重新生成 txt。这步通常在拿到数据集时就要做不要等到训练后才发现。我在第 6 章会给一个完整的自检脚本。5.5 现象训练在第 30 轮就停了best.pt 的 mAP 只有 0.2原因patience 设得太小比如默认 10 或 20而小数据集验证集 loss 波动剧烈连续 20 轮没有提升是很常见的事早停机制过早触发。解决把 patience 提到 50 以上或者先关闭早停跑一版看完整曲线。如果完整跑下来某个类别的 AP 还是低问题不在早停回到第 3.2 节的样本均衡和增强策略上不要在原处反复调参。5.6 一个通用的排查顺序先数据、再划分、最后才动模型参数把这五条串起来就是我拿到任何小数据集都会走的固定顺序。第一步验证标注txt 与 xml 是否一致、坐标是否贴合、类别 ID 是否在合理范围。第二步验证划分train/val 里每个类别框数是否都大于 0。第三步再谈训练参数和增强策略。数据没确认干净之前任何模型层面的调优都是白搭。6. 训练前先做一次「标注体检」自检脚本帮你省掉两小时翻车以前我拿到数据集的第一反应是直接丢进训练反正 YOLO 框架会自动建缓存。后来吃了一次大亏有一个数据集的 txt 类别 ID 顺序和 yaml 反了训练两小时看到 mAP 接近 0 才回头查问题才发现是标注读错位。从那以后我拿到任何带标注的数据集都会强制先跑一遍自检脚本再谈训练这套数据集也不例外。自检脚本只做三件事校验 txt 类别 ID 范围、校验归一化坐标是否在 [0,1] 内、按原图尺寸把 txt 转回绝对坐标检查框是否越界。把这段跑完前面第 5 章说的大部分坑都能提前暴露from pathlib import Path from PIL import Image label_ids [999ganmaoling, banlangen] # 必须与 yaml names 一致 error_count 0 for img_path in sorted(Path(images/train).glob(*.jpg)): txt_path Path(labels/train) / f{img_path.stem}.txt if not txt_path.exists(): print(缺少标注:, img_path) error_count 1 continue w, h Image.open(img_path).size for line in txt_path.read_text().strip().splitlines(): cid, cx, cy, bw, bh map(float, line.split()) if int(cid) len(label_ids) or int(cid) 0: print(类别 ID 超出范围:, img_path, cid) error_count 1 if not (0 cx 1 and 0 cy 1 and 0 bw 1 and 0 bh 1): print(坐标越界:, img_path, line) error_count 1 x2 (cx bw / 2) * w y2 (cy bh / 2) * h if x2 w or y2 h: # 右下角超出原图边界 print(框越界:, img_path, line) error_count 1 print(检查完成错误数:, error_count)脚本逻辑很直接遍历训练集图片找到同名 txt逐行解析五个字段。类别 ID 超出 names 数量说明 ID 错位归一化坐标不在 [0,1] 说明标注转换时用了错误的分母框右下角超出原图尺寸说明 txt 是从错误的 xml 转出来的。任何一个错误命中都值得在训练前花五分钟修掉。自从养成这个习惯小数据集训练翻车的次数明显少了。这份板蓝根数据集压缩包拿回来我建议你的第一步也做一遍这个检查再跑第 4 章的命令。数据干净了模型训练只是时间问题。希望这套流程能帮你在自己的检测任务上少走几步弯路。本文还有配套的精品资源点击获取
上一篇/下一篇内容由系统自动关联
返回资讯列表 →