☰
Kvasir-SEG息肉检测数据集:YOLO格式、可视化脚本与避坑指南
2026/10/9 11:08:41 网站建设 项目流程

简介:一套可直接用于YOLO目标检测训练的息肉检测数据集,面向计算机视觉初学者及医疗影像检测研究者,免去数据采集、清洗与格式转换的繁琐工作。压缩包内共2000个文件,主体为999张jpg图像与一一对应的1000个txt标签文件,另提供1个Python可视化脚本;整体大小约57MB,分为训练集800张、验证集200张。数据为单类别“息肉”,并随包提供类别class文件,标注采用YOLO相对坐标格式,边界框完整,每张图像均有清晰目标。图像分辨率覆盖332x487至1920x1072,均为RGB大图,适用于内镜息肉识别的模型训练与验证。附带脚本可直接运行,随机传入一张图片即可绘制边界框并保存到当前目录,便于直观检查标注效果。目前已有315人学习下载,适合需要标准格式数据集快速搭建检测训练流程或开展息肉识别实验的开发者。

1. 拿到一块能直接开训的 YOLO 息肉检测数据:Kvasir-SEG 的目录、标注与可视化脚本

做检测训练最怕的不是模型效果差,而是数据本身有问题还浑然不觉。我拿到一份 Kvasir-SEG 息肉检测数据集,1 个类别、训练集 800 张、验证集 200 张、压缩后 57 MB,每张图都配好了 YOLO 格式的 txt 标签,还带一个无需改参数就能跑的可视化脚本——按 YOLO 项目目录保存,放进去就能直接开训。这套资源适合三类人:刚开始接触目标检测、想练手数据全流程的学生;跑通 YOLOv5/v8 训练链路但缺一份干净标注的从业者;以及只想在医疗图像小样本上快速做出一个可演示的 baseline、不想自己标框的开发者。它帮你省掉的不是训练时间,而是最烦人的数据准备阶段。

2. 目录结构与 YOLO 标注格式:归一化坐标、train/val 划分与验证方法

这份数据的目录结构是典型的 YOLO 项目组织方式,所有图片和标签按训练、验证两个分支存放,下载解压后不需要额外整理。动手训练之前先把结构和格式吃透,后面所有脚本和调参都建立在这套约定上。

2.1 目录树与文件对应关系:train/val 下 images 和 labels 怎么对上

解压后的数据目录大致如下:

datasets/ ├── images/ │ ├── train/ # 800 张 jpg │ └── val/ # 200 张 jpg ├── labels/ │ ├── train/ # 800 个 txt │ └── val/ # 200 个 txt └── classes.txt # 类别文件,第一行是 polyp

图片和标签是一一对应的,比如cju7eea9b2m0z0801ynqv1fqu.jpg对应的标签文件就是同名主名、同扩展名规则下的cju7eea9b2m0z0801ynqv1fqu.txt。YOLO 系列的 DataLoader 默认就按这个规则找标签:图片在images/下,标签在labels/下,主名一致,训练时不需要你手工配对。

我一般拿到数据集的第一件事,不是打开代码,而是先打印一遍文件数量,确认两边一一对应:

for split in train val; do echo "$split imgs: $(ls datasets/images/$split | wc -l)" echo "$split labels: $(ls datasets/labels/$split | wc -l)" done

如果数量对不上,通常是标签目录里混入了残留文件,或者某张图没有对应 txt。这种问题越早发现越省事,等训练跑到一半才报FileNotFoundError,排查成本会高得多。

这里面有个容易被忽略的设计细节:图片名是随机字符 ID,比如cju0sr5ghl0nd08789uzf1raf,中间没有空格、没有中文,这对脚本处理非常友好,用glob通配也不会踩到编码或特殊字符的坑。我见过有人嫌名字乱想改成 0001.jpg 这种顺序命名,结果把全部标签路径破坏了一遍,属于典型的画蛇添足。

