尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

肺结节目标检测数据集:三种标签格式与YOLO训练实战指南

肺结节目标检测数据集:三种标签格式与YOLO训练实战指南 简介面向肺结节目标检测任务这份资源提供5000张真实场景的高质量图片使用LabelImg标注并同时整理出VOCxml、COCOjson和YOLOtxt三种格式标签分别存放在独立文件夹中可直接用于YOLO系列模型训练。配套内容还包括YOLO环境搭建与训练教程覆盖Windows与Linux版本并附上数据集划分脚本支持按需生成训练集、验证集和测试集。适合医学影像检测、深度学习目标检测方向的开发者与课程学员使用。压缩包共2000个文件以xml标注文件、html教程、py划分脚本和txt列表文件为主整体约77.56MB目录层级清晰标签与脚本分区明确。目前已有402人学习资源详情页还提供数据集展示与更多同类数据下载入口。1. 肺结节目标检测数据集的价值恰恰体现在三套标签和那个划分脚本上肺结节的计算机辅助检测在医学影像AI领域已经足够成熟但真正动手训练一个YOLO模型时最耗时间的不一定是调参而是把标注数据整理成能直接喂给训练器的格式。标题里的这份资源包含5000张肺结节图片同时给出VOC、COCO、YOLO三种格式的标签外加划分脚本和训练教程等于把目标检测项目里最枯燥的数据管道阶段直接打包交付。对算法工程师来说拿到这样的数据集可以跳过格式转换的重复劳动直接对比三种标注体系并验证YOLO系列模型的训练效果对刚接触医学影像检测的研究生来说它又提供了一个可以完整走通整个流程的数据基线。下面的内容就按数据格式、划分脚本、训练流程和验证调优四层展开。2. 读懂VOC、COCO、YOLO三种标签肺结节数据集的坐标体系才算掌握2.1 VOC格式先从size字段读起bndbox的绝对值坐标才有基准VOC格式的标注文件和图片同名后缀是.xml通常放在Annotations目录下。它最容易被忽略的是根节点下的size字段。bndbox提供的是绝对像素坐标但如果图片在标注前被resize过size字段没有同步更新后续任何格式转换都会偏离真实位置。import xml.etree.ElementTree as ET def parse_voc_annotation(xml_path): tree ET.parse(xml_path) root tree.getroot() image_name root.find(filename).text width int(root.find(size/width).text) height int(root.find(size/height).text) objects [] for obj in root.iter(object): name obj.find(name).text 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) objects.append({ name: name, bbox: [int(xmin), int(ymin), int(xmax), int(ymax)] }) return image_name, (width, height), objects这段函数返回图片名、宽高和所有结节框。注意用float再转int是因为很多标注工具会把坐标导出为“180.0”这种字符串直接int()解析会抛异常。拿到这张图的width和height之后后续转YOLO格式的归一化计算才有依据。2.2 COCO格式的JSON集中管理bbox的顺序是x、y、width、heightCOCO格式把整个数据集的图片信息、标注信息和类别信息放进同一个JSON文件。对肺结节这种单类别任务来说categories通常只有一个“nodule”annotations里的每一条记录对应一个结节实例。COCO的bbox是[x, y, width, height]x和y是左上角坐标width和height是框宽高这一点和VOC的xmin/ymin/xmax/ymax表达方式不同用惯了VOC的人最容易在这里出错。{ images: [ {id: 1, file_name: case001_0001.png, width: 640, height: 640} ], annotations: [ { id: 1, image_id: 1, category_id: 1, bbox: [200, 180, 60, 60], area: 3600, iscrowd: 0 } ], categories: [ {id: 1, name: nodule} ] }COCO转YOLO时需要先把左上角坐标加上宽高的一半得到中心点再做归一化。area字段对YOLO训练没有影响但如果要做COCO评估area缺失会导致mAP计算异常所以即使训练用不上转换时也建议保留。2.3 YOLO标签的归一化坐标一行txt背后是对整张图的数学映射YOLO标签是每张图一个同名txt文件名和图片名一致后缀从.jpg或.png变成.txt放在labels目录下。每行对应一个检测目标格式是class_id x_center y_center width height坐标全部除以图片宽高归一化到0到1之间。一个640×640图像里的肺结节假设左上角坐标为(200, 180)右下角坐标为(260, 240)对应的YOLO文本行是0 0.359375 0.328125 0.09375 0.09375拆解这个数字的来路中心点x等于(200260)/2等于230归一化后为230/640等于0.359375中心点y等于(180240)/2等于210归一化后为210/640等于0.328125宽高都是60像素归一化后是60/640等于0.09375。训练前可以反向把这个过程算回去画在原图上看看框的位置对不对。2.4 三种格式的转换对拍表以及一个最实用的校验逻辑格式文件载体坐标体系同一结节示例YOLO训练时可直接用VOCAnnotations/case.xml绝对像素xmin/ymin/xmax/ymax200 180 260 240否需转换COCOannotations.json绝对像素bbox x/y/w/h200 180 60 60否需转换YOLOlabels/case.txt归一化中心点加宽高0.359 0.328 0.094 0.094是做格式转换最稳妥的方式不是直接跑全量脚本而是先挑一张图用三种格式表示同一个结节确认数值关系能对得上再批量处理。这个通用流程在数据处理中很重要我记得不少线上转换脚本就是把VOC的xmin直接当成中心点用转换结果错了一半而不自知。既然这套数据集自带三种标签更值得花几分钟做一次对拍验证随机抽几张图把XML中的object数量、JSON中的annotation数量、txt的行数逐个比对三者一致才说明三套标签确实描述的是同一批框。3. 划分脚本怎么设计才能让5000张图像和三种标签同步拆分3.1 划分不只是随机抽样肺结节数据必须先做患者维度隔离如果数据来自CT序列同一个患者的相邻切片外观高度相似。划分时如果只按图片随机抽样训练集和验证集会出现大量来自同一个患者的相似切片模型在验证集上的指标会虚高测试集评估也会失真。这个数据包的5000张图片是否按患者分组使用方其实需要自己先验证一遍。如果文件名前缀带有case编号划分脚本就应该以case为单位做分组随机而不是逐张图随机。ls images/ | head -5 case001_0001.png case001_0002.png case002_0001.png像上面这种命名case001是患者ID后面四位是切片序号。只看文件名就能判断患者分组是否存在。3.2 一个同时输出train、val、test并同步三种标签的划分脚本常见做法是只维护一份文件清单用复制的方式装配出标准目录。原始目录结构假设为images、labelsYOLO格式、AnnotationsVOC格式和annotations.jsonCOCO格式。import os import json import random import shutil from glob import glob random.seed(42) img_paths sorted(glob(images/*.png)) base_names [os.path.splitext(os.path.basename(p))[0] for p in img_paths] # 按患者ID分组避免同病人切片泄漏到不同的集合 patient_groups {} for name in base_names: patient_id name.split(_)[0] patient_groups.setdefault(patient_id, []).append(name) patients list(patient_groups.keys()) random.shuffle(patients) n_patients len(patients) # 7:2:1划分患者数量 n_train int(n_patients * 0.7) n_val int(n_patients * 0.2) train_patients set(patients[:n_train]) val_patients set(patients[n_train:n_train n_val]) test_patients set(patients[n_train n_val:]) def assign_split(name): pid name.split(_)[0] if pid in train_patients: return train if pid in val_patients: return val return test # 建立目标目录 for split_name in [train, val, test]: os.makedirs(fYOLO/{split_name}/images, exist_okTrue) os.makedirs(fYOLO/{split_name}/labels, exist_okTrue) os.makedirs(fVOC/{split_name}/Annotations, exist_okTrue) # 同步复制图片、YOLO txt、VOC xml for name in base_names: split_name assign_split(name) shutil.copy(fimages/{name}.png, fYOLO/{split_name}/images/{name}.png) shutil.copy(flabels/{name}.txt, fYOLO/{split_name}/labels/{name}.txt) shutil.copy(fAnnotations/{name}.xml, fVOC/{split_name}/Annotations/{name}.xml)脚本的第一个关键点是按患者ID分组这比直接shuffle图片列表更能保证验证指标可信。第二个关键点是复制而不是移动文件保留原始数据方便后面调整划分比例后重新生成实验。第三个关键点是YOLO目录和VOC目录都分成了三个独立的split训练时不会再出现数据集泄露。COCO格式在划分时不能靠简单复制文件完成因为它的标注是汇总在一个JSON里的。通常做法是划分完VOC目录后再对每个split重新生成各自的annotations.jsondef build_coco_from_voc(split_name): coco_images [] coco_annotations [] ann_id 1 voc_root fVOC/{split_name}/Annotations xml_files sorted(glob(os.path.join(voc_root, *.xml))) for img_id, xml_path in enumerate(xml_files, start1): tree ET.parse(xml_path) root tree.getroot() width int(root.find(size/width).text) height int(root.find(size/height).text) file_name root.find(filename).text coco_images.append({ id: img_id, file_name: file_name, width: width, height: height }) for obj in root.iter(object): bbox obj.find(bndbox) xmin float(bbox.find(xmin).text) ymin float(bbox.find(ymin).text) xmax float(bbox.find(xmax).text) ymax float(bbox.find(ymax).text) w xmax - xmin h ymax - ymin coco_annotations.append({ id: ann_id, image_id: img_id, category_id: 1, bbox: [xmin, ymin, w, h], area: w * h, iscrowd: 0 }) ann_id 1 with open(fCOCO/{split_name}/annotations.json, w) as fp: json.dump({ images: coco_images, annotations: coco_annotations, categories: [{id: 1, name: nodule}] }, fp)这段逻辑直接从VOC XML生成COCO JSON保证全流程只依赖一份原始标注不会出现VOC和COCO内容不一致的情况。3.3 划分后的标准目录结构决定YOLO训练脚本能否零改动启动dataset/ ├── train/ │ ├── images/ │ │ └── case001_0001.png │ └── labels/ │ └── case001_0001.txt ├── val/ │ ├── images/ │ └── labels/ └── test/ ├── images/ └── labels/这是YOLOv5和Ultralytics YOLOv8的标准布局。如果划分脚本输出的目录不符合这种结构训练前还得再写一层适配代码。很多开源项目直接硬编码images和labels这两个目录名一旦命名为img或anno就要去改源码里的路径拼接逻辑。3.4 划分完最少要做的三件事文件对齐、画框检查、分布统计第一检查每个split里图片和txt同名对应不能出现有图片没标签、或者有标签没图片的情况。第二随机挑若干张图把YOLO txt解析成像素坐标后叠加到原图上用OpenCV画矩形框检查框是否越界或者明显偏离结节实体。第三统计三个split的类别数量和目标实例数量正常情况下比例应与图片数量接近。划分图片数量目标总数空标签文件数train统计值统计值0val统计值统计值0test统计值统计值0如果某个split的目标数占比偏差超过5个百分点重新设置random seed再跑一遍。做完这三步检查才能确认数据划分是可信的。4. 用这份数据集跑通YOLO训练配置、命令与损失函数观察4.1 安装ultralytics环境先用1个epoch做数据链路验证训练部分以Ultralytics YOLOv8为例。YOLO系列从v5到v8再到v11版本的演进确实不少但数据组织方式一直是images和labels对应的目录结构这套数据集在不同版本上都能直接使用。安装命令很直接pip install ultralytics安装完成后先跑一条最小命令验证路径配置是否正确yolo train datalung_nodule.yaml modelyolov8n.pt epochs1 imgsz640epochs1不是真的训练模型而是确认数据流能走通。如果数据集路径写错、标签解析失败、类别数量对不上都会在这一步暴露。系统提示文件缺失或标签为空时优先检查yaml里的路径前缀。4.2 数据集的yaml配置文件字段越精简越不容易出错path: /data/YOLO/dataset train: train/images val: val/images test: test/images names: 0: nodulepath是划分脚本输出目录的绝对路径train和val写成相对路径指向各自的images子目录。Ultralytics会自动扫描目录下所有图片并找到同名的txt标签。注意不要写成train/images/*.jpg这样的glob表达式也不要让val和train指向同一个目录否则验证集评估失去意义。4.3 训练命令的参数选择针对肺结节小目标场景做调整yolo train \ datalung_nodule.yaml \ modelyolov8s.pt \ epochs150 \ imgsz640 \ batch16 \ device0 \ patience30 \ lr00.0005 \ cos_lrTrue \ close_mosaic10训练参数的取舍逻辑如下表参数推荐值说明modelyolov8s.ptCOCO预训练权重比n版本有更多特征层imgsz640与CT切片原始分辨率保持一致避免重采样batch16显存不足时降到8同时考虑BN层稳定性patience30验证指标连续30轮无提升则早停lr00.0005肺结节框小且正样本少初始学习率要保守cos_lrTrue余弦退火适合150个epoch的偏长训练close_mosaic10最后10轮关闭mosaic增强让模型适应真实分布如果在8GB显存环境下训练batch降到8并改用yolov8n.pt是更稳妥的选择。过小的batch会让BN层统计量不稳定训练后期可能出现损失震荡。4.4 从box_loss、cls_loss和dfl_loss判断训练状态YOLOv8训练过程的损失由三部分组成box_loss对应边界框回归用的是CIoUcls_loss对应分类分支判断区域是结节还是背景dfl_loss是分布焦点损失影响边框与真实标注的贴合精度。肺结节图像中背景像素占绝对多数cls_loss的数值通常会远高于box_loss这是类别不平衡的固有表现不需要刻意把两个值拉到同一量级。重点要观察验证loss的曲线形态。如果val/box_loss早早降到低位但val/dfl_loss仍在波动说明模型已经能大致框住结节只是边界不稳定。这时候继续堆epochs收益不大更值得做的是检查标注框边缘的一致性或者用多尺度推理提升边界精度。5. 用mAP指标反向检查数据集质量以及小目标优化技巧5.1 mAP50-95比mAP50更能暴露标注边界问题训练结束后results.csv里的metrics/mAP50-95是最值得关注的指标。肺结节的形态各异很多小结节在原始CT切片上的像素尺寸不到32×32属于标准的小目标场景。mAP50-95对框位置的精度很敏感如果mAP50已经达到0.85以上mAP50-95却明显偏低大概率是模型预测的框和标注框只有部分重叠边界不稳定。这种情况下往往不是模型能力不足而是标注框本身存在边缘主观性比如不同医生勾画的肺结节边界不完全一致。一般经验值是在5000张规模的单类别肺结节数据上用YOLOv8s训练到150个epochmAP50做到0.85以上、mAP50-95做到0.55以上就说明数据质量和模型选择都合格。如果mAP50低于0.7优先检查训练集是否有漏标的结节或者是否有标注框明显偏移的坏样本污染了损失函数。5.2 三个针对肺结节小目标的调优手段第一推理时启用TTA对边界模糊的小结节有提升yolo predict modelbest.pt sourcetest/images imgsz640 augmentTrueaugmentTrue会做多尺度与翻转推理召回率通常提升2到3个百分点代价是推理时间增加两到三倍适合离线分析。第二对高分辨率CT原图做滑窗切割切成512或640的图块分别检测避免下采样把微小结节抹掉。第三对置信度高于0.9且不与人工标注重叠的预测框做伪标注回灌缓解正样本不足的问题但这一步有噪声风险通常只在数据量大、类别比例严重不均衡时才建议尝试。最后一招是反向验证技巧测试集推理完成后把预测结果和原始VOC或COCO标签做逐图对比统计丢失率最高的那批图片的共性特征。只要做一次这个分析就能判断后续该调的是训练参数、标注规范还是数据增强策略。这一招在肺结节目标检测任务里的价值不亚于再训练一版新模型。本文还有配套的精品资源点击获取
返回列表