☰
基于YOLO的疼痛自动检测:数据集、训练与部署全解析
2026/9/30 5:37:37 网站建设 项目流程

1. 为什么疼痛检测要落到YOLO目标检测上

做医疗AI这行越久,越觉得疼痛评估是块难啃的硬骨头。你说一个病人疼不疼、有多疼,常规做法是让患者自己打分——0到10分,0分不疼10分剧痛,这就是所谓的NRS评分(数字评定量表)。这个方法看着简单,实际用起来问题非常多:婴幼儿说不了话,ICU里镇静状态的患者没法配合,认知障碍的老人表达不清楚,还有一部分患者会因为各种原因刻意隐瞒真实疼痛程度。我见过太多临床场景,护士只能靠经验去猜患者到底舒不舒服,这种主观性强的评估方式,一直是疼痛管理里的老大难问题。

后来我开始尝试用计算机视觉做疼痛的自动检测,核心思路就是把“疼痛”这个抽象状态,映射到面部表情的可观测变化上,再用YOLO这类目标检测模型去捕捉这些信号。之前整理过一批2200张左右的医疗健康数据集,专门用来训练疼痛检测模型,整体跑下来效果还不错。这篇文章就把这条完整链路拆开讲清楚——数据集怎么组织、标注规范怎么定、YOLO训练参数怎么调、有哪些坑是我一步步踩出来的,以及最后怎么从单帧检测做成真正能用的连续视频判断方案。

先说清楚一个容易混淆的概念:为什么我会选择目标检测,而不是更简单的图像分类?这是整个项目的地基,想不明白后面全是白干。

1.1 疼痛的面部信号藏在细节里

疼痛表情不是某种单一的脸部变化,而是一组动作单元的组合激活。FACS(面部动作编码系统)把面部肌肉运动拆成了几十个动作单元(Action Unit,简称AU),和疼痛强相关的AU主要有这么几个:AU4(眉毛下压、皱眉)、AU6(眼轮匝肌收缩、眼睛变窄)、AU7(眼睑收紧)、AU9(鼻根皱起)、AU43(眼睛紧闭)。这些动作组合在一起,就形成了临床上常说的“疼痛面容”。

这里有个很关键的点:疼痛面容和普通情绪表情(比如皱眉生气、眯眼微笑)在静态图片上的差异其实非常细微。同样是皱眉,AU4单独激活可能是思考,AU4加上AU43同时激活才更接近疼痛。所以模型要学到的不是“某个部位长什么样”,而是“多个部位在空间上的共现模式”。YOLO这类检测器天然适合这个任务,因为它的边界框输出能同时框住面部区域,并且为每个框给出独立判断,等于把“疼痛信号的空间分布”作为隐式的学习线索。

1.2 目标检测相比图像分类的三点优势

如果只是判断“这张图里有没有人在疼”,图像分类模型也够用。但真实场景远比这复杂:

一是多目标场景。病房里可能同时出现多张脸,陪护家属、邻床患者、医护人员都会入镜。分类模型会给整张图一个标签,根本分不清是“谁在疼”;检测模型输出的每个框对应一个人,天然解决了这个问题。

二是可解释性。检测框会把模型判定的疼痛区域直接标出来,医生或护士看到框能快速核对——框是不是打在脸上,框内的表情是不是真的表现出疼痛迹象。这是分类模型给不了的可追溯性,在实际使用中非常重要。

三是后续延展。检测框提供了空间位置信息,后面做多人跟踪、做疼痛强度分级、做面部区域与姿态信号的融合,都有基础。分类模型的输出只是一个抽象概率值,想再往深做就很吃力。

所以数据集围绕YOLO来设计是顺理成章的。2200张图片规模不算大,但在医疗场景里已经属于很宝贵的资源,关键是要把每一张图的标注价值榨干。

2. 2200张数据集的构成逻辑与标注细节

