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.87s模型能到0.90左右这个差距说实话不足以弥补s模型推理速度上的劣势所以我最终生产版本用的是n模型。3.2 数据划分的一个隐藏风险这是整篇文章里我觉得最值得反复强调的一个坑训练集/验证集/测试集的划分比例看起来简单我用的是70/15/15但怎么划分才是关键。很多刚接触目标检测的人会直接用随机划分把2200张图片打乱后按比例切分。如果在普通目标检测数据集上这么干问题不大但在疼痛表情数据上会出大问题——因为同一个人的多张表情图片很可能被同时分到训练集和验证集里模型实际上“见过”验证集里的人了。这种情况下验证集的指标会虚高部署到新的人员面前时性能大幅下滑这就是典型的数据泄漏。解决办法是按受试者分组进行划分先把所有图片按人物ID归组保证同一个人的所有图片只出现在训练集、验证集、测试集中的一个集合里。这样验证集和测试集面对的都是“模型从未见过的人”指标才是可信的。我用这个方式重新划分后验证集的mAP比随机划分低了大概5个百分点但这5个百分点才是真实水平。3.3 训练参数的具体配置与迁移学习训练环境的配置直接放出来供参考硬件用的是单张NVIDIA RTX 409024GB显存。但我把batch size限制在16原因有两点一是医疗数据标注噪声大太大batch size会让梯度的方向被少数错误标注样本带偏二是小batch size配合适当学习率相当于给模型加了一点隐性的正则化效果对泛化有好处。训练命令使用的是Ultralytics的标准入口配置文件的写法其实是这样的pip install ultralytics yolo train datapain_dataset.yaml modelyolov8n.pt epochs120 imgsz640 batch16 optimizerAdamW lr00.0005 close_mosaic10datapain_dataset.yaml里指定训练、验证、测试图片的路径以及类别数量nc1和类别名pain。modelyolov8n.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验证集按受试者独立划分指标YOLOv8nYOLOv8smAP500.8730.904mAP50-950.6120.654Precision0.8910.902Recall0.8420.861F1阈值0.50.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.45NMS的IoU跑测试集时看起来F1还行。但部署到实际视频流里才发现连续帧的误报情况非常不稳定——某一帧置信度0.8报了疼痛下一帧同样的表情状态置信度掉到0.3然后又回来。这是因为单帧推理受压缩噪声、运动模糊、瞬时遮挡的影响非常大。解决之道不在阈值而在后处理的时间维度这也直接引出了下一部分要讲的内容。5. 从单帧检测到连续视频段的实用后处理如果你只是拿YOLO检测单张图片里的疼痛表情那训练完模型基本就结束了。但真实的医疗辅助场景里我们面对的是连续的视频流——病房监控、护理过程记录、远程会诊画面全是时间序列。怎么把单帧检测结果变成可靠的视频级判断这是从“能跑通”到“能落地”的关键一步。5.1 为什么不能直接看单帧推理结果单帧误报是不可避免的。人体在呼吸、头部轻微摆动、眨眼这些动作都会导致某个瞬间的面部表情恰好接近疼痛模式给一个很高的置信度。但疼痛作为一种持续状态通常会在一个时间段内反复出现或者持续维持。如果只看单帧等于把一个瞬时噪声和真实疼痛信号放在同一个评判标准下结果自然不稳定。我的做法是把检测扩展到时间维度通过滑窗聚合让判定结果平滑化。5.2 滑窗投票与置信度平滑算法的核心思路其实非常简单取一个滑动时间窗口比如5秒假设视频每秒25帧即125帧统计这个窗口内每一帧YOLO检测出的疼痛置信度然后把整体置信度序列做一个中值滤波或者均值滤波。我用的是一个带权重的平滑策略import numpy as np def temporal_smoothing(scores, fps25, window_sec2.0): window_size int(fps * window_sec) if len(scores) window_size: return scores smoothed np.convolve(scores, np.ones(window_size) / window_size, modevalid) 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检测结果上做扩展是比较顺的。如果你手头也有类似的小规模医疗检测数据集我的建议是这样的先别急着扩大数据量把当前数据的标注一致性做扎实把按受试者划分的评估流程跑通先把模型在一个小圈子里用起来看真实反馈。疼痛检测看似是个很垂直的任务但它踩过的坑——类间混淆、增强策略与目标尺度不匹配、单帧阈值不稳定、时间序列后处理——在很多细粒度医学目标检测项目里都会有类似的影子。把这一套流程吃透换个任务也能复用同一套方法论这是我做这个项目最大的收获。