ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

COCO JSON药片缺陷检测数据集转YOLOv8训练与调优指南

COCO JSON药片缺陷检测数据集转YOLOv8训练与调优指南 简介这是一套面向智能制造与医药质检场景的药片表面缺陷检测数据集适合目标检测、缺陷分类等深度学习项目的训练与算法验证。资源包含8625张真实拍摄的药片图片以及配套的COCO JSON格式标注文件可直接用于识别药品是否存在破损、缺角、污渍等完整性问题。压缩包内有1996张JPG图像、2个标注JSON及2个说明TXT整体大小约42.62MB文件组织简洁便于快速导入主流检测框架。该数据集当前已有746人学习下载适合计算机视觉初学者进行数据预处理实践也可供研究者在工业质检方向开展迁移学习与模型调优。通过JSON标签可灵活读取类别信息配合图像完成从数据加载到评估的全流程开发。1. 药片缺陷检测数据集能解决什么8625张COCO JSON标注图片先把落地场景看清拿到一个药片缺陷检测数据集最容易犯的错就是直接扔给训练脚本跑。标题里的信息其实很具体8625张图片、COCO JSON格式标注、识别药品是否有缺陷、是否完整。它面向的是药品产线外观质检也就是判断一片药是完好、裂片、缺角、污点、印字不全还是完整性有问题的场景。这类数据集通常能用来做三件事验证目标检测算法在药品小目标上的效果、做缺陷检测类别不均衡与增强实验、以及作为微调自己产线数据的起点。适合的人群是有一定图像处理基础、想跑通一个可复现缺陷检测方案的从业者。但别急着调参先按后面步骤把标注质量和类别表盘明白踏空第一个坑往往都在这里。2. 读懂COCO JSON标注的结构与质量先盘点类别、标注数量、框坐标再谈训练2.1 COCO JSON为什么适合做缺陷检测图片、标注、类别三层结构的分工COCO格式并不是某个缺陷检测框架独有的它是一套通用标注协议。顶层JSON里有五个常见字段info、licenses、images、annotations、categories。对训练有实际用的是后面三个。images保存每张图片的id、文件名、宽高categories保存类别id到类别名的映射annotations保存每一个标注框字段一般包括id、image_id、category_id、bbox、area有时还有segmentation和iscrowd。对药片缺陷检测来说bbox是最常用的标注形式四个值分别是左上角x、左上角y、框宽w、框高h单位是像素。segmentation在目标检测任务里通常可以忽略除非你要做实例分割。iscrowd在药片场景一般用不到因为药片通常独立摆放很少出现密集重叠目标。需要特别注意的是COCO JSON里的category_id不保证连续也不保证从0开始。如果直接拿它去当YOLO的类别索引轻则训练错乱重则类别错位但训练还能收敛等到部署时才发现推理结果张冠李戴。所以拿到数据集后第一件事不是训练而是把这个JSON拆开看结构。不要用文本编辑器硬翻大文件写几行脚本统计一下类别分布能省下后面大量排错时间。2.2 用json.load盘点标注文件图片总数、有标注图片数、类别映射一次核对先把JSON文件的整体规模盘出来。下面这段代码适合任何COCO格式的数据集不只是药片缺陷检测也适用于轴承缺陷、螺栓缺陷这类视觉质检数据。import json coco_path annotations/instances_pill.json with open(coco_path, r, encodingutf-8) as f: coco json.load(f) cat_id2name {c[id]: c[name] for c in coco[categories]} ann_count {} img_with_ann set() for ann in coco[annotations]: cid ann[category_id] ann_count[cid] ann_count.get(cid, 0) 1 img_with_ann.add(ann[image_id]) print(图片总数:, len(coco[images])) print(有标注图片数:, len(img_with_ann)) print(标注总条数:, len(coco[annotations])) print(类别对应关系:) for c in coco[categories]: cid c[id] print(f id{cid} name{c[name]} 标注数{ann_count.get(cid, 0)})json.load会一次性读入整个文件8625张图对应的标注文件一般不会太大内存压力可以忽略。但如果文件到了几百MB建议改用json.load配合分片读取或者直接转成内存数据库再处理。这段代码里最值得看的是“有标注图片数”和“图片总数”的差值。如果差得很多说明数据集里混入了大量没有缺陷的负样本图片。对缺陷检测来说这些负样本不是垃圾反而是抑制误报的关键数据。你要做的是确认它们在训练集、验证集里的比例而不是删除它们。2.3 可视化检查标注框缺陷框到底框住了什么边界是否贴住缺陷统计只能告诉你数量对不对不能告诉你边界准不准。药片缺陷检测对框的边界敏感度比一般目标检测更高。比如说“缺角”这一类框稍微往外扩一点模型就把正常药片的圆弧边缘学成了缺陷特征稍微往里缩一点模型又会漏掉轻微微缺角。下面这段可视化代码用OpenCV和Matplotlib把标注框直接画到原图上人工抽几张看看边界质量。import cv2 import matplotlib.pyplot as plt def show_annotations(coco, cat_id2name, k6): plt.figure(figsize(15, 8)) for i, img_info in enumerate(coco[images][:k]): img_path img_info[file_name] img cv2.imread(img_path) if img is None: print(读不到图片:, img_path) continue img cv2.cvtColor(img, cv2.COLOR_BGR2RGB) h, w img.shape[:2] for ann in coco[annotations]: if ann[image_id] ! img_info[id]: continue x, y, bw, bh ann[bbox] # 边界截断避免画框时数组越界 x1 int(min(max(x, 0), w - 1)) y1 int(min(max(y, 0), h - 1)) x2 int(min(max(x bw, 0), w - 1)) y2 int(min(max(y bh, 0), h - 1)) cv2.rectangle(img, (x1, y1), (x2, y2), (255, 0, 0), 2) plt.text(x1, max(0, y1 - 6), cat_id2name[ann[category_id]], fontsize10, colorred) plt.subplot(2, 3, i 1) plt.imshow(img) plt.axis(off) plt.show()bbox里的w和h是宽高不是右下角坐标。很多新手第一次画框会把img[y:yh, x:xw]写反这不影响读图但转训练格式时会引入一整套坐标错位问题。画框时我把坐标做了clip原因是药片紧贴图像边缘时标注软件容易生成负坐标或越界坐标这类框在转换阶段要单独处理而不是强行保留。我一般会在可视化阶段同时检查一张图上是否一个药片多个框、框内是否包含了多个缺陷、类别名和缺陷外观是否对应。如果一张图里有两颗药片其中一颗有缺陷标注框却把两颗药都包进去这类数据是后面模型“学歪”的主要来源建议直接记录下标清洗时剔除。3. 把COCO JSON转成训练格式按图片拆分、类别重映射、坐标换算三个步骤3.1 按image_id拆分训练集和验证集别按标注行拆分很多人在数据准备阶段翻车的第一个操作就是把整个JSON读进来之后直接对annotations列表做train_test_split。这样拆出来的训练集和验证集里同一张图片的标注会被拆到两边。比如一张图上有两颗药片一颗完整、一颗缺角完整那颗进了训练集缺角那颗进了验证集。模型在训练时其实已经见过这张图像内容验证指标会虚高等部署到新图片上再被打回原形。正确做法是只按image_id层级拆分把整张图片连同它所有标注放进同一侧。其次要兼顾“有缺陷”和“干净”两类图片在训练集、验证集里的比例。药片数据集里正常样本多、缺陷样本少是很常见的情况如果随机洗牌验证集里可能一个缺陷框都没有训练曲线看起来正常但模型已经在退化。import json import random random.seed(42) with open(annotations/instances_pill.json, r, encodingutf-8) as f: coco json.load(f) img_ids [im[id] for im in coco[images]] img_has_defect set() for ann in coco[annotations]: img_has_defect.add(ann[image_id]) defect_imgs [iid for iid in img_ids if iid in img_has_defect] clean_imgs [iid for iid in img_ids if iid not in img_has_defect] random.shuffle(defect_imgs) random.shuffle(clean_imgs) val_ratio 0.15 n_defect_val int(len(defect_imgs) * val_ratio) n_clean_val int(len(clean_imgs) * val_ratio) val_ids set(defect_imgs[:n_defect_val] clean_imgs[:n_clean_val]) train_ids set(img_ids) - val_ids print(训练图片数:, len(train_ids), 验证图片数:, len(val_ids))这段拆分逻辑只用了两个桶有缺陷和无缺陷。如果某个缺陷类别特别少比如“缺角”只有几十条标注还应该按类别再做一层分层抽样保证验证集里能覆盖到所有类别。实际项目中我通常会把“缺陷”桶拆成多个子桶每个子桶对应一个category_id再分别按比例抽到验证集。3.2 COCO的bbox转YOLO格式中心点、宽高归一化与越界框处理COCO和YOLO的框格式差异是新手最经常踩的坑。COCO是x, y, w, h左上角加宽高YOLO需要的是归一化的cx, cy, bw, bh也就是中心点横坐标、纵坐标、宽、高四个值都除以图片宽高。这里有两层容易错一是忘记归一化二是把x直接当成了中心点。另外一个容易被忽略的点是越界框。药片在图像边缘时标注框可能超出图像范围转换时如果不处理训练时损失函数会算到一个负面积框上轻则这张图loss异常高重则整个batch被污染。import json import os def coco_to_yolo(coco_path, out_dir, img_id_set, cat_id2idx): os.makedirs(out_dir, exist_okTrue) with open(coco_path, r, encodingutf-8) as f: coco json.load(f) img_id2info {im[id]: im for im in coco[images]} written 0 for img_id in img_id_set: img_info img_id2info[img_id] img_w, img_h img_info[width], img_info[height] if img_w 0 or img_h 0: continue lines [] for ann in coco[annotations]: if ann[image_id] ! img_id: continue x, y, w, h ann[bbox] # 把越界坐标收缩回图像边界 x_min max(0.0, x) y_min max(0.0, y) x_max min(img_w, x w) y_max min(img_h, y h) if x_max - x_min 2 or y_max - y_min 2: continue bw (x_max - x_min) / img_w bh (y_max - y_min) / img_h cx x_min bw * img_w / 2.0 cy y_min bh * img_h / 2.0 lines.append( f{cat_id2idx[ann[category_id]]} f{cx / img_w:.6f} {cy / img_h:.6f} f{bw:.6f} {bh:.6f} ) if lines: stem os.path.splitext(os.path.basename(img_info[file_name]))[0] with open(os.path.join(out_dir, stem .txt), w, encodingutf-8) as f: f.write(\n.join(lines)) written 1 print(写出标签文件数:, written)这段代码里前半段是越界修正后半段是坐标归一化。越界修正的逻辑是把最小边收回到0最大边收回到图像宽高然后再算中心点和宽高。小于2像素的框直接丢弃因为2像素以下在药片缺陷场景里几乎不存在有效特征留着只会给损失函数增加噪声。类别重映射也在这一步完成。cat_id2idx需要在外部构建我一般会先对categories按id排序再生成从0开始的连续索引避免标注JSON里id不连续导致的问题。3.3 用OpenCV做图片体检损坏文件、灰度图、方向问题一次查清转换完标签之后还需要对图片本身做一轮体检。药片数据集来源多样有的是工业相机拍摄有的是手机补拍图片格式和通道数往往不一致。最常见的问题有三个彩色图被存成了单通道灰度图、某些图片文件头损坏读不出来、EXIF旋转信息存在但数据没转正。下面的脚本用来扫描整个图片目录把读不到和尺寸异常的图列出来。import cv2 from pathlib import Path img_dir Path(images) for p in sorted(img_dir.glob(*)): img cv2.imread(str(p), cv2.IMREAD_UNCHANGED) if img is None: print(无法读取:, p.name) continue if img.ndim 2: print(灰度图:, p.name) if img.shape[0] 10 or img.shape[1] 10: print(尺寸异常:, p.name, img.shape)IMREAD_UNCHANGED会把alpha通道也读进来所以能识别出带透明通道的PNG。如果是灰度图训练时统一转成三通道否则模型在推理阶段对输入通道数会报错。EXIF旋转问题在工业相机里少见但如果是手机拍摄的补充数据就必须先用cv2.rotate按方向把图转正同时同步改标注框坐标。这类体检脚本看起来简单但在项目管理上很有用。它能生成一份黑名单让你决定哪些图片进入训练、哪些只做验证而不是在训练中途因为一张坏图导致整个数据管线中断。4. 用YOLOv8训练自己的药片缺陷数据集从YAML配置到关键参数4.1 为什么优先跑YOLO系列而不是从OpenCV形态学开始药片缺陷检测有两种技术路线。传统OpenCV方案通常是灰度化、二值化、找轮廓、算面积和圆度对“缺角”这种轮廓明显的问题有效但遇到药片表面纹理复杂、光照不均、药粉颜色接近背景的情况阈值参数要反复调整往往同一个产线白天和晚上光照稍微一变就要重新调参。COCO JSON标注的价值就在于它能训练监督模型。YOLOv8是目前把这类数据集跑通成本最低的框架原因在于它同时解决定位和分类输出的是缺陷框而不是整图的分数。这不只是“能不能用”的问题而是“能不能交付给产线用”的问题。产线需要知道缺陷在哪个位置、具体是哪类缺陷YOLO的检测框直接给出结果。如果你更偏好无监督异常检测方案DINOMALY这类方法也能在2D缺陷检测上给出参考指标但它需要干净参考图像集合并且最终仍要人工选择异常分数阈值很难直接交付一套带坐标的结果。有标注数据的情况下先跑通YOLOv8作为baseline是所有后续优化的前提。4.2 整理目录结构与YOLO的YAML配置训练前的最后一道工序YOLOv8训练时要求图片和标签分开存放训练集、验证集各自独立目录。目录结构一般是这样data_pill/ ├── images/ │ ├── train/ │ ├── val/ ├── labels/ │ ├── train/ │ ├── val/ └── pill.yaml图片目录和标签目录必须同名YOLO通过图片文件路径把标签文件名的jpg后缀直接替换成txt来找标签。如果目录名不一致训练不会报错但每个batch都是空标签损失曲线会一路跌到接近0最后模型什么都检测不出来。pill.yaml里的类别名要和转换标签时的cat_id2idx完全一致。# pill.yaml path: ./data_pill train: images/train val: images/val names: 0: intact 1: broken 2: chipped 3: stain 4: print_defect类别名是字符串随便写都可以但建议用英文单词避免中文字符在部分环境下编码报错。类别顺序一旦确定就不要改否则之前转换好的标签全部作废。4.3 训练参数怎么设先从yolov8n跑基线再看mAP之外的指标最小可用训练流程很简单把预训练权重加载进来用迁移学习在药片数据集上微调。from ultralytics import YOLO model YOLO(yolov8n.pt) # 用官方预训练权重做初始化不是从头训练 model.train( datapill.yaml, epochs120, imgsz640, batch16, lr00.01, lrf0.01, patience20, cacheTrue, workers4, )imgsz我一般从640起步。药片缺陷框在整幅图里占比往往很小比如缺角只占几十个像素如果分辨率压到320这类小目标会直接消失。显存足够的话可以试960但训练时间几乎翻倍收益不一定划算。lr0用0.01是从预训练权重微调的常见起点如果发现loss震荡降到0.005再试。patience20表示20个epoch没有改善就早停药片数据集类别少、背景单一通常不会真的跑满120轮。训练完不要只看mAP50。药片质检的真实成本不均衡漏掉一个缺陷药片可能整批报废误报一个完好药片只是增加人工复检。mAP50-95对框位置更敏感而产线上通常更需要知道某个置信度阈值下漏检率和误报率是多少。YOLO的model.val()输出里除了mAP50和mAP50-95还有precision和recall两个核心值它们才是决定能不能上线的主要指标。from ultralytics import YOLO model YOLO(runs/detect/train/weights/best.pt) metrics model.val(datapill.yaml, splitval, conf0.25, iou0.5) print(mAP50:, metrics.box.map50) print(mAP50-95:, metrics.box.map) print(precision:, metrics.box.mp) print(recall:, metrics.box.mr)如果recall明显偏低说明模型漏检很多缺陷框先不要调阈值回到数据层面看是哪类缺陷漏检。YOLO默认在验证集上会生成混淆矩阵图如果某个缺陷类别被大量预测成另一类通常是标注边界贴得过松导致两个类别的特征重叠。5. 药片缺陷检测数据集的避坑记录现象、原因、解决5.1 类别ID映射错位模型把裂片识别成完好药片现象训练过程loss正常下降验证mAP也在涨但推理时输出类别和标注完全对不上。比如“裂片”标签被模型预测成“完好”或者所有缺陷都被预测成同一个类别。原因COCO JSON里的category_id不保证连续可能是1、3、5也可能中间缺号。转换脚本如果用category_id - 1作为类别索引索引值就会整体错位。更隐蔽的是用enumerate(coco[categories])生成索引因为categories列表的顺序不一定和id一致。解决转换前先打印categories列表明确id和name的对应关系再构建cat_id2idx映射。我固定用下面这行代码生成映射从0开始连续编号保证和YAML里的names对齐。cat_id2idx {c[id]: idx for idx, c in enumerate(sorted(coco[categories], keylambda c: c[id]))}5.2 标注框越界或坐标为负训练loss突然变高可视化画框报错现象转换标签时没有报错但训练到一半某个epoch的loss突然跳高或者用OpenCV画可视化图时提示coordinates out of range。原因药片贴近图像边界时标注工具可能生成x或y为负数的框也可能x w超出图片宽度。这类框在计算损失时会包含无效区域影响梯度。解决在转换脚本里对坐标做边界截断也就是把起点限制在0以上终点限制在图像宽高以内小于2像素的直接丢弃。可视化脚本也要同步处理否则你看到的框和模型学到的框不是同一个版本。5.3 缺陷样本与正常样本极端不平衡模型学会“全输出正常”现象训练出来的模型把绝大多数药片都判定为完好只有极明显的缺角才被检出轻微污点全部漏检。原因药片数据集中正常样本占绝对多数缺陷样本可能只有几百条标注。模型发现全部输出负样本也能拿到很高的准确率导致收敛到一个“什么都不检测”的局部最优。解决先分层拆分确保训练集和验证集里都有充足缺陷样本。再考虑对少数类做重采样或者在训练参数里提高cls_loss权重。更实用的做法是按缺陷类别分别看precision和recall找出哪个类别的召回率低于0.5单独补数据而不是盲目加总epoch数。5.4 按标注行拆分数据集验证集指标虚高部署后效果断崖现象本地验证mAP50能到0.95模型看起来很好部署到产线新图片后检出率大幅下降。原因如果数据集拆分时直接对annotations列表做随机切分同一张图片的标注会被分到训练集和验证集。训练时模型已经见过验证集里的图像内容等于开卷考试验证指标自然虚高。解决只按image_id级别拆分然后用下面这段检查脚本确认训练集和验证集没有图片交集。这一步应该写进数据管线的自动化检查里不是手动跑一次就结束。if train_ids val_ids: raise ValueError(训练集和验证集存在图片重叠请重新拆分)5.5 增强手段破坏缺陷语义翻转和亮度调整让模型学到伪缺陷现象加了增强后模型的误报明显增加把药片上的正常纹理、印字边缘识别成缺陷。原因药片缺陷有方向性。印字缺陷翻转后虽然还是印字但“缺角”在旋转180度后位置变化很大随机亮度调整可能把药片污渍的对比度拉没也可能把正常药片的阴影拉成伪缺陷。马赛克增强在目标检测里很常用但对药片缺陷不一定适用因为它会把不同药片拼接在一起缺陷边界和被遮挡部分的语义全乱掉。解决增强方案按数据特点裁剪。我会先关掉mosaic只保留hsv_h、hsv_s、hsv_v的小幅扰动、轻微仿射变换和scale抖动。如果确实需要mosaic只在训练后期打开并且设置为小概率触发。验证集上不要加任何增强保证评估指标反映真实分布。6. 进阶用PR曲线校准判定阈值减少产线漏检和误停线6.1 别用默认0.25的置信度阈值让PR曲线替你做决定YOLO推理默认用conf0.25作为阈值也就是置信度高于0.25的框才输出。但这个默认值是从通用目标检测场景来的药片质检的成本结构不一样漏掉一个缺陷药片可能整批退货误报一版完好药片只是多花人工复检。到底该卡0.2还是0.5要看验证集上的PR曲线。PR曲线本质上是在不同阈值下画precision和recall的取舍。阈值调高precision升高、recall下降误报少但漏检变多阈值调低recall升高、precision下降漏检少但误报变多。药片产线上我希望优先保证漏检率不超过某个上限再在这个条件下尽可能压低误报。最省事的做法是让YOLO在验证集上输出PR曲线图训练的results目录下一般会生成PR_curve.png和confusion_matrix.png。但PR图是按所有类聚合的对药片缺陷检测来说不够细。我一般会单独对一个类别画曲线尤其是那个漏检最多、对产线影响最大的缺陷类别。from ultralytics import YOLO model YOLO(runs/detect/train/weights/best.pt) val_results model.val(datapill.yaml, splitval, conf0.05, plotsTrue)把conf设成0.05的目的是让模型先输出所有低置信度的候选框不要提前截断。虽然这里跑出来的指标会很低但PR曲线才是完整的。然后根据曲线找一个目标点如果要求recall不低于0.95就看recall等于0.95时对应哪个precision、哪个阈值用这个阈值去做推理。推理时把conf参数改成这个值即可。产线数据分布如果和数据集差别大比如相机的打光角度变了阈值也要重新校准不是一次调完就永远固定。我见过不少项目模型在实验环境指标漂亮上线后误报多到调试员直接关掉系统最后发现只是阈值没跟着产线光照条件重选。看PR曲线定阈值就是一种低成本且可复现的校准方式单次推理几乎不增加耗时。这几年做视觉质检下来我最后悔的事就是把模型训练和产线判定当成两个割裂阶段。模型输出的置信度不是概率也不能直接当置信度用它只有和你的漏检成本、复检成本放在一起看才有意义。先跑一个baseline再画PR曲线选阈值最后回到数据层补漏检集中的类别这比追求高一个点的mAP有用得多。希望帮到你。本文还有配套的精品资源点击获取
返回列表