2.2 标注格式:x_centre、y_centre、w、h 不只是四个数字

打开任意一个标签 txt,内容格式是:

0 0.438471 0.421621 0.291626 0.185402

这行一共 5 个字段,全部是归一化相对坐标:

  • 第 1 个字段是类别索引,这里只有 0 一种,对应classes.txt第一行的polyp;
  • 第 2、3 个字段是x_centre和y_centre,表示目标框中心点在图片上的相对位置;
  • 第 4、5 个字段是w和h,表示目标框宽度和高度占图片宽高的比例。

用大白话讲,YOLO 标注并不是你眼睛看到的像素坐标,而是告诉你“目标中心在图的百分之多少处、框宽占全图的百分之多少”。这个设计让同一套标注在不同分辨率输入下都能直接复用,但也意味着你在画框、算 IoU、做数据清洗时,必须把归一化坐标还原成像素坐标,这一步是所有后续操作最容易翻车的地方。

还原公式很简单,但值得单独写出来:

img_w, img_h = 1920, 1072 # 假设某张真实图片的宽高 x_c, y_c, bw, bh = 0.438471, 0.421621, 0.291626, 0.185402 left = int((x_c - bw / 2) * img_w) top = int((y_c - bh / 2) * img_h) right = int((x_c + bw / 2) * img_w) bottom = int((y_c + bh / 2) * img_h) print(left, top, right, bottom)

手算验证一下:归一化(0.5, 0.5, 0.3, 0.2)配一张 1280x720 的图,left = (0.5 - 0.15) * 1280 = 448,top = (0.5 - 0.1) * 720 = 288,right = (0.5 + 0.15) * 1280 = 832,bottom = (0.5 + 0.1) * 720 = 432。框中心正好在(640, 360),说明换算正确。这里必须强调一个常见误解:x_centre是中心点,不是左上角,写成左上角公式后所有框都会整体偏移半个框宽。

2.3 为什么这份数据不需要预处理就能挂进 YOLO 训练链路

YOLOv5 和 YOLOv8 的训练代码里,data.yaml是入口配置。这份资源的数据组织方式正好匹配默认行为,只需要写一个简单的配置文件:

path: ./datasets train: images/train val: images/val nc: 1 names: - polyp

参数说明:path指向数据集根目录;train和val是相对路径;nc=1表示只有 1 个类别;names列表的顺序必须和classes.txt里的顺序一致。类别名称可以随便起,但索引顺序不能乱,否则训练后的混淆矩阵和 mAP 计算就会“看似正常实则错位”。

这份数据最省心的地方在于,它跳过了三个常见的数据准备步骤:不需要把分割掩码转成边界框、不需要做 VOC/COCO 与 YOLO 格式互转、不需要清洗图片名里的特殊字符,解压完就能训。用这类下载即用的数据集时,我通常会先跑一次单卡短训练做烟雾测试,比如 10 epoch,确认 loss 能从初始值往下掉,再上完整训练,这个习惯能帮你避开“数据集本身有毒但训练半天才发现”的尴尬。

3. 数据可视化脚本实战:随机抽图、画框保存与参数调优

数据值不值得信,最快的方法是把它画出来看。这份资源自带一个可视化脚本,核心逻辑是随机抽一张图、读同名 txt、把归一化坐标还原成像素框、在图上画矩形并保存到当前目录。别小看这个脚本,它能一次性验证三件事:图片能否被正常解码、标签文件和图片是否对得上、归一化坐标转换是否准确。

3.1 脚本逻辑链路:从随机抽图到输出可视化结果

脚本的工作流程可以拆成五步:

  1. 从指定images/val目录里随机选一张 jpg;
  2. 用图片主名去labels/val找同名 txt;
  3. 读取 txt 每一行,解析出 5 个字段;
  4. 用图片的真实宽高把归一化坐标还原成像素框;
  5. 用 OpenCV 画框、标注类别,保存到当前目录。

