简介:这是一套面向计算机视觉学习者的脸部皮肤病检测数据集,包含粉刺、丘疹、结节、脓疱四类目标,适合用来练习YOLO系列或Faster R-CNN等目标检测模型的训练与验证。数据集中每张jpg图片均配有对应的xml和txt标注,既提供VOC格式便于使用现有检测框架,也提供YOLO格式方便直接训练;图片未做增强,矩形框标注清晰规范,共8827个目标框可供统计与评估。压缩包共2000个文件,其中1590个xml标注、410个txt标签文件,整体大小35.93MB,数据量适中,便于快速下载和迭代实验。目前已有103人学习,适合作为入门或课程设计的数据补充,帮助读者熟悉从标注解析、格式转换到模型训练与mAP评估的完整流程。
1. 脸部皮肤病检测:为什么说这份数据集的瓶颈在标注而不是图片
做皮肤检测的同行应该都经历过这种场景:模型结构选好了,YOLO环境也装好了,结果卡在数据上。去医院要影像数据涉及隐私,自己去网上爬图又面临标签不干净的问题。脸部皮肤病检测数据集(YOLO+VOC格式)解决的正是这个中间环节——拿到手就能直接进训练管线,不用自己写坐标转换脚本,也不用纠结标注格式到底用哪种。它覆盖的是脸部常见皮肤病变的目标检测任务,适合用来跑通算法流程、做技术验证、或者作为迁移学习的起点。
这个数据集最有价值的地方,不是图片数量,而是双格式标注的可用性。许多公开数据集只给一种格式,换框架就得重写转换逻辑,中间不知道要踩多少坑。这份数据集同时提供VOC的XML标注和YOLO的txt标注,等于给训练和验证各留了一份底稿。不过,格式齐全不意味着直接就能拿来训,标注里的细节问题仍然很多。下面从数据组织、训练配置、格式转换到翻车排查,一条线讲清楚。
适合读这篇文章的人有三类:想用YOLO做脸部皮肤异常检测但缺乏干净数据的算法工程师,需要快速验证检测方案的医疗或美容行业技术负责人,以及正在学习目标检测、想拿医疗类数据集练手的学生。接下来每一步都会贴可复现的操作,包括脚本、参数和踩坑记录。
2. 数据集的目录与标签体系:先摸清VOC和YOLO怎么对齐,再谈训练
2.1 目录结构怎么排:一张脸对应四份文件
拿到这类双格式数据集,第一件事不是去看图片长什么样,而是把目录结构完整列出来。常见布局是分为images、Annotations和labels三个目录,外加一个存放训练验证拆分的ImageSets目录。images放jpg原图,Annotations放VOC风格的XML文件,labels放YOLO风格的txt文件,ImageSets里写train.txt和val.txt。用tree命令看一眼就清楚:
dataset/ ├── images/ │ ├── 0001.jpg │ ├── 0002.jpg │ └── ... ├── Annotations/ │ ├── 0001.xml │ ├── 0002.xml │ └── ... ├── labels/ │ ├── 0001.txt │ ├── 0002.txt │ └── ... └── ImageSets/ ├── train.txt └── val.txt一张脸图在数据集里对应四份文件:jpg是原始图像,xml记录基于像素坐标的边界框和类别字符串,txt记录归一化到0-1区间的边界框和类别整数ID,txt里还记录这张图属于训练集还是验证集。这样的结构保证了无论你用哪个框架,都能直接找到对应的标注文件。看这个布局时要确认一个关键点:VOC格式用字符串做类别名,YOLO格式用整数ID做类别索引,两个映射必须一致。如果XML里是nevus,而txt里对应ID是2,那训练时类别2的名字必须也是nevus。
提示:拿到数据集后先跑一遍文件对齐检查,统计images、Annotations、labels三个目录下的文件数量差异。差值大说明有图片漏标,差值为0再往下规划训练。
2.2 标签体系与脸部病灶检测的特殊性
脸部皮肤病变的类别在设计上通常围绕临床常见病种展开。这类数据集覆盖的典型类别包括痤疮、色素痣、脂溢性角化、寻常疣、血管性病变等。每张图里病灶的特点是两个极端:一是目标区域小,二是纹理特征与周围皮肤接近。眼镜、胡茬、刘海都会成为干扰项,模型很容易把高光区域误判成病变区域。
这些特点直接决定了标注细节要求高。VOC的XML里必须精确到像素级别,差几个像素,在脸部这种小目标场景下就可能造成IoU大幅下降。类别映射表是这份数据集的灵魂,建议先建好一个只读的映射表,训练和转换脚本共用一个来源,避免各写各的字典:
| 类别ID | VOC标签字符串 | YOLO标签字符串 | 临床常见表述 |
|---|---|---|---|
| 0 | acne | acne | 痤疮/粉刺 |
| 1 | nevus | nevus | 色素痣 |
| 2 | seborrheic_keratosis | seborrheic_keratosis | 脂溢性角化 |
| 3 | verruca | verruca | 寻常疣 |
| 4 | vascular | vascular | 血管性病变 |
VOC的XML里存的是可读的字符串标签,YOLO的txt里存的是整数ID。标签体系里最常翻车的点就在这里:手写映射字典时把顺序排错,比如把nevus写成ID 0,而XML里acne排在第一位,结果训练时模型把痣全学成了痤疮。这类问题不会报错,只会静默地让指标变得很奇怪。
2.3 动手校验标注质量:一个脚本检查三件事
训练前做一轮标注质量检查,成本远低于训练跑了一半才发现数据有问题。我一般会写一个几十行的Python脚本,检查三件事:一是图片、XML、TXT三方是否一一对齐,二是YOLO的归一化坐标是否都落在0到1区间内,三是检查有没有空标签文件,这类文件往往代表漏标:
import os from pathlib import Path def validate_dataset(img_dir, xml_dir, label_dir): img_files = set(Path(img_dir).glob("*.jpg")) xml_files = set(Path(xml_dir).glob("*.xml")) label_files = set(Path(label_dir).glob("*.txt")) img_stems = {f.stem for f in img_files} xml_stems = {f.stem for f in xml_files} label_stems = {f.stem for f in label_files} # 检查1:三方文件是否一一对应 missing_xml = img_stems - xml_stems missing_label = img_stems - label_stems print(f"缺XML的图片: {len(missing_xml)} 张") print(f"缺txt的图片: {len(missing_label)} 张") # 检查2:TXT坐标是否越界 bad_coords = [] for f in sorted(label_files): for line in f.read_text().strip().splitlines(): parts = line.split() if len(parts) != 5: bad_coords.append((f.name, "字段数不对")) continue cls, cx, cy, w, h = parts if not (0 <= float(cx) <= 1 and 0 <= float(cy) <= 1 and 0 <= float(w) <= 1 and 0 <= float(h) <= 1): bad_coords.append((f.name, "坐标越界")) print(f"坐标越界或格式错误: {len(bad_coords)} 个") # 检查3:空标签文件统计 empty_labels = [f.name for f in label_files if not f.read_text().strip()] print(f"空标签文件: {len(empty_labels)} 个")这段脚本的核心是三方文件名的stem对齐。图片、XML、TXT共用同一个文件名主体,任何一方缺失都意味着该样本不能参与训练。坐标越界检查针对YOLO格式尤其重要,因为txt里的值是归一化浮点数,一旦出现大于1或者小于0的值,训练时的anchor匹配就会出错,而且错误是静默的、不报错。空标签文件意味着这张图没有任何目标框,如果它在train列表里,模型会被迫去学习「这张图没有目标」的模式,污染特征提取器。
参数上需要注意,glob的pattern写死了jpg后缀,如果数据集里有png图片要改成*.png或*.jpg". 脚本只做静态检查,不负责修复,看到空标签文件时先人工确认是漏标还是背景图。若是漏标,这类数据和标注规范相关的问题最好是回到标注工具里补框,而不是用脚本硬填一个假框。
小数据集的标注质量直接影响后续迁移学习的上限。建议把校验脚本保存到数据集根目录,每次增删数据后都跑一遍,确认改动没有破坏标注一致性。
3. 将YOLO格式直接投入训练:数据集配置与关键超参数
3.1 data.yaml配置:不做这三项设置,训练结果很虚
YOLO的训练入口是数据集配置文件。data.yaml的写法直接决定了训练集和验证集从哪里读、类别怎么解释,以及模型最终输出的类别数量。一个典型的脸部皮肤病检测配置如下:
# data.yaml path: /data/face_skin_disease # 数据集根目录,用绝对路径 train: images/train # 训练集图片目录 val: images/val # 验证集图片目录,别把训练图混进来 nc: 5 # 类别数量,必须与标签系统对齐 names: 0: acne 1: nevus 2: seborrheic_keratosis 3: verruca 4: vascular这份配置里最值得留意的是nc参数和names的对应关系。nc=5表示模型最后输出5类预测,names则把ID映射到可读名称。这两处和标签txt里的整数ID必须完全一致,否则训练日志里的类别名会张冠李戴。train和val指向的目录不要有交集,很多人图省事直接把所有图片丢进train,再从train里抽一部分当val,结果验证集指标虚高,部署到现场就翻车。
path路径建议写绝对路径。YOLO在读取data.yaml时,如果只写相对路径,工作目录一变就加载失败,或者更隐蔽地加载到了旧目录下的数据。这个坑在换机器、跑docker时特别常见。
3.2 训练超参数怎么选:小病灶场景的取舍逻辑
脸部病灶检测的难点在于目标小、背景纹理复杂,网络结构的选择要直接服务于这个特点。模型主干用YOLOv8s或YOLOv5s起步,而不是直接上l或x版本。小病灶需要的不是更大规模的参数,而是更强的局部特征敏感性,过大的模型在几千张医疗图上很容易过拟合,把皮肤纹理的噪点学成病灶特征。
输入分辨率建议从640开始试。如果发现小目标区域在640分辨率下只有十几个像素,再往768或832提。病灶检测对分辨率的需求和交通场景不同,交通检测里车很大,640足够;脸部病灶可能只占图像的1%不到,低分辨率下特征会直接被下采样磨没。提高分辨率后,一个直接代价是显存占用增大,所以后面第5章会有专门的显存排查。
损失函数部分使用YOLO自带的三分支损失:分类损失判断病灶类型,回归损失修正边界框位置,置信度损失决定框内是否有目标。脸部病灶的类别容易被混淆,比如色素痣和脂溢性角化在视觉上都很接近肤色,这种情况下不建议去改分类损失的权重,更有效的做法是在增强阶段加入color jitter、hsv增强来压制模型对颜色绝对值的依赖。
3.3 启动训练并观察指标:loss曲线怎么看才不慌
配置文件和超参数确认后,训练命令很短。以YOLOv8为例,命令行传参覆盖data.yaml里部分默认设置:
# 使用yolov8s预训练权重,训练200轮 yolo detect train \ model=yolov8s.pt \ data=/data/face_skin_disease/data.yaml \ epochs=200 \ imgsz=640 \ batch=16 \ patience=30 \ project=/output/face_skin \ name=exp_01启动训练后,终端输出的实时指标里需要关注三条:box_loss、cls_loss、dfl_loss,它们对应框回归、分类和分布聚焦损失。脸部病灶数据集因为目标小,box_loss的初始值通常在0.1到0.2之间,比常规目标检测数据集的初始值高,这是正常的,不必惊慌。训练的决策点是看验证集的box_loss,它和训练集的box_loss之差是最有价值的指标。
patience=30表示连续30轮验证指标不提升就自动停止。对小数据集来说,200轮的上限配合早停机制,通常能避免浪费时间。如果训练跑到一半发现验证集指标震荡剧烈,先别加数据,先去看labels目录里的标注是不是有错框。
训练过程发现val损失在某个点开始回升时,这往往意味着模型开始过拟合训练集的噪声。脸部皮肤图像里的毛孔、油光、阴影都可能被模型记住,导致验证表现下降。此时优先尝试的手段是提高weight_decay、增加数据增强强度,而不是回退epoch数。数据增强方面可以开启mosaic和mixup,但对脸部病灶这种密集小目标,mosaic的尺度缩放可能会把病灶裁出图外,需要配合关闭mosaic的最后10个epoch,让模型在接近真实分布下收敛。
训练结束后,output/face_skin/exp_01目录里会生成weights/best.pt和weights/last.pt。best.pt是验证集表现最好的权重,last.pt是最后一轮权重,只用best.pt做后续验证和部署。
4. VOC与YOLO格式互转:坐标归一化脚本与四个边界坑
4.1 从VOC到YOLO:归一化时最容易错的一步
虽然这份数据集自带双格式,但做迁移学习或交叉验证时,仍然常会遇到只有VOC格式的图片需要转成YOLO格式的情况。VOC的XML里保存的坐标是像素绝对值,xmin、ymin表示左上角坐标,xmax、ymax表示右下角坐标。YOLO需要的是相对于图像宽高的归一化数值,而且要求是中心点坐标和宽高,不是左上角坐标。这一步转换逻辑很简单,但写错的人非常多:
import xml.etree.ElementTree as ET def voc_to_yolo(xml_path, img_w, img_h): tree = ET.parse(xml_path) root = tree.getroot() boxes = [] for obj in root.iter('object'): # 类别名只取basename,避免路径干扰 cls_name = obj.find('name').text.strip() bndbox = obj.find('bndbox') xmin = int(float(bndbox.find('xmin').text)) ymin = int(float(bndbox.find('ymin').text)) xmax = int(float(bndbox.find('xmax').text)) ymax = int(float(bndbox.find('ymax').text)) # 坐标裁剪到图像边界,防止负值或超界 xmin = max(0, xmin) ymin = max(0, ymin) xmax = min(img_w - 1, xmax) ymax = min(img_h - 1, ymax) # VOC是[xmin, ymin, xmax, ymax],转换为[x_center, y_center, w, h] w = xmax - xmin h = ymax - ymin x_center = xmin + w / 2 y_center = ymin + h / 2 # 归一化到[0, 1]区间,保留6位小数 boxes.append(( x_center / img_w, y_center / img_h, w / img_w, h / img_h, cls_name )) return boxes这段代码的关键点在于:坐标裁剪必须在转换之前做,否则越界的坐标归一化后会大于1,YOLO训练时这些框会被全部忽略,导致标注数量悄悄变少。中心点坐标用xmin + w/2,不是(xmin + xmax)/2,两者只在整数运算下等价,浮点舍入时会差出半个像素。精度保留6位足够,转出来是0.123456这样的字符串,再长的浮点对训练没有实际帮助,反而让txt文件变大。
4.2 从YOLO到VOC:回读坐标时的取整与边界问题
反向转换同样有细节问题。YOLO的txt是归一化浮点数,还原成像素坐标时必须做去归一化,然后乘回图像宽高。很多人直接在浮点上运算,最后一步才转int,结果框的位置偏了1到2个像素,验证时IoU掉0.05就是这种微小偏移积累出来的:
def yolo_to_voc(txt_path, img_w, img_h, cat_names): boxes = [] for line in open(txt_path, 'r'): parts = line.strip().split() if len(parts) < 5: continue # 跳过空行和无意义的短行 cat_id, cx, cy, w, h = parts cx, cy, w, h = float(cx), float(cy), float(w), float(h) # 去归一化还原像素坐标 x_center = cx * img_w y_center = cy * img_h box_w = w * img_w box_h = h * img_h # 还原左上角和右下角,四舍五入而不是直接截断 xmin = int(round(x_center - box_w / 2)) ymin = int(round(y_center - box_h / 2)) xmax = int(round(x_center + box_w / 2)) ymax = int(round(y_center + box_h / 2)) boxes.append({ 'name': cat_names[int(cat_id)], # 只看ID映射,不看字符串 'xmin': max(0, xmin), 'ymin': max(0, ymin), 'xmax': min(img_w - 1, xmax), 'ymax': min(img_h - 1, ymax), }) return boxes取整方式用round而不是直接用int截断,差别在0.5像素到1像素之间。对小目标检测来说,一个病灶框如果只有12像素宽,误差1像素就是8%的位移,直接反映到IoU上。yolo_to_voc还原坐标时读的是cat_id,不要从txt里读字符串类别名,txt里存的本就是整数ID,读字符串反而可能因为浮点转字符串的格式差异导致匹配失败。
4.3 转换中的四个边界坑:现象、原因与解决
第一坑是类别错位。现象是转换后训练时发现模型把痣全部检测成痤疮,但看起来损失正常下降。原因是XML里是字符串类别名,YOLO里是整数ID,两个列表的排序不一致,比如VOC的xml按字母序排,而映射表按人工手写顺序排。解决方法是写一个公共映射字典,从XML的类别字符串出发生成ID,用代码自动排序,不要手工维护两套映射。
第二坑是空标签文件被忽略。现象是训练日志里显示的数据总张数少于图片张数,但没人注意。原因是某些图片没有病灶,转换脚本直接把空结果写成了空txt。解决方法是转换时对空标注做单独统计,将无目标图片移到背景目录,并记录数量,让训练集和验证集的组成变得可解释。
第三坑是XML编码问题。现象是某些XML文件的类别名读出来是乱码,导致类别匹配失败。原因是XML头声明的是gb2312或者gbk编码,而Python的ElementTree默认用UTF-8解析。解决方法是打开文件时明确指定编码,在ET.parse前加一层文件流包装,读出来不对时先file.read()再按声明的编码解码。
第四坑是往返转换的精度损失。现象是VOC转YOLO再转回VOC后,框的坐标偏移了2个像素。原因是两次转换间的浮点舍入被累积放大。解决方法是原始标注保留不动,转换只生成副本供训练使用,任何情况下不要用转换后的文件覆盖原始XML。这个原则应该成为处理任何标注数据时的底线。
# 转换时的公共模式:只读输入,新写输出,绝不覆盖 from pathlib import Path def batch_convert(src_dir, out_dir, w, h, names): src_dir = Path(src_dir) out_dir = Path(out_dir) out_dir.mkdir(parents=True, exist_ok=True) for xml_file in src_dir.glob("*.xml"): boxes = voc_to_yolo(str(xml_file), w, h) out_txt = out_dir / (xml_file.stem + ".txt") with open(out_txt, "w") as f: for cx, cy, bw, bh, name in boxes: cat_id = names.index(name) f.write(f"{cat_id} {cx:.6f} {cy:.6f} {bw:.6f} {bh:.6f}\n")这段批量转换脚本展示了写入方式的要点:目录分离、名称保持一致、写入格式带6位小数。names.index(name)的写法虽然简单,但names必须是有序列表,否则ID会错。更稳妥的做法是维护一个字典,name_to_id[name],这样即使类别列表顺序变化也不会影响结果。批量转换完成后,再次运行第2章的校验脚本,确认转换产物通过坐标越界检查。
5. 训练翻车排查:脸部皮肤检测的五个高频问题
5.1 增强把病灶裁没了:模型学到的全是正常皮肤
现象是训练到第50轮loss降得很好,但验证集的mAP卡在0.1到0.2之间不涨。查看训练样本时发现增强后的图片里根本找不到病灶区域。原因是YOLO默认开启的mosaic增强把4张图拼接在一起,病灶小、位置随机,拼接后可能整块区域被裁剪到图外,或者被缩小到几个像素。脸部皮肤病不比交通场景,车子够大不容易被裁掉,病灶只有拳头大小甚至更小,mosaic的随机裁剪很容易把唯一的目标区域扔出去。解决方法是前80轮关闭mosaic,只保留copy_paste和轻微几何增强,最后20轮再开启mosaic做微调;如果病灶占比特别小,干脆全程关闭mosaic,用平移和尺度抖动替代。这个场景下,数据增强强度不是越大越好,病灶检测的增强策略要以「病灶必须保留在画面内」为第一约束。
5.2 验证集指标虚高:train和val没做图片级去重
现象是训练时val的mAP高达0.9,但拿到现场摄像头下一测,检出率远低于预期。原因是数据集拆分时没有按图片去重,同一张脸的不同帧同时进了train和val。脸部皮肤病数据集里很多图片是同一患者的多角度拍摄,正面和侧脸看起来不同,但背景、肤色、光照高度相似,模型见过的纹理特征在验证集里重复出现,指标自然虚高。解决方法是按患者维度去重拆分,至少也要按图片哈希去重,确保没有完全相同的图片跨越train和val。跑完拆分后,做一个交并集检查,输出两边共有的文件名列表,确认交集为空,这一步要写进数据准备流程而不是事后补救。
5.3 类别不均衡导致少数类完全检不出
现象是五类病变里,痤疮样本占60%,寻常疣只占5%,训练结果里寻常疣的recall趋近于0。原因是loss被多数类主导,模型倾向于把所有框都预测成痤疮,因为这样整体损失最小。脸部皮肤病数据的类别分布天然不均衡,粉刺和痣最常见,血管病变和疣少见得多,不做干预的话模型的输出只会集中在头部类别。解决方法是先做类别频率统计,打印每个类别的框数和图片数;然后采用两种手段配合使用:一是对少数类做过采样,让每张图里至少有一个少数类框;二是对少数类使用更高的loss权重,在YOLO的loss配置里提高对应类别的cls_loss系数。需要注意的是,过采样幅度不要超过5倍,过度复制会让模型对同一张图过拟合,泛化能力反而下降。
5.4 显存不足导致训练中断:batch不是只能往小调
现象是batch设16时OutOfMemory,设8能跑但训练速度极慢。这只是表面现象,原因不只是batch本身,而是高分辨率输入和模型深度共同放大了显存占用。脸部病灶检测通常需要768分辨率,比默认640高出15%的显存占用,同时YOLOv8s虽然参数少,但特征图的显存占用并不低。解决思路有两条:一是开梯度累积,把batch分成4个小批次,每个小批次计算梯度后不清空,累积4次再更新权重,等效于batch=16却只用batch=4的显存;二是降imgsz到640并保留主干尺寸,确认小目标不丢失的前提下再考虑其他手段。
# 显存不足时用累积梯度等效大batch yolo detect train \ model=yolov8s.pt \ data=data.yaml \ epochs=200 \ imgsz=768 \ batch=4 \ nbs=32 \ project=/output/face_skin \ name=exp_grad_accumnbs=32表示名义batch设为32,当实际batch=4时,模型每8步做一次权重更新,效果接近batch=32,但显存占用只有batch=4的水平。这个参数是YOLO内置的,不用自己写梯度累积逻辑。如果nbs设置后显存仍然溢出,再考虑torch的checkpointing方式,但会增加计算开销。
5.5 验证损失先降后升:模型把皮肤纹理当成了特征
现象是训练前期val_loss稳步下降,第80轮后开始回升,val mAP同步下降,但train_loss还在下降。原因是模型开始过拟合训练集里特定的皮肤纹理、光照条件和拍摄角度,把油光、毛孔、摄像头噪点当成了病灶特征。脸部皮肤高分辨率图像最容易出现这类虚假特征。解决方法是做更强的正则化:dropout调到0.2以上,weight_decay从0.0005提到0.001,同时增强hsv变换里的饱和度与明度扰动幅度。另外一个常被忽略的做法是early stopping patience调小到20轮,让过拟合还没有完全形成时就停止训练,保留第80轮左右的权重作为最佳模型。更根本的手段是增加数据多样性,例如在使用训练前做光照归一化,统一所有图片的亮度分布,减少模型对特定光照条件的依赖。
训练过程中还有一类问题是标签错标但没有被脚本捕获,比如XML中框的位置正确但类别名拼错了。这类问题不报错、不显示,只能通过人工抽检验证集图片上的预测框与真实框的对比来发现。建议每50轮做一次推理可视化,把预测框画在图上,肉眼扫一遍,效率和效果都比只盯loss曲线好。
6. 验证与进阶:在部署实时检测之前,先做这三件事
模型训练完拿到best.pt之后,不要急着部署到摄像头。先做三个步骤,每一件都会改变你对模型实际效果的判断。
第一步是用固定推理尺寸把验证集完整跑一遍predict,输出每张图的置信度分布。这里的关键是固定尺寸,不要用训练时动态尺寸。脸部病灶的框有大有小,动态尺寸推理会让部分小病灶的尺度跨越预设范围,置信度漂移严重。固定尺寸跑完后,统计每类别的置信度分布,找出置信度普遍偏低的类别,这些通常是数据量少、形态变化大的病变类型,后续需要定向补数据。
第二步是导出ONNX模型并做fp16半精度推理,对比导出前后的mAP差值。导出ONNX的目的是落地部署到TensorRT或OpenVINO,但半精度化会带来精度损失。如果半精度推理的mAP相对原模型掉了超过2个百分点,就要检查推理引擎的显存分配和校准方式;如果掉落在1个点以内,说明模型结构对量化不敏感,可以安心走部署流程:
# 导出ONNX权重 yolo export model=/output/face_skin/exp_01/weights/best.pt format=onnx # 用ONNX权重做验证对比 yolo predict model=/output/face_skin/exp_01/weights/best.onnx imgsz=640 conf=0.25第三步,把模型接到RTSP摄像头上做端到端验证。不要用静态图片验证,因为图片验证无法暴露时序抖动问题:同一病灶在相邻帧里可能出现、消失、再出现,这类现象在静态图上完全看不到。我之前的血泪经验是验证集mAP接近0.8,部署到现场摄像头后连续漏检,排查半天发现是推理前resize没有保持长宽比,模型训练的输入是正方形,而现场画幅是16:9,导致病灶区域被压扁后特征完全失真。后来固定统一为等比例缩放加padding的预处理逻辑,问题才消失。
这个教训让我养成了一个习惯:任何模型在部署前都要确认预处理逻辑与训练时完全一致。布局格式、归一化参数、尺寸变化,任何一环不一致,模型在离线验证集上的表现都可能与现场完全不同。希望帮到你,让你的脸部皮肤病检测模型少走一段弯路。
本文还有配套的精品资源,点击获取