简介本资源为YOLO肺结节目标检测数据集面向医学影像检测方向的学习者与算法开发者可用于目标检测模型训练、课程实验与科研验证。数据来自真实场景图像质量高、场景丰富经labelimg精细标注同时提供voc、coco和yolo三种格式标签分别存放于不同文件夹可直接接入YOLO系列模型训练。压缩包共约2000个文件以1986个xml标注文件为主另含html教程、txt列表与py脚本整体约77.56MB。资源附赠环境搭建、训练教程及数据集划分脚本可按需生成训练集、验证集与测试集并输出ImageSets下的划分列表。已有403人学习下载适合希望快速跑通肺结节检测流程、掌握多格式标签转换与数据划分方法的读者参考使用。1. 从一批肺结节 CT 说起5000 张图 三格式标签到底解决了什么拿到「YOLO肺结节目标检测数据集(含5000张图片)对应voc、coco和yolo三种格式标签划分脚本训练教程.rar」这个标题很多人第一反应是「又一个打包数据集」。但真正做过肺结节检测的人知道卡住项目的从来不是模型结构而是数据这一环DICOM 转 PNG 的窗宽窗位怎么定、结节框该由谁标、标完怎么变成 YOLO 能吃的 txt、训练集验证集怎么切才不泄漏。这批 5000 张图配 VOC、COCO、YOLO 三套标签本质是把「从原始影像到可训练张量」这条链路提前铺好了你拿到手就能直接进train.py。它适合两类人一类是想快速验证 YOLO 在医学小目标上到底行不行的算法工程师另一类是手里有临床数据但被格式转换和划分脚本拖住的学生和初级研发。下面我按自己复现这类数据集的顺序把选型、转换、划分、训练和踩坑讲透。2. 三套标签格式怎么选VOC、COCO、YOLO 的取舍与转换逻辑2.1 为什么同一批数据要同时给三种格式VOC 的 XML 是「一个图一个文件」字段是xmin/ymin/xmax/ymax的绝对像素坐标可读性最好适合人工核对标注质量COCO 的 JSON 是「整个数据集一个文件」带images/annotations/categories三段结构适合做统计、可视化和接入 detectron2 这类框架YOLO 的 txt 是「一个图一个文件」每行class cx cy w h全是归一化到 0~1 的相对值是 ultralytics 系训练脚本直接读的格式。三种格式描述的是同一批框只是坐标系和存储粒度不同。常见做法是用 VOC 做人工抽检用 COCO 做数据分布分析用 YOLO 直接训练。这样一套数据能覆盖从标注审核到训练的全流程不用你反复转来转去。2.2 坐标转换的核心公式与一个可复用的转换脚本转换的坑几乎全在坐标系上。VOC 是左上角 右下角绝对坐标YOLO 是中心点 宽高归一化。公式如下# voc_to_yolo.py # 输入VOC xml 目录、图片目录、类别列表 # 输出YOLO txt 目录与图片同名 import os import xml.etree.ElementTree as ET from PIL import Image CLASSES [nodule] # 肺结节通常单类多类按实际顺序改 def voc_to_yolo(xml_dir, img_dir, out_dir): os.makedirs(out_dir, exist_okTrue) for xml_file in os.listdir(xml_dir): if not xml_file.endswith(.xml): continue tree ET.parse(os.path.join(xml_dir, xml_file)) root tree.getroot() # 用图片真实尺寸不要用 xml 里的 size标注工具偶尔写错 img_name root.find(filename).text img_path os.path.join(img_dir, img_name) w, h Image.open(img_path).size lines [] for obj in root.findall(object): cls_name obj.find(name).text if cls_name not in CLASSES: continue cls_id CLASSES.index(cls_name) 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) # 归一化中心点与宽高 cx (xmin xmax) / 2.0 / w cy (ymin ymax) / 2.0 / h bw (xmax - xmin) / w bh (ymax - ymin) / h # 裁剪到 [0,1]防止越界框导致训练报错 cx, cy min(max(cx, 0), 1), min(max(cy, 0), 1) bw, bh min(max(bw, 0), 1), min(max(bh, 0), 1) lines.append(f{cls_id} {cx:.6f} {cy:.6f} {bw:.6f} {bh:.6f}) with open(os.path.join(out_dir, xml_file.replace(.xml, .txt)), w) as f: f.write(\n.join(lines)) if __name__ __main__: voc_to_yolo(Annotations, JPEGImages, labels)逻辑说明先读图片真实宽高再对每个 object 算中心点和宽高并归一化。参数上CLASSES必须和训练时的data.yaml里names顺序完全一致否则类别会错位:.6f保留六位是 ultralytics 的惯例位数少了对小框精度有影响。注意最后那两行裁剪医学影像里标注工具偶尔会写出超出图像边界的框不裁的话 YOLO 训练时归一化坐标大于 1 会直接抛异常。2.3 COCO 与 YOLO 互转时最容易错的两处COCO 转 YOLO 时bbox字段是[x, y, w, h]绝对坐标不是[xmin, ymin, xmax, ymax]很多人直接套 VOC 公式就翻车。正确做法是cx (x w/2) / img_w。另一处是 COCO 的category_id往往从 1 开始而 YOLO 的 class id 从 0 开始转换时要减 1否则训练时会出现「类别 0 没有样本」的玄学现象。这两处我在不同项目里各踩过一次血泪经验就是转换完一定用脚本统计一遍每类框数量和原始标注对得上再进训练。3. 划分脚本怎么写避免数据泄漏的三种切分策略3.1 随机切分的陷阱同一病人的切片不能跨集肺结节数据有个特殊性一个病人的 CT 会切成很多张切片相邻切片长得几乎一样。如果按图片随机切分同一病人的切片会同时出现在训练集和验证集验证指标虚高实际部署时性能断崖式下跌。这就是典型的数据泄漏。正确做法是按病人 ID 分组切分保证一个病人的所有切片只进一个集合。常见做法是先从文件名或目录里解析出病人 ID再对 ID 列表做切分最后把对应图片分配到各集合。3.2 一个按病人分组、可复现的划分脚本# split_dataset.py # 按病人 ID 分组切分避免同一病人跨集 import os import random import shutil from collections import defaultdict SEED 42 RATIO (0.8, 0.1, 0.1) # train / val / test def get_patient_id(filename): # 按你的命名规则改例如 P0012_slice_034.png - P0012 return filename.split(_)[0] def split(img_dir, label_dir, out_root): random.seed(SEED) patients defaultdict(list) for f in os.listdir(img_dir): if f.lower().endswith((.png, .jpg)): patients[get_patient_id(f)].append(f) pids list(patients.keys()) random.shuffle(pids) n len(pids) n_train int(n * RATIO[0]) n_val int(n * RATIO[1]) splits { train: pids[:n_train], val: pids[n_train:n_train n_val], test: pids[n_train n_val:], } for split_name, pid_list in splits.items(): img_out os.path.join(out_root, images, split_name) lbl_out os.path.join(out_root, labels, split_name) os.makedirs(img_out, exist_okTrue) os.makedirs(lbl_out, exist_okTrue) for pid in pid_list: for img_file in patients[pid]: shutil.copy(os.path.join(img_dir, img_file), img_out) lbl_file img_file.rsplit(., 1)[0] .txt src_lbl os.path.join(label_dir, lbl_file) if os.path.exists(src_lbl): shutil.copy(src_lbl, lbl_out) if __name__ __main__: split(images, labels, dataset)逻辑说明get_patient_id是整套脚本的关键命名规则不同就改这一处。SEED42保证每次切分结果一致方便复现实验。RATIO按 8:1:1 切样本少时可以调成 7:1.5:1.5。注意脚本只复制有对应标签的图片如果某张图没标签会被静默跳过跑完最好统计一下三个集合的图片数之和是否等于原图数对不上就说明有图缺标签。3.3 划分完必须做的两项校验第一项是数量校验train val test的图片数应等于原始图片数标签数也应一致。第二项是泄漏校验写个脚本把所有图片的 patient id 提取出来检查三个集合的 id 集合两两无交集。这两步花不了五分钟但能省掉后面调参时「为什么验证集 mAP 高得离谱」的困惑。我一般会把校验结果打印成表格存下来作为实验记录的一部分。4. 用这批数据跑通 YOLO 训练环境、配置与关键参数4.1 环境配置与 data.yaml 的写法环境用 ultralytics 官方包最省事pip install ultralytics即可它会把 torch 等依赖一起装好。数据目录按上一章切分后的结构放然后写data.yaml# data.yaml path: ./dataset train: images/train val: images/val test: images/test nc: 1 names: [nodule]path是数据集根目录train/val/test是相对路径。nc是类别数肺结节单类就写 1。names的顺序必须和转换脚本里的CLASSES完全一致这是最容易出错的地方。如果后面要加「钙化」「磨玻璃」等子类改这里和转换脚本两处即可。4.2 训练命令与肺结节场景下的参数调整# 单卡训练肺结节小目标场景 yolo detect train \ datadata.yaml \ modelyolov8s.pt \ epochs150 \ imgsz640 \ batch16 \ lr00.01 \ patience30 \ mosaic0.5 \ degrees10 \ fliplr0.5 \ projectruns/nodule \ nameexp1参数说明model选yolov8s是精度和速度的折中显存紧张换yolov8n追求精度换yolov8m。imgsz640是默认值肺结节在 CT 里往往只占几十像素可以试imgsz1024但显存和耗时都会涨。mosaic0.5比默认的 1.0 低因为医学影像拼接过多会引入不真实的解剖结构反而干扰学习。degrees10和fliplr0.5是轻量几何增强肺结节对翻转和轻微旋转不敏感可以放心用。patience30是早停验证指标 30 轮不涨就停省时间。4.3 训练中该盯哪些指标重点看三个metrics/mAP50、metrics/mAP50-95和每类的precision/recall。肺结节检测里 recall 通常比 precision 更重要漏检一个结节的代价远大于多报一个。如果 recall 偏低先查标注框是不是太小被过滤了YOLO 默认会忽略宽或高小于 2 像素的框。如果 mAP 一直上不去用验证集跑一次预测把预测框和真值框画在同一张图上肉眼看看是漏检还是定位不准比盯着数字猜有效得多。5. 避坑与排查肺结节 YOLO 训练里最常见的五个翻车点5.1 现象训练 loss 正常下降但 mAP 始终接近 0原因data.yaml里的names顺序和标签文件里的 class id 对不上或者标签根本没被读到。解决先确认labels/train下的 txt 和images/train下的图片同名再随便打开一个 txt 看 class id 是不是 0。如果 txt 是空的说明转换时类别名没匹配上CLASSES回去检查 VOC 里的name字段。5.2 现象训练几轮后 loss 突然变成 nan原因学习率过大或某个批次的框坐标异常。医学数据里偶尔有宽高为 0 的退化框归一化后参与损失计算会炸。解决在转换脚本里加一行过滤if bw 0 or bh 0: continue同时把lr0从 0.01 降到 0.001 试一次。如果还炸把batch调小排除个别坏样本。5.3 现象验证集 mAP 很高但拿新数据预测全是误检原因数据泄漏同一病人的切片跨了训练集和验证集。解决按第 3 章的脚本重新按病人 ID 切分切完做一次 id 交集校验。这个坑最隐蔽因为指标好看人容易放松警惕等部署才发现问题。5.4 现象小节点小于 10 像素几乎全漏检原因YOLO 的下采样倍率导致小目标特征在深层特征图上消失。解决把imgsz提到 1024或者改用带 P2 检测层的模型配置。另一个办法是把大图裁成小块再训练让结节在单块里的相对尺寸变大。这三种我都试过提 imgsz 最省事裁块效果最好但工程量大。5.5 现象训练速度异常慢GPU 利用率只有 30%原因数据加载成了瓶颈通常是图片太大或磁盘 IO 慢。解决先把图片统一缩放到训练尺寸再存一份别让 dataloader 每次现场 resizeworkers调到 CPU 核数的 0.75 左右如果数据在机械盘上拷到 SSD 再跑。这三步做完GPU 利用率一般能上到 80% 以上。6. 进阶技巧把 5000 张图的价值榨干数据量固定的时候提升效果靠的是「让每张图被更充分地学」。我常用的两个手段一个是难例挖掘一个是交叉验证。难例挖掘的做法是第一轮训练完用模型在训练集上跑预测把漏检和误检的图挑出来人工复核标注后加入下一轮训练。肺结节里最难的是贴着血管或胸膜的结节这类样本往往只占 5%但贡献了大部分错误把它们找出来单独加权mAP 提升比调参明显。交叉验证则是把病人按 5 折切每折轮流做验证集最后把 5 个模型的预测做集成。5000 张图按病人切可能只有几百个病人单次切分的验证集方差很大5 折能给出更稳的指标估计也能顺带产出 5 个模型做集成召回率通常能再涨两三个点。下面这个脚本用来统计每折的类别分布确保切分后各折正负样本比例接近# cv_stats.py # 统计 K 折切分后每折的框数量检查分布是否均衡 import os def count_boxes(label_dir): total 0 for f in os.listdir(label_dir): if f.endswith(.txt): with open(os.path.join(label_dir, f)) as fp: total len([l for l in fp if l.strip()]) return total for fold in range(5): n count_boxes(fdataset/fold{fold}/labels/val) print(ffold{fold} val boxes: {n})逻辑说明遍历每折验证集的标签目录统计非空行数即框总数。如果某折框数明显偏离均值说明该折病人构成特殊要么重新切要么在报告指标时注明。参数上没什么可调的纯粹是个体检脚本。最后说个习惯我每次拿到新数据集第一件事不是跑训练而是花半小时把标签可视化一遍随机抽 20 张图把框画上去看。这半小时能提前发现坐标系错位、类别错标、图片和标签不对应这三类问题比训练跑完再回头查省太多时间。肺结节数据尤其如此标注质量参差不齐肉眼过一遍心里才有底。希望帮到你。本文还有配套的精品资源点击获取