
简介面向医学影像与YOLO目标检测领域这份数据集专注于皮肤癌及常见皮肤病变的自动识别适合研究者、算法工程师及医学影像入门学习者使用。资源共1570张高分辨率皮肤镜图像训练集1256张、验证集314张标注覆盖基底细胞癌、复杂痣、皮内痣、交界痣、扁平苔藓、黑色素瘤、银屑病、脂溢性角化病与日光性角化病9类病变每张jpg原图均配有对应的txt标注文件并附有YOLO配置文件和数据集说明文档可直接用于迁移学习与模型微调。压缩包共2000个文件以1570个txt标注、428张jpg原图为主txt为YOLO格式边界框标注另含1个yaml配置与1个docx说明整体约68MB目录结构清晰便于按类别检索。已有142人学习下载对皮肤癌识别算法验证和医学影像目标检测实践有明确参考价值。1. 皮肤癌目标检测数据集一份zip到底能帮你省多少事做医学影像目标检测的人听到“皮肤癌目标检测数据集.zip”这个名字第一反应不是兴奋而是先确认一件事里面是只有图还是连标注一起给了。很多公开项目光给图片不给框光是找皮肤科医生标bounding box这一个环节就能让一个算法工程师在伦理审批和沟通成本上耗掉两周。这份zip如果真的带好了病灶位置和类别那它就是帮你把最贵的那块砌好了。我拿这类数据集的典型用法是这个思路先看标注格式转成YOLO能直接读的结构然后做一次最小训练确认数据没坏再调增强和平衡策略最后用独立验证集看真实效果。这套流程能跑通你就可以在这个方向上继续投入跑不通问题多半出在数据本身而不是模型。这篇文章面向的读者是想用现成皮肤镜图像做检测的算法工程师、医学影像方向的研究生、以及做辅助筛查产品原型的小团队。我会以YOLOv8为例但中间关于标注检查、数据划分、类别平衡的坑换任何检测框架都适用。你可以照着命令一步步复现也能在前面踩过的坑里省掉几个晚上的排查时间。2. 拆开zip之前皮肤癌检测的标注体系与格式选型2.1 目标检测 vs 分类分割这份数据该按哪种任务用皮肤癌相关的公开数据最常见的是HAM10000和ISIC系列但这两类大多只给图像级标签或分割掩码目标检测框反而不容易找。如果你的zip里是目标检测数据那大概率是有人专门整理过皮肤镜图像并用矩形框把病灶圈了出来。这时不能直接用分类的思路去做也不能把它当分割任务因为标注形式决定了标签能力。我需要先提醒一个反直觉的点皮肤病灶尤其是黑色素瘤形状很不规则矩形框会把大量正常皮肤包进来。这会让模型学到框内背景的纹理而背景又因患者肤色、毛发、油光而千差万别。如果你的zip里有mask我通常建议优先做分割但既然标题写的是“目标检测数据集”那我们就按检测来处理同时接受矩形框带来的噪声。类别名字一般会出现在labels目录或classes.txt里。常见的有nevus痣、melanoma恶性黑色素瘤、basal cell carcinoma基底细胞癌、squamous cell carcinoma鳞状细胞癌。你最好先看一眼类别列表因为后续所有训练指标比如mAP和混淆矩阵都依赖这个列表的顺序。如果zip里只有images和xml没有类别名优先级说明那你要自己读xml里的标签名并做映射。2.2 标注格式VOC/COCO/YOLO 怎么选以及转换脚本拿到zip先解压检查标注是三种格式中的哪一种VOC格式每张图片对应一个xml文件里面存object的name和bndbox坐标。COCO格式一个json文件包含所有图片、annotations和categories。YOLO格式每个图片一个txt文件每行是“类别 中心x 中心y 宽 高”坐标都是归一化到0~1的。YOLOv8原生直接支持COCO和YOLO格式但很多皮肤癌数据集整理时习惯给VOC格式因为标注工具LabelImg默认输出xml。我一般会统一转成YOLO格式因为目录干净、训练时不用写自定义解析器。下面是一个从VOC xml转YOLO txt的脚本你可以直接放到数据集根目录跑。import os import xml.etree.ElementTree as ET from PIL import Image classes [nevus, melanoma, bcc, scc] # 按你的classes.txt顺序 def convert_voc(xml_path, txt_path, img_path): tree ET.parse(xml_path) root tree.getroot() # 读取原图宽高避免用xml里的尺寸导致偏移 with Image.open(img_path) as im: img_w, img_h im.size lines [] for obj in root.findall(object): name obj.find(name).text if name not in classes: continue cls_id classes.index(name) box obj.find(bndbox) xmin float(box.find(xmin).text) ymin float(box.find(ymin).text) xmax float(box.find(xmax).text) ymax float(box.find(ymax).text) # 处理边界越界检测框超出图片范围会导致训练nan xmin max(0, min(xmin, img_w - 1)) xmax max(0, min(xmax, img_w - 1)) ymin max(0, min(ymin, img_h - 1)) ymax max(0, min(ymax, img_h - 1)) if xmax xmin or ymax ymin: continue cx (xmin xmax) / 2.0 / img_w cy (ymin ymax) / 2.0 / img_h w (xmax - xmin) / img_w h (ymax - ymin) / img_h lines.append(f{cls_id} {cx:.6f} {cy:.6f} {w:.6f} {h:.6f}) with open(txt_path, w) as f: f.write(\n.join(lines)) # 遍历images目录假设xml位于同名xml目录 for xml_name in os.listdir(xmls): base os.path.splitext(xml_name)[0] xml_path os.path.join(xmls, xml_name) img_path os.path.join(images, base .jpg) txt_path os.path.join(labels, base .txt) if os.path.exists(img_path): convert_voc(xml_path, txt_path, img_path)逻辑说明脚本先从原图读宽高避免xml里存的分辨率和实际图不一致然后把绝对坐标转成相对坐标同时限制越界框。皮肤镜图像常见的问题是四周有黑色边框如果标注框不小心包含了边框区域转出来的框坐标会偏。转换前最好用代码把所有图裁掉黑边并重新计算框我在后面避坑章会再提。参数说明classes列表顺序必须和你之后写data.yaml的names顺序完全一致否则类别会错位训练出的模型就是“黑匣子”。如果xml中有你没有见过的类名先打印出来确认不要静默跳过。2.3 数据目录结构训练前先看清这四类文件我建议你解压后先跑一个命令看整体目录树而不是急着装环境find . -maxdepth 2 -type d find . -maxdepth 2 -type f | head -50一个可用的数据集目录通常包含这四类东西images/原始皮肤镜图像可能是jpg/png/bmp建议统一转成jpg或png。labels/与图片同名的txt标注文件每一行一个目标。classes.txt或names.txt类别名文件一行一个类。train.txt/val.txt或train/val子目录预先划分好的训练/验证集列表。如果没有预先划分你不能简单把所有图片洗牌后按比例切。皮肤癌数据里同一个患者可能有不同角度拍摄的多张图随机划分会导致同患者图像同时出现在train和val这就是“泄漏”会导致验证指标虚高。正确做法是按患者ID分组划分。如果文件名里没有患者ID就只能用文件名前缀或拍摄时间做近似这个坑后面会专门讲。3. 用 YOLOv8 跑通皮肤癌检测环境配置到训练命令3.1 最小环境配置与依赖安装我一般用conda管理环境避免把系统Python搞乱。下面这套配置在Linux和Windows上都能跑通核心是ultralytics库。conda create -n skin python3.9 conda activate skin pip install ultralytics pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118逻辑说明ultralytics是YOLOv8的官方封装库它会自动拉取所需的opencv、pandas等依赖。torch单独装是因为默认的pytorch源不带CUDA支持如果你只有CPU机器可以直接pip install torch训练速度会慢很多但小规模验证是够用的。参数说明Python版本建议3.8到3.11太高或太低都容易出现opencv或pandas的二进制依赖冲突。我踩过Python 3.12装opencv失败的坑后来直接回退到3.9就顺利了。装完可以跑一句yolo help确认命令行可用。3.2 把标注转成YOLO格式的实操脚本上一章已经给了VOC转YOLO的脚本这里再补一步生成训练所需的data.yaml文件。path: /home/user/skin_cancer_data train: images/train val: images/val names: 0: nevus 1: melanoma 2: bcc 3: scc如果目录是扁平结构没有train/val子目录就把train和val都指向images目录然后通过train.txt/val.txt指定图片列表YOLO也支持这种方式path: /home/user/skin_cancer_data train: train.txt val: val.txt names: 0: nevus 1: melanoma 2: bcc 3: scc写完后可以用一行命令快速验证数据路径是否有问题yolo detect train datadata.yaml modelyolov8n.pt epochs1 imgsz640 batch8逻辑说明先跑1个epoch目的不是训练而是让YOLO读取并预处理所有图片。如果标注文件缺失、图片路径错、或者图片分辨率异常会在这里直接报错。你看到类似labels /home/... not found的信息说明txt没有和图片对齐基本就是写脚本时文件名后缀不一致。参数说明modelyolov8n.pt表示用nano预训练权重文件会自动下载到当前目录。记住不要一上来就用yolov8x数据要是有问题大模型只会让你浪费更多时间。epochs1和imgsz640的组合足够完成一次数据读取日志输出。3.3 第一次训练参数、batch_size与样本均衡数据检查通过后我会开启正式训练。预算有限的话第一轮别追求精度目标是拿到loss曲线和基础mAP作为后续调参的基线。yolo detect train datadata.yaml modelyolov8m.pt epochs80 imgsz640 batch16 device0 patience15 projectskin_runs namebaseline下面是几个我每次都会认真盯的参数改动前先想清楚影响参数我的经验值说明imgsz640显存不够再降皮肤镜病灶不需要太高的分辨率因为框本身包含背景但小目标需要提高batch能放到显存里就尽量大batch太小会让BN统计不稳定常用16到64之间patience15到20验证集mAP连续不涨就提前停省时间optimizerAdamW或SGDAdamW收敛快SGD最终精度更稳mosaic1.0默认如果训练不收敛可以关掉mosaic试一下皮肤癌数据几乎必然存在类别不平衡。早期黑色素瘤样本本来就少痣的样本可能占比超过60%。你在训练命令里可以加一个类别权重参数YOLOv8提供的是cls它控制分类损失占总loss的比例不是逐类权重。更直接的做法是在读取数据时做过采样对样本少的类别复制几次。import os from pathlib import Path labels_dir Path(labels) counts {} for p in labels_dir.glob(*.txt): with open(p) as f: first_line f.readline().strip() if not first_line: continue cid first_line.split()[0] counts[cid] counts.get(cid, 0) 1 print(counts)逻辑说明这个脚本统计每个类别的目标数量而不是统计图片数量。一张图上可能同时有多个类别目标级别不平衡直接决定检测器偏向。统计完之后你再决定是复刻稀有类别样本还是调整NMS后的置信度阈值。参数说明YOLOv8的cls0.5是默认分类损失权重如果你发现稀有类别老是漏检可以提高到0.7但不要超过1.0否则定位框会变差。另一种做法是在后处理里对不同类别用不同置信度阈值这个放到最后一章讲。3.4 评估指标mAP0.5和mAP0.5:0.95怎么看训练完会在skin_runs/baseline/下生成results.csv和val子目录。YOLO默认输出的核心指标是mAP0.5和mAP0.5:0.95两者含义差别很大。mAP0.5表示预测框和真值框的IoU大于0.5就算检测成功。皮肤癌检测标注框本身就不精确比如黑色素瘤边缘不规则标注者画框时会有几个像素的差异所以0.5的IoU能够容忍这部分误差。mAP0.5:0.95从0.5到0.95每隔0.05算一次平均它更严格对框的位置要求很高。医学场景里你更需要保证“别漏”而不是框的位置精确到落点所以重点看mAP0.5和每类的召回率。我习惯先跑验证并输出每类的指标yolo detect val modelskin_runs/baseline/weights/best.pt datadata.yaml batch16终端会逐类打印精确率和召回率。注意看melanoma的recall如果低于0.5说明模型漏了一半以上的恶性黑色素瘤这在应用场景里是不可接受的。此时别急着调模型结构先回看数据增强和类别平衡。4. 皮肤癌目标检测的避坑实录标注错位、类不平衡与过拟合4.1 现象验证集mAP很高测试集一塌糊涂有一次我拿一份皮肤癌数据训练验证集mAP0.5接近0.9心里还挺高兴。结果放到自己留出的真实测试集上mAP直接掉到0.4每个类别的召回率都不像训练时那么漂亮。后来检查划分逻辑才发现问题原数据是按图片随机划分的同一患者不同部位的图像被同时分到了train和val模型在val里“见过”这个患者的肤色纹理和拍摄环境自然能猜出病灶位置。原因就是数据泄漏。解决方式很简单按患者ID分组划分保证一个人的所有图只在一边。import os from sklearn.model_selection import GroupShuffleSplit # 假设文件名格式为 patient_{id}_view_{n}.jpg files [f for f in os.listdir(images) if f.endswith(.jpg)] groups [f.split(_)[1] for f in files] # 提取患者ID split GroupShuffleSplit(n_splits1, test_size0.2, random_state42) for train_idx, val_idx in split.split(files, groupsgroups): train_files [files[i] for i in train_idx] val_files [files[i] for i in val_idx]逻辑说明GroupShuffleSplit会把同一个group患者ID的所有样本分到同一个集合从源头掐断泄漏。如果文件名没有患者ID就用文件名开头固定位数。参数说明test_size0.2保留20%作为验证集对品类不平衡的数据还可以再加一个分层参数按类别分布分层避免某个类别全跑进验证集导致训练样本更少。4.2 现象类别权重失衡基底细胞癌检测不出来训练日志显示mAP整体还行但一打开混淆矩阵基底细胞癌bcc的检出率几乎为零全部预测成了痣。原因很直白数据里痣的样本数量可能是bcc的十倍模型为了降低总损失选择把不确定的框全都判成高频类。我尝试过直接提高损失权重但效果有限因为YOLO的分类损失是全局的不是对每个类单独设置权重。更靠谱的办法是过采样对bcc样本在每次epoch复制2到3份或者用增强手段让模型看到更多bcc的变化。还有一招是把bcc和melanoma合并为“恶性肿瘤”类做二分类等模型能区分“病变/痣”之后再细分细粒度类别。这个策略在医学检测里很常用虽然会丢失细分类信息但能先把最重要的“是否恶性”做对。4.3 现象标注框偏移导致loss不收敛训练了十几个epochbox_loss一直很高val的mAP卡在0.2上不去。我怀疑是数据的问题于是检查了几张标注图发现预测框和实际病灶位置差得离谱。最后定位到原因原始标注用了双线性插值后的图像但坐标是从原始大图直接转过来的缩放比例不一致。解决方法是回到转格式那一步用原图宽高而不是xml里的width/height字段。很多标注工具在保存xml时会写入图片当时的尺寸但图片后来可能被缩放或裁剪过这个尺寸就不准了。我这边的经验是转换脚本里必须用PIL.Image.open或cv2.imread读取图片的真实形状而不是直接信任xml里的值。另外有些数据集从DICOM转JPG后标注框坐标跟着转但图片里混有黑色背景或标尺坐标没有扣除偏移也会导致同样的现象。处理办法是先统一裁掉黑边并把所有坐标同步减去偏移量。4.4 现象zip解压后图片损坏或颜色异常皮肤癌数据集压缩包通常很大网络传输中途损坏是常事。解压后如果训练时发现某张图读不进来或者颜色发绿发紫先别怀疑代码先验证文件完整性。unzip -t skin_cancer_dataset.zipunzip -t会测试zip内所有文件的CRC校验值输出OK就说明文件没问题。如果有损坏文件重新下载即可。颜色异常则通常是图像色彩空间问题皮肤镜原图可能是16位灰度或DICOM的彩色映射直接存成jpg后被错误地按RGB三通道读入导致色调完全错位。处理办法是统一用OpenCV的imgproc.cvtColor转成RGB并在训练前对所有图像做一次像素值范围统计异常图像会明显偏离正常范围。另外有些zip会设密码如果解压提示密码找数据集来源说明不要用来路不明的“密码移除”工具这既可能损坏文件也容易本机中招。5. 扩充与预处理把单组数据跑出更好的鲁棒性5.1 图像标准化与毛发遮挡处理皮肤镜图像最烦人的两个东西是毛发和反光。深色毛发覆盖病灶区域时模型容易把毛发的方向纹理当成病灶特征。我尝试过在训练前用黑帽变换去除毛发效果参差不齐白人的金色毛发处理完还行深肤色患者的黑色毛发反而会被放大。后来我改用更克制的方式在训练阶段加入随机遮挡增强让模型学会忽视局部遮挡。import cv2 import numpy as np def remove_black_border(img, thr10): # 皮肤镜图像常见四周黑边裁掉它同时把非黑区域内容保留 gray cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) mask gray thr coords cv2.findNonZero(mask.astype(np.uint8)) x, y, w, h cv2.boundingRect(coords) return img[y:yh, x:xw]逻辑说明这个函数裁掉四周黑色边框避免模型学到黑边特征。皮肤镜图像如果四角有黑色遮罩它的亮度很低而病灶不会出现在那里。参数说明thr10是亮度阈值不同设备采集的图像底色不同如果裁不干净可以提高到20但别太高否则会裁掉浅色皮肤。裁完黑边后如果数据有标注框必须同步调整框坐标也就是在转换脚本里先裁图再算坐标。5.2 数据增强策略针对皮肤镜图像的翻转、缩放与颜色抖动YOLOv8自带的增强参数在data.yaml里可以配置我针对皮肤癌数据会做三处改动hsv_h: 0.015 # 色调扰动原默认0.015 hsv_s: 0.5 # 饱和度扰动调低因为肤色饱和度会影响判断 hsv_v: 0.4 # 亮度扰动保持默认 fliplr: 0.5 # 水平翻转可用皮肤病灶左右镜像不影响诊断使用yolo detect train时这些参数可以直接写进命令yolo detect train datadata.yaml modelyolov8m.pt epochs80 imgsz640 batch16 hsv_s0.3 fliplr0.5逻辑说明皮肤镜图像的颜色受设备、光照、肤色的影响很大适量颜色抖动能增强泛化性但饱和度抖动不要太大否则病变区域的红色特征会失真。垂直翻转flipud默认是关闭的一般不需要开因为皮肤镜采集方向固定。5.3 迁移学习用ImageNet预训练还是用医学预训练YOLOv8的预训练权重来自COCO数据集全是自然图像。有人担心这个权重对皮肤镜图像帮助不大我的看法是别完全从零训练但也不要过度依赖COCO的backbone特征。第一个epoch训完你如果发现loss下降很慢可以把backbone冻结几轮让靠近输出的层先适应医学数据的类别分布。yolo detect train datadata.yaml modelyolov8m.pt epochs80 imgsz640 freeze10freeze10表示冻结前10层backbone只训练检测头。训练20轮之后再解冻全部层用一个较小的学习率继续微调。这个做法在样本量小的时候能有效防止模型把皮肤镜图像的背景纹理学进主干特征。如果你手里有额外的皮肤癌分类模型比如在HAM10000上训练过的ResNet理论上可以把它作为backbone替换进YOLO但工程复杂度很高。对多数人来说用COCO权重冻结预训练层已经能获得不错的结果不要为了“医学专用”而折腾不稳定的框架。5.4 小目标病灶的切片检测与多尺度推理早期黑色素瘤在图像上可能只占几十个像素在640分辨率下经过几次下采样特征图上的响应非常微弱。我遇到过一个案例病灶直径在图上只有约30像素yolov8m在640下完全没检出来。解决办法是“切片推理”把原图裁成多个重叠的patch分别检测再把结果合并回原图坐标。import cv2 from ultralytics import YOLO model YOLO(best.pt) img cv2.imread(derm.jpg) h, w img.shape[:2] patch_size 640 stride 320 # 重叠率50% detections [] for y in range(0, h - patch_size 1, stride): for x in range(0, w - patch_size 1, stride): patch img[y:ypatch_size, x:xpatch_size] results model(patch, imgsz640, conf0.25) for box in results[0].boxes: # 把patch内坐标换算回原图 x1, y1, x2, y2 box.xyxy[0].tolist() detections.append([x x1, y y1, x x2, y y2, box.conf[0].item(), int(box.cls[0])])逻辑说明切片重叠率取50%避免病灶正好被切在patch边界。合并时需要对重复检测做NMS简单做法是如果两个框的IoU大于0.5就保留置信度高的那个。参数说明patch_size要和你训练时的imgsz保持一致否则模型效果打折。切片推理的速度比整图推理慢好几倍但如果应用场景是辅助医生诊断速度不是第一优先级。6. 验证模型是否可用从混淆矩阵到实际部署的取舍6.1 混淆矩阵与每类精确率/召回率训练完成后除了看mAP我会立即打开验证输出里的confusion_matrix.png。医生最怕的是漏诊所以对恶性黑色素瘤我要求recall至少到0.85哪怕precision降到0.4。别觉得这个标准低在皮肤镜图像上误报一张痣最多是让医生多看一眼而漏报一个恶性黑色素瘤就是医疗事故。yolo detect val modelskin_runs/baseline/weights/best.pt datadata.yaml conf0.25 iou0.45你可以加上conf和iou参数看不同阈值下的指标。如果melanoma的recall一直上不去我会先怀疑两类情况一是标注框把多个病灶合成一个框二是增强太强导致病灶外观被破坏。6.2 在独立验证集上的复现清单验证一个模型能不能用我有一套固定流程确认验证集和训练集没有同患者的图也就是前面说过的人维度划分。确认验证集中的类别比例和真实场景一致如果你的产品主要筛查黑色素瘤就不能拿一堆痣样图来当验证集。对每个类别分别看P/R曲线而不是只看mAP。在几个有代表性的难例上看模型输出比如边界模糊的病灶、被毛发遮挡的病灶、手掌和脚底位置的病灶。这套清单花不了一个小时但能避免模型在部署时翻车。6.3 部署时的输入尺寸与后处理阈值调整推理部署时我会把输入尺寸固定为训练时的imgsz不要为了速度随便降否则小病灶会被压缩没。置信度阈值建议设低到0.2然后在后处理里对不同类别设置各自的门槛。比如痣类预测置信度低于0.6就过滤掉而黑色素瘤预测只要超过0.2就保留并标记为“建议医生复核”。这种方式在医学AI里更实用因为它允许模型表达“我不确定但值得警惕”。后来我学到的教训是不要只看总mAP就定模型可用一定要看最低类别召回率。我之前那个模型总mAP有0.88但scc的recall只有0.3放到真实门诊场景就是把鳞状细胞癌漏给患者。改成了类别级阈值后漏检率直线下降虽然整体误报多一些但在辅助诊断场景里多请看一眼远比漏掉安全得多。希望这个思路能帮到你少走一段弯路。本文还有配套的精品资源点击获取