药丸缺陷检测数据集2759张VOC+YOLO双格式训练实战指南
简介药丸表面缺陷检测数据集面向工业质检与深度学习目标检测任务适合算法工程师、研究人员用于药丸外观缺陷识别模型的训练与算法验证。数据涵盖污染(contamination)、裂纹(crack)、合格(good)三类共2759张图像样本标注框总数2798个三类框数分别为904、960、934类别分布相对均衡可直接用于目标检测模型的训练与评估也可作为医药包装质量检测、外观分选等项目的基准数据。压缩包大小约411.78MB包含2000个文件主要类型为Pascal VOC格式的xml标注与YOLO格式的txt标注xml便于阅读与转换txt则能直接接入YOLO系列训练流程可灵活适配YOLOv5、Darknet、SSD等主流框架免去格式转换环节。所有标注由labelImg工具按矩形框规则绘制图片与标注一一对应标注规范和边界框定位清晰。目前已有218人学习下载是药丸外观质量检测场景中可直接投入数据预处理阶段的规范数据集。1. 药丸缺陷检测数据集2759张VOCYOLO双标注图3类标签能直接开训吗直接给结论能但别急着把压缩包丢给训练脚本。药丸的缺陷检测数据集VOCYOLO格式2759张3类别.7z这个文件名里已包含最关键的三个信息——2759张真实药丸图片、3个缺陷类别、VOC和YOLO两套标注同时交付。对制药产线视觉质检这件事来说最贵的从来不是模型而是标数据的时间。这个包把“数据双格式标注”一次给齐省掉的实际上是从零开始的半天功夫。适合刚拿到检测需求、手上只有一批无标注图片的工程师也适合想把YOLO快速接入现有视觉系统、又不想从零抠数据格式的团队。但“能用”和“好用”是两回事——数据怎么划分、类别语义怎么理解、训练参数怎么给都会影响最终模型能不能落地到产线上。2. 先拆压缩包VOC与YOLO双格式的文件结构和标注逻辑做缺陷检测数据集的第一步永远不是写训练命令而是把目录结构和标注格式看清楚。标注格式决定了后续脚本怎么写也决定了踩坑的位置在哪里。我接手过的很多检测项目里数据本身没问题翻车往往发生在“没搞懂标注文件怎么组织”这一步。2.1 解压后常见的目录布局JPEGImages、Annotations与labels怎么对应VOC格式的标准落地方式一般长这样三个目录各司其职pill_dataset/ ├── JPEGImages/ # 原始图片jpg格式 │ ├── pill_0001.jpg │ ├── pill_0002.jpg │ └── ... ├── Annotations/ # VOC标注xml格式与图片同名 │ ├── pill_0001.xml │ ├── pill_0002.xml │ └── ... └── ImageSets/ └── Main/ ├── train.txt # 训练集图片名前缀每行一个 └── val.txt # 验证集图片名前缀YOLO格式则是另一套组织方式图片和标签分离pill_dataset/ ├── images/ │ ├── train/ │ ├── val/ │ └── ... └── labels/ ├── train/ ├── val/ └── ...两套格式里最核心的约定是图片文件和标注文件必须同名只是后缀不同。YOLO读取时会通过替换扩展名的方式去找txt所以pill_0001.jpg对应pill_0001.xml和pill_0001.txt这三个文件在各自目录里的前缀名必须一字不差。常见的翻车点包括文件名里有空格、大小写不一致、或者txt内容为空导致yolo训练时报“image without labels”的警告。这个警告不会让训练停止但会在val阶段静默拉低mAP等你排查的时候已经浪费了十几个epoch。我拿到这类压缩包后的习惯是先写一条命令扫一遍文件对应关系不要把信任交给压缩包内部的目录结构宁可多花两分钟验证。用python遍历一下JPEGImages和Annotations找出前缀名对不上的文件比训练到一半再发现缺标注要便宜得多。2.2 三类别标注的语义边界破损、污渍和正常怎么定标题里写了3类别具体命名要以包内标注为准但药丸缺陷检测领域最常见的三分类布局是正常药丸normal、破损药丸broken、表面污渍stain。破损类通常包含缺角、碎裂、压扁变形等物理损伤污渍类则指表面有斑点、颜色异常或异物附着。这里最需要警惕的是“语义边界”问题——同一颗药丸如果同时有破损和污渍标注员到底该标哪个框常规做法是各标各的框允许同图多框也允许一框多标签但YOLO格式的txt每行只能写一个类别所以一框多标签在YOLO里做不了。实际处理时只能选一个主要缺陷类别或者把“同时损坏”单独设一个类别。这个边界看着简单实际在训练阶段影响很大。如果数据里defect和stain的边界划得不清val时模型就会把轻度破损判成污渍混淆矩阵里两个类之间总有交叉。我接手过类似项目最后回头重标了三分之一的数据才把两个类别的mAP分开。所以拿到数据集后第一件事是抽检50张图的标注肉眼看一下各类别的框是不是都打在合理位置、有没有明显的错标混标。花20分钟做这件事后面省得掉一整天的排错时间。2.3 2759张怎么切分train/val划分策略与类别不平衡陷阱2759张图常规按90/10切分train约2400多张val约270张。但如果只是把图片随机打乱切两份很容易踩中数据泄漏的坑——同一颗药丸在传送带不同位置拍了多张图随机切分会把同一目标同时放进训练集和验证集val的mAP看起来很高一到真实场景就现原形。正确的做法是先找数据里有没有“样本分组”的概念再决定按什么维度切。常见做法有两种如果每张图之间是独立的不同药丸、不同批次直接随机切分问题不大如果同一物体有多视角多帧就必须按物体ID分组切分。import os import random from collections import defaultdict # 假设文件名格式为 pill_001_A.jpg / pill_001_B.jpgpill_001 是药丸ID img_names [f for f in os.listdir(JPEGImages) if f.endswith(.jpg)] id_to_imgs defaultdict(list) for f in img_names: pill_id f.rsplit(_, 1)[0] # 取最后一个下划线前的部分 id_to_imgs[pill_id].append(f) ids list(id_to_imgs.keys()) random.seed(42) random.shuffle(ids) val_ids set(ids[:int(len(ids) * 0.1)]) os.makedirs(ImageSets/Main, exist_okTrue) with open(ImageSets/Main/train.txt, w) as ft, \ open(ImageSets/Main/val.txt, w) as fv: for pid, fs in id_to_imgs.items(): target fv if pid in val_ids else ft for f in fs: target.write(f.rsplit(., 1)[0] \n)这段代码的核心逻辑是先按药丸ID把图片聚组再按ID切分保证同一药丸的所有视角图只进train或只进val。random.seed(42)是为了让每次切分结果一致后续如果发现val效果诡异想回滚还能复现当时的划分。切分之后还要看一眼类别分布。如果train里normal占八成、stain只占一成训练时模型会严重偏向normal类val的recall在defect类上会低得可怜。如果发现这种情况别急着调损失函数先在目录层面把每个类的图片数量拉平一遍或者用后文提到的过采样。3. 把VOC转成YOLO格式转换脚本与四个必须调对的参数虽然标题里说同时给了VOC和YOLO两种格式但实际工作中你拿到的数据往往只验证了一个标准或者你想要训练的框架只认其中一种格式。自己写一个VOC转YOLO的转换脚本是最稳的路既确认了标注内容又能灵活处理边界情况。3.1 VOC XML里的关键字段size和bndboxVOC标注文件是XML结构一个典型的药丸标注长这样annotation folderJPEGImages/folder filenamepill_0001.jpg/filename size width1920/width height1080/height depth3/depth /size object namebroken/name bndbox xmin562/xmin ymin213/ymin xmax617/xmax ymax268/ymax /bndbox /object object namenormal/name bndbox xmin980/xmin ymin240/ymin xmax1035/xmax ymax295/ymax /bndbox /object /annotationsize标签里的width和height是整张原始图的分辨率转换时必须以这个尺寸为分母而不是以训练时的imgsz为分母。bndbox里的四个值是像素坐标xmin和ymin是左上角xmax和ymax是右下角全部是整数。注意XML解析时读出来的是字符串要做一次float转换。有个容易忽略的细节标注坐标有可能超出图像边界。比如标注工具里手滑把框拉出去了xmax比width还大。这种情况转成YOLO坐标后会出现大于1的值训练框架一般会报坐标越界警告严重会导致损失不收敛。所以转换脚本里必须做边界裁剪。3.2 YOLO txt的归一化坐标公式center_x、center_y、w、h怎么算YOLO格式每行一个目标五个值分别是类别ID、中心点x、中心点y、宽度w、高度h全部做归一化center_x (xmin xmax) / 2 / img_widthcenter_y (ymin ymax) / 2 / img_heightwidth (xmax - xmin) / img_widthheight (ymax - ymin) / img_height这个公式本身不难但有两个细节坑。第一除法必须用浮点数python里用浮点除法没问题但如果你从XML里拿到的是整数字符串直接除也会自动转float这不影响第二归一化之后要clip到[0, 1]区间否则训练时会因为标签越界报错。3.3 转换脚本把XML批量变成txt下面这个脚本可以直接用处理的是VOC转YOLO的标准流程import xml.etree.ElementTree as ET import os from glob import glob # 类别名到ID的映射顺序必须和数据yaml里的names一致 CLASS_MAP {normal: 0, broken: 1, stain: 2} def convert_voc_to_yolo(xml_path, txt_path): tree ET.parse(xml_path) root tree.getroot() size root.find(size) img_w int(size.find(width).text) img_h int(size.find(height).text) lines [] for obj in root.iter(object): name obj.find(name).text if name not in CLASS_MAP: print(f[warn] {xml_path} 包含未知类别: {name}) continue cls_id CLASS_MAP[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.0) / img_w cy ((ymin ymax) / 2.0) / img_h w (xmax - xmin) / img_w h (ymax - ymin) / img_h # 避免标注越界导致的训练异常 cx min(max(cx, 0.0), 1.0) cy min(max(cy, 0.0), 1.0) w min(max(w, 0.0), 1.0) h min(max(h, 0.0), 1.0) lines.append(f{cls_id} {cx:.6f} {cy:.6f} {w:.6f} {h:.6f}) with open(txt_path, w, encodingutf-8) as f: f.write(\n.join(lines)) xml_dir Annotations txt_dir YOLOLabels os.makedirs(txt_dir, exist_okTrue) for xml_file in glob(os.path.join(xml_dir, *.xml)): base os.path.splitext(os.path.basename(xml_file))[0] convert_voc_to_yolo(xml_file, os.path.join(txt_dir, base .txt))脚本逻辑分四步解析XML、读取图像尺寸、遍历object标签取坐标、写归一化结果。CLASS_MAP是全局唯一的“类名到ID”映射表改类别顺序会影响模型输出含义所以务必和后续训练的yaml文件对齐。裁剪参数w和h这里也做了clip虽然会轻微改变目标框大小但至少不会让训练崩。如果不想改框尺寸更精细的边界裁剪逻辑应该只裁剪超出图像范围的部分。3.4 转换后三件事查空txt、渲染检查、核对类别ID转换完成不代表数据能用我一般会做三件验证。第一统计空txt的数量——如果某个XML里所有类别都不在CLASS_MAP里转换出的txt就是空的对应的图片在训练里会被跳过。第二用OpenCV把txt标注画回图片上随机抽20张肉眼检查框的位置和类别对不对。第三核对类别ID和yaml names的对应关系。import cv2 import os img_dir JPEGImages label_dir YOLOLabels out_dir CheckImages os.makedirs(out_dir, exist_okTrue) for label_name in os.listdir(label_dir): if not label_name.endswith(.txt): continue base label_name[:-4] img_path os.path.join(img_dir, base .jpg) img cv2.imread(img_path) if img is None: continue h, w img.shape[:2] with open(os.path.join(label_dir, label_name), r) as f: for line in f: parts line.strip().split() if len(parts) ! 5: continue cls_id, cx, cy, bw, bh parts cls_id int(cls_id) cx, cy, bw, bh float(cx), float(cy), float(bw), float(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) cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.putText(img, str(cls_id), (x1, y1 - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 255), 2) cv2.imwrite(os.path.join(out_dir, base .jpg), img)渲染检查最能暴露问题。如果框全部整体性偏移大概率是归一化公式除错了如果某个类别ID的颜色信息对不上就是CLASS_MAP和yaml没对齐。校验通过之后再进训练流程基本能杜绝“数据格式错误导致训练出烂模型”的低级翻车。4. 用YOLO训练药丸缺陷检测数据配置文件、命令行与超参数设定转换好格式后训练阶段就进入了YOLO生态。YOLO系列有很多开源实现常见的是ultralytics的yolov8和yolov5。两者的训练配置大同小异下面以yolov8为例讲一套能直接跑通的流程。4.1 数据yaml怎么配path、train、val、nc、names数据配置文件是整个训练流程里的“地图”路径配错整个训练直接崩。最小可用的pill.yaml长这样# pill.yaml path: /home/user/pill_dataset train: images/train val: images/val nc: 3 names: 0: normal 1: broken 2: stainpath是数据集根目录的绝对路径train和val是相对于path的路径不能用绝对路径覆盖。names的索引顺序必须和之前转换脚本里的CLASS_MAP一致——如果CLASS_MAP里broken是1这里names在索引1是broken两者一旦错位模型学到的类别就是错的推理时输出标签全部混乱。如果压缩包里已经给好了现成的yaml打开盯一眼names顺序就行。如果给的yaml里用的是VOC的class_list字段记得改成YOLO格式的names否则yolo命令会不认识这个字段。4.2 使用预训练权重还是从零训练药丸场景选哪个缺陷检测数据集通常规模不大2759张在深度学习里算偏小的量级。从零训练一个YOLO模型在这种数据量下很容易过拟合收敛也慢。常规做法是直接下载官方预训练权重yolov8n.pt或yolov8s.pt用COCO上预训练过的模型做迁移学习。虽然COCO里没有药丸这个类别但低层的边缘、纹理、颜色特征是可以迁移的模型不需要重新学“什么是轮廓”只需要学“怎样的一组轮廓是药丸”训练epoch数能省一半。我说一下选择模型规模的逻辑yolov8n最快最省显存适合快速验证yolov8s精度略有提升但显存占用和推理耗时会涨。如果是初期跑通流程先上n验证数据没问题后再换s。不要一上来就用yolov8x2759张图的训练集喂不饱大模型结果往往不如小模型。4.3 训练命令与关键超参数imgsz、batch、epochs不是越大越好数据集和yaml就绪后训练命令本身很简短yolo detect train \ datapill.yaml \ modelyolov8s.pt \ epochs120 \ imgsz640 \ batch16 \ device0 \ patience30 \ namepill_run每个参数值得单独掰扯一下。imgsz是输入分辨率。药丸是小目标640一般是起步。如果你用V100这类显存充裕的卡或者供电行业的红外检测、硅片表面缺陷这类小目标缺陷场景调到960或1280会有明显提升但显存占用是平方级上涨。batch默认是16但以我的经验batch在小数据集上不是越大越好——batch太大会让梯度方向过于平滑小数据集下收敛反而慢batch太小又会梯度噪声大。常规区间是8到32按显存余量定。epochs设在120左右但配合patience30连续30轮验证集mAP不涨就自动早停避免无效训练浪费时间。还有一点yolo训练时默认会做数据增强包括随机翻转、旋转、缩放等。药丸本身是圆形旋转增强问题不大但如果药丸表面有刻字或凹槽水平翻转会让文字左右颠倒模型可能学到错误的方向特征。如果发现这类问题可以在数据增强参数里关掉flipud和fliplr后面进阶章节细说。4.4 训练日志解读只看loss和mAP之外还要看什么训练结束后日志里会输出一堆指标很多人只看mAP50就结束。但对缺陷检测来说三个指标比mAP50更重要。第一train/obj_loss和val/obj_loss的差距train_loss持续下降而val_loss回升说明过拟合这时该回退epoch或加强数据增强。第二per-class的recallnormal类recall高是正常的但如果stain类recall低于70%大概率是类别不平衡或标注边界不够清晰导致模型没分开两类。第三混淆矩阵里的FP分布看broken误判成normal的比例是不是过高——如果过高不是模型问题是标注本身就偏了。训练输出目录里会同时生成last.pt和best.pt。best.pt是验证集表现最好的权重last.pt是最后一个epoch的权重。产线部署默认用best.pt但要注意best.pt和last.pt的mAP差距如果特别大说明训练末期过拟合严重best.pt可能在真实场景反而更差。用的时候两个都拿真实视频测一遍再决定。5. 药丸缺陷检测的常见问题排查5个高频坑与对应解法数据格式没问题、训练流程没跑错模型效果还是不行这类问题是缺陷检测项目里最磨人的。下面这5个坑是我在药丸、胶囊和类似小物件检测项目里反复撞过的每条都按照现象、原因、解决三个层次说清。5.1 类别严重不平衡正常样本占八成的后果现象训练时loss稳定下降validation的mAP50看着有0.9但打开每一类的precision和recallbroken和stain的recall只有0.5左右。产线跑起来缺陷药丸漏检严重。原因2759张图里normal占了80%以上broken和stain加在一起不到20%。YOLO默认按均匀类别设计损失函数多数类主导了梯度方向少数类的特征学不充分。解决优先做数据层面的调整。对少数类做离线过采样——复制broken和stain样本到训练目录让三类数量大致接近1:1:1。不要指望yolo的cls_loss自动加权改动损失函数需要改源码而且容易引发其他问题。如果过采样不够配合在线数据增强里的复制粘贴copy-paste把切割出的缺陷药丸粘贴到正常图里生成更多合成缺陷样本。5.2 小目标药丸被漏检anchor与图像分辨率现象正常尺寸的药丸能检到但视野边缘的、占比非常小的药丸检不到批次里size偏小的药丸整颗漏检。原因产线相机分辨率高但目标在整张图里占比可能只有几十个像素。yolov8默认anchor的尺寸是从COCO数据集统计出来的COCO里的目标普遍比较大药丸这种小目标落在anchor的覆盖盲区。另外imgsz640会把原图缩小小目标进一步损失细节。解决先检查标注框的分布——统计所有框的宽度高度中位数。如果大多数框的尺寸小于32x32就把imgsz调到960或1280显存不够就降batch或者用yolov8n把分辨率撑上去。另外可以试一下把所有anchor重新聚类到药丸的尺寸分布上yolo框架支持自定义anchors参数把默认anchor换成聚类结果。5.3 标注边界偏差框偏了几个像素带来的mAP波动现象训练损失降到0.05就不再下降val的mAP50在0.85附近震荡不上去。抽查渲染结果发现某些框边缘明显没贴合药丸轮廓有的框框进去了一半背景。原因标注员在打框时对“紧贴轮廓”的标准不一致有的打得松、有的打得紧。边框不精确等于给模型输入了噪声标签模型不知道该学到多精确的边界。解决如果偏差在几个像素之内其实可以接受YOLO的回归头本身对标签噪声有一定鲁棒性。如果偏差超过目标尺寸的10%就需要重新标注或清洗。我习惯写一个脚本统计每个标注框的面积和宽高比把明显异常值打印出来人工复核。另外推荐在训练时使用yolo的scale参数调小比例减少数据增强对框的随机缩放干扰。5.4 数据泄漏同一药丸多张视角图被随机切分现象train和val的mAP都很高模型在自己测试视频上表现也很棒但一到客户产线的实拍图就漏检。这个落差会让你怀疑模型是不是只在记忆图像。原因数据采集时同一颗药丸可能从不同角度、不同光照条件下拍了多张图随机切分时这些图一部分进了train、一部分进了val。验证集里有很多和训练图“长得几乎一样”的样本模型在val上看似泛化良好实际是在作弊。解决回到2.3节按药丸ID或采集批次分组切分。压缩包里如果不提供ID信息就按文件名中的拍摄序列号或用图像相似度聚类来分组。相同样本两帧之间的相似度超过一定阈值就归到同一组。这个坑最隐蔽一旦踩中前面的调参等于白做。5.5 7z解压与中文路径文件和cv2.imread都读不到现象Linux服务器上解压后目录名或文件名里有中文yolo训练时一直报图像读取失败或者OpenCV的cv2.imread返回None。Windows上解压出来的文件名出现乱码。原因7z压缩包内部保存文件名用的是原始编码部分Windows解压工具默认按GBK解Linux按UTF-8解两种环境交叉解压就会出问题。文件名乱码后图片和标注txt的前缀对不上训练自然失败。解决Linux推荐用p7zip解压命令行执行7z x pill.7z -so | 7z x -si需要额外处理最简单的是直接7z x pill.7z解压后立刻在Python里用os.listdir检查文件名是否可读。整个项目路径改成纯英文。Windows下用7-Zip解压时在选项里把“使用原始文件名编码”勾上避免乱码。如果已经乱码先用convmv或者Python的os.rename批量改回ASCII文件名再开始转换脚本。6. 用2759张练出稳模型的三个实操技巧数据集规模不大但把下面三个技巧用上输出的模型在产线上会更稳。6.1 数据增强别乱开药丸场景该改动哪些默认参数YOLO默认的增强会做随机水平翻转和上下翻转。圆形药丸翻转没有影响但如果表面有刻字翻转后语义就反了。我的做法是在训练参数里显式关掉这两个翻转增强只保留mosaic、旋转、缩放和HSV色彩扰动。HSV的饱和度扰动可以开小一点因为药丸颜色是稳定的质检标准颜色扰动过大会让模型对色差过敏。yolo detect train datapill.yaml modelyolov8s.pt \ epochs120 imgsz640 batch16 \ flipud0.0 fliplr0.0 \ hsv_s0.2 hsv_v0.2 \ mosaic0.8mosaic保留0.8即可如果数据集小就开满1.0mixup在缺陷检测场景里收益一般不用刻意开。6.2 用混淆矩阵而不是单看mAP50判断缺陷类型混没混训练结束后的val阶段会生成混淆矩阵图关注normal被误判成broken的占比以及stain和broken之间的互相混淆。这两个方向的混判在药丸质检里性质完全不同——normal误判成broken产线会把好药丸当废品浪费的是产值broken误判成normal废品流进下一道工序问题更严重。如果混淆矩阵里第二类错误明显偏高就不要急着部署回到标注阶段把语义边界重新理一遍。6.3 导出与验证pt到engine的量化精度对比部署到产线时通常走TensorRT加速。yolo官方提供export命令导出前先在验证集上跑一遍pt模型的mAP导出后再用同样的验证集跑一遍engine版对比两个mAP的差值。这个差值在1%以内是正常的如果超过2%说明量化精度损失偏大退回FP16或者改用动态shape。另外导出后一定要用真实产线视频测试延迟不要把GPU利用率当推理时间。以前我在一个胶囊缺陷项目上吃了不少亏——训练时只看mAP不管混淆矩阵部署后才发现broken和stain混判严重产线返工了一整批。从那以后养成了习惯每轮训练结束先拉混淆矩阵再决定要不要test。这次2759张的药丸数据集只要在切分、转换、训练三个环节守住边界走上线基本能少走一半弯路希望帮到你。本文还有配套的精品资源点击获取
上一篇/下一篇内容由系统自动关联
返回资讯列表 →