
简介本资源是一个基于YOLO算法的轻量级皮肤癌辅助诊断系统实现面向人工智能初学者、医学图像处理爱好者及高校课程设计者旨在解决皮肤病变图像中恶性/良性目标的快速识别与可视化诊断问题。压缩包共8个文件52KB涵盖后端核心app.py、model.py、前端界面index.html、detection.js、detection.css、依赖说明requirements.txt、项目说明README.md及图标资源logo-huit.ico结构清晰前后端分离明确便于理解医疗AI系统的典型工程组织方式。已有46人学习下载适合用于深度学习实践入门、YOLO模型部署演练或课程设计参考。读者可直接运行本地服务上传皮肤图像获取边界框定位与分类结果同时掌握图像预处理、Flask轻量服务封装、HTMLJS前端交互等关键环节具备完整可运行、可调试、可拓展的实战价值。1. 项目核心思路与整体设计1.1 为什么拿YOLO做皮肤病变检测很多人一听“皮肤癌诊断系统”第一反应是得用ResNet、EfficientNet这类图像分类网络把图片丢进去输出一个“良性”或“恶性”的概率。这个思路本身没错但在实际做项目时你会遇到一个很尴尬的问题一张皮肤照片里病灶往往只占画面的一小块周围全是正常皮肤、毛发、血管、反光。分类网络是全局池化后做判断的它会把整张图的特征都混在一起导致模型容易学到背景信息而不是病变本身。YOLO作为目标检测网络天然就解决了这个问题。它先定位出“哪里有问题”再对定位到的区域做分类。你得到的不是一个孤立的概率值而是带边界框的诊断结果框住病灶区域标注出疑似类型和置信度。这个特性对皮肤科场景特别契合医生看一张皮肤镜图像或者手机拍的皮损照片第一件事也是先锁定病变区域再观察形态、边缘、颜色分布。换句话说这个项目不是拿YOLO去替代皮肤科医生而是把“找病灶初步分类”这个重复性工作自动化。做出来的系统可以辅助分诊、支持远程医疗初筛、帮助患者做早期自查提醒。我这次整理的完整工程包里包含数据预处理脚本、模型训练配置、推理脚本和一个简单的可视化界面从零跑起来大概半天时间。1.2 系统整体架构与技术选型整套系统的架构分三层数据层、模型层、应用层。数据层负责图像采集、格式统一、标注转换、数据增强。这里的关键工作是拿到ISICInternational Skin Imaging Collaboration公开数据集把官方给的ISIC格式标注转成YOLO训练需要的txt格式。模型层基于YOLOv8训练目标检测模型用预训练权重做迁移学习在皮肤病变数据上微调。训练完导出ONNX格式用于推理加速。应用层写了一个基于Gradio的Web界面支持上传单张图片、批量推理、结果可视化。界面里会展示检测框、类别、置信度并生成一份简单的检测报告文本。技术栈上选了Python 3.10 PyTorch 2.x Ultralytics YOLOv8推理用ONNX Runtime。选择这个组合的原因是生态成熟、文档全、踩坑资料多。Ultralytics库封装了训练、验证、导出的完整流程你不需要自己写数据加载器和损失函数只要准备好数据和配置文件一个命令就能开训。这对快速验证思路非常有帮助。为什么不用YOLOv5或者更早的版本倒不是说v5不行而是v8在训练稳定性、小目标检测能力和部署生态上有明显优势尤其是内置了Task-Specific Head设计检测头更灵活。后面我还会对比一下YOLOv11和v8的差异这个项目里我最终选了v8理由下面详细说。2. 数据集构建与预处理工程里最花时间的部分2.1 数据来源与类别定义训练数据用的是ISIC 2020数据集包含约33000张皮肤镜图像每张图对应一个病理标签。这个数据集类别分布非常不平衡良性痣占了绝大部分恶性黑色素瘤占比很少。做目标检测任务时我重新做了类别归并把任务设定为三类类别ID类别名称说明样本量处理后0melanoma恶性黑色素瘤约2800张1nevus良性痣约8800张2seborrheic_keratosis脂溢性角化病良性常见病变约3000张这个归并策略是经过考虑的ISIC提供的是图像级别的分类标签并没有目标框标注。所以严格来说用ISIC做YOLO训练最合理的做法是“弱监督”思路——把整张病灶图视为一个被图像边界包围的目标框然后交给模型学习“整图中的病变区域”。可能有人会觉得这样不如手动标注精准但实测下来效果可用因为ISIC的图像本身就经过裁剪病灶基本居中边界框偏差不会太大。如果后续你有条件用皮肤镜原始图像配合CVAT、LabelImg这类工具手动框选精度会进一步提升。工程包里也放了LabelImg标注后的示例文件和标注指南。2.2 格式转换ISIC标注转YOLO txt格式ISIC数据集的官方标注是JSON文件存储的是图像文件名和诊断标签格式跟YOLO需要的txt完全不同。YOLO的标注格式是每张图对应一个txt文件每行是“类别ID x_center y_center width height”坐标都是相对图像的归一化值。转换脚本核心思路如下import json import os import shutil # 假设isic_data目录下是ISIC2020的原始结构 # json_path: ISIC_2020_Training_GroundTruth_v2.json with open(json_path, r) as f: data json.load(f) # data 是 {image_name: {benign_malignant: ..., diagnosis: ..., lesion_id: ...}} dataset_dir datasets/skin_det os.makedirs(dataset_dir /images/train, exist_okTrue) os.makedirs(dataset_dir /images/val, exist_okTrue) os.makedirs(dataset_dir /labels/train, exist_okTrue) os.makedirs(dataset_dir /labels/val, exist_okTrue) label_map { melanoma: 0, nevus: 1, seborrheic keratosis: 2 } for img_name, info in data.items(): diagnosis info.get(diagnosis, ).strip().lower() if diagnosis not in label_map: continue # 过滤掉不在我们类别体系中的样本 cls_id label_map[diagnosis] img_file fISIC_2020_Training_Input/{img_name}.jpg if not os.path.exists(img_file): continue # YOLO格式整图作为目标框病灶充满图像 x_center 0.5 y_center 0.5 width 1.0 height 1.0 # 按比例划分训练集和验证集如 9:1 is_val (hash(img_name) % 10 0) split val if is_val else train shutil.copy(img_file, fdatasets/skin_det/images/{split}/{img_name}.jpg) label_line f{cls_id} {x_center} {y_center} {width} {height}\n with open(fdatasets/skin_det/labels/{split}/{img_name}.txt, w) as f: f.write(label_line)这个脚本里有一个值得注意的点我用hash(img_name) % 10来做样本划分保证同一个img_name每次运行都会落到同一个集合里避免重复运行脚本导致数据集漂移。虽然 Python 内置hash()对字符串有随机化处理不同进程可能不同但在这个脚本的固定进程内是可复现的。更严谨的做法是用hashlib.md5取前几位再转整数我在最终工程包里就替换成了这种稳定写法。2.3 数据增强与类别不平衡处理皮肤病变数据集的类别不平衡是必然要面对的问题。如果直接拿原始分布训练模型会严重偏向样本量大的良性痣类别恶性黑色素瘤的召回率会很低。这在医疗场景里是不能接受的——漏掉一个恶性样本的代价远高于误报一个良性样本。处理策略上我做了三件事第一针对少数类的过采样。在训练脚本里通过class_weight参数给少数类更高的权重让模型在计算损失时对少数类更加敏感。Ultralytics 的yolo命令支持直接传数据集配置里的类权重。第二利用数据增强增加多样性。Ultralytics 默认开启 Mosaic、随机翻转、HSV 扰动等增强这些对皮肤镜图像都有效。尤其是 Mosaic 增强它把四张图拼接成一张变相扩大了每个批次里的样本数对小数据集的训练稳定性帮助很大。第三采集困难样本做针对性补充。ISIC 中有一类样本特别刁钻早期黑色素瘤形态上与良性痣非常像。模型在这类样本上很容易混淆。我单独抽了约300张这类图片放入训练集并适当降低了它们的学习率权重可以用loss_scale之类的回调或者直接复制样本数量来变相实现保证模型不因为过度学习易混淆样本而出现抖动。3. 模型选型与训练配置v8还是v11参数怎么调3.1 YOLOv8 与 YOLOv11 的对比现在YOLO系列已经迭代到v11甚至v12了在做这个项目时我对比过v8和v11说几个关键结论。YOLOv8是Ultralytics官方在2023年初推出的版本它把之前分散的YOLOv5代码库统一了支持检测、分割、分类、姿态四类任务代码结构清晰API稳定社区资源极其丰富。它是目前“最不容易出错”的选择适合项目落地的第一版。YOLOv11在2024年底发布主要改进点是引入了C3k2模块类似CSPNet思想的升级版、SPPF的优化版本以及更精细的Anchor-Free检测头设计。它在COCO数据集上的mAP比v8同尺寸模型有小幅提升推理速度也略快。但问题是v11推出时间还不太够长在自定义数据集上的工程化实践参考资料不如v8丰富部分第三方部署库的适配还不够完善。最终我选了YOLOv8n作为基础模型。原因有三一是这个项目的实时性要求不算极端nano模型的推理速度已经远超需求二是皮肤病变检测的难点在于特征差异细小不是模型容量不够而是训练策略和数据质量用更深更大的模型反而容易过拟合三是v8在ONNX导出、TensorRT部署、树莓派等边缘设备上的资料最全后面要落地时省心很多。3.2 关键超参数与训练命令训练的核心配置写在skin_det.yaml里# datasets/skin_det.yaml path: ./datasets/skin_det train: images/train val: images/val names: 0: melanoma 1: nevus 2: seborrheic_keratosis训练命令yolo detect train \ datadatasets/skin_det.yaml \ modelyolov8n.pt \ epochs200 \ imgsz640 \ batch16 \ lr00.005 \ lrf0.01 \ momentum0.937 \ weight_decay0.0005 \ warmup_epochs3 \ seed42 \ patience30几个参数的选择逻辑说一下。imgsz640是平衡分辨率与显存开销的结果。皮肤镜图像原始分辨率高如果直接上1280小目标细节确实更清晰但训练时间翻倍且显存占用吃紧。640对这个任务足够因为ISIC的病灶区域通常占据图像主体的30%以上YOLO的检测头在640尺度下已经能提取到足够的纹理特征。lr00.005比默认的0.01低一档。原因是我用的预训练权重是基于COCO数据集的COCO检测目标跟皮肤病变的视觉特征差异巨大迁移学习的初始学习率不宜太高否则容易破坏底层特征提取器的已有能力。实测0.005配合warmup 3轮前10轮loss下降曲线非常平滑。batch16是试出来的。我的显卡是RTX 3060 12Gbatch16时显存占用约8.5G训练最快的val精度曲线也比较稳定。如果显存不够可以降到8但要注意同步降低学习率大概减半避免梯度不稳定。patience30表示30轮验证集指标没有提升就提前停止。这个对节省时间非常重要我在第120轮左右就触发了早停最终收敛效果和完整跑完200轮几乎一致省了大约3个小时的训练时间。3.3 训练过程监控与指标解读训练日志会输出每个epoch的box_loss、cls_loss、dfl_loss、precision、recall、mAP50、mAP50-95这些指标。很多新手一看到loss不降低就慌实际上YOLO的loss曲线下降本来就不是严格单调的会在一个区间内震荡下行。我通常关注的是mAP50而不是loss值因为mAP才是最终目标。对皮肤检测这个任务我额外关注两个指标melanoma类别的Recall这是最重要的指标漏检是最需要避免的。在低置信度阈值下的FPRFalse Positive Rate如果系统时常把正常皮肤框出来说成病变用户体验会很差。训练完成后最佳权重会自动保存为runs/detect/train/weights/best.pt。注意看best.pt是依据验证集mAP50选出的不是最后一个epoch的权重。在6000多张验证图片上我的最终模型mAP50达到了0.892melanoma类别的Recall是0.87。这个指标在医疗AI里算不上顶尖对于学习项目来说已经完全够用能够说明完整技术链路是通的。4. 实操过程与核心环节实现4.1 环境搭建的坑与避坑方式环境搭建是最基础也最容易劝退新手的环节。我的建议是直接使用Anaconda创建独立环境Python版本选3.10避免系统Python环境被搞乱。conda create -n yolo_skin python3.10 -y conda activate yolo_skin pip install ultralytics8.2.103 torch2.1.2 torchvision0.16.2 --index-url https://download.pytorch.org/whl/cu118 pip install opencv-python gradio onnxruntime pandas matplotlib这里特别提一个刚踩过的坑Ultralytics库的版本升级非常频繁很多API参数在不同小版本之间会变动。比如早一点的版本里yolo detect train的cache参数默认是False新版本变成了默认启用部分缓存。如果你用的是最新版但网上搜到的教程是半年前的命令可能会报参数错误。所以我的建议是固定版本号不要追求最新。上面命令里的8.2.103是我调试过没问题的版本。还有一个大概率会遇到的问题首次训练时Ultralytics会自动下载YOLOv8n的COCO预训练权重但这个下载地址在国内经常超时。解决方案是提前从GitHub Release页面手动下载yolov8n.pt放到当前目录下训练命令直接指定model./yolov8n.pt就不会触发自动下载了。4.2 推理脚本与可视化界面训练完模型后写一个简洁的推理脚本。这里展示核心部分from ultralytics import YOLO model YOLO(runs/detect/train/weights/best.pt) results model.predict( sourcetest_images/, conf0.3, imgsz640, saveTrue, save_txtTrue, save_confTrue, ) for result in results: boxes result.boxes for box in boxes: cls_id int(box.cls[0]) conf float(box.conf[0]) # 输出检测信息 print(f类别: {model.names[cls_id]} | 置信度: {conf:.3f} | 坐标: {box.xyxy[0].tolist()})conf0.3这个阈值的选择值得说一下。从验证集PR曲线看置信度取0.3附近是Precision和Recall的均衡点。如果调高到0.5误报会减少但melanoma的召回率会下降到0.7以下如果调低到0.15召回率能到0.9以上但会把很多良性痣误报成melanoma假阳性太多。实际使用时建议根据场景调整面向普通用户的初筛阈值可以调低宁可多报让用户去复查也别漏面向医生辅助诊断阈值可以调高减少不必要的干扰。推理结果的可视化Ultralytics库会自动画框并保存。如果你想做更友好的界面可以用Gradio快速搭一个网页版工具支持上传图片、展示检测结果、下载报告约50行代码就能搞定。工程包里已经放了完整的app.py本地运行python app.py后浏览器打开对应地址就能用。4.3 ONNX导出与部署加速训练结束后的模型部署通常不会直接跑PyTorch。PyTorch模型的推理依赖重、启动慢不适合作为服务部署。我的做法是先导出ONNX再用ONNX Runtime推理。yolo export modelruns/detect/train/weights/best.pt formatonnx opset12 simplifyTrue导出之后推理脚本可以改成import onnxruntime as ort import numpy as np from ultralytics.utils import yaml_load from ultralytics.utils.checks import check_yaml session ort.InferenceSession(best.onnx, providers[CUDAExecutionProvider, CPUExecutionProvider]) # 图像预处理resize到640x640归一化通道转CHW加batch维 ... # 推理 outputs session.run(None, {session.get_inputs()[0].name: input_tensor})[0]ONNX Runtime在CPU上的推理速度比PyTorch快约2倍如果装CUDA版本支持GPU速度更快。这里要提醒的是Ultralytics导出的ONNX模型输出的格式是(1, 84, 8400)其中84是“4个框坐标 80个类别COCO或你自定义的类别数”8400是不同尺度特征图上的候选框数量。如果你的自定义类别是3类那输出形状就是(1, 7, 8400)。后处理时需要对输出做转置和过滤不能直接把模型输出当结果用。这一块在工程包里我写了完整的后处理函数包括置信度过滤、NMS非极大值抑制、坐标还原。5. 常见问题与排查技巧实录5.1 训练指标全部为0根本学不进去这是最让人崩溃的问题之一尤其是数据刚准备好、满怀期待跑训练的时候。出现这个现象的原因我排查过几轮总结下来90%出在数据标签上。最常见的情况是标签文件里坐标值不在[0,1]范围内。YOLO要求归一化坐标如果某一张图的txt文件里写的是像素坐标比如0 320 240 640 480那模型算损失时坐标就会乱套指标自然无法有效上升。第二种情况是图片文件夹和标签文件夹的名称对不上。Ultralytics查找标签是依据“图片文件名相同、后缀为.txt”的逻辑如果你图片叫image_001.jpg标签却叫image_0001.txt模型会认为这张图没有标签训练时跳过。排查方法很简单# 检查是否有标签文件 find datasets/skin_det/labels/train -name *.txt | wc -l # 检查某个标签内容是否合法 cat datasets/skin_det/labels/train/ISIC_0012345.txt # 打印图片和标签的文件名对比 ls datasets/skin_det/images/train | head -n 5 ls datasets/skin_det/labels/train | head -n 5如果上面三步都正常另一个小概率原因是数据集配置yaml里的图片路径写错了。记住path是相对于yaml文件位置的路径不是相对于命令行执行位置的路径少一个./或../就会导致图片加载失败。5.2 类别不平衡导致模型偏好严重我在第一节数据处理时已经做了类别归并和过采样但训练过程中还是发现了一个现象nevus类别的Recall很高0.93melanoma的Recall偏低0.85。这说明模型确实有偏好不过偏差还能接受。如果偏差比较大按照我的经验优先级排序处理方案提升少数类的采样权重在Ultralytics配置里用class_weights或者写自定义采样器。复制少数类样本但配合小幅度的增强比如只做翻转和噪声扰动避免过拟合。调整NMS阈值和置信度阈值对少数类类别单独设置较低的置信度阈值。收集更多目标是少数类的数据这是治本之策。5.3 模型在验证集好测试集完全拉胯这类问题通常是数据分布差异导致的。ISIC数据集本身是皮肤镜图像而实际推理场景可能是手机拍的普通照片。皮肤镜图像有固定的光照条件、放大倍数和色彩风格手机照片则千差万别。我在项目早期就吃过这个亏验证集mAP50有0.85实际拿手机拍的痣测试基本识别不出来。解决方案有两个方向。一是做更强鲁棒性的数据增强包括随机亮度、对比度、色调变化、高斯模糊、jpeg压缩噪声让模型适应不同采集设备的差异。二是实际部署时考虑是否可以先加一个图像质量校准的预处理步骤比如颜色校正、白平衡归一化。现实中前者更可行效果也更直接。我当时在增强里加了随机光度扰动和模糊扰动后手机图片的检测率从不到20%提升到了60%左右虽然还远不及皮肤镜图片但已经具备了初步可用性。5.4 显存不足怎么继续训练如果显存只有8G甚至更小用YOLOv8n batch4 imgsz480通常可以跑起来但精度会有损失。更推荐的做法是开启Ultralytics的梯度累积功能accumulate参数相当于用多个小batch的梯度累加来模拟一个大batch。我的建议参数组合显存imgszbatchaccumulate备注8G51244等效batch166G41644分辨率下降较明显12G64082等效batch16还有一个思路是开启AMP混合精度训练Ultralytics默认开启这能让显存占用降低约30%同时训练速度提升。如果之前关闭了AMP建议先打开试试。5.5 后处理坐标错乱检测框乱飘这个问题出现在自己写后处理函数的时候。ONNX输出的坐标是相对于640x640输入图像的但显示到原始图片上时需要先做坐标还原x_original x_normalized * original_width。很多人直接拿640的输出坐标往原图上画结果就是框的位置完全对不上。再一个坑是NMS的IoU阈值。如果阈值设得过高比如0.9同一个病灶会输出多个重叠框看起来像“框乱飞”如果设得过低比如0.3多个靠近的良性痣可能会被合并成一个框把病灶数量算少了。一般设置在0.5到0.7之间皮肤病灶这种密集出现的情况推荐0.5。6. 系统落地与医学AI的现实思考6.1 模型性能评估拿什么指标说话做医疗AI项目不能只盯着mAP看。mAP是目标检测领域的通用指标但医疗场景更关心的是临床意义上的灵敏度和特异度。我额外做了一组阈值扫描画出不同置信度阈值下的TPR-FPR曲线然后选了一个特定工作点在验证集上取TPR0.9对应的阈值此时FPR约为0.35。意思是系统会把9成的恶性黑色素瘤找出来代价是3成多的良性样本被标记为“需要进一步检查”。这个工作点你不可能通过mAP看出来但恰恰是医疗场景真正需要的指标。所以我在工程包里专门放了一个evaluate_clinical.py输出每个类别的TPR、FPR、PPV、NPV以及在不同置信度阈值下的变化表。希望你在做类似项目时也能从“模型指标好”走向“临床应用指标合理”。6.2 为什么这类系统只能叫“辅助工具”不能叫“诊断系统”这里我必须说点严肃的话。虽然项目名叫“皮肤癌诊断系统”但从技术成熟度来看它本质上是一个“基于深度学习的皮肤病灶检测与分类演示系统”离真正的临床诊断还有很长的距离。原因很现实训练数据ISIC 2020来自特定地区、特定设备的皮肤镜图像存在严重的肤色偏移问题——深肤色人群的样本严重不足模型在深肤色人群上的表现会大幅下降。皮肤癌的种类远不止三类黑色素瘤、基底细胞癌、鳞状细胞癌、Bowen病等几十种形态高度相似当前模型覆盖远不够。病理确诊的金标准是组织活检影像学只能提供概率性判断任何声称“只看照片就能确诊皮肤癌”的系统都是不负责任的。所以正确的落地方式是把模型定位为“初筛助手”。比如患者在皮肤科挂号前先做个AI自查如果系统提示有异常就建议尽快就医医生接诊时可以用系统做第二意见辅助判断是否需要活检。这种定位下的模型即使出现一定比例的误报也不会造成严重的医疗事故反而能提高潜在重症患者的发现率。如果真要朝着医疗器械方向走后续需要做的事是更大规模、多中心、多肤色的数据采集与标注临床专家参与评估体系设计完成三类医疗器械的注册和临床验证。这一块需要由医疗机构和专业团队推进而程序员的职责是先把模型做好、做透明、做可解释让医生敢用、能用。6.3 从隐私与合规角度看部署医疗图像涉及个人健康信息部署时不能像普通图片分类项目那样随意。如果你是在医院内网部署可以简单一些如果面向C端用户做在线服务图片上传、存储、模型推理都必须走合规流程比如数据脱敏、加密传输、用户授权协议。即使只是在本地科研使用也建议在代码层面做两个习惯性动作一是在读取图片前先清除EXIF信息中的地理位置等元数据二是推理结果报告中不出现可识别的患者姓名改用匿名ID。这些不是技术难点但体现的是医疗AI从业者最基本的伦理意识。写在最后的个人体会这个项目做完给我最大的收获不是模型精度刷到了多高而是完整走通了一条从原始医疗图像到目标检测模型的工程链路数据清洗、格式转换、弱监督训练、评估分析、模型导出、界面部署。你自己会踩很多坑但踩完之后你对YOLO和医学影像结合这件事就有了实打实的手感。最后再分享两个小技巧。第一训练医疗影像模型时一定要把数据划分的随机种子固定下来最好连数据集的hash划分也做成缓存文件否则每次跑实验结果对不上你根本没法判断是模型改进了还是数据划分变了。第二保存最终模型时不要只存best.pt把训练曲线图、验证集预测结果图和一份参数表格一起归档。等到你一个月后再回头做改进时你会发现这些东西比模型权重本身更值钱它们能帮你快速回忆起当时为什么做了那些决策。本文还有配套的精品资源点击获取