默认从验证集抽图是个好设计。训练集有 800 张,抽中某类特殊样本的概率摊薄了;验证集只有 200 张,随机抽 1~3 张更容易覆盖典型样本,肉眼检查起来效率更高。

3.2 一个可以直接运行的参考脚本

下面的脚本按前述流程实现,目录路径改成你实际解压的位置就能跑:

import os import random import cv2 IMG_DIR = "datasets/images/val" # 从 val 抽图,也可以改成 train LBL_DIR = "datasets/labels/val" OUT_DIR = "." # 保存到当前目录 NUM_SAMPLES = 1 # 随机抽几张,默认 1 def draw_boxes(img_path: str, lbl_path: str, out_path: str) -> None: img = cv2.imread(img_path) if img is None: raise FileNotFoundError(f"无法读取图片: {img_path}") h, w = img.shape[:2] # 必须用真实图片尺寸作为分母 boxes = [] with open(lbl_path, "r", encoding="utf-8") as f: for line in f: parts = line.strip().split() if len(parts) != 5: continue # 跳过异常行,防止脏数据中断 cls_id, x_c, y_c, bw, bh = map(float, parts) left = int((x_c - bw / 2) * w) top = int((y_c - bh / 2) * h) right = int((x_c + bw / 2) * w) bottom = int((y_c + bh / 2) * h) boxes.append((cls_id, left, top, right, bottom)) for cls_id, left, top, right, bottom in boxes: cv2.rectangle(img, (left, top), (right, bottom), (0, 255, 0), 2) cv2.putText(img, f"polyp:{cls_id}", (left, max(0, top - 5)), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 1) cv2.imwrite(out_path, img) print(f"已保存: {out_path} (图尺寸 {w}x{h}, 框数 {len(boxes)})") def main(): os.makedirs(OUT_DIR, exist_ok=True) names = [f for f in os.listdir(IMG_DIR) if f.lower().endswith(".jpg")] sample = random.sample(names, min(NUM_SAMPLES, len(names))) for name in sample: base = os.path.splitext(name)[0] lbl_path = os.path.join(LBL_DIR, base + ".txt") draw_boxes(os.path.join(IMG_DIR, name), lbl_path, os.path.join(OUT_DIR, "vis_" + base + ".jpg")) if __name__ == "__main__": main()

逻辑说明,按照实际运行顺序展开:

先看main()。它负责列出目录下所有 jpg、随机抽样、拼接标签路径。random.sample(names, min(NUM_SAMPLES, len(names)))这里的min是为了防止抽样数量大于文件总数时抛异常。文件名用os.path.splitext去掉扩展名再拼.txt,不会出现大小写或扩展名不一致的问题。

再看draw_boxes()。cv2.imread读取失败时返回None,脚本直接抛异常,避免画出一张全黑图还误以为成功。h, w = img.shape[:2]是全部逻辑里最关键的一行,因为这份数据里图片尺寸跨度很大,从 332x487 到 1920x1072 都有,任何写死宽高的做法都会让框偏掉。坐标换算公式统一按“中心点减半宽”处理,和上一章讲的还原公式一致。

参数说明如下:

  • IMG_DIR和LBL_DIR分别指向图片和标签目录,必须一一对应;改成datasets/images/train就能抽训练集的图。
  • OUT_DIR默认是当前目录,建议改成./vis_out之类,避免可视化结果污染原始数据目录。
  • NUM_SAMPLES控制抽图数量,我一般抽 5 张做一轮检查,覆盖不同分辨率样本。
  • 标签编码用utf-8,Windows 环境下脚本文件本身如果是 GBK,中文注释可能报错,可以把注释去掉或转换文件编码。

3.3 跑通后怎么检查结果:什么样的图算合格

可视化结果不是“能画出框”就够了,合格的产出要同时满足三个条件:框是矩形且紧贴病灶边缘;框没有明显溢出图片边界;同一张图每次画出的框位置一致。如果发现框整体向上或向左漂移,或者框明显比病灶大一圈,基本都是坐标还原公式或分母用错的问题。

