肺结节目标检测数据集:三种标签格式与YOLO训练实战指南
2026/9/13 13:59:01 网站建设 项目流程

简介:面向肺结节目标检测任务,这份资源提供5000张真实场景的高质量图片,使用LabelImg标注,并同时整理出VOC(xml)、COCO(json)和YOLO(txt)三种格式标签,分别存放在独立文件夹中,可直接用于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、height

COCO格式把整个数据集的图片信息、标注信息和类别信息放进同一个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等于(200+260)/2等于230,归一化后为230/640等于0.359375;中心点y等于(180+240)/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、labels(YOLO格式)、Annotations(VOC格式)和annotations.json(COCO格式)。

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(f'YOLO/{split_name}/images', exist_ok=True) os.makedirs(f'YOLO/{split_name}/labels', exist_ok=True) os.makedirs(f'VOC/{split_name}/Annotations', exist_ok=True) # 同步复制图片、YOLO txt、VOC xml for name in base_names: split_name = assign_split(name) shutil.copy(f'images/{name}.png', f'YOLO/{split_name}/images/{name}.png') shutil.copy(f'labels/{name}.txt', f'YOLO/{split_name}/labels/{name}.txt') shutil.copy(f'Annotations/{name}.xml', f'VOC/{split_name}/Annotations/{name}.xml')

脚本的第一个关键点是按患者ID分组,这比直接shuffle图片列表更能保证验证指标可信。第二个关键点是复制而不是移动文件,保留原始数据,方便后面调整划分比例后重新生成实验。第三个关键点是YOLO目录和VOC目录都分成了三个独立的split,训练时不会再出现数据集泄露。

COCO格式在划分时不能靠简单复制文件完成,因为它的标注是汇总在一个JSON里的。通常做法是划分完VOC目录后,再对每个split重新生成各自的annotations.json:

def build_coco_from_voc(split_name): coco_images = [] coco_annotations = [] ann_id = 1 voc_root = f'VOC/{split_name}/Annotations' xml_files = sorted(glob(os.path.join(voc_root, '*.xml'))) for img_id, xml_path in enumerate(xml_files, start=1): 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(f'COCO/{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统计值统计值0
val统计值统计值0
test统计值统计值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 data=lung_nodule.yaml model=yolov8n.pt epochs=1 imgsz=640

epochs=1不是真的训练模型,而是确认数据流能走通。如果数据集路径写错、标签解析失败、类别数量对不上,都会在这一步暴露。系统提示文件缺失或标签为空时,优先检查yaml里的路径前缀。

4.2 数据集的yaml配置文件,字段越精简越不容易出错

path: /data/YOLO/dataset train: train/images val: val/images test: test/images names: 0: nodule

path是划分脚本输出目录的绝对路径,train和val写成相对路径,指向各自的images子目录。Ultralytics会自动扫描目录下所有图片并找到同名的txt标签。注意不要写成train/images/*.jpg这样的glob表达式,也不要让val和train指向同一个目录,否则验证集评估失去意义。

4.3 训练命令的参数选择,针对肺结节小目标场景做调整

yolo train \ data=lung_nodule.yaml \ model=yolov8s.pt \ epochs=150 \ imgsz=640 \ batch=16 \ device=0 \ patience=30 \ lr0=0.0005 \ cos_lr=True \ close_mosaic=10

训练参数的取舍逻辑如下表:

参数推荐值说明
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对应边界框回归,用的是CIoU;cls_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个epoch,mAP50做到0.85以上、mAP50-95做到0.55以上就说明数据质量和模型选择都合格。如果mAP50低于0.7,优先检查训练集是否有漏标的结节,或者是否有标注框明显偏移的坏样本污染了损失函数。

5.2 三个针对肺结节小目标的调优手段

第一,推理时启用TTA,对边界模糊的小结节有提升:

yolo predict model=best.pt source=test/images imgsz=640 augment=True

augment=True会做多尺度与翻转推理,召回率通常提升2到3个百分点,代价是推理时间增加两到三倍,适合离线分析。第二,对高分辨率CT原图做滑窗切割,切成512或640的图块分别检测,避免下采样把微小结节抹掉。第三,对置信度高于0.9且不与人工标注重叠的预测框做伪标注回灌,缓解正样本不足的问题,但这一步有噪声风险,通常只在数据量大、类别比例严重不均衡时才建议尝试。

最后一招是反向验证技巧:测试集推理完成后,把预测结果和原始VOC或COCO标签做逐图对比,统计丢失率最高的那批图片的共性特征。只要做一次这个分析,就能判断后续该调的是训练参数、标注规范,还是数据增强策略。这一招在肺结节目标检测任务里的价值,不亚于再训练一版新模型。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询