数据集的质量直接决定模型上限。这一点在疼痛检测任务上比普通目标检测更敏感,因为疼痛表情的类间差异小、类内差异大,标注稍微松懈一点,训练出来的模型就会在边缘样本上反复翻车。我整理这套数据时,花在设计和质检上的时间比训练模型多得多。

2.1 数据的源与场景覆盖

这批2200张图片不是随便网上爬的,而是围绕疼痛表情的典型出现场景做了定向收集。主体是自发性疼痛表情(比如患者接受静脉穿刺、伤口换药时被抓拍的瞬间),补充了部分模拟疼痛表情(由非专业人员根据疼痛面部模式标准表演拍摄)和少量被误认为疼痛的对照表情(普通皱眉、用力屏气、光线不佳下的眯眼等)。

场景覆盖上我有意识地控制了几个维度:室内外光照差异(病房日光灯、自然窗光、夜间暖光)、拍摄角度(正面、30度侧面、俯拍)、面部遮挡程度(口罩遮下半脸、刘海遮眉、佩戴眼镜),以及人物肤色和年龄段分布。为什么这么做?因为疼痛检测的模型最后要部署的医院环境非常杂,不同科室的光照条件差异极大。如果数据集里全是同一种光照和角度,训练出来的模型换个科室就废了。

分布比例大约是这样:自发性疼痛表情占55%,模拟疼痛表情占25%,对照干扰表情占20%。有意保留这20%的干扰样本,是为了让模型学会区分“看起来像疼但其实不是疼”的情况,否则部署时误报率高得吓人。

2.2 类别与标注框的逻辑设计

这批数据当前版本只定义了一个目标类别pain。没有做疼痛强度的细分,因为强度分级需要更严格的临床标注标准,靠图像表面特征去标轻度/中度/重度,主观性太强,容易把噪声喂给模型。与其要一个不可靠的多类别,不如先把“有/无疼痛”这个二分类做到极致。

标注框的边界规则是这样的:框住整张脸的主体区域,包含完整的眉毛、眼睛、鼻根和嘴部周围,不包含头发和耳朵。为什么强调这点?因为疼痛检测算法最依赖的信息源是眼眶周围和眉间区域,如果你把框画得太小只圈住眼睛部分,模型看不到嘴部动作(比如抿嘴、咧嘴),会丢失一部分关键特征;如果框画得太大把头发和背景圈进来,又会引入大量无效纹理,干扰特征提取。统一标注为“面部中庭到口周”的矩形区域,是最稳妥的方案。

还有一个细节:对于同一张图里出现多张脸的情况,只要脸的面积大于图片短边的8%,就全部标注;小于这个比例的模糊人脸直接跳过不标。这个8%阈值是为了避免让模型去学一些根本看不清的极小目标,减少训练时的无谓噪声。

2.3 标注工具与质检流程

标注工具我用的两套方案:早期小批量用LabelImg,界面简单,适合一个人慢慢标;后期数据量上来之后换成X-AnyLabeling,支持半自动预标注,能先用一个在COCO上预训练的人脸检测模型打出候选框,人工再小幅调整。这个流程能节省将近一半的时间。

真正决定数据质量的不是工具,是质检环节。我做了两道校验:

第一道是框位置校验。每张图的标注框和图片复制一份,不带标签地过一遍,看框是不是对齐了眉毛、眼睛、口周。有偏差的重新调整。

第二道是类别一致性校验。随机抽20%的样本,请第二位标注人员独立标注同样的图片,计算两人标注结果的一致性。我用的是Kappa系数,目标是超过0.7。实际跑下来自发性表情样本的一致性在0.82左右,模拟表情样本在0.65左右,模拟样本偏低很正常——它本身就是“表演出来的痛”,真实度因人而异。所有低于0.6的样本直接剔除,替换成新的标注样本。

