ARTICLE DETAIL

资讯详情

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

小样本医学图像检测实战:111张皮肤镜图像跑通YOLOv8闭环

小样本医学图像检测实战:111张皮肤镜图像跑通YOLOv8闭环 简介这份资源是面向皮肤疾病检测方向的YOLO系列目标检测数据集适合从事医学图像识别、算法验证与课程实践的研究者与开发者使用覆盖白癜风、皮炎、黄褐斑三类常见皮肤问题可直接用于模型训练与验证测试。压缩包共334个文件包含111张jpg图像、111个txt标签、111个xml标签以及1个data.yaml配置文件整体约4MB体积轻量便于快速加载。标签同时提供YOLO格式与VOC格式两套YOLO格式以类别索引和归一化中心点、宽高比例记录目标框VOC格式则以xml结构保存方便在不同框架间切换。数据集已按训练与验证需求划分完毕并附带data.yaml可无缝适配yolov5、yolov8、yolov9、yolov7、yolov10及yolo11等主流版本。目前已有160人学习下载适合作为小样本医学目标检测的入门练手与算法对比素材帮助读者省去标注与格式转换环节把精力集中在模型调参与效果评估上。1. 111 张皮肤镜图像凭什么值得用 YOLO 跑一遍手里只有 111 张带标签的皮肤图像还分白癜风、皮炎、黄褐斑三类第一反应多半是「这点数据也配训 YOLO」。我一开始也这么想直到把数据摊开看每张图里病灶区域大小不一白癜风是边界模糊的色素脱失斑皮炎是红斑加鳞屑黄褐斑是成片褐色沉着三类的形态差异其实相当明显。问题不在数据少而在你会不会用。这个标题拆开就是三件事YOLO 目标检测算法、111 张带标签的皮肤疾病图像、白癜风/皮炎/黄褐斑三个类别。它解决的是「小样本医学图像能不能跑通检测闭环」这个问题适合手上只有几十到几百张标注图、想验证方案可行性的算法工程师和医学图像方向的学生。111 张不是拿来刷 SOTA 的是拿来验证流程、调通参数、看清数据瓶颈的。把这条链路走通后面换成 1111 张、11111 张你只是换数据不是换脑子。2. 小样本皮肤检测的选型为什么是 YOLO 而不是分类网络2.1 检测和分类在这批数据上的本质差别很多人拿到「皮肤疾病数据集」第一反应是训个 ResNet 做三分类。但皮肤镜图像有个特点一张图里可能同时出现黄褐斑和皮炎病灶位置本身就是诊断信息。分类网络只告诉你「这张图是什么病」检测网络告诉你「病在哪、有多大、边界在哪」。对白癜风这种需要评估面积的疾病检测输出比分类标签有用得多。另一个现实原因是标注格式。标题写的是「带标签」医学图像数据集常见的标注是 XMLVOC 格式或 TXTYOLO 格式框出病灶区域。如果标签是边界框你硬要做分类就得把框裁出来再训等于浪费了位置信息。所以选 YOLO 不是因为它时髦是因为标签形态决定了任务形态。2.2 YOLOv8 在这类小数据上的实际表现YOLO 系列迭代到 v8最大的变化是 anchor-free 和解耦头。对小数据集来说anchor-free 省掉了聚类 anchor 的步骤少了一个需要调的玄学参数。解耦头把分类和回归分开在类别少3 类、样本少111 张的场景下收敛比耦合头稳。我一般会从 yolov8n 或 yolov8s 起步不直接上 m 或 l。原因很直接111 张图模型参数量越大过拟合来得越快。n 版本参数量约 300 万s 版本约 1100 万在这个数据量下 n 已经够用s 是上限。再大就是给自己找麻烦。提示不要因为「数据少就多训几轮」来补偿小数据上训太多轮验证集 loss 会先降后升那个拐点才是你要停的地方。2.3 数据增强是小样本检测的命根子111 张图按 8:1:1 划分训练集不到 90 张。不做增强模型见到的样本多样性严重不足。皮肤图像有个好处病灶的旋转、翻转、缩放不改变病理特征所以几何增强可以放心用。颜色增强要谨慎因为黄褐斑和皮炎的区别很大程度靠颜色HSV 抖动幅度开大了会把类别特征搅乱。我常用的增强组合是随机翻转水平垂直、随机旋转 ±15°、随机缩放 0.5~1.5、Mosaic 开启但 close_mosaic 设 10最后 10 轮关掉 Mosaic让模型在真实分布上收尾。HSV 的 h 增益控制在 0.015 以内s 和 v 控制在 0.4 以内。3. 从 zip 到可训练数据集111 张图的清洗与格式转换3.1 解压后先做三件事拿到 zip 解压别急着写训练脚本。先做三件事数文件、看标注、查类别分布。# 1. 统计图像数量和格式 find ./skin_data -type f \( -name *.jpg -o -name *.png -o -name *.jpeg \) | wc -l # 2. 查看标注文件类型VOC 的 xml 还是 YOLO 的 txt find ./skin_data -type f \( -name *.xml -o -name *.txt \) | head -20 # 3. 统计每个类别的标注框数量 grep -rho vitiligo\|dermatitis\|chloasma ./skin_data/Annotations 2/dev/null | sort | uniq -c这三条命令分别回答图像总数对不对、标注是什么格式、三类是否均衡。111 张图如果三类严重不均衡比如白癜风 80 张、黄褐斑 10 张后面训练必须做类别加权否则模型会偏向多数类。3.2 VOC 转 YOLO 格式的转换脚本如果标注是 XMLVOC需要转成 YOLO 的归一化 TXT。转换的核心是把绝对坐标 (xmin, ymin, xmax, ymax) 变成归一化的 (cx, cy, w, h)。import os import xml.etree.ElementTree as ET # 类别映射顺序决定 label 索引必须和 data.yaml 一致 CLASS_MAP {vitiligo: 0, dermatitis: 1, chloasma: 2} def voc_to_yolo(xml_path, img_w, img_h, out_path): tree ET.parse(xml_path) root tree.getroot() lines [] for obj in root.findall(object): name obj.find(name).text.strip().lower() if name not in CLASS_MAP: continue # 跳过未定义类别避免训练时报 index 越界 cls_id CLASS_MAP[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) # 归一化中心点和宽高YOLO 要求 0~1 cx (xmin xmax) / 2.0 / img_w cy (ymin ymax) / 2.0 / img_h w (xmax - xmin) / img_w h (ymax - ymin) / img_h # 裁剪到 [0,1]防止标注越界导致训练异常 cx, cy min(max(cx, 0), 1), min(max(cy, 0), 1) w, h min(max(w, 0), 1), min(max(h, 0), 1) lines.append(f{cls_id} {cx:.6f} {cy:.6f} {w:.6f} {h:.6f}) with open(out_path, w) as f: f.write(\n.join(lines))逻辑说明CLASS_MAP 的索引顺序必须和后面 data.yaml 里的 names 完全一致否则模型学到的类别是错位的。归一化用图像实际宽高不是固定值。裁剪到 [0,1] 是因为有些标注框会超出图像边界不裁剪会导致 YOLO 训练时 loss 异常。参数说明xml_path 是单个 XML 路径img_w/img_h 从对应图像读取用 PIL 或 cv2out_path 是输出的 TXT 路径。批量处理时遍历 Annotations 目录用图像文件名匹配。3.3 划分数据集和写 data.yaml转换完成后按 8:1:1 划分目录结构必须是 images/labels 分离且文件名一一对应。# 目录结构 # dataset/ # images/train/ images/val/ images/test/ # labels/train/ labels/val/ labels/test/ # 用 sklearn 划分在 Python 里执行 python -c from sklearn.model_selection import train_test_split import os, shutil imgs [f for f in os.listdir(images_all) if f.endswith((.jpg,.png))] train, tmp train_test_split(imgs, test_size0.2, random_state42) val, test train_test_split(tmp, test_size0.5, random_state42) print(len(train), len(val), len(test)) data.yaml 内容如下path: ./dataset train: images/train val: images/val test: images/test nc: 3 names: [vitiligo, dermatitis, chloasma]注意names 的顺序必须和转换脚本里的 CLASS_MAP 完全一致这是最常见的翻车点类别错位后模型精度会莫名其妙地低但你看 loss 曲线又看不出问题。4. 训练参数怎么设111 张图的 YOLOv8 配置清单4.1 关键参数逐项说明小数据集训练参数比模型结构重要。下面是我在 111 张图上实际用的配置逐项说清楚为什么。from ultralytics import YOLO model YOLO(yolov8n.pt) # 从预训练权重起步小数据必须迁移学习 results model.train( datadata.yaml, epochs150, # 小数据收敛快150 轮足够配合早停 imgsz640, # 皮肤镜图像分辨率通常够640 是性价比平衡点 batch8, # 111 张图batch 8 约 11 个 iteration/epoch lr00.001, # 初始学习率预训练模型微调用 1e-3 比较稳 lrf0.01, # 最终学习率 lr0 * lrf余弦退火到 1e-5 warmup_epochs5, # 前 5 轮预热防止小数据上初期梯度震荡 patience30, # 30 轮无提升就停省时间也防过拟合 augmentTrue, hsv_h0.015, # 色相抖动压到最低保护颜色特征 hsv_s0.4, hsv_v0.4, degrees15.0, # 旋转 ±15 度 translate0.1, scale0.5, # 缩放 0.5~1.5 fliplr0.5, flipud0.5, # 皮肤镜图像上下翻转不改变病理 mosaic1.0, close_mosaic10, # 最后 10 轮关闭 mosaic device0, workers4, pretrainedTrue, )参数说明lr0 设 1e-3 是因为用了预训练权重微调不需要大学习率。patience 设 30 是经验值小数据上验证集波动大太小会误停太大浪费时间。close_mosaic10 让模型最后在无拼接的真实分布上收敛mAP 通常能涨 1~2 个点。4.2 训练过程看什么指标启动训练后重点盯三个东西box_loss、cls_loss、mAP50。前 20 轮 box_loss 应该稳定下降如果震荡剧烈说明 lr0 太大或 batch 太小。cls_loss 在 3 类任务上通常比 box_loss 降得慢这是正常的。mAP50 在 50 轮左右应该能看到明显上升如果 80 轮还趴在 0.1 以下大概率是数据或标签有问题不是参数问题。验证集 mAP 和训练集 mAP 的差距是关键信号。如果训练集 mAP 到 0.9 而验证集只有 0.3过拟合已经发生这时候加增强或减模型容量比继续训有用。4.3 推理和验证确认模型真的学到了东西训练完用 best.pt 跑推理别只看指标要看框。from ultralytics import YOLO model YOLO(runs/detect/train/weights/best.pt) results model.predict( sourcedataset/images/test, conf0.25, # 置信度阈值医学图像宁可漏检不要误检可调高 iou0.45, # NMS 的 IoU 阈值 saveTrue, save_txtTrue, # 保存预测框坐标方便和 GT 对比 )conf 设 0.25 是默认值但在医学场景下如果误检代价高把正常皮肤判成病灶可以提到 0.4~0.5。iou 0.45 是 NMS 标准值如果同一病灶出现多个重叠框降到 0.3~0.4 能压掉重复。5. 小样本皮肤检测的避坑清单5 个血泪教训5.1 类别不均衡导致模型「只会认一种病」现象训练完发现模型对白癜风检测很准皮炎和黄褐斑几乎检不出。原因111 张图里三类分布可能是 70/25/16模型偏向多数类。解决在 data.yaml 同级加类别权重或用 ultralytics 的cls_pw参数部分版本支持更直接的办法是对少数类做过采样把黄褐斑的图复制 2~3 份进训练集验证集不动。5.2 标注框越界导致 loss 变 NaN现象训练几个 epoch 后 loss 突然变成 nan。原因VOC 转 YOLO 时没做坐标裁剪某些标注框的 xmax 超过图像宽度归一化后 w 大于 1。解决转换脚本里加min(max(w,0),1)裁剪转换后跑一遍校验检查所有 TXT 里的数值是否都在 [0,1]。5.3 图像和标签文件名不匹配现象训练启动时报「No labels found」但 labels 目录里明明有文件。原因图像是img_001.jpg标签是img_001.xml.txt或img_001_jpg.txtYOLO 要求标签名和图像名去扩展名完全一致。解决批量重命名用os.path.splitext取图像主名标签存为主名.txt。5.4 验证集太小导致指标剧烈波动现象每个 epoch 的 mAP 上下跳从 0.5 跳到 0.2 再跳回 0.6。原因111 张按 8:1:1 分验证集只有 11 张一张图预测对错就能让 mAP 变动 0.09。解决小数据下改成 7:2:1 或 6:2:2验证集至少 20 张指标才稳定。或者用交叉验证但 111 张做 5 折每折训练集不到 90 张效果也有限。5.5 直接 resize 到 640 丢失小病灶现象模型对大面积黄褐斑检测好对小白癜风斑块漏检严重。原因皮肤镜原图可能是 2000×2000直接 resize 到 640 后小病灶只剩几个像素。解决训练时用rectTrue保持长宽比或把 imgsz 提到 1024显存够的话推理时用滑动窗口切图检测。111 张图用 1024 训练batch 降到 4显存 8G 也能跑。6. 把 111 张用出 1111 张的效果进阶技巧与验证方法小数据检测的天花板不在模型在数据。但 111 张图不是只能干等更多数据有几个技巧能把现有数据榨干。第一个是测试时增强TTA。推理时对同一张图做翻转、多尺度把多次预测结果融合。ultralytics 支持augmentTrue在 predict 阶段开启 TTAmAP 通常能涨 1~3 个点代价是推理时间翻几倍。对 111 张的验证集来说这个代价完全可接受。第二个是伪标签半监督。用训好的模型对未标注的皮肤图像如果你能拿到更多无标注图跑推理把高置信度conf0.7的预测框当伪标签加入训练集再训一轮。这个做法在医学图像里很常见但要注意伪标签的错误会累积所以只取高置信度样本且伪标签数据在 loss 里降权。第三个是验证方法本身。111 张图训出来的模型不要只看 mAP50。医学检测更关心召回率——漏掉一个病灶比多标一个框严重得多。我一般会单独看每一类的 recall如果白癜风 recall 低于 0.8即使 mAP 好看也不能上线。另外做一个混淆矩阵看三类之间有没有互相误判白癜风和黄褐斑在颜色上有时会混如果混淆严重说明颜色增强开大了或类别定义本身有重叠。# 用验证集跑混淆矩阵和各类 recall from ultralytics import YOLO model YOLO(runs/detect/train/weights/best.pt) metrics model.val(datadata.yaml, conf0.25, iou0.45) print(各类 recall:, metrics.box.r) # 每类召回率 print(混淆矩阵:, metrics.confusion_matrix.matrix)最后说个习惯每次训完模型我会把预测结果和原图叠在一起看 20 张不看指标看框。指标会骗人框不会。有一次 mAP 0.75 看着还行叠图一看模型把黄褐斑的框标到了正常皮肤上纯粹是颜色阈值碰巧对了。这种问题只有肉眼能发现。111 张图不多但足够让你把「数据→训练→验证→发现问题→改数据」这个闭环走三遍。走完三遍你手里就不只是 111 张图了是一套能复用的流程。希望帮到你。本文还有配套的精品资源点击获取
返回列表