简介一套面向药丸外观缺陷检测的视觉数据集覆盖污染、裂纹与合格三类目标同时提供Pascal VOC和YOLO两种标注格式可直接用于常见目标检测模型的训练与评估也适用于工业质检、药品包装等视觉场景。压缩包内包含1049个XML标注文件与951个TXT标注文件共2000个文件其中XML保存VOC格式的框信息TXT保存YOLO格式的类别与坐标信息压缩后整体约411.78MB。目前已有218人学习/下载。数据集合计2798个标注框三类样本数量分别为904、960、934分布较为均衡可用于药品包装质检、无人售药视觉识别等场景的基线实验。两种格式标注并存可节省数据转换时间标注由labelImg工具完成并附使用前必读文档便于快速理解目录结构与标注规范。需要说明的是资源仅保证标注准确合理不对模型精度作担保适合作为缺陷检测项目的数据基础。1. 直接可训的药丸缺陷检测数据集2759张图、双格式、三类标签做药品产线视觉检测的人应该都有这种体会缺陷样本比好样本难找标注更是耗时。这套药丸缺陷检测数据集给到的是2759张jpg、2759个xml、2759个txt共2798个标注框类别就是contamination、crack、good三个。它不需要你再从标注软件重新导出Pascal VOC和YOLO两份格式已经对齐。适合两类人刚用YOLO训练自定义数据集的新手需要一套结构干净的样本先跑通流程以及做药品外观检测方案验证的工程师拿来做可行性测试。前提是先花十分钟把格式和结构核对清楚再进训练。2. 摸清双格式底细VOC的xml与YOLO的txt怎么对齐以哪份为准这套数据的核心关系是“jpg、xml、txt三个文件同名同前缀”。xml是labelImg导出的Pascal VOC格式txt是训练框架直接吃的YOLO格式。动手训练之前第一件事不是跑模型而是搞清楚两份标签如何对应因为后面划分train/val、写data.yaml、排查漏检全都建立在这个对应关系上。2.1 文件名是唯一的关联键解压后能看到典型的三件套文件组命名规律如下pill_mint_contamination_076.jpg pill_mint_contamination_076.xml pill_mint_contamination_076.txt pill_mint_good_019.jpg pill_mint_good_019.xml pill_mint_good_019.txt前缀一致就是同一张图的三份文件。文件名里虽然带了类别片段contamination、crack、good但不要拿文件名当标签它只是给人看的训练框架只读txt和xml内容。重命名时三个文件必须一起改否则训练阶段会报“found no labels”一类的错误。我一般习惯先把文件按jpg、xml、txt分目录放好保持同名前缀不变后续脚本处理会省很多事。2.2 两种标签文件分别怎么读xml是典型的VOC标注结构annotation object name bndbox坐标是像素绝对值。txt则是YOLO归一化格式每行5个字段。分别给出解析函数。import xml.etree.ElementTree as ET def parse_voc_xml(xml_path): tree ET.parse(xml_path) root tree.getroot() objects [] for obj in root.findall(object): name obj.find(name).text box obj.find(bndbox) objects.append({ name: name, box: [ int(box.find(xmin).text), int(box.find(ymin).text), int(box.find(xmax).text), int(box.find(ymax).text), ], }) return objects # 用法示例 objs parse_voc_xml(pill_mint_good_019.xml) print(objs[0][name], objs[0][box]) # good [xmin, ymin, xmax, ymax]这个函数返回的是类别名和像素坐标四元组左上右下顺序。VOC格式里一个object就是一个矩形框一张图有多个object就说明有多个目标。labelImg导出时还会写truncated、difficult这些字段它们是对象属性而不是类别解析时只取name即可。txt侧解析更直接def parse_yolo_txt(txt_path): boxes [] with open(txt_path) as f: for line in f: parts line.strip().split() if len(parts) ! 5: continue cls_id, cx, cy, w, h parts boxes.append((int(float(cls_id)), float(cx), float(cy), float(w), float(h))) return boxes boxes parse_yolo_txt(pill_mint_good_019.txt) print(boxes[0])每行5个字段分别是类别ID、中心点x、中心点y、宽、高。注意cx、cy、w、h都是相对于原图宽高的归一化值取值在0到1之间不是像素值。如果想把VOC坐标转成YOLO格式公式是cx (xmin xmax) / 2 / widthw (xmax - xmin) / widthwidth取原图宽度。这套数据集的txt字段恒为5说明它只做目标检测不包含分割polygon路径不要用实例分割的解析方式去读。类别ID与标签名的对应关系如下类别名YOLO ID框数contamination0904crack1960good2934这个ID顺序决定了txt里每行第一个数字的含义后面写data.yaml时names数组的顺序必须和它完全一致否则类别会错位。2.3 XML与TXT的一致性校验拿到这种双格式数据集我建议先跑一遍框数一致性校验确认xml和txt描述的是同一批目标。这个动作花费不到一分钟但能拦住大部分训练翻车。import os import xml.etree.ElementTree as ET xml_dir path/to/xml txt_dir path/to/txt mismatch [] for xml_name in sorted(os.listdir(xml_dir)): if not xml_name.endswith(.xml): continue prefix xml_name[:-4] txt_path os.path.join(txt_dir, prefix .txt) if not os.path.exists(txt_path): mismatch.append((prefix, txt缺失)) continue root ET.parse(os.path.join(xml_dir, xml_name)).getroot() n_xml len(root.findall(object)) with open(txt_path) as f: n_txt len([ln for ln in f if ln.strip()]) if n_xml ! n_txt: mismatch.append((prefix, fxml框数{n_xml}, txt框数{n_txt})) print(不一致文件数量:, len(mismatch)) for item in mismatch[:20]: print(item)把xml_dir和txt_dir改成你的实际路径即可。脚本只比框数坐标合法性校验在后面章节单独写。正常情况mismatch应该为空一旦非空说明某份标签被改动过这类文件要挑出来重新标注不能直接送训练。3. 用YOLOv8把这套数据训起来目录划分、data.yaml、训练参数labelImg导出的数据是平铺文件没有train/val划分。YOLO训练前需要自己整理目录结构同时确认标签文件和图片一一对应。这里我用YOLOv8的常规目录规范来讲这套结构同样适用于YOLOv5。3.1 划分train/val目录并固定随机种子常见做法是把图片和标签分别放入images和labels目录各自再分train、val两个子目录。划分比例用9:12759张图对应验证集约276张对缺陷检测场景足够。import os import random import shutil src_img path/to/images src_lab path/to/labels out pill_mint_yolo for split in [train, val]: os.makedirs(f{out}/images/{split}, exist_okTrue) os.makedirs(f{out}/labels/{split}, exist_okTrue) files [f for f in os.listdir(src_img) if f.endswith(.jpg)] random.seed(20240611) random.shuffle(files) val_n int(len(files) * 0.1) val_files files[:val_n] train_files files[val_n:] def copy_sample(img_name, split): prefix img_name[:-4] shutil.copy2(os.path.join(src_img, img_name), f{out}/images/{split}/{img_name}) shutil.copy2(os.path.join(src_lab, prefix .txt), f{out}/labels/{split}/{prefix}.txt) for f in train_files: copy_sample(f, train) for f in val_files: copy_sample(f, val) print(ftrain{len(train_files)}, val{len(val_files)})random.seed(20240611)固定了洗牌顺序任何机器上跑结果都一样方便复现对比实验。9:1不是硬性规定如果你的场景里缺陷形态很多可以改成85:15让验证集覆盖更全。这里只copy不移动原文件避免破坏源数据万一后面划分失误还有后悔药。3.2 写data.yaml并核对names顺序YOLOv8要求的data.yaml包含路径、训练目录、验证目录、类别数量和类别名。names顺序必须与txt中的class_id一致这是最容易错位的地方。path: /absolute/path/to/pill_mint_yolo train: images/train val: images/val nc: 3 names: 0: contamination 1: crack 2: goodpath必须写绝对路径写相对路径在部分环境下会解析出错。train和val是相对于path的路径不需要加前导斜杠。names的0、1、2顺序就是txt里class_id的取值顺序如果写成0: good、1: contamination训练不会报错但mAP曲线和混淆矩阵会完全错乱这类错误通常要等看验证结果才发现。3.3 训练命令与参数选择从YOLOv8n开始跑是个稳妥选择数据量不大可以先验证流程再换更大的模型拉精度。yolo detect train \ datapill_mint_yolo/data.yaml \ modelyolov8n.pt \ epochs120 \ imgsz640 \ batch16 \ patience20 \ projectruns/pill_mint \ namebase_n_640 \ seed42 \ cos_lrTrue几个参数我分别说明。imgsz640是起步值药丸上的裂纹属于小目标640下能训通但后续想提点建议升到1280batch16按显存确定显存不够就降到8或4patience20表示验证指标20轮不涨就提前停止省时间cos_lr用余弦学习率衰减收敛曲线更稳。训练过程中主要看loss下降趋势是不是平滑如果loss反复横跳优先调低学习率或者检查标签坐标。3.4 验证与结果读取训练结束后用best.pt做验证YOLOv8会输出mAP50、mAP50-95、precision、recall等指标以及confusion matrix和results.png。yolo detect val \ modelruns/pill_mint/base_n_640/weights/best.pt \ datapill_mint_yolo/data.yaml \ imgsz640对缺陷检测我习惯重点看recall而不是precision。产线场景漏检比误检代价高得多——漏掉一粒污染药丸比多报警一次更严重。混淆矩阵里重点关注good被误判成contamination或crack的比例如果这个值偏高说明良品和缺陷在视觉特征上边界模糊下一步要靠增强和调参解决。4. 数据平衡性与批量合法性校验类别占比、坐标边界、文件对应很多数据集拿到手就直接训练等到loss不收敛才开始怀疑标签有问题。与其事后排查不如在训练前把三类信息核对清楚类别框数平衡性、坐标合法性、图片与标签文件对应关系。4.1 三个类别的平衡性分析contamination 904框、crack 960框、good 934框三者比例约1:1:1这在缺陷检测数据集里相当少见。常见缺陷数据大多是长尾分布正样本几百框、缺陷样本只有几十框。这套数据每个类别都接近一千框训练时不需要额外做类别重加权也不容易因为样本不均衡导致某一类被模型忽略。需要注意一个细节good不是背景。good类别有934个框说明标注的是“药丸本体”而不是“无缺陷的图片背景”。模型要完成的任务是同时区分位置和类别——找出药丸在哪并判断它是污染、裂纹还是良品。总框数2798比图片数2759多39个说明存在一张图里多个框的情况比如同一张图既有污染又有裂纹YOLO的多目标输出天然支持这类样本。4.2 坐标合法性批量校验YOLO格式最常见的坑有三个class_id不在合法范围、中心点坐标越界、宽高为负或超过1。这些问题不会让训练直接崩溃但会让loss异常或者模型输出奇怪框。我每次拿到新数据集都会跑一遍下面的脚本。import os label_dir path/to/labels valid_ids {0, 1, 2} issues [] for txt_name in sorted(os.listdir(label_dir)): if not txt_name.endswith(.txt): continue path os.path.join(label_dir, txt_name) with open(path) as f: for line_no, line in enumerate(f, 1): parts line.strip().split() if len(parts) ! 5: issues.append((txt_name, line_no, f字段数{len(parts)})) continue cls int(float(parts[0])) if cls not in valid_ids: issues.append((txt_name, line_no, f非法class_id{cls})) cx, cy, w, h map(float, parts[1:]) if not (0 cx 1 and 0 cy 1): issues.append((txt_name, line_no, f中心点越界 cx{cx}, cy{cy})) if w 0 or h 0 or w 1 or h 1: issues.append((txt_name, line_no, f宽高非法 w{w}, h{h})) print(问题行数:, len(issues)) for it in issues[:30]: print(it)中心点越界指的是cx或cy等于0或1这种框在实际图像上只有一个像素落在图内基本是标注误操作。w或h超过1说明框比整张图还大同样不合理。脚本把问题行挑出来后根据txt_name和line_no定位到具体文件用labelImg重新检查。坐标问题越早发现越好因为训练到一半发现数据有问题前面所有实验都要推倒重来。4.3 图片尺寸与文件数量核对除了坐标还建议检查两件事图片尺寸是否统一文件数量是否严格对应。图片分辨率可以通过VOC xml里的size字段读取不需要真的去解码jpg。import os import xml.etree.ElementTree as ET xml_dir path/to/xml img_dir path/to/images txt_dir path/to/txt sizes {} xml_count 0 txt_count 0 img_count 0 for name in os.listdir(xml_dir): if name.endswith(.xml): xml_count 1 root ET.parse(os.path.join(xml_dir, name)).getroot() size root.find(size) key (size.find(width).text, size.find(height).text) sizes[key] sizes.get(key, 0) 1 txt_count len([f for f in os.listdir(txt_dir) if f.endswith(.txt)]) img_count len([f for f in os.listdir(img_dir) if f.endswith(.jpg)]) print(fjpg{img_count}, xml{xml_count}, txt{txt_count}) print(尺寸分布:, sizes)如果jpg、xml、txt三个数字不是2759说明有文件缺失或多余先补全再训练。尺寸分布如果存在多种分辨率说明原始图片来源不一训练时YOLO会自动resize但极端宽高比会影响小目标检测建议统一到同一边长或按短边padding后再用。labelImg标注时不会改写原图尺寸所以这个字段基本反映了数据源头。5. 避坑清单五条让训练翻车的血泪记录数据格式干净不代表训练一定顺利。下面这五条是我在处理同类VOCYOLO数据集时实际踩过的坑每条都按现象、原因、解决三步写清楚。5.1 现象loss正常下降但mAP整体偏低训练曲线很漂亮loss一路走低验证时mAP50却只有零点几和预期差很远。原因划分train/val时文件没有打乱或者用了默认顺序导致验证集和训练集分布不一致。比如按文件名排序后前90%进train、后10%进val如果文件名本身按批次编号验证集就全是靠后批次的样本光照和拍摄角度可能与训练集有明显差异。解决回看3.1的划分脚本确认random.shuffle生效并且seed固定。如果数据集来自多个拍摄批次更严谨的做法是按批次分组划分而不是按单张图随机划分避免同一批次的相似样本同时泄漏到train和val。5.2 现象验证阶段报标签shape断言错误训练能启动验证时抛assertion failed: labels shape或者提示标签维度不对。原因txt里混入了非法行。常见的有class_id写成浮点数如1.0、2.0但代码里用了int转换失败或者坐标没归一化仍在像素范围也有少数行字段数超过5可能混入了分割格式坐标。解决跑4.2的合法性校验脚本把非5字段的行全部打印出来。之后用脚本把非法行修正或剔除不要手工改2759个文件逐个看会看到手麻。5.3 现象验证集mAP不错换真实产线照片漏检严重这是缺陷检测落地最典型的翻车。训练集和验证集都表现良好一到现场数据就召回骤降。原因训练图片和产线实际图片存在系统性差异可能是光照色温、可能拍摄角度、也可能摄像头分辨率不同。数据只在“所见即所得”的分布上有效对分布外样本没有任何保证。解决训练时叠加数据增强模拟现场变化重点加亮度、对比度、色调扰动。如果条件允许从产线抽几十张真实照片做外验证不进训练集专看模型在陌生分布上的表现。这一步能提前暴露泛化问题比上线后再返工强得多。5.4 现象类别数对不上训练时报nc3但标签里出现4val时报错说找不到第4个类别的名称映射。原因有人改了txt里的class_id但没同步data.yaml或者txt里混入了xml中truncated、difficult字段被误当成类别的样本。labelImg导出的xml里包含pose、truncated、difficult等属性字段如果转换脚本错误地读了这些字段就会生成多余的类别ID。解决重新检查转换脚本只把object name作为类别依据。对现有txt跑一遍class_id分布统计把出现频率不为0、但不在{0,1,2}中的ID找出来定位到具体文件重标。5.5 现象直接yolo train报找不到标签文件命令看起来没问题data.yaml路径也对但训练日志里大量出现WARNING: image without labels甚至直接报找不到label目录。原因YOLO框架要求data.yaml里的train和val路径下必须有images和labels两个子目录且label要和图片同名同前缀。常见错误是把path指到最外层目录或者漏掉labels这一层框架扫描不到txt自然当作无标签图片处理。解决按3.1产出的目录结构逐层确认pill_mint_yolo/images/train和pill_mint_yolo/labels/train必须都存在。path用绝对路径train和val只写相对子目录名。写完后先跑一遍find . -name *.txt | wc -l确认标签数量对得上再启动训练。6. 把2759张的价值再挖一层增强参数与消融实验训练跑通只是开始。如果想拿这套数据做更严谨的方案验证下一步值得做两件事一套有节制的数据增强一组有对照的消融实验。6.1 增强参数怎么设缺陷检测里mosaic和mixup是性价比最高的两个增强。YOLOv8内置支持在训练命令里直接开yolo detect train \ datapill_mint_yolo/data.yaml \ modelyolov8n.pt \ epochs150 \ imgsz1280 \ batch8 \ mosaic1.0 \ mixup0.2 \ fliplr0.5 \ hsv_h0.015 \ hsv_s0.7 \ hsv_v0.4 \ projectruns/pill_mint \ namebase_n_1280_augmosaic1.0让每张训练图都由4张图拼接等于变相扩大了batch内的上下文多样性mixup0.2按比例混合两张图对裂纹这类边缘连续的目标有正则效果。hsv三行是颜色扰动模拟产线光照波动。imgsz升到1280后batch要降到8显存不够就继续减半不要硬撑。6.2 消融实验怎么排我一般固定epoch和data.yaml只动一个变量跑三组对比yolov8n at 640、yolov8n at 1280、yolov8s at 1280。记录mAP50、mAP50-95和推理耗时做一张小表实验组预期作用yolov8n 640流程基线yolov8n 1280分辨率对召回的影响yolov8s 1280模型容量对精度的影响对比结果出来后如果1280比640提升明显而s比n提升有限说明瓶颈在图像分辨率而不是模型容量反过来则说明模型容量不够。后续还可以把best.pt导出成ONNX跑一下TensorRT的部署链路yolo export modelbest.pt formatonnx opset12就能拿到推理用的onnx文件。从那以后我每次拿到别人的数据集都会强制走一遍“文件名对应检查、坐标合法性校验、固定seed划分、先跑n模型出基线”这条链路磨刀不误砍柴工。这套流程在药丸缺陷数据上适用换到其他VOCYOLO双格式数据同样成立。希望帮到你。本文还有配套的精品资源点击获取