实际操作时我会固定抽查两类样本:一张小分辨率图,比如 332x487,确认框占整图比例接近归一化值;一张大分辨率图,比如 1920x1072,看框在大图上是否还贴合。一张 640x640 缩略图里看起来正常的框,放大到原图后常常能暴露边界溢出问题。这个抽查习惯能提前拦住大多数标注质量问题,比训练完再回头查省太多时间。

4. 避坑指南:跑这份数据和可视化脚本时最容易翻车的五个问题

以下五条全是实际跑这类“下载即用”数据集时踩过的坑,每一条都按现象、原因、解决三个步骤拆开讲,方便你遇到同样问题时直接对照排查。

4.1 坑一:可视化结果“框偏了”,但看着又不太离谱

现象:部分图片上画出的框和病灶有明显错位,不是完全对不上,而是差个一两成宽度的偏移;另一些图又完全正常。

原因:最常见的是脚本里把分母写成了固定值,比如640或416。这份数据的图片分辨率从 332x487 到 1920x1072 参差不齐,归一化坐标乘以固定宽高后,只有恰好等于该尺寸的图才显示正确,其余全部偏移。第二个可能原因是代码混淆了x_centre和左上角坐标,框中心变成了框原点,整体漂移半个框宽。

解决:统一改用img.shape[:2]取真实宽高。拿到任何新数据集时,先跑一段脚本把图片分辨率极值打印出来,确认范围后再写转换逻辑:

import cv2 import os for root, _, files in os.walk("datasets/images"): for f in files: img = cv2.imread(os.path.join(root, f)) if img is not None: h, w = img.shape[:2] print(f, w, h)

之后每张图都用自身宽高做分母,问题自然消失。

4.2 坑二:训练时报标签文件找不到,但目录里明明有

现象:训练脚本运行到某个 epoch 时报FileNotFoundError,提示某个标签 txt 不存在,手动去目录里查,文件明明就在那里。

原因:不是文件缺失,而是拼接路径时的扩展名不一致。比如图片是.jpg,标签写成.jpeg.txt,或者遍历时用了大小写敏感的匹配方式。另一个隐蔽原因是文件名含有不可见字符,比如复制粘贴时混入了回车或空格。

解决:不要手工拼路径,用os.path.splitext(name)[0] + ".txt"这种基于图片主名的拼接方式。保持这份数据原有的随机 ID 命名,不要自己重命名图片或标签,重命名是这类问题最大的触发源。数据量不大时,直接用fc或diff对比两个目录的文件清单,一分钟就能定位到缺失项。

4.3 坑三:cv2.imread 返回 None,文件路径和文件名都没问题

现象:在无显示器的 Linux 服务器或 Docker 容器里跑可视化脚本,cv2.imread一直返回None,程序抛“无法读取图片”。

原因:这类机器上没装桌面组件,OpenCV 默认走 GTK/X11 显示通道,虽然读图本身不依赖显示器,但某些版本在无 GUI 环境下解码器会异常。另一种常见情况是安装的是opencv-python而不是opencv-python-headless,两者在无桌面环境中行为不同。

解决:无界面机器上优先装 headless 版本:

pip install opencv-python-headless

如果不想折腾 OpenCV,也可以用 PIL 读图再转 numpy 数组,能同时绕开 GUI 依赖和中文路径两个问题:

import numpy as np from PIL import Image img = cv2.cvtColor(np.array(Image.open(img_path).convert("RGB")), cv2.COLOR_RGB2BGR)

这个方案我在 Docker 环境里救过两次,建议可视化脚本里直接写成 PIL 优先。

4.4 坑四:nc 填成 2,训练前几个 epoch 的 loss 一直高位震荡