标注完成后的格式统一转成了YOLO官方要求的txt格式,每行一个框:class_id x_center y_center width height,坐标基于图片宽高的归一化值。这里有个坑我必须提醒一句:很多标注工具导出时坐标归一化是用整数宽高直接算的,有的工具用的是浮点宽高,两种算法在缩放图片后会产生微妙偏差。虽然对训练影响不大,但在做严格评估时卖个关子:统一用一个数据转换脚本来生成txt格式,不要靠手工在工具里来回导出。

3. YOLO训练的关键配置与参数调优

数据集准备好了,进入模型训练阶段。这个环节的每个决策点都有讲究,我从版本选型到最后的训练监控逐一说明,顺便把过程中踩过的坑一并交代清楚。

3.1 为什么选YOLOv8而不是其他版本

标题里只写了YOLO,没指定版本。如果你去翻各个YOLO版本,v5、v8、v9、v10现在都有各自的拥趸,但做医疗任务我最终锁定了YOLOv8。

理由不复杂:这是一个对模型稳定性要求远高于刷分上限的任务。YOLOv8把Anchor-Free检测头和C2f特征提取结构结合,在中小尺寸模型上的收敛速度和稳定性比v5的Anchor-Based方案好,训练时不需要额外做anchor聚类。v9和v10虽然在一些公开榜单上精度更高,但架构改动较大,社区生态和配套工具链还不够成熟,遇到问题查资料都费劲。v8的Ultralytics生态足够完善,不管是数据格式转换、训练监控还是导出部署,都有清楚的文档和大量实战帖可参考。

模型尺寸我用了两档对比:YOLOv8n和YOLOv8s。为什么不用更大尺寸?因为这套2200张的数据量摆在那,模型越大过拟合风险越高。很多人一上来就上YOLOv8x,结果验证集mAP还不如小模型,本质就是数据量撑不起模型容量。在中小规模数据集上,更稳妥的路线是先用小模型跑通训练流程、验证数据标注质量,再逐步往大模型试。我个人的经验是:n模型在疼痛检测上能够跑到mAP50约0.87,s模型能到0.90左右,这个差距说实话不足以弥补s模型推理速度上的劣势,所以我最终生产版本用的是n模型。

3.2 数据划分的一个隐藏风险

这是整篇文章里我觉得最值得反复强调的一个坑:训练集/验证集/测试集的划分比例看起来简单(我用的是70/15/15),但怎么划分才是关键。

很多刚接触目标检测的人会直接用随机划分,把2200张图片打乱后按比例切分。如果在普通目标检测数据集上这么干问题不大,但在疼痛表情数据上会出大问题——因为同一个人的多张表情图片很可能被同时分到训练集和验证集里,模型实际上“见过”验证集里的人了。这种情况下验证集的指标会虚高,部署到新的人员面前时性能大幅下滑,这就是典型的数据泄漏。

解决办法是按受试者分组进行划分:先把所有图片按人物ID归组,保证同一个人的所有图片只出现在训练集、验证集、测试集中的一个集合里。这样验证集和测试集面对的都是“模型从未见过的人”,指标才是可信的。我用这个方式重新划分后,验证集的mAP比随机划分低了大概5个百分点,但这5个百分点才是真实水平。

3.3 训练参数的具体配置与迁移学习

训练环境的配置直接放出来供参考:

硬件用的是单张NVIDIA RTX 4090,24GB显存。但我把batch size限制在16,原因有两点:一是医疗数据标注噪声大,太大batch size会让梯度的方向被少数错误标注样本带偏;二是小batch size配合适当学习率,相当于给模型加了一点隐性的正则化效果,对泛化有好处。

训练命令使用的是Ultralytics的标准入口,配置文件的写法其实是这样的:

pip install ultralytics yolo train data=pain_dataset.yaml model=yolov8n.pt epochs=120 imgsz=640 batch=16 optimizer=AdamW lr0=0.0005 close_mosaic=10

