
简介这是一份面向计算机视觉与工业质检场景的药丸缺陷检测数据集提供2759张药丸图像的目标检测标注数据覆盖污染、裂纹与合格品三类目标适用于训练YOLO、Faster R-CNN等主流检测模型。数据同时给出Pascal VOC格式的xml标注与YOLO格式的txt标注并配有使用前必读说明方便直接接入训练流程。压缩包内共2000个文件以xml和txt标注文件为主约1049个xml、951个txt整体大小411.78MB便于一次下载使用。三类目标总框数为2798个分布相对均衡由labelImg按矩形框规则完成标注基础质量可靠。目前已有218人浏览学习适合需要现成数据开展药丸外观检测实验或毕业设计的开发者能大幅节省数据标注时间将精力集中于模型训练与优化。1. 药丸的缺陷检测数据集VOCYOLO双格式2759张图能直接开训吗药丸的缺陷检测数据集听起来像个小众资源包但做过产线外观检验的人知道它卡住了多少人缺角、裂纹、污点要靠人工盯眼睛疲劳后漏检率压不下来想上视觉检测又卡在没有带标注的数据。这个数据集恰好提供了2759张图片、3个类别的标注同时给VOC和YOLO两种格式解压就能喂给主流检测框架。适合刚接触缺陷检测、想跑通目标检测全流程的新手也适合要快速验证药丸瑕疵检测可行性、再决定要不要投入产线的团队。下面按落地顺序拆目录结构、格式转换、训练参数、排错验证。2. 解压后先别急着训VOC目录和YOLO标签怎么对得上2.1 解压命令与目录结构中文文件名是个坑拿到 .7z 包后先把文件放到纯英文路径下比如~/projects/pill_defect避免 YOLO 在读取路径时因为中文目录出现编码异常。Linux 下解压命令是7z x 药丸的缺陷检测数据集VOCYOLO格式2759张3类别.7z -o./pill_defect注意-o后面不跟空格直接接输出目录这是 7z 的参数格式写错了会解压到意外位置。如果服务器上没有 7z需要先安装 p7zip-fullWindows 下用 7-Zip 或 PeaZip 都行但解压后我建议第一时间改名成pill_defect这种英文目录。解压完成后不要急着找图片先用tree或ls看整体布局。以我处理过的双格式数据集来说最常见的目录是 VOC 和 YOLO 并列各自维护一份标注图片内容完全一致。典型结构是pill_defect/ ├── VOC/ │ ├── Annotations/ │ ├── JPEGImages/ │ └── ImageSets/ │ └── Main/ │ ├── train.txt │ ├── val.txt │ └── test.txt └── YOLO/ ├── images/ │ ├── train/ │ └── val/ └── labels/ ├── train/ └── val/这个结构对应两种用法VOC 目录喂给 MMDetection 这类需要标准 Pascal VOC 格式的框架YOLO 目录直接给 YOLO 系列训练。两个目录里的图片文件名是同一套标注内容也一致只是表达方式不同。如果你的压缩包解压出来不是这个布局只要内容还在花十分钟把它整理成标准结构后边会省很多麻烦。2.2 VOC 的 XML 标注框坐标和 size 字段怎么读打开一个 XML格式如下annotation folderpill_defect/folder filenamepill_0001.jpg/filename size width640/width height480/height depth3/depth /size object namechip/name poseUnspecified/pose truncated0/truncated difficult0/difficult bndbox xmin120/xmin ymin80/ymin xmax180/xmax ymax130/ymax /bndbox /object /annotation这里类别名我用chip做例子实际包里的 3 个类别以Annotations下的name为准。XML 里最关键的信息是name和bndbox分别是类别名和左上右上、左下右下的绝对像素坐标。difficult标记在 YOLO 训练里没有对应概念它表示这个框很难辨认VOC 评估时会忽略这类框但转成 YOLO 后difficult 信息会被丢掉等于把难例当成普通框参与训练。如果这类框本身标注质量差就会带偏模型。还有一个容易被忽略的点size字段不一定和图片真实分辨率一致。有的标注工具在保存时会缓存图片尺寸如果图片后来被压缩过XML 里的尺寸就没更新。所以做格式转换时宁可用 PIL 去读实际图片的宽高也不要直接信任 XML 里的size尤其是 width 和 height。另外XML 里的filename有时会带相对路径比如JPEGImages/pill_0001.jpg读取时要用os.path.basename做一次清洗否则找图片会失败。2.3 YOLO 的 TXT 标签归一化坐标和类别索引要对着 data.yaml 读YOLO 标签每个 txt 文件里一行表示一个框0 0.234375 0.208333 0.093750 0.104167这五个数字依次是类别索引、归一化中心 x、归一化中心 y、归一化宽、归一化高。药丸的缺陷框通常比较小所以后两个数字会经常出现低于 0.1 的情况。类别索引 0 对应的是你在data.yaml里第一个类别的名字不是 XML 里写的类名。如果 3 个类别的顺序排成了 chip、crack、stain那么索引 0 就是 chip索引 1 是 crack索引 2 是 stain。这个环节最容易翻车。之前有团队拿到的标签里索引 0 本应是 crack但data.yaml里第一个类别写成了 chip模型训练时 loss 正常下降推理出来所有框的类别名都错位。所以无论用现成工具还是自己写脚本先打印一张图的 XML 和 TXT 对照确认坐标和类别索引对得上再批量转换。为了便于对照写一个小函数把 YOLO 归一化坐标转回绝对像素def yolo_to_pixel(cx, cy, w, h, img_w, img_h): xmin (cx - w / 2) * img_w ymin (cy - h / 2) * img_h xmax (cx w / 2) * img_w ymax (cy h / 2) * img_h return xmin, ymin, xmax, ymax print(yolo_to_pixel(0.234375, 0.208333, 0.093750, 0.104167, 640, 480))输出结果大概落在(120, 80, 180, 130)附近正好和上面 XML 的bndbox对齐。这里用的是归一化坐标加减宽高的一半再乘图片尺寸不要套用 VOC 的绝对坐标公式。如果输出和 XML 差得很远说明标签不是同一份图片生成的需要重新确认数据来源。2.4 双格式为什么都存在一份数据要能喂给两套框架VOC 格式是检测领域的通用格式保存的信息更全除了框和类别还有难例标记、截断状态YOLO 格式是简化后的训练格式只有类别索引和归一化坐标。双格式同时存在是为了让使用者不用统一转换工具直接在不同训练生态里切换。比如算法横向对比时用 VOC正式训 YOLO 时直接用 YOLO 目录。但双格式也有个隐患两份标注可能不同步。打包的人可能更新了 VOC 里的 XML忘了同步 YOLO 的 TXT。所以训练前先做一次计数比对下面的命令能快速发现数量不一致find VOC/Annotations -name *.xml | wc -l find VOC/JPEGImages -name *.jpg | wc -l find YOLO/images/train -name *.jpg | wc -l find YOLO/labels/train -name *.txt | wc -l正常情况下图片数等于 XML 数允许个别纯背景图没有 XMLYOLO 目录里的标签数和图片数也应一致。如果发现标签数明显少于图片数说明有图片没有标注这些图片在 YOLO 训练里会被当成纯背景如果缺陷图片没标注就相当于漏标正样本训练出的模型会在这些图上漏检。提示双格式之间的差异排查只要花两分钟但能排掉一大半后续“莫名其妙指标不对”的玄学问题。两个目录的 train/val 划分也必须对齐否则验证集污染会直接抬高 mAP后面第 5 章再细说。3. 把 VOC 转 YOLO自写转换脚本与四个边界坑3.1 为什么自己写格式转换不能只靠在线工具很多在线转换工具需要上传数据药丸缺陷图片往往涉及生产环境不宜外传而且在线工具对错误标注只能简单跳过无法知道为什么跳。我一般会在本地用 Python 写一个一次性脚本也就几十行还能针对越界框、异常框做定制处理。这里给一个能直接跑的版本它读取 VOC 的Annotations生成 YOLO 训练所需的labels。import os import xml.etree.ElementTree as ET from PIL import Image voc_annotations VOC/Annotations image_dir VOC/JPEGImages label_dir YOLO/labels class_names [chip, crack, stain] # 必须和最终data.yaml一致 os.makedirs(label_dir, exist_okTrue) for xml_file in sorted(os.listdir(voc_annotations)): if not xml_file.endswith(.xml): continue tree ET.parse(os.path.join(voc_annotations, xml_file)) root tree.getroot() img_name os.path.basename(root.find(filename).text) img_path os.path.join(image_dir, img_name) img Image.open(img_path) img_w, img_h img.size lines [] for obj in root.iter(object): name obj.find(name).text if name not in class_names: print(f跳过未知类别 {name} 在 {xml_file}) continue cls_idx class_names.index(name) bndbox obj.find(bndbox) xmin float(bndbox.find(xmin).text) ymin float(bndbox.find(ymin).text) xmax float(bndbox.find(xmax).text) ymax float(bndbox.find(ymax).text) center_x (xmin xmax) / 2.0 / img_w center_y (ymin ymax) / 2.0 / img_h width (xmax - xmin) / img_w height (ymax - ymin) / img_h if width 0 or height 0: print(f跳过异常框 {xml_file} 的 {name}) continue lines.append(f{cls_idx} {center_x:.6f} {center_y:.6f} {width:.6f} {height:.6f}) out_name os.path.splitext(xml_file)[0] .txt with open(os.path.join(label_dir, out_name), w, encodingutf-8) as f: f.write(\n.join(lines)) print(f转换完成共处理 {len(os.listdir(voc_annotations))} 个XML)这个脚本的核心逻辑是先取filename找图片用 PIL 读真实宽高再遍历每个object把类别名映射成索引把绝对坐标除以宽高转为归一化坐标最后把每个框写成一个 txt 行。class_names的顺序是唯一的映射来源顺序一旦变了所有类别索引都会变因此这里要与后面data.yaml里的names严格一致。脚本里跳过未知类别和异常框如果跳过数量很多需要打印统计信息不要静默处理。3.2 边界坑一类别名和索引错位训练不报错但结果全乱这个坑在 2.3 提过这里展开讲。VOC 的 XML 里name是字符串YOLO 的 TXT 里是整数转换脚本里class_names的顺序决定了每个字符串对应哪个索引。如果顺序写错模型训练过程中 loss 不会异常甚至 mAP 可能还不错但实际推理时类别名和位置对不上。比如真正的裂纹被预测成污点产线判级就会出错。更隐蔽的错位发生在你追加新类别时。比如原始 3 个类别是 chip、crack、stain你为了实验又往 XML 里加了其他类别但class_names没有同步增加转换脚本会把所有新类别当成未知类别跳过静默丢失一部分框。所以脚本里最好不要用list.index这种方式而是先扫描所有 XML动态建立“类别名到索引”的映射字典并输出一份classes.txt。这样每次转换后比对classes.txt和data.yaml能手动确认有没有错位。还有一个常见乌龙XML 里的类别名带前后空格比如chip直接拿来比较会匹配不上。写脚本时对name.strip()是基本操作不要省。3.3 边界坑二越界框和非正框训练时产生 nan药丸图片里缺陷靠近边缘很常见。标注时为了框住完整缺陷很容易把 xmax 标到图片外面。这种框直接转 YOLO归一化后坐标可能大于 1YOLO 训练时不一定报错但损失计算会异常。处理办法是 clip 到边界内然后判断有效尺寸。xmin max(0.0, min(xmin, img_w - 1)) ymin max(0.0, min(ymin, img_h - 1)) xmax max(0.0, min(xmax, img_w - 1)) ymax max(0.0, min(ymax, img_h - 1)) if xmax xmin or ymax ymin: print(f过滤无效框: {xml_file} {name}) continue这段代码放在归一化之前把坐标限制在图片像素范围内。注意min(xmax, img_w - 1)而不是img_w是为了避免像素索引等于宽度时转成归一化坐标 1.0在数据增强的随机缩放里容易出问题。clip 后如果框宽或高被压缩到 0说明原始标注质量太差直接丢弃。还有一个容易被忽略的点VOC 坐标是像素角点坐标xmin10、xmax20 表示框宽 10 像素算宽度时要用xmax - xmin不要画蛇添足加 1。YOLO 归一化时中心点用(xmin xmax) / 2这是标准做法。如果把 1 带进去所有框都会偏移半个像素缺陷框小的时候影响会被放大训练出的框边缘会普遍贴近缺陷一边。3.4 边界坑三目录划分不一致导致验证集污染VOC 的ImageSets/Main里有 train.txt、val.txtYOLO 目录里也分了 train、val。这两份划分如果来源不同可能出现同一张图既在训练又在验证mAP 虚高。比如 VOC 的 ImageSets 按时间先后划分YOLO 目录里却是随机划分两边比例不一致。转换脚本只负责生成标签不会自动纠正划分。解决办法是统一以 VOC 的ImageSets/Main下的 txt 为准根据它把图片拷贝到 YOLO 的 images/train 或 val再根据图片文件名把对应标签拷入 labels。一个简单的 bash 做法如下for split in train val; do while read line; do cp VOC/JPEGImages/$line.jpg YOLO/images/$split/ cp YOLO/labels/$line.txt YOLO/labels/$split/ done VOC/ImageSets/Main/${split}.txt done解释读一行文件名把 jpg 和同名的 txt 拷过去。注意这里的$line不带扩展名如果你的 ImageSets 里带 .jpg需要先去后缀。这个脚本会覆盖 YOLO 目录原本的划分确保两边一致。跑完后再用 2.4 的比对命令检查 train 和 val 的文件数额外用comm -12检查两个列表有没有交集确认没有同一张图同时出现在 train 和 val。3.5 转换后自检类别计数和框尺寸分布转换完还要做一次自检。我习惯写一个很短的统计脚本确认各类别的实例数、空标签数量、框尺寸范围。import os from collections import Counter label_dir YOLO/labels counter Counter() box_sizes [] empty 0 for f in os.listdir(label_dir): path os.path.join(label_dir, f) if not f.endswith(.txt): continue with open(path, r, encodingutf-8) as fh: lines fh.readlines() if len(lines) 0: empty 1 continue for line in lines: parts line.strip().split() if len(parts) ! 5: continue counter[int(parts[0])] 1 box_sizes.append((float(parts[3]), float(parts[4]))) print(类别索引计数:, counter) print(空标签图片数:, empty) if box_sizes: print(最小归一化框尺寸:, min(box_sizes)) print(最大归一化框尺寸:, max(box_sizes))这里要注意归一化框尺寸是指框宽在整张图宽度中的占比。药丸缺陷如果本身很小min 会低于 0.01此时训练时最好开高分辨率输入或者做针对性增强。如果某个类别框数为 0说明类别映射或源标注有问题如果空标签图片数特别多说明大量图片没有缺陷训练时要保留作为背景但也要意识到模型可能会更倾向于预测“无缺陷”。4. 用 YOLOv8 训练药丸缺陷模型数据集配置与参数选型4.1 写 data.yaml路径、类别名、类别数YOLOv8 使用 YAML 文件描述数据集。最简配置是 path、train、val、names 四项。下面这份能直接用path: ./pill_defect train: YOLO/images/train val: YOLO/images/val names: 0: chip 1: crack 2: stain没有写ncYOLOv8 会根据 names 长度推断。path是相对路径train和val是图片目录的路径。需要特别留意的是YOLOv8 加载标签时默认从 images 路径的同级 labels 目录找也就是images/train对应labels/train。如果你把标签放在别处可以在 yaml 里额外指定但我不建议这么做保持默认能减少很多路径排查时间。这份 yaml 里的类别名称顺序必须和转换脚本里的class_names顺序完全一致。如果转换脚本里是[chip, crack, stain]yaml 里也必须按同名顺序写。建议把classes.txt放在数据集根目录每次训练前 cat 出来和 yaml 对照一遍。4.2 训练命令与关键参数先跑 n 再换 s在 YOLOv8 的权重系列里n、s、m 对应网络深度和宽度递增。2759 张图的规模不算大我通常先用yolov8n跑通流程再用yolov8s看效果上限。训练命令yolo train datapill_defect.yaml modelyolov8s.pt epochs100 batch16 imgsz640 device0 seed0命令参数data指向 yamlmodel表示基础权重第一次运行会自动下载预训练模型到缓存目录epochs是训练轮数batch是单卡批大小imgsz是输入分辨率device指定显卡seed固定随机种子保证可复现。对于显存只有 8G 的显卡batch16 可能爆显存降到 8 或 4 即可。如果小目标缺陷比较多imgsz提到 704 或 768 通常能提升召回但训练时间会明显变长。为什么先选yolov8n因为缺陷检测的第一目标是判断数据是否可用而不是刷榜单。yolov8n 训练一轮很快能快速看出 loss 曲线是否正常、mAP 有没有上升趋势。如果 n 的基础结果太差再换 s 或 m而不是一开始就上大模型。数据本身只有 2759 张大模型容易过拟合val mAP 反而可能下降。4.3 训练过程的常见报错bn 崩溃、loss 为 nan、显存不足这一节是给新手排雷的。三类最常见的报错现象与处置如下第一显存不足CUDA out of memory。报错出现在第一个 epoch 开始时。解决顺序是先降 batch 到 8 或 4再不动 batch 降 imgsz 到 512如果还不行只能换更大显存。不建议用梯度累积替代因为梯度累积只是降低更新频率显存峰值并没有降下来。第二loss 变成 nan。现象是训练日志里 loss 首次出现 nan之后一直 nan。原因多数是标签坐标出现了 inf 或 nan或者输入图片本身全黑、全白、损坏。解决先用自检脚本扫一遍标签数值然后检查图片是否完整用 PIL 打开并转成 numpy 数组判断数组的最大最小值是否正常。如果数据没问题再考虑学习率太高把初始学习率降到 0.001 以内重试。第三bn 崩溃。YOLO 的 BN 层在训练中会因为某个 batch 的统计量异常而输出 nan常见原因是某个 batch 里恰好全是空标签图片或者图片尺寸差异过大。解决把 batch 调大一点让每个 batch 里正负样本更均衡同时关闭过强的数据增强特别是 Mosaic在数据量不足时 Mosaic 后小目标被裁剪BN 统计容易出错。这个小数据集上确实有点玄学但多发生在这种样本量不够大的场景里。还有一类报错是训练开始前提示找不到标签文件。现象是日志里出现大量WARNING: impossible to load或No labels found。原因多半是 yaml 里的train路径写错或者标签目录不是 images 同级的 labels。解决回到 2.4 的比对命令确认标签文件都在再检查 yaml 的 path 是不是相对于当前执行目录。这个问题最多也最不值得浪费一晚上。4.4 训练完先看验证集结果图再决定是否调参训练结束后不要只盯 mAP。用 best 权重跑一遍验证集预测yolo predict modelruns/train/exp/weights/best.pt sourceYOLO/images/val save_txtTrue conf0.25conf默认 0.25预测结果会存到runs/detect/exp下。打开这些结果图人工看一圈重点关注有没有漏检明显的缺陷、有没有把反光误检为缺陷、框是否贴合边缘。这一步能发现许多 mAP 体现不了的问题。比如有些框虽然 IoU 高但中心点偏了产线机械手去抓就很难对准。还可以统计验证集上每个类别的平均置信度。如果某个类别的置信度普遍偏低说明模型对这个类别的特征还没学会下一轮训练时考虑增加该类别样本的增强权重或者补充样本而不是继续加训练轮数。训练轮数堆太多往往是过拟合验证集 loss 已经开始反弹了。5. 缺陷检测场景避坑指南标注、反光与样本不均衡5.1 标注框偏移让 mAP 虚高独立验证才能露馅现象训练时验证集 mAP50 高达 90%部署到现场后错漏明显。原因是标注框存在系统性偏移比如标注统一偏左 2 个像素模型学到了这个偏移在同一个数据分布下的验证集上指标好看到新产线就失效。解决训练前随机抽 10 张图用 OpenCV 把标签框画上去人工核对训练后再抽 10 张预测图对比预测框和真实框。画框脚本import cv2 img cv2.imread(VOC/JPEGImages/pill_0001.jpg) box (120, 80, 180, 130) # xmin, ymin, xmax, ymax cv2.rectangle(img, (box[0], box[1]), (box[2], box[3]), (0, 0, 255), 2) cv2.imwrite(check_box.jpg, img)画出来的框如果明显没有包住整个缺陷就需要重新评估这个数据集的标注质量。缺陷检测和通用目标检测不同缺陷框小边缘稍微偏一点IoU 就从 0.9 掉到 0.5。这也是为什么这类数据集训练后指标的绝对值通常没有通用检测高。如果发现几十张图都有系统性偏移最有效的做法不是写脚本平移标签而是返回标注工具里做一次整体修正。5.2 药丸反光被识别成缺陷先从光照入手现象推理时药丸表面高光区域产生大量预测框而这些区域实际是灯打出来的反光不是裂纹。原因训练数据里反光样本少模型把高光纹理当成缺陷特征。解决分两步。第一步在训练数据中加入不同光源角度的反光阴性样本让模型学会区分反光和裂纹。第二步在数据预处理里做光照归一化例如把图片转成灰度后计算直方图均值和标准差再决定是否做 CLAHE 对比度均衡。OpenCV 的createCLAHE能压掉过强的高光但参数要控制clipLimit设到 2 以下否则药丸本身的纹理也会消失。这条经验来自药丸检测项目里的血泪踩坑换模型远不如把光照问题在数据层面解决来得彻底。如果只是快速验证也可以临时在推理阶段给图片外侧加一个遮罩把固定光源造成的高光区域排除掉但这是治标不治本。产线换一套光源后遮罩位置就失效了还是要在训练数据里覆盖足够多的光照角度。5.3 类别样本不均衡先复采样再考虑加权现象三个类别里某个类别样本很少模型对这个类别的召回率始终上不去。原因少数类在总样本中占比过低损失被多数类主导。解决顺序建议先统计每个类别的框数如果少数类占比低于 10%先做类别复采样在每次 epoch 里对少数类图片做重复抽样而不是直接改 Loss。YOLO 训练没有直接暴露 class weights 参数盲目加权很难找到合适尺度反而会过拟合少数类。另一种做法是针对少数类做基于缺陷框的局部增强对框区域做小角度旋转、亮度扰动、轻微平移生成更多训练样本。需要注意的是增强幅度要小药丸缺陷的形态和位置都很敏感扭曲太大会让缺陷变成另一种东西。如果做了复采样和增强后少数类还是不行再考虑最粗暴的办法多收集真实缺陷样本尤其是不常见的那一类。缺陷检测的数据集不像通用物体目标检测那样能靠爬虫补充只能从产线积累这也是这类数据集的真实成本所在。5.4 固定随机种子否则你分不清改动是提升还是玄学现象同一份数据、同一套参数只换一次随机种子mAP 变化 2 个点有时候类别 A 的召回涨了但类别 B 暴跌。原因训练过程中的数据加载顺序、Mosaic 增强的随机采样都没固定。解决在训练命令里加seed0并固定数据加载线程和 shuffle 的种子更严格一点在训练脚本开头调用random.seed、numpy.random.seed、torch.manual_seed。这个习惯看起来简单但在小数据集上特别重要因为样本少随机波动比大数据集大得多。固定种子后每次实验只改变一个变量调参才有意义。否则你很难判断 mAP 的提升是模型改动带来的还是某次随机采样恰好抽到了更简单的验证图片。6. 从 mAP 到产线验收先看混淆矩阵再算漏杀和过杀6.1 混淆矩阵里看漏检而不是只看 mAPmAP 是排序指标对低置信度预测有一定宽容但产线只看最终判决缺陷有没有被框出来、框是不是对准了。训练完跑一次验证集生成混淆矩阵重点看两处一是每个类别被漏检成了哪一类二是背景被误检成缺陷的比例。药丸缺陷里裂纹和反光的混淆最典型如果矩阵里大量“裂纹被判成污点”说明这两个类别的特征边界没有拉开要么加样本要么考虑合并类别重新标注。混淆矩阵建议用验证集生成不要用训练集训练集上的混淆矩阵几乎没有参考价值。6.2 漏杀和过杀的成本决定阈值怎么调药丸质检通常漏杀成本远高于过杀坏药丸流出去会出品质事故好药丸被拦截最多是重新检测一次。因此部署时置信度阈值可以从默认的 0.25 下调到 0.15多召回一些低置信度缺陷框同时配合连续帧过滤同一颗药丸在视野中出现多次至少连续两帧都报缺陷才判为 NG能明显降低偶发误检。这个组合拳比单独调模型参数更值得先做。如果现场过杀率仍高于线体要求再逐步提高阈值直到找到漏杀和过杀的平衡点。需要在产线连续运行一段时间后重新统计一次混淆矩阵以真实样本为准。我自己的习惯是拿到任何缺陷检测数据集先跑一轮基线只调置信度阈值和 NMS 参数记录下这两组数如果效果不满足再动训练参数和增强策略。这样能避免把数据和模型问题混在一起。希望帮到你。本文还有配套的精品资源点击获取