ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

612张肠道息肉检测数据集:VOC/YOLO格式解析与YOLOv8训练实战

612张肠道息肉检测数据集:VOC/YOLO格式解析与YOLOv8训练实战 简介面向计算机视觉目标检测入门及医学影像AI研究者的肠道息肉检测数据集包含612张肠道内镜图像由labelImg人工绘制矩形框统一标注为单类别“xirou”共712个有效目标框可用于YOLO、Faster R-CNN、SSD等主流检测框架的训练与评估。资源同时提供Pascal VOC和YOLO两种标准格式每张jpg图片均配有对应的xml标注文件和txt标注文件便于直接按比例划分训练集与验证集免去自行编写转换脚本的工作。压缩包共1838个文件以612张jpg、612个xml和614个txt为主体整包仅14.63MB轻量易用特别适合快速搭建肠道息肉检测基线实验或做标注格式对照学习。需注意数据集保证标注准确合理但不附带任何模型权重或精度承诺适合关注数据预处理和标注规范的开发者参考。目前已有150人学习下载可用于目标检测课程设计、算法对比以及医学影像检测入门实践。1. 肠道息肉检测数据集612张图如何撑起一个医疗目标检测项目拿到「【目标检测数据集】肠道息肉检测数据集612张VOCYOLO格式.7z」这个压缩包你的第一反应可能是612张是不是太少了做医疗影像目标检测常见论文里动不动就是几千上万个样本。但实际做过内镜图像检测的工程师都清楚肠道息肉检测的难点从来不在数量而在样本质量和标注一致性。612张经过筛选的肠镜图像标注成VOC和YOLO两种格式足够支撑一次完整的模型选型验证也能让一个医疗AI原型项目从零跑到初步可用。这篇文章围绕这个数据集把从解压、格式核对、质量体检到训练配置的完整链路拆开讲适合刚入手医疗目标检测的算法工程师也适合需要快速评估数据集可用性的技术负责人。2. 解压与目录结构先搞清楚VOC和YOLO两种格式到底差在哪2.1 拿到.7z压缩包之后的第一步这个数据集的存储格式是.7z相比zip和rar7z在压缩医学影像这类重复度较高的图像数据时体积更小。但这也意味着你的机器上需要装7-Zip或者支持7z解压的工具。在Linux服务器上常见做法是用p7zip工具集Windows环境则直接用7-Zip图形界面或命令行版本。# Linux / Ubuntu / Debian 环境安装 p7zip sudo apt-get install -y p7zip-full # 解压数据集到指定目录 7z x 肠道息肉检测数据集612张VOCYOLO格式.7z -o./polyp_dataset # 查看解压后的目录结构 find ./polyp_dataset -maxdepth 2 -type d | sort解压完成后你会看到典型的VOC和YOLO双格式目录。其中-o参数指定解压输出路径注意-o后面不要有空格这是p7zip的一个使用习惯很多人踩过坑。解压完成后先不要急着打开图片看先把目录结构完整列出来确认VOC格式的JPEGImages、Annotations目录和YOLO格式的images、labels目录是否齐全。2.2 VOC格式的标注文件到底写了什么VOC格式的核心是Annotations目录下的XML文件每个XML对应一张图片。你不需要记住VOC格式的全部规范但要能读懂它的关键字段因为后续做数据清洗和格式转换时你操作最多的就是这几个标签annotation folderJPEGImages/folder filenamepolyp_000123.jpg/filename size width640/width height480/height depth3/depth /size object namepolyp/name bndbox xmin120/xmin ymin98/ymin xmax322/xmax ymax264/ymax /bndbox /object /annotation这里需要特别强调的是filename字段与真实图片名的一致性。我自己在处理医疗数据集时遇到过不止一次XML里的文件名和JPEGImages目录下的实际文件对不上原因是标注人员重命名了图片但没同步更新XML。你可以用下面这段代码快速做一次交叉校验import os import xml.etree.ElementTree as ET annotation_dir ./polyp_dataset/VOC/Annotations image_dir ./polyp_dataset/VOC/JPEGImages xml_files set(os.listdir(annotation_dir)) image_files set(os.listdir(image_dir)) for xml_file in xml_files: tree ET.parse(os.path.join(annotation_dir, xml_file)) root tree.getroot() filename root.findtext(filename) if filename not in image_files: print(f[MISMATCH] {xml_file} 引用了不存在的图片: {filename}) print(fXML文件数: {len(xml_files)}, 图片文件数: {len(image_files)})这段脚本做的事情很简单读每个XML里的filename去图片目录里查这个文件是否存在。如果612张图全部能对上说明数据集基础文件完整可以进入下一步。2.3 YOLO格式的txt标注归一化坐标是最大陷阱YOLO格式的标注文件是txt文本每一行代表一个目标框格式为class_id x_center y_center width height。注意这里所有数值都是归一化到0~1之间的小数不是像素坐标。用下面这段代码把YOLO标注还原回像素坐标你能直观看到标注框在图片上的实际位置import os import cv2 label_dir ./polyp_dataset/YOLO/labels image_dir ./polyp_dataset/YOLO/images for label_file in sorted(os.listdir(label_dir))[:5]: img_file label_file.replace(.txt, .jpg) img_path os.path.join(image_dir, img_file) if not os.path.exists(img_path): continue img cv2.imread(img_path) h, w img.shape[:2] with open(os.path.join(label_dir, label_file), r) as f: lines f.readlines() for line in lines: parts line.strip().split() cls_id, x_c, y_c, bw, bh parts x_c, y_c, bw, bh map(float, [x_c, y_c, bw, bh]) # 反归一化还原像素坐标 x1 int((x_c - bw / 2) * w) y1 int((y_c - bh / 2) * h) x2 int((x_c bw / 2) * w) y2 int((y_c bh / 2) * h) cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.putText(img, fclass {cls_id}, (x1, y1 - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 1) cv2.imwrite(f./debug_{img_file}, img) print(f已生成可视化文件: debug_{img_file})这个反归一化的过程是YOLO格式使用中最容易出错的环节。如果你直接把txt里的坐标当作像素坐标去画框画出来的框会全部挤在图片左上角——这个现象几乎每个用过YOLO格式的人都会遇到。上面代码里map(float, ...)把字符串转成浮点数cv2.rectangle接收的是整数坐标所以要先反归一化再取整。3. 数据集质量体检612张图能不能直接喂给模型3.1 图像尺寸分布医疗影像的隐藏问题很多人在拿到数据集后直接开始训练跑到一半发现输入尺寸和模型要求不匹配或者训练时频繁报错回头才去查图像尺寸——这一步应该在训练前就完成。对于肠道息肉这种内镜图像常见分辨率是640x480、720x576、1280x720但同一个数据集里混着多种分辨率并不罕见。import os import cv2 from collections import Counter image_dir ./polyp_dataset/YOLO/images size_counter Counter() for img_file in os.listdir(image_dir): if not img_file.endswith((.jpg, .jpeg, .png)): continue img_path os.path.join(image_dir, img_file) img cv2.imread(img_path) if img is None: print(f[READ_ERROR] 无法读取: {img_path}) continue h, w img.shape[:2] size_counter[(w, h)] 1 for size, count in size_counter.most_common(10): print(f分辨率 {size[0]}x{size[1]}: {count} 张)这段脚本同时承担了图像完整性的检查任务。cv2.imread返回None意味着图片文件损坏或格式不被OpenCV支持。我在实际项目中碰到过内镜图片实际是BMP格式但扩展名是.jpg的情况——OpenCV能读出来但其他框架不一定能。如果有图像读取失败直接看文件头是不是JFIF或者用file命令确认真实格式。分辨率分布查完以后你还要确认一个关键问题同一个数据集里是否所有图都带标注。有些数据集合了多来源的图片部分图没有对应的标注文件这类图要么补标注要么从训练集里剔除否则训练时会报「图片存在但无标签文件」的错误。3.2 标注框的面积分布小目标占比决定模型选型肠道息肉在肠镜图像里的形态差异极大——有的息肉占画面主体有的只是画面角落里的一个小隆起。标注框的面积分布直接决定了你该选YOLOv8还是更激进的小目标检测方案。import os import numpy as np label_dir ./polyp_dataset/YOLO/labels image_dir ./polyp_dataset/YOLO/images box_areas [] extreme_small 0 total_boxes 0 for label_file in os.listdir(label_dir): img_file label_file.replace(.txt, .jpg) img_path os.path.join(image_dir, img_file) if not os.path.exists(img_path): continue img cv2.imread(img_path) h, w img.shape[:2] with open(os.path.join(label_dir, label_file), r) as f: lines f.readlines() for line in lines: parts line.strip().split() _, x_c, y_c, bw, bh parts x_c, y_c, bw, bh map(float, [x_c, y_c, bw, bh]) # 还原像素面积 box_w bw * w box_h bh * h area box_w * box_h box_areas.append(area) total_boxes 1 # 面积小于 32x32 判定为小目标这个阈值来自COCO标准 if box_w 32 and box_h 32: extreme_small 1 box_areas np.array(box_areas) print(f总标注框数: {total_boxes}) print(f标注框面积均值: {box_areas.mean():.1f} 像素) print(f标注框面积中位数: {np.median(box_areas):.1f} 像素) print(f小目标(小于32x32)占比: {extreme_small / total_boxes * 100:.1f}%)这段脚本的价值不是算几个统计数据而是帮你决定后续用哪种训练策略。如果小目标占比超过20%你就不能简单地把输入分辨率设成640x640然后直接用YOLOv8默认配置而需要考虑将输入分辨率提升到1280或者在推理时开启TTA测试时增强。如果小目标占比很低就可以正常使用默认配置省下的显存还能把batch size开大。3.3 类别分布与单张多框分析这个数据集标注类别大概率只有一个polyp但612张图里每张的息肉数量差异很大。有的图只有一个息肉有的图可能有三四个。你需要确认的是每张图平均有多少个框最多有多少个框以及是否存在「有图无标注」的情况。import os label_dir ./polyp_dataset/YOLO/labels box_count_per_image [] for label_file in os.listdir(label_dir): with open(os.path.join(label_dir, label_file), r) as f: lines f.readlines() box_count_per_image.append(len(lines)) import numpy as np box_counts np.array(box_count_per_image) print(f图片总数: {len(box_counts)}) print(f平均每张图的框数: {box_counts.mean():.2f}) print(f最多一张图框数: {box_counts.max()}) print(f无标注的图片数: {(box_counts 0).sum()})这个统计结果直接影响损失函数和NMS参数的选择。如果单张图的框数普遍在1~2个说明标注相对稀疏模型训练时正负样本比例会失衡可以采用focal loss或者在数据加载时做在线难例挖掘。如果存在无标注的图片但数量很少直接剔除就好如果数量很多要警惕是不是标注批次出了问题某几批图片被整批漏标了。4. 从VOC/YOLO格式到实际训练两种数据组织方式的选型与转换4.1 统一成YOLO格式以YOLOv8训练为例的最小配置前面确认了VOC和YOLO两套格式但训练时只用一套就够了。我的习惯是全部转成YOLO格式原因很简单YOLOv8、YOLOv5这些主流框架原生支持txt标注不用在训练时写额外的Dataset类来解析XML。下面这段代码把VOC格式的XML标注转换到YOLO格式的txtimport os import xml.etree.ElementTree as ET voc_annotation_dir ./polyp_dataset/VOC/Annotations voc_image_dir ./polyp_dataset/VOC/JPEGImages yolo_label_dir ./polyp_dataset/YOLO/labels os.makedirs(yolo_label_dir, exist_okTrue) # 类别名到ID的映射只有一个类别时固定为0 class_name_to_id {polyp: 0} for xml_file in os.listdir(voc_annotation_dir): if not xml_file.endswith(.xml): continue tree ET.parse(os.path.join(voc_annotation_dir, xml_file)) root tree.getroot() # 读取图片尺寸用于归一化 size_elem root.find(size) img_w int(size_elem.findtext(width)) img_h int(size_elem.findtext(height)) yolo_lines [] for obj in root.iter(object): name obj.findtext(name) if name not in class_name_to_id: continue bndbox obj.find(bndbox) xmin float(bndbox.findtext(xmin)) ymin float(bndbox.findtext(ymin)) xmax float(bndbox.findtext(xmax)) ymax float(bndbox.findtext(ymax)) # 边界裁剪防止越界坐标导致训练时NaN xmin max(0, min(xmin, img_w)) ymin max(0, min(ymin, img_h)) xmax max(0, min(xmax, img_w)) ymax max(0, min(ymax, img_h)) # 转YOLO归一化格式 x_center ((xmin xmax) / 2) / img_w y_center ((ymin ymax) / 2) / img_h box_w (xmax - xmin) / img_w box_h (ymax - ymin) / img_h yolo_lines.append(f{class_name_to_id[name]} {x_center:.6f} {y_center:.6f} {box_w:.6f} {box_h:.6f}) txt_name xml_file.replace(.xml, .txt) with open(os.path.join(yolo_label_dir, txt_name), w) as f: f.write(\n.join(yolo_lines)) print(f转换完成共处理 {len(os.listdir(voc_annotation_dir))} 个XML文件)这段转换脚本里最容易被忽略的是解码异常和文件名匹配问题。建议先在循环里加一个try...except把解析失败的XML文件记录下来不要中断整个批次。bndbox数据是字符串计算前要做float()转换这是Python写标注转换脚本最容易犯的类型错误。最关键的是坐标边界裁剪肠镜图像里标注框偶尔会超出图像边界几个像素不裁剪的话训练时归一化坐标会大于1轻则Loss变成NaN重则模型直接不收敛。4.2 数据划分医疗数据集不能简单随机切常规目标检测项目通常按7:2:1或8:1:1随机划分训练集、验证集、测试集但医疗数据要额外考虑一个维度——同一患者的连续帧内镜图高度相似如果随机划分训练集和验证集可能出现同一患者的不同帧造成验证集虚高。不过这个612张的数据集大概率没有提供患者ID信息所以实际操作中退而求其次按图像来源文件的命名前缀做分组至少保证同一序列的图尽量落在同一个集合里import os import random import shutil from collections import defaultdict random.seed(42) image_dir ./polyp_dataset/YOLO/images label_dir ./polyp_dataset/YOLO/labels # 按文件名前缀分组假设命名格式为 polyp_000123.jpg grouped_files defaultdict(list) for img_file in os.listdir(image_dir): prefix img_file.split(_)[0] # 取前缀作为分组依据 grouped_files[prefix].append(img_file) train_files, val_files, test_files [], [], [] for prefix, files in grouped_files.items(): random.shuffle(files) n len(files) train_files.extend(files[:int(n * 0.7)]) val_files.extend(files[int(n * 0.7):int(n * 0.8)]) test_files.extend(files[int(n * 0.8):]) # 输出三个集合的文件数 print(f训练集: {len(train_files)} 张) print(f验证集: {len(val_files)} 张) print(f测试集: {len(test_files)} 张)打印结果后你大概率会发现612张图按7:2:1切出来大约是428:122:122。这个比例对于医疗任务来说是能接受的但要求是测试集也源自同一分布——如果数据集本身包含多中心来源的图像建议用stratify按来源做分层抽样而不是无脑按前缀分组。4.3 训练配置与超参数YOLOv8的医疗场景参数表数据集准备好后训练命令很短但参数选择需要结合数据集特征。以YOLOv8为例先写一个数据配置文件polyp.yaml# polyp.yaml path: ./polyp_dataset # 数据集根目录 train: images/train val: images/val test: images/test nc: 1 names: [polyp]然后启动训练yolo detect train datapolyp.yaml modelyolov8s.pt epochs100 imgsz640 batch16 patience10针对这个数据集我的建议是不要一上来就用yolov8x或yolov8l。612张图对于大模型的容量来说偏少容易过拟合。用s或m作为起点先把baseline跑出来看验证集mAP50有没有进入0.5以上再决定要不要加数据增强或换更大的模型。patience10表示验证不再提升时提前10个epoch停止这个参数在医疗数据集上很实用因为训练集小过拟合通常在20~30个epoch后出现。imgsz640是内存和精度的折中——如果你前面统计出小目标占比高建议改成imgsz1280代价是显存占用大约翻三倍A100 40GB以下显存的卡可能需要把batch size降到8甚至4。5. 训练与验证中的常见坑基于612张小数据集的踩坑记录5.1 解压后标签文件为空或只有一行空格现象训练启动时报image X has no labels或者验证集mAP一直是0。原因YOLO格式的txt文件在高版本Ultralytics中要求必须包含class_id x_center y_center width height五个字段。如果某个txt文件是空文件或只有一行空格训练时会直接跳过该图。在数据量只有612张的情况下少几张图影响不大但如果是整批格式转换脚本出错导致大量txt为空训练会变得极其不稳定。解决用前面质量体检的脚本先跑一遍统计txt文件的行数分布。如果发现大量txt文件行数为0检查VOC转YOLO脚本里bndbox字段是否存在——我遇到过VOC标注里有segmented但没有bndbox的XML文件解析后object循环里找不到坐标整张图就被跳过了。5.2 训练到一半Loss变成NaN现象训练跑到第20~40个epochLoss突然变成nan日志里出现RuntimeError: value cannot be converted to type float。原因标注框坐标存在越界或坐标值异常比如归一化后宽度为负数、x_center大于1。这在肠道息肉数据集里不少见——肠镜画面边缘有时只露出一小部分息肉标注人员会把框画到图像边界外。解决严格控制坐标范围。在VOC转YOLO的脚本中把xmin、ymin、xmax、ymax全部clamp到[0, img_w]和[0, img_h]区间然后再计算归一化坐标。训练前用Python写一个逐行检查脚本把坐标范围不合理的标注文件挑出来单独修复。5.3 验证集mAP很高但实际推理效果差现象训练完在验证集上mAP50达到0.85以上但把模型部署到新的肠镜视频上频繁漏检。原因数据划分时随机分割导致同一患者或同一内镜视频的相似帧同时出现在训练集和验证集验证变成「开卷考试」。612张图的量级下这个问题非常容易被放大。解决重新检查数据划分脚本确保按视频序列或患者ID分组而不是按图片随机切。如果数据集没有提供患者ID可以根据文件名前缀和图像相似度做聚类把相似度高的帧归到同一个集合。5.4 小目标息肉完全检测不到现象训练正常收敛但推理时对画面中占比很小的息肉完全不响应框全都漏掉。原因YOLO系列模型在默认640输入下对32x32像素以下的目标响应很弱。前面面积统计显示小目标占比超过20%时这类漏检几乎是必然的。解决有两个方向组合使用。第一是提高输入分辨率到1280第二个是调整loss中的box损失权重或者使用yolov8-seg做实例分割作为兜底分割对细长小目标的鲁棒性比检测框更好。5.5 图片解码异常但程序不报错现象训练过程中某个epoch突然停止日志里出现corrupted image之类的警告但程序没有立即退出而是整个训练卡住。原因部分图片文件头损坏、JPEG数据不完整cv2.imread返回None时程序没有做空值判断后续操作直接触发异常。解决在数据加载脚本或训练前统一过一遍cv2.imread把返回None的图片文件路径输出到日志并剔除。这个检查看似多余但在612张的数据集上花两分钟跑一遍能避免训练中途调度器挂起或显存泄漏。6. 从baseline到可用模型mAP之外还要看什么数据集整理干净、模型能正常收敛之后很多人停在了mAP50大于0.5这一步但对于肠道息肉检测mAP只是及格线临床场景更关心的是「漏检率」和「假阳性率」。这里分享一个我常用的验证思路把验证集预测结果按置信度阈值0.25和0.5分别统计给出不同置信度下的precision和recall对照表。from ultralytics import YOLO model YOLO(./runs/detect/train/weights/best.pt) results model.predict( source./polyp_dataset/YOLO/images/test, conf0.25, save_txtTrue, save_confTrue, saveFalse, imgsz640, )跑完save_txtTrue后去runs/detect/predict/labels目录里看每个txt的预测结果。对照真实标注文件手动数一遍漏检的框。医疗影像的底线逻辑很简单漏检一个真实息肉比误检三个假阳性更严重。如果你的模型在0.25置信度下recall还不到0.85不要急着提高阈值调精度而是回到数据处理环节找原因——是不是小目标占比过高、是不是训练集里某些形态的息肉数量太少。我习惯在每个医疗目标检测项目结束时做一件固定的事把模型预测出错的图全部导出成一个单独目录命名为hard_cases按「漏检」「误检」「定位偏差」三个子目录分类。下次迭代数据集时优先补充这些hard cases的同类样本。这个612张的数据集完全可以作为起点但大概率需要扩充尤其是那些形态复杂、边界模糊的息肉样本。把整个方案从解压、格式核对、质量体检、转换到训练验证完整走一遍你已经有一套可复用的数据处理管线之后无论是加到2000张还是5000张走的都是同一条路。希望帮到你。本文还有配套的精品资源点击获取
返回列表