基于YOLOv5的直肠息肉检测系统:从数据标注到部署的完整实战指南

📅 发布时间:2026/10/12 21:43:43
基于YOLOv5的直肠息肉检测系统:从数据标注到部署的完整实战指南
简介本资源面向医学影像分析人员、机器学习开发者及疾病筛查研发工程师提供一套基于YOLOv5的直肠息肉检测系统完整实现方案用于辅助医疗专家进行息肉自动检测与早期筛查。压缩包内共1个docx文档约41KB以图文形式系统梳理了从环境准备、数据准备与数据集配置到模型训练、ONNX模型导出、性能评估、评估指标可视化再到GUI界面搭建与代码整合的完整流程并涵盖精确度、召回率、F1分数等多维评估指标。文档还给出项目特点、参考资料、未来改进方向与注意事项如多类检测扩展、数据增强优化、集成学习及云部署思路并提醒数据质量、GPU硬件要求与过拟合监控等关键点。目前已有120人学习适合希望快速掌握医疗图像目标检测落地流程、对照目录结构查漏补缺的读者参考。1. 直肠息肉检测为什么值得用 YOLOv5 做一遍肠镜画面里找息肉本质上是一个小目标、低对比度、强反光的检测任务。息肉可能只有十几个像素颜色和周围黏膜几乎一样还经常被肠壁褶皱和气泡遮挡。传统做法靠医生盯着屏幕逐帧看漏检率跟疲劳程度直接挂钩。YOLOv5 在这个场景里被反复提起不是因为它有多新而是它的工程链路足够短标注、训练、导出、推理一套流程能在单卡上跑通改几个参数就能看到效果变化。这套「基于 YOLOv5 的直肠息肉检测系统」要解决的就是把肠镜帧里的息肉框出来并给出置信度适合想入门医学图像检测的算法工程师、做内镜辅助诊断方向的研究生以及手里有几百到几千张标注图、想快速验证可行性的人。它不承诺临床级精度但能让你在一周内拿到一个可量化、可迭代的基线。2. 数据准备与标注从原始肠镜帧到 YOLO 格式2.1 数据从哪来、要多少、怎么分肠镜数据不像 COCO 那样随手可下。常见做法是找公开的内镜数据集或者和某高校、某实验室合作拿脱敏帧。我一般会先把视频按固定间隔抽帧比如每 15 帧抽一张避免相邻帧高度重复导致训练集和验证集泄漏。数量上500 到 1000 张带标注的帧就能跑出一个能看的基线但息肉样本本身稀少正负样本比例经常到 1:20 甚至更极端。划分比例用 8:1:1 比较稳但要注意按「病例」划分而不是按「帧」划分。同一个病例的帧如果同时出现在训练和验证里验证指标会虚高这是血泪经验。下面这个脚本做抽帧和划分注释里写了关键参数。import os import cv2 import random from pathlib import Path # 输入视频目录和输出帧目录 VIDEO_DIR Path(data/videos) FRAME_DIR Path(data/frames) FRAME_DIR.mkdir(parentsTrue, exist_okTrue) # 每 15 帧抽一张可按视频帧率调整 SAMPLE_INTERVAL 15 for video_path in VIDEO_DIR.glob(*.mp4): cap cv2.VideoCapture(str(video_path)) idx 0 saved 0 while True: ret, frame cap.read() if not ret: break if idx % SAMPLE_INTERVAL 0: # 用视频名加帧号命名方便回溯 out_name f{video_path.stem}_{idx:06d}.jpg cv2.imwrite(str(FRAME_DIR / out_name), frame) saved 1 idx 1 cap.release() print(f{video_path.name}: 抽帧 {saved} 张)抽帧间隔是核心参数。间隔太小相邻帧几乎一样模型容易记住背景间隔太大可能漏掉只出现几帧的小息肉。我一般先按 15 抽一遍看正样本数量不够再降到 10。2.2 标注规范框怎么画才不坑自己标注用 LabelImg 或 CVAT 都行但规则必须统一。息肉框要贴紧可见边界不要把反光点和气泡框进去遇到被遮挡的息肉只标可见部分不要脑补。类别名统一用polyp不要一会儿polyp一会儿polyp_0否则训练时类别数对不上。YOLO 格式是class x_center y_center width height全部归一化到 0 到 1。下面这个转换脚本把 LabelImg 的 XML 转成 YOLO 的 txt同时做一次越界检查。import xml.etree.ElementTree as ET from pathlib import Path def convert_bbox(size, box): # size: (w, h)box: (xmin, ymin, xmax, ymax) dw, dh 1.0 / size[0], 1.0 / size[1] x (box[0] box[2]) / 2.0 * dw y (box[1] box[3]) / 2.0 * dh w (box[2] - box[0]) * dw h (box[3] - box[1]) * dh # 裁剪到 [0,1]防止标注越界导致训练报错 x, y min(max(x, 0), 1), min(max(y, 0), 1) w, h min(max(w, 0), 1), min(max(h, 0), 1) return x, y, w, h xml_dir Path(data/annotations) out_dir Path(data/labels) out_dir.mkdir(parentsTrue, exist_okTrue) for xml_file in xml_dir.glob(*.xml): tree ET.parse(xml_file) root tree.getroot() size root.find(size) w, h int(size.find(width).text), int(size.find(height).text) lines [] for obj in root.findall(object): cls_name obj.find(name).text if cls_name ! polyp: continue bnd obj.find(bndbox) box (float(bnd.find(xmin).text), float(bnd.find(ymin).text), float(bnd.find(xmax).text), float(bnd.find(ymax).text)) x, y, bw, bh convert_bbox((w, h), box) lines.append(f0 {x:.6f} {y:.6f} {bw:.6f} {bh:.6f}) if lines: (out_dir / f{xml_file.stem}.txt).write_text(\n.join(lines))越界裁剪这一步看着多余但实际标注时手一抖框超出图像边界很常见不裁剪训练直接报NaN排查半天才发现是标注问题。2.3 数据集配置文件怎么写YOLOv5 需要一个 yaml 描述数据路径和类别。路径用绝对路径最稳相对路径在不同工作目录下容易翻车。# data/polyp.yaml path: /home/user/polyp_dataset train: images/train val: images/val test: images/test nc: 1 names: [polyp]nc是类别数这里只有息肉一类所以是 1。names的顺序必须和标注里的 class id 对应标0就对应polyp。改完 yaml 后建议先用train.py的--data指过去跑一个 epoch确认能找到图再开正式训练。3. 训练配置YOLOv5 参数怎么调才不白跑3.1 模型选型s/m/l 到底选哪个YOLOv5 有 n、s、m、l、x 几个尺度。肠镜息肉是小目标理论上大模型特征提取更强但显存和速度代价高。我的经验是数据少于 1000 张时用yolov5s就够模型太大反而过拟合数据到 3000 张以上、显存够再上yolov5m或yolov5l。输入尺寸默认 640小息肉多的话可以提到 960但显存占用会明显上升先确认显卡扛得住。预训练权重用 COCO 的yolov5s.pt不要从零训。医学图像和自然图像分布不同但底层边缘、纹理特征仍然可迁移从零训在小数据集上基本收敛不了。3.2 关键训练命令与参数解释下面这条命令是我常用的基线配置注释里写了每个参数的作用。python train.py \ --img 640 \ # 输入分辨率小目标多可改 960 --batch 16 \ # 批大小显存不够就降到 8 --epochs 100 \ # 轮数小数据集 100 够看验证曲线早停 --data data/polyp.yaml \ --weights yolov5s.pt \ --cfg models/yolov5s.yaml \ --hyp data/hyps/hyp.scratch-low.yaml \ # 低学习率增强配置适合小数据集 --name polyp_exp1 \ --cache # 缓存图像加速内存够就开--hyp选hyp.scratch-low.yaml是因为医学图像标注量少默认增强mosaic、mixup太猛容易把息肉这种小目标混没。低配置里 mosaic 概率和缩放范围都更保守。--cache把图缓存到内存训练速度能快一截但数据集大、内存小就别开会 OOM。训练过程中重点看mAP0.5和mAP0.5:0.95。息肉检测里mAP0.5到 0.7 以上算可用mAP0.5:0.95通常低不少因为框的定位精度要求高。如果训练 loss 一直降但验证 mAP 不涨大概率是过拟合减 epoch 或加数据。3.3 数据增强哪些能用、哪些会帮倒忙YOLOv5 内置的增强里mosaic 对小目标其实有副作用——四张图拼一起后息肉更小模型更难学。我一般把 mosaic 概率从 1.0 降到 0.5。HSV 增强可以开肠镜画面色偏常见色调抖动能让模型更鲁棒。翻转可以用但上下翻转要谨慎肠镜画面有方向性上下翻转后解剖结构不自然。如果正样本太少可以针对性过采样把含息肉的图复制多份或者用--weights做一次微调后再训。但别过度复制太多一样过拟合。4. 推理与部署从权重到能看的检测结果4.1 单张图和视频推理命令训练完权重在runs/train/polyp_exp1/weights/best.pt。单张图推理python detect.py \ --weights runs/train/polyp_exp1/weights/best.pt \ --source data/test_images \ --img 640 \ --conf 0.25 \ # 置信度阈值低了误检多高了漏检多 --iou 0.45 \ # NMS 的 IoU 阈值重叠框多就调低 --save-txt # 同时保存检测框坐标方便后续分析--conf是调优重点。息肉检测宁可多报也别漏报所以阈值可以设低一点比如 0.2然后靠后处理过滤。--iou控制非极大值抑制如果同一息肉出多个框把它降到 0.4 或 0.35。4.2 用 Python 脚本批量跑并统计指标实际评估不能只看几张图要跑整个测试集算 precision、recall。下面脚本加载模型批量推理和标注对比。import torch from pathlib import Path from models.common import DetectMultiBackend from utils.augmentations import letterbox from utils.general import non_max_suppression import cv2 import numpy as np weights runs/train/polyp_exp1/weights/best.pt device torch.device(cuda if torch.cuda.is_available() else cpu) model DetectMultiBackend(weights, devicedevice) model.eval() def detect_one(img_path, conf_thres0.25, iou_thres0.45): img0 cv2.imread(str(img_path)) # letterbox 保持比例缩放并填充和训练预处理一致 img letterbox(img0, 640, stride32, autoTrue)[0] img img.transpose((2, 0, 1))[::-1] # HWC - CHW, BGR - RGB img np.ascontiguousarray(img) img torch.from_numpy(img).to(device).float() / 255.0 img img.unsqueeze(0) pred model(img) pred non_max_suppression(pred, conf_thres, iou_thres) return pred[0] # 返回 [x1,y1,x2,y2,conf,cls] test_dir Path(data/test_images) results {} for p in test_dir.glob(*.jpg): det detect_one(p) results[p.name] 0 if det is None else len(det) print(f测试图总数: {len(results)}) print(f有检测结果的图: {sum(1 for v in results.values() if v 0)})letterbox的填充方式必须和训练一致否则推理时目标比例变形框会偏。non_max_suppression的阈值就是命令行里的--conf和--iou脚本里显式传参方便调。4.3 导出 ONNX 做轻量部署如果要在没有 PyTorch 的环境跑导出 ONNXpython export.py \ --weights runs/train/polyp_exp1/weights/best.pt \ --include onnx \ --img 640 \ --batch 1 \ --simplify # 简化计算图推理更快导出后可以用 onnxruntime 加载输入输出名字在导出日志里会打印。注意--batch 1导出的模型只能单张推理要批量就改大但动态 batch 需要额外设置。5. 避坑与排查息肉检测里最容易翻车的几件事5.1 验证集指标很高实际推理一塌糊涂现象训练日志里 mAP0.5 到 0.85拿新视频一跑框得乱七八糟。原因训练验证集按帧随机划分同一病例的相邻帧同时进了训练和验证模型记住了背景。解决按病例划分同一病例的帧只出现在一个集合里。如果数据来源单一至少按视频文件划分。5.2 小息肉全漏检大息肉框得还行现象大于 50 像素的息肉能检出小的完全没反应。原因输入分辨率 640 下小目标经过下采样后特征几乎消失mosaic 增强又进一步缩小了目标。解决把--img提到 960 或 1280同时把 mosaic 概率降到 0.3 以下。如果显存不够用切片推理把大图切小块分别检测再合并。5.3 训练 loss 变 NaN现象跑几十个 epoch 后 loss 突然变 NaN训练中断。原因标注框越界或宽高为 0归一化后出现非法值学习率过高也会导致梯度爆炸。解决用 2.2 的转换脚本做越界裁剪检查有没有宽高为 0 的框把初始学习率从 0.01 降到 0.001加梯度裁剪。5.4 推理速度慢达不到实时现象单张图推理要几百毫秒视频卡顿。原因模型太大或输入分辨率太高没用半精度。解决换yolov5s输入降到 640推理时加--half用 FP16。如果还慢导出 ONNX 或 TensorRTTensorRT 在 N 卡上通常能快 2 到 3 倍。5.5 置信度阈值调来调去都不对现象阈值 0.5 漏检多降到 0.2 误检爆炸。原因模型输出的置信度分布和实际需求不匹配单靠一个全局阈值解决不了。解决先画 PR 曲线看 recall 和 precision 的权衡点选一个业务能接受的平衡再对低置信度框做二次过滤比如按框面积或长宽比筛掉明显不合理的。6. 把 mAP 再往上推一档的两个具体手法第一个手法是难例挖掘。跑完一轮推理后把漏检和误检的图挑出来人工复核标注补进训练集再训一轮。这个循环做两到三次mAP 通常能涨 3 到 5 个点。关键是漏检的图要优先补因为息肉检测里漏检的代价远大于误检。第二个手法是测试时增强TTA。推理时把图水平翻转、多尺度缩放各跑一遍再把框合并做 NMS。YOLOv5 的detect.py自带--augment参数直接开就行。代价是推理时间翻几倍适合离线分析不适合实时。python detect.py \ --weights runs/train/polyp_exp1/weights/best.pt \ --source data/test_images \ --augment \ # 开启 TTA --conf 0.25 \ --iou 0.45TTA 对小息肉提升明显因为翻转和缩放能让原本模糊的特征在不同视角下被捕捉到。但如果模型本身过拟合严重TTA 提升有限先把数据划分和增强调对再说。我自己踩过最深的坑是早期图省事按帧随机划分验证 mAP 冲到 0.9换了个病例的视频直接崩到 0.3后来老老实实按病例划分指标虽然降了但真实可用。做医学图像检测指标好看不重要换一批数据还能稳住才重要。希望帮到你。本文还有配套的精品资源点击获取