data=pain_dataset.yaml里指定训练、验证、测试图片的路径,以及类别数量(nc=1)和类别名(pain)。model=yolov8n.pt这一步很关键:用的是COCO预训练权重做迁移学习,不是从零开始训练。为什么必须这样?因为2200张医疗图实在太少了,从零开始训练随机初始化的模型几乎不可能收敛好,预训练权重已经把通用视觉特征(边缘、纹理、基本形状)学好了,我们只需要微调它在疼痛表情上的差异化特征。实测下来,用预训练权重相比从零训练的收敛速度快了接近三倍,稳定后的精度也高一截。

优化器用的是AdamW而不是默认的SGD。在这个小众任务上,AdamW的收敛曲线更平滑,对学习率的敏感度也更低一些,减少调参的精力消耗。学习率设为0.0005,这是结合batch size 16和小模型规模给出的经验值——如果batch size翻倍,学习率可以相应扩大到0.001,这是线性缩放规则。

训练轮数120轮,但实际有价值的训练集中在60轮以后。前60轮模型在快速适应疼痛表情的分布,后面40轮才是精度逐步爬升的阶段。如果训练资源紧张,80轮也可以接受,但mAP会下降一到两个点,视应用场景决定取舍。

3.4 数据增强策略的取舍

Ultralytics默认开启了一整套数据增强,包括Mosaic(把四张图拼成一张)、随机翻转、色彩抖动、平移旋转缩放等。但医疗任务里我单独调了两个选项:把Mosaic关闭掉放在最后十轮(close_mosaic参数),以及弱化色彩抖动强度。

原因是我在训练过程中发现一个现象:疼痛表情依赖的是面部肌肉纹理的细微变化,特别是眼轮匝肌和眉间区域的褶皱形态。如果色彩抖动太强,肤色被大幅偏移,那些细微的阴影变化会被抹平,模型学到反而是一些颜色伪相关。Mosaic类似,它把四张不同光照的图拼在一起,会让前景面部的有效分辨率下降,对细粒度纹理识别并不友好。所以我最后只保留了轻度翻转和轻度缩放的增强策略,并加上平移训练后期的关闭机制,给模型一个稳定精调的最后阶段。

4. 训练结果解读与三个典型踩坑点

模型训练完不是看一眼mAP完事,疼痛检测任务的评估有一套自己的特殊逻辑。这里把结果指标拆开讲清楚,然后重点说我实际踩过的三个坑。

4.1 基线指标如何解读

先看一组最终的参考指标(单类pain,验证集按受试者独立划分):

指标YOLOv8nYOLOv8s
mAP500.8730.904
mAP50-950.6120.654
Precision0.8910.902
Recall0.8420.861
F1(阈值0.5)0.8660.881

初学者最容易盯着mAP50看,但医疗任务里我更关注F1,尤其是Recall不能太低。漏掉一个真正的疼痛事件,远比多报一次假警报严重——假警报顶多让护士多看一眼,漏报可能导致镇痛不及时。所以我在调阈值的时候,会尽量往低一点调:让Recall保持在0.85以上,Precision在0.85附近,达到一个偏向“宁可多报不可漏报”的平衡点。

mAP50-95只有0.6出头,这个指标说实话一般,但它反映的是不同IoU阈值下的综合性能。疼痛检测框本身不需要像素级精准——框的范围稍微大一点小一点,不影响后续判断“这个人有没有在疼”,所以不用太纠结mAP50-95的数值,保证mAP50达标就行。

4.2 坑一:疼痛表情与非疼痛表情的混淆

第一次训练完,我拿验证集做了错误分析,发现最大的混淆来源集中在两类:一类是“用力闭眼+抿嘴”的表情(比如患者憋着一口气做某个动作),另一类是“强光照射下的眯眼”表情。这两个都会触发眼部收紧的特征,被模型误判成疼痛。

处理思路不是简单加数据,而是针对性地补了60张标注为no-pain的对照图,把这些容易误判的样本扩充进去。同时训练了一个辅助分支思路:在标注pain的同时标注了一部分“用力表情”作为难例负样本参与训练。但这块数据量目前还不够支撑多类别训练,所以当前版本还是单类pain,难例负样本的作用是让模型在困难样本上不敢随便给高置信度。这样调整之后,误报率下降了大约三分之一。