现象:训练启动后 loss 降不下去,mAP 曲线像过山车一样上下乱跳,验证集准确率在不同 epoch 之间波动极大,整个训练过程看起来像在撞运气,被我形容为“玄学波动”。

原因:把“图片里只有一个目标类别”误解成“数据集分两个 split 所以类别数也是 2”,或者从别的项目抄了data.yaml忘记改nc。YOLO 的nc指的是类别数量,不是训练集或验证集的个数,这份数据标签里只出现类别 0,nc=1才对。

解决:打开classes.txt数一遍行数,把nc固定写 1。写data.yaml时养成自查习惯:nc、names列表长度、标签文件里的最大类别编号三者必须一致。顺带看一眼验证集标签里有没有编号为 0 以外的行,如果有说明标注本身有隐患。

4.5 坑五:觉得训练集只有 800 张太小,猛开数据增强后 mAP 反而下降

现象:拿到数据后嫌 800 张太少,把旋转、裁剪、HSV 抖动、翻转全开,训练 50 epoch 后 mAP 比默认增强还低,小目标漏检明显变多。

原因:这份数据的特点是分辨率跨度大,大图上息肉可能只占一个小角落,小图上息肉几乎撑满整图。强几何增强例如大角度旋转会让病灶比例严重失真,模型学到的不是病灶特征而是增强伪影;过度 HSV 抖动又让背景纹理变得更强,小目标更不易区分。57 MB 看似小,但息肉图像里大片黑色或灰色背景压缩率高,数据量并不能只看文件大小。

解决:初始阶段只用默认 mosaic 加轻度缩放,scale控制在 0.5~1.5 之间,关闭或保持默认fliplr,先跑出基线再逐步加增强项。判断增强是否有效的标准只有一个:验证集 mAP 是否真实提升。口径不提升就回退配置,这个坑我交过学费之后,再也不敢拿 800 张图做激进增强实验。

5. 进阶验证:用 mAP 曲线和框尺寸分布判断数据是否值得训练

5.1 先跑 50 epoch 的默认基线,看 mAP@0.5 是否能爬到 0.7 以上

拿到这份数据后,我建议你做的第一件正经事不是调参,而是用默认参数跑一个 50 epoch 的基线:

python train.py --data datasets.yaml --epochs 50 --batch 16 --imgsz 640

训练过程中盯两个信号:loss 曲线是否前 10 个 epoch 明显下降;mAP@0.5 是否在 20 epoch 前后进入爬升阶段。如果 50 epoch 后 mAP@0.5 始终在 0.4 以下,大概率不是模型能力问题,而是标注坐标或类别编号有系统性错误。一份干净的单类别小数据,默认参数下通常能跑到 0.7 以上。

5.2 用框尺寸分布判断是否需要多尺度训练

由于图像分辨率跨度大,同一个归一化w值在不同图上对应的像素宽度能差好几倍。我习惯跑一段快速统计看归一化宽度的分位数:

import glob import numpy as np ws = [] for f in glob.glob("datasets/labels/val/*.txt"): for line in open(f): parts = line.strip().split() if len(parts) == 5: ws.append(float(parts[3])) ws = np.array(ws) print("归一化宽度分位:", np.percentile(ws, [10, 25, 50, 75, 90]))

如果中位数在 0.15 到 0.3 之间且分布比较集中,使用 640 的输入尺寸就能覆盖大多数目标;如果大量框的w低于 0.1,说明小目标占比高,应该考虑把imgsz提到 960 或开启多尺度训练,并在推理时使用更大输入尺寸。这个判断比盲目堆数据更有效,也是我评估一份数据集是否值得深挖的第一道工序。

从那以后我每次拿到下载的数据都会强制走一遍“可视化 → 数量核对 → 跑默认基线 → 看框分布”这套流程,肉眼确认样本没问题才让训练循环启动,这个习惯帮我避开过不少表面漂亮实则坐标错乱的数据。希望帮到你。

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

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

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

立即咨询