药丸缺陷检测数据集实践:VOC+YOLO双格式与YOLOv8训练全流程 简介这是一套面向药丸外观缺陷检测的专用数据集基于2759张药丸图像构建覆盖污染、裂纹、合格品三个类别共2798个标注框适用于工业质检、医疗包装检测等目标检测场景可直接用于训练YOLO系列或Pascal VOC架构的检测模型省去格式转换环节。压缩包内共有2000个文件以1049个XML标注文件和951个TXT标注文件为主分别对应VOC格式与YOLO格式整体体积约411.78MB。数据由labelImg工具按矩形框标准标注三个类别标注框数均衡污染904个、裂纹960个、合格品934个标注信息准确合理虽然不附带训练权重与精度保证但能省去手动收集与清洗数据的流程便于直接开展模型训练与效果验证。目前已有218人学习适合具备一定目标检测基础、需要真实工业缺陷数据的算法工程师与研究人员。1. 为什么是 2759 张而不是 5 万张这套药丸缺陷检测数据集够用在哪“药丸缺陷检测数据集VOCYOLO格式2759张3类别.7z”这个名字在视觉检测项目里出现的频率越来越高。药丸质检的难点从来不是选模型而是没有带缺陷标注的现场图2759张是中型偏小的规模但对崩边、污渍、裂纹这类缺陷来说足够让一个YOLO基线从零跑出可信度。更值钱的是它同时给了Pascal VOC和YOLO两种标注省掉最耗时的标注格式转换。它适合刚上手目标检测的工程师也适合做产线可行性验证的算法负责人按解压、看结构、训练、避坑、验证这条线走拿到压缩包一小时就能有产出。2. 先拆开 .7zVOC 与 YOLO 双格式的目录结构和标注差异拿到压缩包第一步是解压。这套数据用到 7z 压缩是因为标注文件大多是小文本图片重复度不高7z 能把 2759 张图压得比 zip 更小。Windows 下装个 7-Zip 或用 Bandizip 都能解Linux 上没有自带 7z 命令要先装 p7zip。sudo apt update sudo apt install -y p7zip-full装好后解压7z x 药丸的缺陷检测数据集VOCYOLO格式2759张3类别.7z -o pill_db这里有两个细节-o后面不能有空格压缩包名带中文和加号命令行里必须用引号包住否则 shell 会把当成特殊字符。解压完成后我建议第一时间把目录重命名成纯英文比如pill_db省得后面 YAML 配置和深度学习框架因为中文路径出莫名其妙的错。一个常见的拆包目录结构如下pill_db/ ├── VOCdevkit/ # Pascal VOC 格式 │ ├── JPEGImages/ # 全部原图 │ ├── Annotations/ # 全部 XML 标注 │ └── ImageSets/Main/ │ ├── train.txt │ ├── val.txt │ └── trainval.txt └── YOLO/ # YOLO 格式 ├── images/ │ ├── train/ │ ├── val/ │ └── test/ ├── labels/ │ ├── train/ │ ├── val/ │ └── test/ └── data.yaml不一定每份包都叫VOCdevkit和YOLO但作用就是这六个文件夹。VOC 格式对应JPEGImages / Annotations / ImageSetsYOLO 格式对应images / labels / data.yaml。两者是一套标注的两种表达不是两份不同数据所以图片张数一致。2.1 Pascal VOC 的 XML 标注长什么样Pascal VOC 是目标检测领域最经典的标注格式很多老框架和公开评测都吃这一套。药丸数据集的 XML 放在 Annotations 下面一个图对应一个同名 XML 文件。打开一个 XML 你会看到filename、size和多个object每个object里写类别名和矩形框坐标。用占位类别名展示一个单缺陷的标注结构annotation folderJPEGImages/folder filenamepill_0001.jpg/filename sourcedatabasepill_defect/database/source size width640/width height480/height depth3/depth /size object namechip/name poseUnspecified/pose truncated0/truncated difficult0/difficult bndbox xmin120/xmin ymin87/ymin xmax328/xmax ymax391/ymax /bndbox /object /annotationXML 里的坐标是原始像素绝对值用左上角xmin/ymin和右下角xmax/ymax表示。一张图有多个缺陷时会有多个object。这里要注意difficult字段如果值为 1表示框容易被误判很多转换脚本默认跳过它这套数据如果你发现某些 XML 用到了 difficult转换时得单独处理不能直接丢。2.2 YOLO 的 TXT 标注与 names 映射怎么对应YOLO 格式是现在训练主流格式每张图对应一个.txt文件名和图片同名。每一行代表一个目标格式是class_id x_center y_center width height四个框坐标都除以图片宽高归一化到 01。同样是上面的占位框写成 YOLO 格式是0 0.5170 0.3940 0.2670 0.6420 1 0.8030 0.6510 0.1520 0.1880第一列的 0、1 是类别 ID从 0 开始不是从 1 开始。ID 和类别名的对应关系写在data.yaml里一般长这样names: 0: chip 1: stain 2: crack这里chip/stain/crack是占位名实际类别名以你解压出来的data.yaml为准。很多新手在这一步翻车拿 VOC 格式时习惯类别名叫什么就用什么但 YOLO 训练只看 ID不看名字训练前不打开 yaml 核对 names等到预测时框是对的、标签名字错位完全看不出来。VOC 和 YOLO 双格式的意义就在这里老框架 Faster R-CNN、SSD 默认吃 VOC新框架 YOLOv8、YOLOv5 默认吃 YOLO 格式。有了两套你就不用自己写转换脚本直接挑需要的用。要是想自己重新划分或合并类别后面第 4 章再讲转换逻辑。2.3 解压后第一时间做的三个核对解压不是任务解压后才是。我习惯先做三件事核对图片数、核对标注数、核对类别数。find pill_db -path *JPEGImages* -name *.jpg | wc -l find pill_db -path *Annotations* -name *.xml | wc -l find pill_db -path *labels/train* -name *.txt | wc -l2759 张图的包正常情况 JPEGImages 下是 2759 个 jpgAnnotations 下也是 2759 个 xmlYOLO 的 labels 总数量也会在 2759 附近。如果数量对不上说明包里有未标注或标注缺失的图直接训练会导致验证集总在少样本上抖动。另一个核对是打开data.yaml看类别名是否和你的业务口径一致。中药胶囊、西药片和维生素软胶囊的缺陷定义完全不同这包数据的 3 类别如果和你产线不一致不要硬套宁可重新定义类别再迁移。坐标换算公式也值得固化下来YOLO 中心点就是 VOC 框四角的平均值再除以宽高。看起来简单但有一个很隐蔽的坑必须用 XMLsize里的真实图尺寸做分母不能想当然用 640。如果 XML 写的是 1920x1080而你统一用 640 算框全部偏移。3. 用 YOLOv8 跑通药丸缺陷检测最小配置文件和训练命令很多人搜 yolov8训练自己的数据集 时会看到一堆视频教程其实核心就三步数据到位、yaml 写对、跑训练命令。这份药丸数据集里已经带了 YOLO 格式跳过了最脏的转换环节所以只剩配置和命令。3.1 把数据放成 YOLO 需要的结构YOLOv8 训练逻辑找两个东西图片和标签。默认结构是 images/ 和 labels/ 下面再分 train、val、test。如果解压出来的 YOLO 目录已经是这个结构直接改 data.yaml 就行如果不是需要手动排一下。先看原始结构find YOLO -maxdepth 2 -type d | sort正常输出是 images/train、images/val、labels/train、labels/val 四条目录。如果不规范比如所有图片都在 images 下没有 train/val就需要手动划分mkdir -p YOLO/images/{train,val,test} mkdir -p YOLO/labels/{train,val,test}然后把图片和标签按比例复制进去。此时标签目录必须保持相同子目录名否则训练时报No labels found in ...。接下来写 data.yaml。路径用绝对路径最保险避免相对路径受运行目录影响path: /home/user/pill_db/YOLO train: images/train val: images/val names: 0: chip 1: stain 2: crack这里names的顺序必须和 labels 里的第一列完全一致。你可以随机打开一个标签 txt看第一列是 0、1 还是 2再去 data.yaml 里对名字。如果发现数据集的 YOLO 标签里第一列是 13而不是 02说明打包者按 1 开始编号了要写个小脚本批量减 1YOLO 规范要求必须从 0 开始。3.2 训练命令和关键参数环境安装就一条命令pip install ultralytics权重会自动下载。训练命令用最小参数集yolo detect train \ data/home/user/pill_db/YOLO/data.yaml \ modelyolov8n.pt \ epochs100 \ imgsz640 \ batch16 \ device0 \ projectpill_defect \ namebaseline逐项说明modelyolov8n.pt是 nano 版本权重小对药丸这种缺陷检测本身够用显存不够就把 batch 降到 8。imgsz640是训练输入尺寸如果图片是大分辨率近照可以试试 1280但显存和耗时都涨很多。epochs100是缺陷检测的参考值第一次跑可以只跑 30确认 loss 下降再拉长。device0指定一张 GPUCPU 就写devicecpu但速度会慢很多。project和name决定结果输出目录方便你不覆盖别人跑的实验。训练过程中要看的不是自己的眼睛而是存到runs/detect/baseline/下的曲线。YOLOv8 会画results.png里面有训练损失和验证指标。我一般关注box_loss和cls_loss如果 loss 前 20 个 epoch 还是锯齿状剧烈抖动先停下来查数据集不要盲目加 epoch。很多人喜欢盯着 yolo 损失函数看但损失降到接近 0 不代表模型好更多是数据在过拟合。3.3 验证一波best.pt 推理一张药丸图训练结束后runs/detect/baseline/weights/下会有best.pt和last.pt。先用 best 跑一张图看直觉yolo detect predict \ modelruns/detect/baseline/weights/best.pt \ source/home/user/test_images/pill_0100.jpg \ saveTrue \ projectpill_defect \ nameinfer预测结果会存档成带框图片用来快速看模型有没有把药丸边缘、阴影误检成缺陷。如果这一眼没问题再进 val 做正规评估。这里有一个隐性坑best.pt 是按验证集 loss 选的不是按 mAP 选的如果验证集和产线分布差别大best 不一定可信推理图片最好用没进过训练/验证的留样图。另外如果显存只有 8G不要一上来就跑imgsz1280先用imgsz640, batch8跑通再看results.csv里的精度和召回。数据集只有 2759 张nano 模型在单卡上大约 20 分钟能跑完 100 epoch不用一上来就上yolov8x。越大模型越容易在这个规模上过拟合缺陷检测优先看稳定复现而不是刷点数。4. 如果要从 VOC 自己再造一遍 YOLO 格式解析脚本与 train/val 划分即使数据集已经给了 YOLO 格式你仍然可能要重排。比如想把某几类缺陷合并成一个大类、想换一种划分比例或者要跑老框架时只需要 VOC。此时不能只在文件管理器里拖来拖去正确做法是把 XML 解析和 txt 写入自动化。4.1 用 xml.etree 从 Annotations 批量转成 YOLO 标签我一般用 Python 自带的xml.etree.ElementTree不需要额外依赖。脚本按下面思路写import xml.etree.ElementTree as ET import os CLASSES [chip, stain, crack] # 占位必须和你的 data.yaml names 一致 def voc_to_yolo(xml_path, label_dir): tree ET.parse(xml_path) root tree.getroot() size root.find(size) width float(size.find(width).text) height float(size.find(height).text) lines [] for obj in root.iter(object): name obj.find(name).text.strip() if name not in CLASSES: continue cls_id CLASSES.index(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) cx ((xmin xmax) / 2) / width cy ((ymin ymax) / 2) / height bw (xmax - xmin) / width bh (ymax - ymin) / height # 防止标注越界影响训练 cx min(max(cx, 0.0), 1.0) cy min(max(cy, 0.0), 1.0) bw min(max(bw, 0.0), 1.0) bh min(max(bh, 0.0), 1.0) lines.append(f{cls_id} {cx:.6f} {cy:.6f} {bw:.6f} {bh:.6f}) out_txt os.path.join( label_dir, os.path.splitext(os.path.basename(xml_path))[0] .txt ) with open(out_txt, w, encodingutf-8) as f: f.write(\n.join(lines))脚本的核心是CLASSES的索引顺序它决定了 YOLO 类别 ID所以顺序不能按“看起来顺眼”排要先读原始 XML 里的类别名和data.yaml的 names 串一遍。遇到difficult1的 object建议在循环里判断后continue否则会把难例也送去训练。转换后要抽几个 txt 人工核对尤其看坐标是否全部落在 01 之间有没有负数。现成转换工具我也用但很多工具默认类表写死或者硬编码从 XML 里取name遇到类别名带冒号或中文直接 break。自己写脚本只有十几行还能顺手加过滤规则放在项目里也好维护。这套数据踩的坑很多都是工具转换后没检查造成的。4.2 train/val 划分先过滤有图无标和有标无图划分数据时最容易犯的错误是直接用图片文件名列表生成 train 和 val而不看标签。药丸这种带背景的工业图偶尔会有无缺陷的图因此没有对应标注。如果把无标注图放进 val模型没有 ground truth 可以对比显示 mAP 偏低放进 train又会告诉模型“这张图没有任何缺陷”抑制缺陷类预测。我一般先做文件交集import os import random img_dir JPEGImages label_dir Annotations train_ratio 0.8 random.seed(42) imgs [f[:-4] for f in os.listdir(img_dir) if f.endswith(.jpg)] valid [s for s in imgs if os.path.exists(os.path.join(label_dir, s .xml))] valid.sort() random.shuffle(valid) split_idx int(len(valid) * train_ratio) train_files valid[:split_idx] val_files valid[split_idx:] def save_list(files, path): with open(path, w, encodingutf-8) as f: f.write(\n.join(files) \n) save_list(train_files, ImageSets/Main/train.txt) save_list(val_files, ImageSets/Main/val.txt)train_ratio按产线需要调整0.70.8 都可以。最关键的是valid构造时过滤掉缺少 xml 的图片如果后面还要再转 YOLO txt标签目录也只生成这些有效样本的名字。否则 val 的空标签会让 mAP50 看起来非常差你还会以为是模型不收敛。划分完用一个命令确认两边比例wc -l ImageSets/Main/train.txt ImageSets/Main/val.txt这样就知道实际进了多少样本不会被 2759 这个总张数误导。5. 药丸数据集落地避坑清单解压路径、标签顺序与类别不平衡这一章专门记录我自己跑这类数据集时反复出现的五个坑。每一条按现象、原因、解决三部分写你可以对照自己的报错去排查。5.1 解压与路径相关的两个坑坑一Linux 解压 7z 后中文目录找不到训练数据为空。现象执行训练命令后YOLOv8 日志没有报错但Dataset一栏的图片数显示 0或者 loss 一直不下降。原因压缩包文件夹名包含“药丸的缺陷检测数据集VOCYOLO格式2759张3类别”里面既有中文又有加号。命令行解压时号在部分 shell 里会被当作特殊字符或者解压出来的路径在 YAML 解析时因空格/中文被截断。Ultralytics 在读取路径时也会遇到非 ASCII 字符但它不一定会报文件名具体位置只表现成找不到图片。解决解压后第一步就重命名不要直接在原目录上干活mv 药丸的缺陷检测数据集VOCYOLO格式2759张3类别 pill_data然后data.yaml里的path用绝对路径并检查训练目录存在ls pill_data/YOLO/images/train | head -3坑二在 Windows 解压 7z 后多了一层目录导致标注文件“消失”。现象用 7-Zip 右键解压后进入 YOLO/labels/train 发现只有空文件夹或者找不到任何 txt。原因有些打包工具把根目录和一层内目录都写进压缩包双击解压时默认保留完整路径解压出来是pill_data/YOLO/YOLO/train这种双层结构而你的脚本写死了找YOLO/labels/train。解决用 7-Zip 的“解压到指定目录”时先看压缩包内的顶层目录名。如果已经解压错了执行find . -maxdepth 4 -type d -name labels -o -name Annotations如果出现两条 labels 目录说明层级重复把外层的 YOLO 目录内容合并回想要的层次。5.2 标签顺序与训练相关的三个坑坑三训练正常但预测框全部错位所有缺陷都预测成同一个类别。现象训练 loss 掉到可以接受val mAP 却不升跑预测时发现每个药丸都被框成同一个缺陷类别模型退化成了二分类器。原因YOLO 标签第一列和data.yaml的names顺序不一致。比如data.yaml写0: chip, 1: stain, 2: crack但 labels 里的 txt 第一列其实是按另一个顺序生成的0 代表 stain1 代表 crack2 代表 chip。训练时模型学会的就是错误配对推理自然全错。解决训练前随机看三个 txt再对照 nameshead -3 YOLO/labels/train/*.txt | head -15如果发现类别 ID 偏移写一个批量映射脚本把 ID 换成正确值不要手改文件。另外尽量别改data.yaml的 names 顺序去适配标签因为转完还要把源 XML 也改掉否则下次重转又会错。坑四训练时报assertion failed或No labels found in ...却找不到明显原因。现象训练过程中蹦出 warning说某张图没有标签但图片明明和 txt 同名。严重时整个 epoch 的 loss 偏低模型学不到东西。原因YOLO 的 txt 可能用了全角逗号、多余引号或者\r\n换行。Ultralytics 解析时遇到空行或多余列会跳过该标注严重时一张图的所有目标都被跳过等于空图片。解决写一次脚本清洗标签import os for split in [train, val]: folder fYOLO/labels/{split} for f in os.listdir(folder): if not f.endswith(.txt): continue p os.path.join(folder, f) with open(p, r) as fp: lines [ln.strip() for ln in fp if ln.strip()] new_lines [] for ln in lines: parts ln.replace(,, ).split() if len(parts) ! 5: continue new_lines.append( .join(parts)) with open(p, w) as fp: fp.write(\n.join(new_lines))这段脚本把逗号替换成空格并过滤掉不是 5 列的行。跑完后再用find labels -name *.txt -empty | head确认没有全空的标签。坑五类别分布极度不平衡模型对少数缺陷类召回很低。现象训练结束results.png里cls_loss正常下降但 val 每个类别的 mAP 出来某一个缺陷类的 recall 只有 0.2 左右。原因药丸缺陷本身是小概率事件。2759 张图里可能大部分是正常药丸缺陷样本集中在少数几类。模型把大部分能力用来拟合占比大的类别缺陷类的 loss 被淹没。解决先统计标签分布for cls in 0 1 2; do echo -n class $cls: ; grep -r $cls YOLO/labels/train | wc -l; done然后按最少类别做过采样把缺陷图在训练集里复制几份更稳的是在数据增强里加大对比度扰动让缺陷区域更明显。采样之后每个 epoch 的 batch 里缺陷样本比例会提升但平衡倍数不要超过 3否则模型会对重复样本过拟合验证集精确率反而下降。6. 用混淆矩阵和类别权重验证模型三步判断能不能上线训练完不是结束而是开始检验数据集质量。我建议流程是先看混淆矩阵再看分类别指标最后决定要不要加类别权重。6.1 验证三件套混淆矩阵、分类别指标、带权重的对比实验第一步训练目录runs/detect/baseline/下会自动生成confusion_matrix.png。这张图能一眼看出哪两类在互相误判。药丸的崩边和裂纹如果混淆严重可能是标注框本身边界不清晰也可能是图片分辨率不够模型只能靠形状区分。发现混淆带我一般先回查样本图很多问题是标注框里同时包含了两个缺陷模型不知道该学哪个。第二步跑官方验证命令yolo detect val \ modelruns/detect/baseline/weights/best.pt \ data/home/user/pill_db/YOLO/data.yaml \ splitval输出会打印每个类别的 mAP50、mAP50-95、precision、recall。对于药丸产线我更关注缺陷类的 recall而不是 mAP。因为后段还有人工复检漏检造成的损失远大于误检如果某个缺陷类 recall 太低就不要用这个 best.pt 上线。第三步给少数类加权重。YOLOv8 默认会按类别分布算 loss但工业场景不平衡更极端时我会直接做正样本采样或者在训练时调整分类损失比例。不要只调epochs那是治标不治本。等重训完再跑一遍 val 看 recall 是否抬升。我自己吃过不看混淆矩阵就改权重的亏结果召回抬高了误检也暴增回炉后才明白先诊断再开药。希望帮到你。本文还有配套的精品资源点击获取