简介:这份YOLO肺结节目标检测数据集面向医学影像检测方向的算法学习者与研究者,提供真实场景下的高质量肺结节图片,可用于YOLO系列模型的训练与验证。资源包共2000个文件,以1986个xml标注文件为主,另含少量html说明文档、txt列表与py脚本,压缩包约77.56MB,标注由labelimg完成,同时提供voc、coco和yolo三种格式标签,分别存放于不同文件夹,可直接对接主流检测框架。配套内容还包含数据集划分脚本,可按需生成训练集、验证集与测试集,并附有Windows与Linux环境搭建及训练教程,帮助读者从环境配置到模型训练完整跑通流程。目前已有403人学习下载,适合希望快速上手肺结节检测任务、减少数据准备成本的读者参考使用。
1. 从一批肺结节 CT 片子说起:这套 YOLO 数据集到底能省掉多少标注功夫
手里有一批肺部 CT 影像,想跑一个肺结节目标检测模型,最耗时的环节从来不是调网络结构,而是把几千张片子的结节位置一个个框出来、再转成训练框架认的格式。这套 YOLO 肺结节目标检测数据集就是冲着这个痛点来的:5000 张真实场景图片,用 labelImg 标注,同时给出 VOC(xml)、COCO(json)、YOLO(txt)三种格式标签,分文件夹放好,拿到手就能直接喂给 YOLO 系列。它还附了环境搭建、训练教程和三个数据集划分脚本,Windows 和 Linux 两套流程都覆盖了。适合谁?刚入门 YOLO 想找个真实医学场景练手的人,或者手头有检测任务、想先拿一份标注质量过关的数据把 pipeline 跑通再换自己数据的从业者。下面我按「数据长什么样 → 怎么划分 → 怎么训 → 坑在哪」拆一遍。
2. 三种标签格式与目录结构:先搞清 VOC、COCO、YOLO 各存了什么
拿到压缩包别急着解压完就开训,先把标签格式和目录结构理清楚,否则后面划分脚本一跑,路径对不上,报错能让你查半天。这一章讲清楚三种格式的差异、各自适用场景,以及怎么用脚本把它们读进来验证一遍。
2.1 VOC、COCO、YOLO 三种标注格式的本质区别
同样是「一个框」,三种格式记录方式完全不同,这也是很多人第一次接触时最容易懵的地方。
VOC 格式是每张图对应一个同名 xml 文件,框的信息写在<object>节点里,坐标是左上角xmin,ymin和右下角xmax,ymax的绝对像素值,类别名写在<name>里。它的好处是可读性强,labelImg 默认就存这个,缺点是文件多、解析慢。
COCO 格式是整份数据集一个 json,里面images、annotations、categories三个大数组,框的坐标是[x, y, width, height]绝对像素,category_id是数字。它适合做评测和跨框架迁移,但单看 json 很难肉眼核对。
YOLO 格式是每张图一个同名 txt,每行一个目标,格式是class_id x_center y_center width height,后四个全是归一化到 0~1 的相对值。它最紧凑,训练时读取最快,但归一化坐标一旦算错,框会整体偏移,而且肉眼不容易发现。
| 格式 | 存储方式 | 坐标类型 | 类别表示 | 典型用途 |
|---|---|---|---|---|
| VOC | 每图一个 xml | 绝对像素 | 字符串名 | labelImg 原生、可读核对 |
| COCO | 单 json | 绝对像素 | 数字 id | 评测、跨框架迁移 |
| YOLO | 每图一个 txt | 归一化相对值 | 数字 id | YOLO 系列直接训练 |
提示:三种格式的类别顺序不一定一致。VOC 里是字符串名,COCO 和 YOLO 里是数字 id,转换或混用时务必先确认
classes.txt或categories的顺序,否则会出现「框对了但类别全错」的玄学现象。
2.2 用一段脚本把三种格式读进来做一致性校验
在正式划分之前,我习惯先写个小脚本,把三种格式各读一遍,确认图片数量、标注数量、类别集合能对上。这一步花五分钟,能省掉后面几小时的排查。
import os import json import xml.etree.ElementTree as ET # 三个标签目录,按实际解压路径改 VOC_DIR = "Annotations" # xml 所在 COCO_JSON = "annotations.json" # coco 单文件 YOLO_DIR = "labels" # txt 所在 IMG_DIR = "images" # 图片所在 # 1. 统计图片 imgs = [f for f in os.listdir(IMG_DIR) if f.lower().endswith((".jpg", ".png", ".jpeg"))] print("图片数量:", len(imgs)) # 2. 统计 VOC 标注与类别 voc_classes = set() voc_count = 0 for f in os.listdir(VOC_DIR): if not f.endswith(".xml"): continue voc_count += 1 tree = ET.parse(os.path.join(VOC_DIR, f)) for obj in tree.getroot().findall("object"): voc_classes.add(obj.find("name").text) print("VOC 标注文件数:", voc_count, "类别:", voc_classes) # 3. 统计 COCO 类别 with open(COCO_JSON, "r", encoding="utf-8") as fp: coco = json.load(fp) coco_classes = {c["id"]: c["name"] for c in coco["categories"]} print("COCO 类别:", coco_classes, "标注数:", len(coco["annotations"])) # 4. 统计 YOLO 类别 id yolo_ids = set() yolo_count = 0 for f in os.listdir(YOLO_DIR): if not f.endswith(".txt"): continue yolo_count += 1 with open(os.path.join(YOLO_DIR, f)) as fp: for line in fp: if line.strip(): yolo_ids.add(int(line.split()[0])) print("YOLO 标注文件数:", yolo_count, "类别 id:", yolo_ids)逻辑说明:先数图片,再分别数三种标签,最后把类别集合打印出来对比。参数上,VOC_DIR、COCO_JSON、YOLO_DIR、IMG_DIR四个路径按你解压后的实际目录改,别照抄。跑完如果发现 VOC 类别是{'nodule'},COCO 是{0: 'nodule'},YOLO 的 id 只有{0},那就说明三套标签是一致的,可以放心往下走。如果数量对不上,先别划分,回去查是不是有图片漏标或者标签文件名和图片名不匹配。
2.3 目录结构建议:划分前先规整成标准布局
原始压缩包里三种格式是分文件夹放的,但划分脚本通常期望一个更规整的布局。我一般会整理成这样再跑脚本:
dataset/ ├── images/ # 所有图片 ├── Annotations/ # VOC xml ├── labels/ # YOLO txt ├── annotations.json # COCO ├── classes.txt # 类别名,一行一个 └── ImageSets/ # 划分脚本输出目录classes.txt很关键,很多划分和训练脚本都靠它确定类别数和顺序。肺结节这个场景一般就一个类,写一行nodule即可。整理好之后,图片名和标签名必须严格一一对应(除了扩展名),这是后面所有脚本能跑通的前提。
3. 三个划分脚本怎么用:训练集、验证集、测试集的切分逻辑与参数
数据集划分看着简单,其实是最容易埋雷的一步。划分比例、是否分层、随机种子、输出路径,任何一个没处理好,都会导致训练时验证指标虚高或者直接找不到文件。这一章把压缩包里三个划分脚本的用途、调用方式和参数讲透。
3.1 三个脚本分别解决什么问题
压缩包里给了三个划分脚本,名字不同,用途也不同,别混着用。
第一个是「训练集、验证集、测试集划分脚本(图片标签划分写入新文件夹).py」,它做的是三路划分,并且会把图片和标签实际复制到新的 train/val/test 文件夹里,适合你想物理隔离三份数据的情况。
第二个是「训练集、验证集划分脚本(图片标签划分写入新文件夹).py」,只切 train 和 val 两路,同样复制文件,适合数据量不大、不需要单独测试集的场景。
第三个是「split_train_val生成ImageSets下txt文件划分脚本.py」,它不复制图片,只在ImageSets下生成train.txt、val.txt这类索引文件,里面写图片路径。YOLO 官方训练流程更常用这种索引方式,省磁盘、改划分也方便。
选哪个取决于你的训练框架怎么读数据。用 Ultralytics 的 YOLO,通常用索引 txt 就够了;如果你用的是自己写的 dataloader,可能更习惯物理分文件夹。
3.2 三路划分脚本的调用与参数调整
以三路划分脚本为例,核心逻辑是读所有图片名,打乱后按比例切分,再把图片和对应标签复制过去。调用前先看脚本头部的配置区。
# 划分脚本核心参数区(按实际脚本变量名对照修改) import os import random import shutil random.seed(42) # 固定随机种子,保证可复现 train_ratio = 0.7 # 训练集比例 val_ratio = 0.2 # 验证集比例 # 剩下 0.1 自动归测试集 IMG_SRC = "images" # 原图目录 LABEL_SRC = "labels" # YOLO txt 目录 OUT_ROOT = "split_dataset" # 输出根目录 for split in ["train", "val", "test"]: os.makedirs(os.path.join(OUT_ROOT, "images", split), exist_ok=True) os.makedirs(os.path.join(OUT_ROOT, "labels", split), exist_ok=True) names = [os.path.splitext(f)[0] for f in os.listdir(IMG_SRC) if f.lower().endswith((".jpg", ".png", ".jpeg"))] random.shuffle(names) n = len(names) n_train = int(n * train_ratio) n_val = int(n * val_ratio) splits = { "train": names[:n_train], "val": names[n_train:n_train + n_val], "test": names[n_train + n_val:] } for split, items in splits.items(): for name in items: # 复制图片,扩展名按实际改 shutil.copy(os.path.join(IMG_SRC, name + ".jpg"), os.path.join(OUT_ROOT, "images", split, name + ".jpg")) # 复制标签 shutil.copy(os.path.join(LABEL_SRC, name + ".txt"), os.path.join(OUT_ROOT, "labels", split, name + ".txt")) print(split, len(items))逻辑说明:random.seed(42)是后悔药,固定种子后每次划分结果一致,方便复现实验。train_ratio和val_ratio按需改,医学数据量不大时常见 7:2:1 或 8:1:1。IMG_SRC、LABEL_SRC、OUT_ROOT三个路径按你的目录改。脚本跑完会打印每份的数量,三个数加起来应该等于总图片数,对不上就说明有图片没有对应标签,被跳过了。
参数上要特别注意扩展名。脚本里写死了.jpg,如果你的图片是.png,复制那一步会直接报文件不存在。稳妥做法是把扩展名也做成可配置,或者先统一转成 jpg。
3.3 生成 ImageSets 索引的脚本与 YOLO 训练配置对接
如果你走索引路线,用第三个脚本生成train.txt、val.txt。它不复制文件,只写路径,速度快很多。
# 生成的 ImageSets 目录结构 ImageSets/ ├── train.txt # 每行一个图片路径,如 images/xxx.jpg ├── val.txt └── test.txt生成之后,在 YOLO 的data.yaml里对接:
path: /abs/path/to/dataset train: ImageSets/train.txt val: ImageSets/val.txt test: ImageSets/test.txt nc: 1 names: ['nodule']逻辑说明:path是数据集根目录,train/val/test指向索引 txt,nc是类别数,肺结节单类就写 1,names顺序必须和classes.txt一致。这里最常见的翻车是路径写成相对路径但训练时工作目录变了,导致找不到文件。我一般直接写绝对路径,省心。
注意:索引 txt 里如果写的是相对路径,是相对于
path还是相对于运行目录,不同版本行为可能不同。拿不准就写绝对路径,或者先跑一个 epoch 看能不能读到数据。
3.4 划分比例与随机种子的取舍
划分比例没有标准答案,但有几个经验值。数据量 5000 张这个级别,7:2:1 比较稳,验证集留 1000 张足够评估。如果类别极不平衡(比如小结节样本很少),纯随机划分可能导致某个子集里几乎没有正样本,这时候要做分层抽样,按类别比例切。压缩包里的脚本是纯随机,遇到不平衡场景需要自己改。
随机种子建议固定,尤其是你要对比不同模型或不同超参时,划分必须一致,否则指标波动你分不清是模型变了还是数据变了。我见过有人每次训练都重新随机划分,然后纳闷为什么同样的配置结果差好几个点,这就是典型的没固定种子。
4. 环境搭建与训练:Windows 和 Linux 两套流程的关键差异
压缩包里给了 Windows 和 Linux 两套环境搭建和训练教程,还有 Ubuntu 安装教程。这一章不重复教程里的每一步,而是把两套流程里最容易出问题的地方挑出来讲,尤其是 CUDA、PyTorch、Ultralytics 版本匹配这几个老生常谈但每次都有人栽的环节。
4.1 Windows 环境搭建:CUDA 与 PyTorch 版本对齐
Windows 上搭 YOLO 环境,翻车重灾区是 CUDA 版本、显卡驱动、PyTorch 版本三者不匹配。正确顺序是先看显卡驱动支持的最高 CUDA 版本,再选对应的 PyTorch。
# 1. 查看显卡驱动支持的 CUDA 版本 nvidia-smi # 右上角 CUDA Version 就是驱动支持的上限 # 2. 建虚拟环境(conda 为例) conda create -n yolo python=3.10 -y conda activate yolo # 3. 按 nvidia-smi 显示的版本装 PyTorch # 假设显示 CUDA 12.1,去 PyTorch 官网找对应命令,例如: pip install torch torchvision --index-url https://download.pytorch.org/whl/cu121 # 4. 验证 GPU 可用 python -c "import torch; print(torch.cuda.is_available())"逻辑说明:nvidia-smi显示的 CUDA Version 是驱动能支持的最高版本,不是你必须装的版本,装低一点通常也能用。torch.cuda.is_available()返回 True 才算成功。如果返回 False,八成是装成了 CPU 版或者版本不匹配,卸载重装。虚拟环境一定要建,别在 base 里折腾,否则依赖冲突能让你重装系统。
4.2 Linux/Ubuntu 环境搭建:权限与依赖的坑
Linux 上流程类似,但多了权限和系统依赖的问题。Ubuntu 装完显卡驱动后,同样先nvidia-smi确认。常见坑是 conda 装完 PyTorch 后torch.cuda.is_available()为 False,原因往往是驱动没装好或者装了开源驱动 nouveau。
# 确认驱动 nvidia-smi # 如果没输出或报错,检查是否装了官方驱动 ubuntu-drivers devices # 按推荐版本安装,例如: sudo apt install nvidia-driver-535 # 装完必须重启 sudo reboot逻辑说明:ubuntu-drivers devices会列出推荐驱动版本,按推荐的装最稳。装完驱动一定要重启,不重启nvidia-smi可能还是报错。另外 Linux 下如果用了 conda,注意LD_LIBRARY_PATH有时会干扰 CUDA 库加载,遇到诡异报错可以先unset LD_LIBRARY_PATH试试。
4.3 用案例配置改自己的数据集:data.yaml 与模型选择
教程里给的是案例配置,换成肺结节数据集主要改两处:data.yaml和模型配置文件里的类别数。
# data.yaml path: /abs/path/to/split_dataset train: images/train val: images/val test: images/test nc: 1 names: ['nodule']# 训练命令(以 Ultralytics YOLO 为例) yolo detect train data=data.yaml model=yolov8n.pt epochs=100 imgsz=640 batch=16逻辑说明:model用预训练权重起步,收敛快很多。imgsz是输入尺寸,肺结节在 CT 上通常不大,640 是常见起点,如果小结节多可以试 1024,但显存和速度要权衡。batch按显存调,爆显存就减半。epochs100 起步,看验证指标是否还在降再决定加不加。训练日志里重点看mAP50和mAP50-95,前者宽松后者严格,医学检测更该关注后者。
4.4 训练过程监控与中断恢复
训练跑起来后别干等,盯几个信号。loss 不降反升,可能是学习率太大或者标注有问题;mAP 一直上不去,可能是数据量不够或者类别定义有误。Ultralytics 默认会在runs/detect/train下存权重和日志,中断了可以用resume接着跑。
# 中断后恢复训练 yolo detect train resume model=runs/detect/train/weights/last.pt逻辑说明:resume会从last.pt恢复优化器状态和 epoch,比重新加载权重接着训更完整。前提是原来的runs目录还在。我一般训练前先确认磁盘空间够,权重文件加上日志,跑几百 epoch 也能占几个 G。
5. 避坑与排查:标注、划分、训练里最常见的五类翻车
这一章全是血泪经验,每条按「现象 → 原因 → 解决」写,遇到问题直接对号入座。
5.1 训练报错找不到标签文件
现象:训练启动后报No labels found或者images and labels count mismatch。
原因:图片名和标签名不一致,或者data.yaml里的路径指向了图片目录但标签在另一个目录,YOLO 默认按同名规则找标签。
解决:先跑第 2.2 节的一致性校验脚本,确认每张图都有同名 txt。如果标签和图片不在同一级目录,检查data.yaml的路径配置,必要时把标签和图片放到对应结构下。
5.2 框整体偏移或尺寸不对
现象:训练能跑,但预测出来的框位置明显偏,或者框特别大特别小。
原因:YOLO 格式要求归一化坐标,如果转换时用了绝对像素没除以宽高,或者 VOC 转 YOLO 时把xmax,ymax当成了宽高,框就会错。
解决:抽查几个 txt,确认后四个值都在 0~1 之间。如果大于 1,说明没归一化。VOC 转 YOLO 的正确公式是x_center=(xmin+xmax)/2/width,width=(xmax-xmin)/width,别搞反。
5.3 验证集指标虚高
现象:验证集 mAP 很高,但拿新数据一测就崩。
原因:划分时没有固定随机种子,或者训练集和验证集有重复图片,导致验证集泄漏。
解决:固定random.seed,划分后去重,确认 train 和 val 没有交集。另外检查是不是同一张图的不同增强版本同时进了训练和验证。
5.4 CUDA out of memory
现象:训练中途报显存不足。
原因:batch或imgsz太大,或者没释放上一次的缓存。
解决:先减batch,再考虑减imgsz。训练前torch.cuda.empty_cache()清一下。如果还是不够,用梯度累积模拟大 batch。
5.5 类别 id 从 1 开始导致全错
现象:训练不报错,但预测类别全是背景或者错位。
原因:VOC 转 YOLO 时类别 id 从 1 开始编号,而 YOLO 要求从 0 开始。
解决:转换时统一减 1,或者检查classes.txt和实际 txt 里的 id 是否从 0 开始。这个坑很隐蔽,因为训练不会报错,只是结果不对。
6. 进阶技巧:用预训练权重和分层抽样把肺结节检测效果再拉一档
数据划分和训练跑通只是及格线,想把肺结节检测做到能用,还有两个技巧值得花时间:一是用好预训练权重和迁移学习,二是针对小结节做分层抽样和尺寸适配。
先说预训练权重。肺结节数据集 5000 张,说多不多,从零训容易过拟合。常见做法是加载 COCO 预训练的 YOLO 权重,冻结 backbone 先训几轮,再解冻全量微调。冻结阶段学习率可以设大一点,解冻后调小。我一般分两段:前 20 epoch 冻结,lr0=0.01;后 80 epoch 解冻,lr0=0.001。这样收敛比从头训快,最终 mAP 通常也高几个点。
# 第一阶段:冻结 backbone yolo detect train data=data.yaml model=yolov8n.pt epochs=20 freeze=10 lr0=0.01 # 第二阶段:解冻微调 yolo detect train data=data.yaml model=runs/detect/train/weights/last.pt epochs=80 lr0=0.001freeze=10表示冻结前 10 层,具体层数看模型结构,backbone 一般在前十几层。这个参数不是越大越好,冻太多反而限制拟合能力。
再说分层抽样。肺结节有大有小,纯随机划分可能让验证集里全是大结节,指标好看但没意义。改进做法是按结节面积分档,每档按比例抽到 train/val/test。实现上先读每个 YOLO txt 算框面积,分箱后再切分。这样验证集能覆盖各种尺寸,指标更可信。
最后是输入尺寸。640 对小结节可能不够,结节在 CT 上可能只占几十像素,缩到 640 后更小。如果显存允许,试 1024 甚至 1280,配合rect训练减少填充。代价是速度慢,但医学检测对召回更敏感,值得。
从那以后我每次拿到新数据集,都强制先跑一遍一致性校验、固定种子划分、再抽查几个标注框的归一化值,这三步走完才开训。希望帮到你。
本文还有配套的精品资源,点击获取