4.3 坑二:剧烈数据增强导致小目标漏检

最初训练时我图省事用了默认增强配置,结果发现训练集loss降得很漂亮,验证集mAP50却老是卡在0.79上不去。排查到最后发现是Mosaic增强把多张图缩小拼在一起的机制,让大量面部区域变成了小目标,模型把注意力都放在学习“中等大小的脸怎么检测”上,真正的小脸反而漏了。病房监控场景里人脸常常离镜头较远,尺寸本来就小,这个问题不解决根本没法用。

确认根因后,我把Mosaic关闭,改用轻度的随机缩放和水平翻转,验证集mAP50立刻跳了4个点。这是一个很典型的案例:数据增强不是越多越好,它必须和数据集的真实目标尺度分布匹配,否则反而会毒化模型。

4.4 坑三:单一置信度阈值不可靠

训练完成后默认推理阈值是0.25(置信度)和0.45(NMS的IoU),跑测试集时看起来F1还行。但部署到实际视频流里才发现,连续帧的误报情况非常不稳定——某一帧置信度0.8报了疼痛,下一帧同样的表情状态置信度掉到0.3然后又回来。

这是因为单帧推理受压缩噪声、运动模糊、瞬时遮挡的影响非常大。解决之道不在阈值,而在后处理的时间维度,这也直接引出了下一部分要讲的内容。

5. 从单帧检测到连续视频段的实用后处理

如果你只是拿YOLO检测单张图片里的疼痛表情,那训练完模型基本就结束了。但真实的医疗辅助场景里,我们面对的是连续的视频流——病房监控、护理过程记录、远程会诊画面,全是时间序列。怎么把单帧检测结果变成可靠的视频级判断,这是从“能跑通”到“能落地”的关键一步。

5.1 为什么不能直接看单帧推理结果

单帧误报是不可避免的。人体在呼吸、头部轻微摆动、眨眼,这些动作都会导致某个瞬间的面部表情恰好接近疼痛模式,给一个很高的置信度。但疼痛作为一种持续状态,通常会在一个时间段内反复出现或者持续维持。如果只看单帧,等于把一个瞬时噪声和真实疼痛信号放在同一个评判标准下,结果自然不稳定。

我的做法是把检测扩展到时间维度,通过滑窗聚合让判定结果平滑化。

5.2 滑窗投票与置信度平滑

算法的核心思路其实非常简单:取一个滑动时间窗口,比如5秒(假设视频每秒25帧,即125帧),统计这个窗口内每一帧YOLO检测出的疼痛置信度,然后把整体置信度序列做一个中值滤波或者均值滤波。我用的是一个带权重的平滑策略:

import numpy as np def temporal_smoothing(scores, fps=25, window_sec=2.0): window_size = int(fps * window_sec) if len(scores) < window_size: return scores smoothed = np.convolve(scores, np.ones(window_size) / window_size, mode='valid') return smoothed

窗口大小的选择是个平衡问题:窗口太短(1秒以内),平滑效果不明显,误报还是会出现;窗口太长(10秒以上),反应迟钝,患者都疼完了才报警。我个人测试下来2秒窗口在灵敏度和稳定性之间最均衡。

另外我还会做一个“持续性判定”:要求连续N帧(比如连续8帧)都出现超过阈值的检测结果,才判定为一次疼痛事件开始;随后如果连续20帧都低于阈值,才判定事件结束。这样单个帧的偶然高置信度无法触发报警,真实的疼痛片段也不会被轻易打断。实测这套后处理方案能把误报率再压掉50%以上,而真正疼痛片段的召回率几乎不受影响。

5.3 推理性能与部署考量

模型选n尺寸的另一个重要原因就是推理性能。在单张4090上批量推理,每秒能处理超过300帧,完全满足实时需求。但实际病房场景不可能每间都配4090,更现实的部署方式是边缘设备或普通CPU服务器。

