ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

COCO JSON转YOLOv8实战:煤矸石小样本目标检测训练全流程

COCO JSON转YOLOv8实战:煤矸石小样本目标检测训练全流程 简介面向煤炭行业智能分选与矿山场景目标检测该数据集提供识别煤炭、煤矸石、高岭石的COCO格式标注样本适合需要训练或评测目标检测模型的开发者、研究者和学生使用。包内共105个文件包括102张现场采集原始jpg图片、1份COCO JSON标注文件及2个txt辅助文件压缩包仅2.27MB轻量易用。目前已有426人学习下载。数据集免去手动标注成本标注格式可直接被YOLO、MMDetection等主流检测框架读取便于快速验证模型效果现场原始图片覆盖不同形态的煤矸石与高岭石有助于提升模型在真实环境中的鲁棒性也可作为算法对比研究的基准数据。对入门级学习者而言这是一份低门槛、实用性强的目标检测训练资源。1. 102张煤矸石现场图配合COCO JSON也能把三类检测跑起来做煤矸石识别最容易被卡住的地方不是模型选型而是没有一套能直接喂给目标检测框架的数据。这套数据给出了一个接近项目下限的组合102张现场采集的原始图片每个目标对象用COCO JSON格式标注类别固定为煤炭、煤矸石、高岭石三类。数据量小但标注格式正规图片也不是网络下载的干净素材而是带着传送带粉尘、逆光和遮挡的真实画面后面对模型调参该看的干扰因素基本都在。拿到它可以做两件事。算法工程师可以拿它练习COCO JSON与YOLO训练格式的转换验证标注工具导出的一致性现场项目实施时则可以把它当成冷启动数据集先训练一个能用的检测模型再配合半自动标注工具把规模扩到足够部署。下面按读、转、训、验、扩的顺序把从拿到这个数据到出模型的过程梳理一遍。这部分技能在yolov8训练自己的数据集时同样适用。2. 先读透COCO JSONcategories、annotations与三类目标的字段映射2.1 顶层字段和categories映射COCO JSON是一个标准的json文件用Python打开后顶层键一般是info、licenses、images、annotations、categories。其中真正参与模型训练的只有images、annotations、categories三个。info和licenses记录数据来源和版权信息区域内容不参与任何计算。import json coco json.load(open(annotations/instances_default.json, r, encodingutf-8)) print(coco.keys()) # dict_keys([info, licenses, images, annotations, categories]) for c in coco[categories]: print(c[id], c[name]) # 1 coal # 2 gangue # 3 kaolinite这段代码的用途是确认类别id到类别名的映射。COCO格式中category id从1开始但不同标注工具导出的id顺序可能不同。若由labelimg导出为COCO格式或cvat标注工具重新导出id顺序未必还是coal、gangue、kaolinite这种必须先把这张映射表打印出来否则后面按顺序转YOLO索引时会把类名“位移”。2.2 images与annotations字段对齐别被bbox的四个坐标绕晕images数组里的每个元素描述一张图通常至少包含id、file_name、width、height。annotations数组里的每个元素描述一个目标框至少要提供image_id、category_id、bbox、area、iscrowd。print(coco[images][0]) # {id: 1, file_name: IMG_001.jpg, width: 1920, height: 1080} anns [a for a in coco[annotations] if a[image_id] 1] print(anns[0]) # {id: 1, image_id: 1, category_id: 1, bbox: [1185, 140, 321, 188], # area: 60348, iscrowd: 0, segmentation: []}这里的bbox是[x, y, width, height]x和y是框左上角的像素坐标width和height是框的宽度和高度。注意它与YOLO训练格式要求的“中心点坐标加归一化宽高”并不一致。用下面这张表记着转换时不至于写错。字段来源坐标含义转成YOLO txt时需要bbox[0]、bbox[1]左上角x、y先加一半宽高得到中心点bbox[2]、bbox[3]框宽、框高除以图片宽、高得到比例category_idCOCO类id从1开始映射成从0开始的类索引image_id对应images数组里的id关联图片不直接写进txtarea字段与bbox计算出的面积应该一致多数评测脚本会用它做大中小目标分组。一张图如果有多个框annotations里会有多条记录对应同一个image_id读数据时按image_id过滤即可。2.3 统计图像分布看清三类目标的不均衡程度读JSON后不能只做可视化扫描建议先把目标数理一遍。对102张图这种规模人工翻图也只要几分钟但用脚本统计能提前发现标注层面的潜在问题比如某张图annotation缺失、某个类别只出现在少数图里。from collections import Counter cat_names {c[id]: c[name] for c in coco[categories]} target_counter Counter() image_counter {} for ann in coco[annotations]: name cat_names[ann[category_id]] target_counter[name] 1 image_counter.setdefault(name, set()).add(ann[image_id]) print(target_counter) # Counter({gangue: 2148, coal: 1370, kaolinite: 240}) print({k: len(v) for k, v in image_counter.items()}) # {gangue: 98, coal: 90, kaolinite: 31}打印出来的分布大致是这样煤矸石最多煤炭居中高岭石最少且高岭石只集中在31张图里。这意味着后续做train/val划分必须按图片粒度分层而不是按目标对象随机抽。高岭石类一旦在训练集和验证集之间分配不均模型要么学不到这一类的共同特征要么验证时完全没见过这一类的目标mAP会直接显示出零值类别导致整个模型评估失真。3. 从COCO JSON到YOLOv8训练数据用Python做json转换并核对边界框3.1 解析COCO JSON并逐图写出YOLO txt拿到COCO JSON后第一步几乎都是转成YOLO的txt格式。每行五个数值类索引、中心点x、中心点y、框宽、框高全部相对于图像宽高归一化。常见做法是写一个coco转yolo的函数读入原JSON循环images再把匹配的annotations逐条转成坐标行。import json import os def coco_to_yolo(coco_path, out_dir): os.makedirs(out_dir, exist_okTrue) with open(coco_path, r, encodingutf-8) as f: coco json.load(f) old2new {c[id]: i for i, c in enumerate(coco[categories])} for img in coco[images]: w, h img[width], img[height] lines [] for ann in coco[annotations]: if ann[image_id] ! img[id]: continue x, y, bw, bh ann[bbox] cx (x bw / 2.0) / w cy (y bh / 2.0) / h nw bw / w nh bh / h if nw 0 or nh 0: continue lines.append(f{old2new[ann[category_id]]} {cx:.6f} {cy:.6f} {nw:.6f} {nh:.6f}) if not lines: continue base os.path.splitext(img[file_name])[0] with open(os.path.join(out_dir, base .txt), w, encodingutf-8) as f: f.write(\n.join(lines)) coco_to_yolo(annotations/instances_default.json, gangue/labels_all)old2new把COCO里从1开始的id映射成从0开始的YOLO类索引。不要用“category_id减一”这类粗暴做法因为一旦categories列表里出现跳号或者中途有无关类别被过滤掉类名与class索引就会错位。bbox的四个数值中x和y在COCO里表示左上角必须先用它们算出中心点再除以图片原始宽高。1080p图像用1920和1080做分母不能用resize后的尺寸否则坐标与训练输入不匹配。3.2 目录布局与data.yaml配置yolov8训练自己的数据集时默认按images/labels两个根目录组织图片和标注文件各自的子目录为train/val。将102张图按7比3划分后目录结构如下gangue/ images/ train/ val/ labels/ train/ val/ gangue.yamlyaml文件内容path: ./gangue train: images/train val: images/val names: 0: coal 1: gangue 2: kaolinitepath写成相对路径的前提是训练命令在数据集根目录的同级位置执行。如果项目跑在Docker或远程服务器里挂载路径不一致把path改成容器内的绝对路径更省事。names的索引顺序必须和coco_to_yolo转换时的old2new一致任何错位都会让可视化时煤块和矸石互换颜色。val划分要避开同一段连续拍摄的图片。现场采集图片通常按拍摄时间连续编号如果直接取前70张做训练、后32张做验证模型在验证集上看到的背景、光照几乎和训练集一样指标会虚高。我一般先把图片按文件名打乱再分层采样。3.3 反向验证把txt标签画回原图检查转换完成不等于标注正确。txt文件里的坐标都是0到1之间的浮点数单看数字很难发现某个框偏移。用OpenCV把归一化坐标乘回原图尺寸再画框是标注流程里最实用的一步。import cv2 def draw_yolo_label(image_path, txt_path, names): img cv2.imread(image_path) h, w img.shape[:2] with open(txt_path, r) as f: for line in f: parts line.strip().split() cls, cx, cy, bw, bh map(float, parts) cx, cy, bw, bh cx * w, cy * h, bw * w, bh * h x1 int(cx - bw / 2) y1 int(cy - bh / 2) x2 int(cx bw / 2) y2 int(cy bh / 2) cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.putText(img, names[int(cls)], (x1, max(0, y1 - 4)), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 1) return img img draw_yolo_label(gangue/images/train/IMG_001.jpg, gangue/labels/train/IMG_001.txt, [coal, gangue, kaolinite]) cv2.imwrite(check_001.jpg, img)这个脚本随机挑10到20张图跑一遍。检查点依次是框与目标边缘是否贴合、有没有整块漏框、有没有两个框重叠在同一目标上。目标检测常用标注工具比如labelimg、cvat、Label Studio无论用哪个生成的数据这步反向验证都值得保留。如果画完发现框整体偏左上多半是标注坐标基准理解错了也就是把bbox当成了中心点如果框大面积偏小常见原因是标注时只框住了目标最亮的核心区域没有包含边缘。这时应回到标注工具修正原始标注再重新导出而不是只改txt文件否则下次再转格式还会错一遍。4. 在煤矸石小样本上调YOLOv8n训练参数增强参数、epochs与过拟合4.1 为什么首选用nano而不是m或l小数据上最常见的错误是“模型越大效果越好”。102张图最多提供两万多个目标框用YOLOv8x会让backbone在几十轮后开始记忆背景纹理验证mAP反而不升。煤矸石识别的实际难点在于类别间灰度接近、遮挡和粉尘干扰并不是细粒度特征不足。这类场景用YOLOv8n起步参数量少、训练快拿到baseline后判断问题出在数据还是模型成本低得多。对比维度上yolov8n在同等数据量下通常只比s低一到两个点的AP但训练和推理时间能省三成。对现场传送带识别来说推理速度往往比那一个点AP更值钱。后面如果想提精度优先改数据质量和输入分辨率而不是无脑换大模型。4.2 训练命令与关键参数训练命令保持简洁先默认全部增强只改几项明显不适合现场场景的参数yolo detect train \ datagangue/gangue.yaml \ modelyolov8n.pt \ epochs100 \ imgsz640 \ batch8 \ workers2 \ optimizerSGD \ lr00.01 \ cos_lrTrue \ seed42 \ hsv_h0.0 \ hsv_s0.0 \ hsv_v0.1 \ translate0.1 \ scale0.3 \ fliplr0.0 \ mosaic0.0参数需要说明的重点modelyolov8n.pt从COCO预训练权重继续训练模型已经具备通用纹理特征比随机初始化收敛快得多。现场图里的煤矸石点状纹理和自然图像里的岩石纹理有可迁移的部分。imgsz640默认分辨率。若图中煤块多数大于80乘60像素640够用。目标普遍偏小时先升到960升分辨率比加模型容量更容易提升小目标召回。batch8单卡8到12GB显存的安全配置。若显存紧张降到4epochs需要提高到150左右否则BN层的均值和方差估计不稳。optimizerSGD小数据集上SGD配合cos_lr比较稳AdamW后期更新步长偏大容易把最后几轮权重推向过拟合。mosaic0.0先关掉让模型从完整、真实的现场图中学习。拼接四张图会把煤矸石的边缘切碎在目标密集的传送带画面上反而制造错误样本。fliplr0.0现场图片一旦出现设备铭牌或固定遮挡物水平翻转会制造镜像伪目标第一轮训练直接关掉。4.3 增强参数怎么调先关mosaic再逐步开scale上面的命令等于只保留亮度微调、小幅平移和尺度变化。这是因为煤炭、煤矸石在RGB空间都是灰黑色若饱和度增强被放大黑色煤块可能被渲染成褐色模型学到错误的颜色先验。hsv_v保留0.1的亮度扰动模拟现场光照波动已经足够。第二轮实验可以从scale下手。把scale从0.3提高到0.5看验证集mAP是否变化如果下降恢复0.3。mosaic可以在第三轮以0.5的值重新开启但要同时观察train loss和val loss的分离程度。小数据集跑100轮时第40到60轮经常已经过拟合表现在train loss继续下降、val loss上升。此时先回头调增强不要立刻加epochs。增强参数一次只改一个否则没法判断是哪一个改动带来了收益。5. 小样本验证怎么排错混淆矩阵、置信度阈值与单图边界测试5.1 验证集划分要按图片分层而不是随机硬切102张图做训练验证拆分简单随机一次不够。因为高岭石只出现在31张图里简单随机分割有接近三分之一的概率把高岭石图全部或大部分切进train验证集里一个高岭石都没有混淆矩阵那一行直接变成空白。更好的做法是先按类别聚合图片再分层采样import random from collections import defaultdict random.seed(42) img_to_anns defaultdict(list) for ann in coco[annotations]: img_to_anns[ann[image_id]].append(ann) imgs_by_cat defaultdict(set) for img in coco[images]: cat_set {ann[category_id] for ann in img_to_anns[img[id]]} for cid in cat_set: imgs_by_cat[cid].add(img[id])代码先把每张图包含哪些类别聚合出来然后按类别分配图片。高岭石图单独抽一部分到val剩下的类别再随机补齐。关键是保持同图同归同一张现场照片不能既出现在train又出现在val否则背景高度重复会让验证结果虚高。现场图之间本来背景就相似“同图泄漏”是少数几个能彻底毁掉评估的失误。如果时间允许用5折交叉验证。每训练一个模型用其余四折验证最后五个模型平均分数比单次划分稳定得多。代价是多跑五次但对102张这种小数据来说值得。5.2 混淆矩阵主要看两个角yolo训练完成后results目录下的confusion_matrix.png是排错的第一入口。对三类任务的解读分几种情况gangue被误判成coal颜色特征没有拉开。煤与矸石在灰黑色区域重叠时单靠RGB难以区分可以考虑在数据层面增加边缘纹理的对比或在测试时提高输入亮度校准。kaolinite被漏检成background高岭石样本量太少或目标太小。此时调整置信度作用有限重点应该是补充这类别的标注图。对角线数值都低优先怀疑标签转换环节训练样本根本没对上而不是模型问题。看矩阵时不能只看数字要配合val_batch0_pred.jpg这类保存下来的预测图。yolo在训练过程中会把一个batch的预测结果和标注结果并排保存用它们对照矩阵里数值异常的格子能直观看到误检和漏检的边缘案例。5.3 用predict做单图边界测试训练完拿几张没参与训练的现场图做边界测试用低于部署值的conf看漏检情况from ultralytics import YOLO model YOLO(runs/detect/train/weights/best.pt) results model.predict( sourcegangue/images/val/IMG_031.jpg, conf0.25, iou0.5, saveTrue, save_txtTrue, )conf0.25是偏低的阈值模型会输出大量候选框正好用来观察漏检。现场部署时阈值再根据漏检和误报的成本取定漏检会导致煤矸石混入精煤那就把conf降到0.15到0.2误报会导致机械臂空抓或下游设备误动作conf就得抬到0.45以上。小目标天然置信度低如果只关心中等以上块体conf0.35是个合理起点。iou保持0.5即可目标重叠多时降一点但不要低于0.4否则NMS会合并本应分开的相邻煤块。6. 数据不够时这样扩Label Studio半自动标注与COCO JSON合并6.1 用现有模型做预标注人工复核代替从零画框102张图做完上面的流程已经能验证标注质量和模型选型。但现场部署通常需要更多数据。我习惯先训练baseline再拿它预测新增现场图然后把预测结果作为候选框导入标注工具人工只做修改和补漏。这样比用labelimg从零一张张画快很多也更贴近现场条件。以Label Studio为例流程是新建Object Detection项目配置coal、gangue、kaolinite三个标签把模型预测结果整理成predictions格式每一条包含from_name、to_name、type、value四个字段value里放矩形框坐标和类别名导入后逐张复核。cvat则可以在自动标注里挂载模型权重直接在task上执行Auto Annotation导出时选COCO JSON格式即可。两套方案后半段一致都是导出标注、合并数据集、重新训练。每完成10张图就导出一批不要积累到100张再一次性处理中途发现漏检规律能及时调整预测阈值。6.2 合并多个COCO JSON时重排id避免坐标错位分批标注后得到多个COCO JSON文件直接拼在一起会撞id。annotations里的image_id一旦冲突所有框都会画到错误的图上。合并时用统一偏移量重排def merge_coco_json(coco_paths): merged {images: [], annotations: [], categories: None} image_id_offset 0 ann_id 1 for path in coco_paths: with open(path, r, encodingutf-8) as f: coco json.load(f) if merged[categories] is None: merged[categories] coco[categories] for image in coco[images]: image[id] image_id_offset merged[images].append(image) for ann in coco[annotations]: ann[id] ann_id ann[image_id] image_id_offset ann_id 1 merged[annotations].append(ann) image_id_offset len(coco[images]) return mergedimage_id_offset表示已合并文件里的图片总数每合并完一个文件累加一次。annotations通过image_id引用images所以重排时两张表必须同时加同一个偏移量。我的经验是合并后不要立刻开训先跑一遍前面的反向可视化脚本在图上验证高岭石类别框是否对应正确的白色浅色对象。若发现类别id错位检查categories是否来自同一个导出模板两次标注的类顺序一旦不同必须先把所有category_id统一成新映射再合并。最终补完的高岭石图与原有数据混合后再拿一批当天新拍的现场图做盲测如果新旧样本mAP差距明显说明数据集之间光照或角度差异过大先做颜色校准和亮度归一化这已经不是标注问题而是设备级问题了。本文还有配套的精品资源点击获取
返回列表