疼痛检测这个方向,很多做视觉的人第一反应是"不就是人脸表情识别吗",但真正落到医疗场景里,它比想象中复杂得多。我最近在整理一套用于疼痛检测的YOLO医疗健康数据集,一共2200张图像,标注统一转成了YOLO格式,可以直接拿去训练目标检测模型。这篇就把这套数据集从结构、标注规范、训练踩坑到评估部署完整梳理一遍,给正在做医疗AI或者准备入坑YOLO检测的同学一个能直接复现的参考。
我自己在把这个数据集喂给模型之前,也走过不少弯路——一开始以为有图有标注就能训,结果loss曲线各种诡异,后来才发现是标注规范和训练策略的问题。所以这篇文章我不只聊数据本身,也会把整个流程里那些"文档上不会写"的细节讲清楚。
1. 疼痛检测为什么值得用目标检测来做
1.1 疼痛信号到底能不能被视觉模型捕捉
疼痛本质上是主观体验,临床上最常用的评估方式是让患者自己打分,比如数字评分法(NRS)或者面部表情疼痛量表。但问题在于,有很多场景下患者根本没法自述——ICU里镇静状态的患者、术后还没清醒的人、婴幼儿、认知障碍的老年人。这个时候就需要通过外部可观察的指标来判断疼痛程度。
大量医学研究表明,疼痛会通过面部动作、身体姿态、手部接触部位等方式外显。面部动作单位(AU)里的皱眉、眯眼、鼻唇沟加深,还有身体上的蜷缩、按揉痛处、躲避动作,都是强信号。这些信号天然具有空间位置属性——在图像里,它们对应着某个具体的区域。所以用目标检测来做疼痛识别是有依据的:先定位到人脸或身体区域,再判断这个区域里是否存在疼痛表现以及疼痛的级别。
YOLO在这里的角色不是"端到端判断疼不疼",而是先解决"疼在哪里"的问题。检测框把视觉注意锚定在关键区域(比如面部、手部、关节位置),后续可以接分类网络或者直接让YOLO自己输出带类别的框。这套思路比单纯用图像分类更贴近临床实际,因为疼痛表现常常是局部的,一个全局分类模型容易被背景干扰。
1.2 医疗场景对目标检测的三重特殊约束
做通用检测(比如行人、车辆)时,模型错了顶多就是漏检一个目标。但医疗场景完全不是这个逻辑,这套数据集在设计时就考虑了三个特殊约束,这也是它和普通数据集最本质的区别。
第一是标注的一致性。疼痛表现是连续渐变的,两个标注员看同一张图,一个觉得"明显疼痛"另一个可能觉得"只是不舒服"。所以标注规范里必须定义清楚每个类别的判定标准,最好有参考图例,否则模型学到的边界会很模糊。这套2200张的数据集在标注时,我对每个类别都做了交叉校验,抽了20%的样本让两位标注员独立标注,然后计算一致率,只保留一致率高的图像进入最终版本。
第二是类别不平衡的必然性。真实临床数据里,无疼痛或轻度疼痛的样本远多于中重度疼痛样本。这会导致模型偏向多数类,漏掉真正需要关注的疼痛样本。做数据准备时不能只做随机划分,要有意识地控制各类别在训练集和验证集中的比例。
第三是伦理与隐私。医疗图像涉及患者隐私,数据集不能随便从网上爬。这套数据集里凡是可识别身份的人脸信息都做了脱敏处理,部分图像来自公开学术数据集的重整理,部分来自合作机构的匿名化采集。如果你自己构建医疗数据集,这一步一定要提前走流程,不然后续发布和商用都会出问题。
2. 数据集解剖:2200张图像里到底装了什么
2.1 图像来源与分布逻辑
这套数据集一共2200张图像,涵盖了三类主要场景:临床采集(患者就诊或住院期间拍摄)、公开学术数据集的筛选重标注、以及模拟场景的补充拍摄。为什么要有三类来源?因为单一来源会让模型过拟合到该场景的光线、角度和画质上。
- 临床采集部分占比约六成,特点是环境复杂——有病房的暖光、走廊的冷光、设备的阴影,这些反而是好事,能让模型学会在真实环境里工作。
- 公开学术数据集筛选重标注的部分,因为原始数据不是YOLO格式,需要把原来的关键点标注或者分类标签转成目标检测框,这个转换过程有专门的坐标系处理逻辑,后面会细说。
- 模拟场景补充拍摄主要是为了弥补中重度疼痛样本的不足——请志愿者在指导下模拟疼痛表情和动作,虽然和真实疼痛有差距,但能让模型先见过这类形态。
图像分辨率不统一,从720p到4K都有。我没有全部压缩到同一个尺寸,因为YOLO训练时会做resize,统一像素反而是浪费时间,保留原始分辨率还能让数据增强有更多裁剪空间。
2.2 YOLO标注格式的逐字段解读
这套数据集的标注格式是标准YOLO格式:每张图像对应一个同名txt文件,每行代表一个检测目标,格式为:
<类别索引> <中心点x> <中心点y> <框宽> <框高>所有坐标都是归一化到[0,1]区间的浮点数,除以图像的实际宽高。举个例子,一张1920x1080的图,想标一个中心在(960, 540)、宽400、高300的框,那txt里对应的行就是:
2 0.5 0.5 0.2083 0.2778类别索引从0开始计数。这套数据集目前用了三种标签方案,根据任务需求可以切换:
- 二分类方案:0代表无疼痛,1代表有疼痛,适合快速验证流程。
- 三级方案:0无疼痛、1轻度疼痛、2中重度疼痛,这个用得最多,因为临床干预的决策点往往在"中重度"这个分界上。
- 部位+状态方案:把疼痛部位(头部、手部、腿部等)和状态联合编码,类别数量会到8-10个,适合需要定位到具体身体部位的项目。
需要特别提醒的是,用YOLO格式做多类别时,类别索引和yaml配置文件里的names列表必须一一对应。我见过不止一个人因为names顺序写错,训练出来的模型输出张冠李戴,排查半天才发现是配置文件的问题。
2.3 标签体系设计:连续评分如何转成检测框
临床上疼痛常用0-10分连续评分,但这个分数不能直接搬来做检测标签。YOLO输出的是离散类别,所以必须做分级映射。我在做这套数据集时参考了面部表情疼痛量表的思路:0分对应无疼痛表情,1-3分对应轻度(眉毛轻微收紧、面部肌肉轻度紧张),4-6分对应中度(眯眼、鼻唇沟明显),7-10分对应重度(眼睛紧闭、嘴巴张开呈痛苦状)。
这里有一个值得琢磨的细节:检测框到底框什么。框整个人脸,还是框疼痛表现区域?我的实践是,如果任务是判断"这个区域内的人是否处于疼痛状态",那就框人脸或上半身,类别标疼痛等级;如果任务是"找出哪些部位在痛",那就框具体的疼痛表现区域(比如手按住膝盖的位置),类别标部位+状态。两种方案各有适用场景,前者适合监护监测,后者适合辅助诊断,不建议混着标。
3. 训练前必须做对的数据准备细节
3.1 目录结构与配置文件的标准写法
拿到数据集第一步是把它整理成YOLO能直接读取的目录结构。我用的是这个规范:
pain_dataset/ ├── images/ │ ├── train/ │ └── val/ ├── labels/ │ ├── train/ │ └── val/ └── pain_data.yamlimages和labels要严格一一对应,同名不同后缀。数据集划分比例建议按8:2或者7:3,这套2200张我用了8:2,也就是train里1760张,val里440张。
pain_data.yaml的内容大概是这样的:
path: /your/path/pain_dataset train: images/train val: images/val names: 0: no_pain 1: mild_pain 2: severe_pain这里有个很多人踩的坑:path字段如果写成相对路径,训练时工作目录不对就会报错找不到图片。建议直接用绝对路径,或者把path指向数据集根目录之后,train和val统一不带斜杠前缀。我自己习惯在训练脚本里动态拼接路径,比写死yaml更灵活。
3.2 划分训练集和验证集时最容易犯的错
这个错误隐蔽但致命:直接随机划分,导致同一个人的多张图像同时出现在训练集和验证集。医疗数据的特点是一个人通常有多张连续拍摄的图像,这些图像之间的差异很小(微小的表情变化、角度偏移),如果同一人的图像横跨train和val,验证集的指标会虚高,模型看上去很厉害,但换一个新患者就崩了。
正确做法是按患者ID分组划分:先把所有图像按人员ID归类,然后以人员为单位分配,确保验证集里出现的人从未在训练集里出现过。这套数据集在发布时已经按此逻辑做了划分,但如果你后续自己扩充数据,一定要记得保持这个原则。这是医疗数据划分和通用数据划分最大的区别。
另外,划分的时候还要注意类别分布。我遇到过按人划分后,验证集里重度疼痛样本特别少的情况,因为某个重度患者的大部分图像都进了训练集。所以按人划分之后,要检查一下各类别在两边的大致比例,必要时手动调整。
3.3 数据增强的边界:医疗图像经不起乱折腾
YOLO自带的增强策略(如mosaic、random flip、hsv变化)在通用数据集上效果很好,但医疗场景要谨慎。
- 左右翻转(flip):人脸表情左右基本对称,这个可以用。但如果是标注身体单侧疼痛(比如左膝疼痛),翻转之后类别语义就变了,这种情况建议关掉flip。
- Mosaic拼接:把四张图拼在一起训练确实能提升小目标检测能力,但对人脸检测来说,拼接会破坏面部结构完整性,而且医疗图像语义连续性很重要,我实际测试下来mosaic占比设低一点(比如0.5)效果更好。
- HSV颜色增强:可以保留,因为不同设备的色温差异确实存在,稍微扰动色彩能提升泛化性。但幅度要小,hsv_h、hsv_s、hsv_v这三个参数建议用YOLO默认值的一半左右,别把肤色改得面目全非。
还有一个很实用的做法:医疗检测模型对图像画质很敏感,训练时可以加入轻微的高斯模糊和随机噪声增强,模拟真实监控设备的画质损失。YOLO本身不直接支持这个,可以在预处理阶段用OpenCV做一次离线增强,生成一批增强副本混进训练集。
4. YOLO训练的参数调优与损失曲线判读
4.1 预训练权重怎么选最省路
2200张的数据量不算大,从零训练几乎不可能收敛出好效果,必须用预训练权重。选择逻辑很简单:数据量越小,模型越要小,预训练域越接近越好。我实测下来的推荐组合:
- 首选YOLOv8n(nano版)搭配COCO预训练权重,模型小、收敛快、不容易过拟合。
- 如果设备性能允许且需要更高精度,可以上YOLOv8s,但2200张的量级用s以上的模型容易过拟合。
- 训练医学图像前,可以先用预训练权重在公开人脸表情数据集(如果有)上做一轮微调,让模型先学会处理面部特征,再迁移到疼痛检测。这比直接跳转效果稳定。
关于下载预训练模型,YOLO官方代码里一般都有自动下载逻辑(yolov8n.pt),国内网络环境不好的话,可以从镜像源手动下载后放到指定目录,代码检测到本地文件就不会再去拉取。这个细节能省不少事。
4.2 从loss曲线判断模型是否真的在学
训练时我会同时盯三条loss曲线:box_loss(边框回归损失)、cls_loss(分类损失)、dfl_loss(分布焦点损失)。很多人只看总loss,这是不够的。
正常运行的状态是:三条loss在初始阶段快速下降,大约30个epoch之后下降速度放缓,进入平台期;val loss在前期跟着下降,然后趋于平稳。如果val cls_loss出现U形回升,说明过拟合开始了,这时候即使train loss还在下降,也应该考虑早停。
我在这套数据集上的实操配置是:imgsz设为640、batch设为16(80GB显存够用,小显存就8)、epochs设200但开了early stopping patience=30。学习率用YOLO默认的AdamW基础配置,lr0=0.01,但如果你发现loss震荡剧烈,可以把lr0降到0.005再试。
补充一个容易被忽略的参数:warmup_epochs。YOLO默认有3个epoch的学习率热身,这个其实很重要,尤其是用预训练权重继续训练时,热身阶段能防止前期loss突变。别把它改成0,除非你很清楚自己在做什么。
4.3 训练中常见的异常信号与对策
我在这套数据集训练过程中遇到过的几个异常,按踩坑频率排个序:
- loss变为NaN:最常见原因是学习率过大或batch里有异常数据。先检查数据集图片是否完整(有没有全黑图、截断图),再尝试把lr0降到0.001、关闭mosaic。如果问题出在个别坏图上,把那个batch的图片单独提出来检查。
- box_loss不降:大概率是标注框本身有问题——比如box坐标越界、宽度或高度为0、归一化算错。写个小脚本检查label文件,任何坐标小于0或大于1的行都要修掉。
- 训练很慢但指标不动:可能是数据加载瓶颈而非模型问题。检查是否用了正确的workers数、数据是否在机械硬盘上。把图片转成512px的缓存目录有时能提速一倍以上。
- 类别之间精度悬殊:比如no_pain的AP很高但severe_pain的AP只有零点几,基本可以断定重度样本数量太少。对策是给severe_pain类的loss加权,YOLO里可以通过修改class weights实现,或者靠后续数据扩充解决。
5. 医疗评估指标:别拿mAP当唯一救命稻草
5.1 敏感度和特异度才是临床关注的
做通用检测时大家习惯看mAP,但医疗场景里临床医生问你的第一个问题是:"疼痛患者你能找出多少?"这对应的是敏感度(Sensitivity/Recall);第二个问题是:"没疼的患者你会不会误报成疼?"这对应的是特异度(Specificity)。
注意一个关键差异:目标检测的召回率是"框选对了且类别对了"才算真正阳性,所以疼痛检测里的敏感度本质上是"疼痛框的召回率"。实际使用中,我建议把训练好的模型在验证集300张图上做一次详细的PR分析,找到那个能让敏感度达到90%以上的置信度阈值。即使这意味着精确率会降到60-70%也没关系——在监护场景里,漏报一个疼痛事件可能导致镇痛不及时,而误报顶多让护士多看一眼。
评估时应选用的指标组合我个人推荐:
- mAP50和mAP50-95作为基础参考,但不过度依赖。
- 每个类别的recall单独列出,尤其关注sever类。
- F1-score在某个置信度阈值下的最大值,用于选推理时的置信度。
- 如果做的是二分类"有无疼痛",还建议补一个AUC值。
5.2 类别不平衡下的混淆矩阵分析
训练完之后,我会在验证集上生成混淆矩阵(YOLO训练时会在runs目录下自动输出confusion_matrix.png),这个图比任何指标都能说明问题。
我在这套数据集上最典型的混淆模式是:severe_pain被误判为mild_pain的频率明显高于mild_pain被误判为severe_pain。原因很简单——轻度疼痛的样本量大,模型对它的特征拟合更充分,相邻等级之间的边界本来就模糊。
针对这个问题的处理办法有两类:一是把轻度样本里靠近中度边界的那些重新审视一遍,标注时确实存在边界样本归属不明确的情况;二是调整标签策略,如果临床决策只需要"需要干预/不需要干预"的二分类,那就把mild和severe合并成"有疼痛",no_pain保持不变,这样模型的任务更简单,准确率会有明显提升。这个取舍看起来很反直觉,但医疗场景里经常出现"简化标签反而更可用"的情况。
5.3 真实部署时的边界情况与失败模式
验证集指标好看,不代表真机好用。我在实际测试中发现了几类典型的失败模式,都是验证集指标体现不出来的:
- 遮挡:患者戴口罩、戴氧气面罩、手捂住半边脸时,人脸检测可能直接失效。对策是在验证集里单独预留一组遮挡样本,专门测这个场景。如果遮挡严重,建议在标注时给"戴口罩"单独开辟一个类别,而不是硬让模型在没有见过口罩的情况下识别面部。
- 多目标重叠:病房里可能同时出现患者和家属两张脸,模型可能框错对象。如果系统只需要关注患者本人,推理时要加一个人脸追踪/筛选逻辑,不能直接把所有检测框都当成患者。
- 时间连续性:单帧偶尔误报是正常的,临床上更关注的是趋势。我建议部署时对检测结果做时序平滑——连续5帧中至少3帧判定为疼痛才触发报警,这个简单策略能把误报率降低一个量级,成本几乎为零。
- 光线剧变:病房灯管频闪、走廊逆光都会导致人脸检测漂移。训练数据里有意识加入了一些过曝和欠曝样本,但真机上还是需要配合摄像头设置做曝光控制。
6. 我实测下来的几点心得与可能的扩展
6.1 数据质量比模型结构更值钱
这句话在通用检测里是口号,在医疗检测里是硬道理。我拿这套2200张数据集试过YOLOv5、YOLOv8和几个改进结构(比如加了注意力机制的版本),在相同训练配置下,模型结构带来的mAP差异大概在2-3个点,但重新整理一遍标注质量(修正边界框、删掉错误类别、统一边界样本归属)让mAP提升了6个点以上。
具体操作建议:训练第一版模型后,把验证集上错误样本全部导出成图,逐张看。你会发现大量错误本质上不是模型笨,而是标注模糊——两个标注员对"轻度"和"中度"的理解不同。把这类样本拉齐归类,比换任何模型都有效。做医疗数据集,花在标注质检上的时间应该至少占整个项目的一半。
6.2 后续扩充与主动学习的思路
2200张是起点,不是终点。医疗数据的扩充有两条路:一是找更多合作机构要数据,二是用主动学习减少人工标注成本。
主动学习的玩法是:用当前模型对一批未标注图像做预测,筛选出那些模型置信度在0.4-0.7之间(最不确定)的样本,让标注员优先标这批。这批样本对模型提升最大,远比随机采样标注划算。我在扩充过程中用这个策略,大约每标500张新图,就能让模型在验证集上有2-3个点的提升。
还有一个思路:用已训练的模型做伪标注(auto-label),然后人工修正。在YOLO生态里可以用SAHI或者Label Studio配合模型推理做预标注,人工只需要调整框和改类别,标注效率能提升一半以上。
6.3 轻量化部署的一点现实建议
如果最终要部署到病房监控或者移动端,模型大小是个绕不开的问题。我的建议是先在训练阶段就用nano或small版本把业务跑通,量化推理放在后面优化。
具体来说:YOLOv8n训练完成后,用ONNX导出,再用TensorRT做FP16量化,在嵌入式设备上帧率能到30fps以上,精度损失控制的好的话mAP只掉1个点左右。如果设备更紧张(比如手机端),考虑把输入图像分辨率从640降到480,检测框用的是人脸或身体区域,这个分辨率损失完全可接受。
需要警惕的是,医疗场景里的模型更新不是"发个版本就行"。重训练新数据后,要做回归测试——把旧验证集和新验证集合并跑一遍,确认没有让之前能识别的场景退步。我见过有人新增数据后整体mAP涨了,结果旧场景里某一类疼痛反而识别不了了。这种回退在医疗场景里是绝对不能接受的。
最后说一句个人体会:疼痛检测这套任务,技术难度本身不算是计算机视觉里最难的那一档,真正的门槛全在数据和场景理解上。2200张数据集能让你完整跑通整个pipeline,但想要做出能真正辅助临床决策的系统,后面还有数据扩充、边界情况处理、与临床流程对接一大串活儿。做之前想清楚应用场景,比急着调参重要得多。