我做了两个层面的优化。一是把模型导出为TensorRT格式,在半精度FP16下推理,帧率比PyTorch原生推理快3倍左右。二是做了抽帧策略:不需要每帧都跑推理,每秒抽8到10帧就够了,配合时间平滑后的事件判定精度几乎不变。这能把单路视频流的计算量砍掉六成以上。

如果你没有NVIDIA GPU环境,也可以用ONNX Runtime跑CPU推理,YOLOv8n在普通Xeon处理器上单帧推理大约50到80毫秒,配合抽帧策略也够用。唯一要注意的是,CPU推理时输入的图片需要先等比缩放补边到640乘640,不要直接拉伸变形,否则边界框的位置会偏。

6. 数据合规、伦理与后续扩展方向

最后这部分不是说套话,而是我实际推进这个项目时绕不过去的现实问题。做医疗数据集和做通用物体检测数据集,性质完全不同。

6.1 医疗数据的三道红线

第一道是隐私。任何一张包含人脸或其他可识别身份特征的医疗数据,都必须经过匿名化处理。我在数据整理时对所有面部图片做了去标识化流程:删除原始文件名中的患者编号等元信息,对人脸特征做了轻度模糊化处理(保留表情可辨识度但降低个体识别度),这个折中很关键——模糊太狠表情特征也没了,模型学不到东西;完全不做处理,隐私合规又过不了关。

第二道是授权。每一批数据需要有明确的来源授权和知情同意记录。使用公开数据时,要一一核对数据集的license条款,确认它是否允许用于模型训练和二次发布。很多学术数据集只允许非商业用途,这一点在商用化之前要格外小心,别等模型训练完了才发现在授权上有硬伤。

第三道是用途边界。基于这套数据集训练的模型定位是“辅助观察工具”,它的输出仅供参考筛查,不能作为独立诊断依据。所有实际使用场景都需要专业医护人员介入审核,这一点也必须在项目文档和部署说明里写清楚。

6.2 从“有无疼痛”到疼痛强度分级的扩展路线

当前版本只做疼痛有无的二分类,是考虑到数据基础还撑不起更细的分级。但临床上强度信息非常关键,所以我给出了下一步的扩展思路:在数据集标注中引入强度标签,分轻度、中度、重度三档。标注标准可以锚定在面部动作单元的激活数量和持续时长上,比如:只有AU4单独激活算轻度,AU4加AU43共同激活算中度,AU4加AU43加AU6持续超过3秒算重度。这个标准基于FACS的已有研究,虽然仍有主观成分,但比没有锚定的“我觉得很疼”要可靠得多。

数据量需求上,每个强度级别至少要300张以上有效样本,三个级别加起来大概需要1000张增量数据,加上现有基础才能把分级模型训稳。

6.3 多模态融合的探索空间

疼痛并不仅仅反映在脸上。身体姿态的退缩动作、局部按压时的回避反应、声音嘶哑或呻吟,都是有效信号。YOLO负责面部框检测之外,还可以用姿态估计模型拾取身体骨架信息,把“面部疼痛表情”和“身体动作紧迫度”做特征级融合。这个方向目前在学术界也有不少论文在做,但工程化的落地还少。好消息是面部分支和姿态分支可以独立训练再融合,不需要推翻现有方案,所以在现有YOLO检测结果上做扩展是比较顺的。

如果你手头也有类似的小规模医疗检测数据集,我的建议是这样的:先别急着扩大数据量,把当前数据的标注一致性做扎实,把按受试者划分的评估流程跑通,先把模型在一个小圈子里用起来看真实反馈。疼痛检测看似是个很垂直的任务,但它踩过的坑——类间混淆、增强策略与目标尺度不匹配、单帧阈值不稳定、时间序列后处理——在很多细粒度医学目标检测项目里都会有类似的影子。把这一套流程吃透,换个任务也能复用同一套方法论,这是我做这个项目最大的收获。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询