
疼痛识别这个方向我最早接触是在做养老监护类项目的时候。当时客户提的需求很直接老人卧床或者术后恢复期间疼不疼、疼到什么程度护士不可能24小时盯着能不能用摄像头自动判断。一开始我觉得这事挺玄的疼痛这种东西主观性太强后来查了一圈资料才发现面部表情是疼痛评估里被研究得最透的一个客观通道而目标检测恰好能把这件事工程化落地。这次拿到的是一份2200张规模的YOLO格式疼痛检测数据集我打算把它从数据构成、标注逻辑、训练配置到实际踩坑完整走一遍顺便把医疗健康类数据集和通用数据集在工程处理上的差异讲清楚。1. 疼痛检测数据集到底在解决什么问题1.1 从临床需求到算法任务的翻译疼痛评估在临床上有一套成熟但尴尬的现状。意识清醒、能沟通的成年人可以用0到10的数字评分量表自报疼痛但婴幼儿、失智老人、镇静状态患者、气管插管患者根本没法自报。这时候临床会退而求其次用行为观察量表比如FLACC量表看面部表情、腿部动作、活动、哭泣、可安抚性五个维度或者用PAINAD量表针对失智人群。这些量表的共同点是依赖护士肉眼观察主观性强而且没法连续监测。把这件事翻译成算法任务最可行的切入点是面部表情。人在经历疼痛时眉部收紧、眼睑闭合、鼻唇沟加深、嘴部张开这些动作单元会组合出现这套组合在学术界叫Prkachin和Solomon疼痛表情量表简称PSPI。所以疼痛检测数据集本质上是一个面部表情的细粒度分类或检测任务只不过类别不是高兴/悲伤这种情绪而是无痛/轻度疼痛/中度疼痛/重度疼痛这种疼痛强度分级。用YOLO来做这件事核心思路是把疼痛表情区域当成目标框出来同时给出类别。相比纯分类网络检测框架的好处是能定位到具体是哪张脸、哪个区域在表达疼痛在多人的病房场景里这一点非常关键。1.2 2200张这个规模意味着什么很多人看到2200张第一反应是太少了。这个判断对通用目标检测任务成立对医疗垂直场景要分开看。COCO这种通用数据集动辄十几万张是因为它要覆盖80个类别、各种光照背景尺度。疼痛检测的类别空间窄得多场景也相对收敛——基本就是病床、诊室、居家监护这几种环境人脸姿态和光照条件的变化范围比野外场景小很多。2200张如果标注质量过关、类别分布均衡训练一个能用的YOLO模型是够的。我做过对比一个垂直场景的检测任务标注精准的2000张往往比标注粗糙的8000张效果更好因为医疗数据的噪声容忍度极低一张错标的脸可能就让模型学到错误的疼痛特征。真正决定这份数据集价值的不是数量而是标注一致性、类别定义清晰度和样本多样性。1.3 适合谁来用这份数据这份数据集的目标用户我梳理成三类。第一类是做智慧医疗、养老监护、术后护理产品的工程团队需要快速验证疼痛自动识别的可行性不想从零标注。第二类是医学图像分析方向的研究生需要一个有临床意义的检测任务做实验疼痛检测比单纯的人脸检测更有故事可讲。第三类是YOLO学习者想找一个非通用场景的数据集练手理解垂直领域数据和COCO的差异。需要提前说明的是这份数据不能直接用于临床诊断。它更适合做辅助提醒、趋势监测、科研验证。任何涉及医疗决策的场景算法输出都必须经过专业人员复核这是底线。2. 数据集结构与标注体系拆解2.1 目录组织与YOLO格式规范一份规范的YOLO数据集目录结构应该是这样的pain_dataset/ ├── images/ │ ├── train/ │ ├── val/ │ └── test/ ├── labels/ │ ├── train/ │ ├── val/ │ └── test/ └── data.yamlimages和labels下的子目录必须严格对应文件名不含扩展名必须一一匹配。我见过太多人栽在这一点上图片叫patient_001.jpg标签叫patient_001.txt看着没问题但如果图片是.jpeg而标签按.jpg去找训练时就会报找不到标签或者更隐蔽地跳过这些样本导致实际训练数据比你以为的少一截。标签文件里每一行代表一个目标格式是class_id x_center y_center width height这五个值里后四个都是归一化到0到1的浮点数相对于图片宽高。这里有个新手最容易犯的错把像素坐标直接写进去。YOLO不认像素坐标必须除以图片的宽和高。比如一张1920x1080的图框的中心在(960, 540)宽400高300正确写法是0 0.5 0.5 0.2083 0.2778。2.2 疼痛等级类别定义疼痛检测的类别划分直接决定模型能输出什么。常见的做法是四级类别ID类别名称临床对应表情特征0no_pain无痛面部放松眉部平展1mild_pain轻度眉部轻微收紧眼睑略窄2moderate_pain中度眉部明显下压鼻唇沟加深3severe_pain重度闭眼、皱眉、嘴张开、面部扭曲这个划分和PSPI量表的动作单元组合是对应的。需要提醒的是轻度与中度的边界在标注时最容易产生分歧因为轻微收紧和明显下压之间没有硬性阈值。如果这份数据集的标注指南没有明确定义边界训练出来的模型在这两个类别上混淆率会偏高。我的建议是如果发现mild和moderate的混淆矩阵特别乱可以考虑合并成三级牺牲一点粒度换稳定性。2.3 标注质量的自检方法拿到一份数据集别急着训练先做标注自检。我常用的几个检查手段第一可视化抽查。写个脚本把标签框画回图片上随机抽50到100张看。重点看框是否贴合面部、类别是否合理、有没有漏标。这个步骤花半小时能省下后面几小时的无效训练。第二统计类别分布。用脚本统计每个类别的框数量如果某一类占比超过70%或者低于5%就要警惕类别不平衡。疼痛数据里no_pain通常最多这是正常的但如果severe_pain只有几十个框模型基本学不会这一类。第三检查框的尺寸分布。疼痛检测的框应该集中在人脸区域如果出现特别大接近整图或者特别小几个像素的框多半是标注错误。import os from collections import Counter label_dir pain_dataset/labels/train class_counter Counter() box_areas [] for fname in os.listdir(label_dir): with open(os.path.join(label_dir, fname)) as f: for line in f: parts line.strip().split() if len(parts) ! 5: print(f格式异常: {fname} - {line}) continue cid int(parts[0]) w, h float(parts[3]), float(parts[4]) class_counter[cid] 1 box_areas.append(w * h) print(类别分布:, dict(class_counter)) print(平均框面积占比:, sum(box_areas) / len(box_areas))这段脚本跑一遍类别分布和框尺寸心里就有数了。格式异常的行会直接打出来方便定位问题文件。3. YOLO训练配置与参数选择3.1 版本选择v5、v8还是更新的疼痛检测这种垂直小数据集我一般推荐YOLOv8或者v5。理由很实际这两个版本的生态最成熟文档、预训练权重、部署工具链都齐全遇到问题搜得到答案。更新的版本性能可能有提升但医疗项目往往要求稳定可复现没必要为了几个点的mAP去踩新版本的坑。如果团队已经在用v5继续用v5完全没问题它的anchor机制对固定场景的人脸检测其实挺友好。如果从零开始v8的无anchor设计和更简洁的API会更省心。至于v8之后的版本除非有明确的性能需求否则我倾向于观望。预训练权重的选择也有讲究。官方在COCO上训练的权重是通用起点但COCO里没有疼痛类别所以它提供的主要是底层特征提取能力。我的经验是用COCO预训练权重初始化然后冻结backbone训练几轮让head适应新任务再解冻全量微调这个两阶段策略在小数据集上比直接端到端训练更稳。3.2 关键超参数与计算逻辑训练配置里几个参数需要重点说。输入尺寸imgsz。疼痛检测的目标是人脸人脸在画面里的占比取决于拍摄距离。如果是病床监护场景人脸可能只占画面的十分之一。这时候imgsz设太小比如416会导致人脸被缩得只剩几十个像素特征丢失严重。我一般从640起步如果发现小目标漏检多提到960甚至1280。代价是显存和训练时间上升640到1280显存占用大约翻四倍。batch size。这个受显存限制但有个经验值小数据集上batch不要太大8到16比较合适。batch太大梯度更新次数少小数据集本来就样本少容易欠拟合。如果显存不够用梯度累积模拟大batch。学习率。YOLO默认的初始学习率是0.01配合余弦退火。小数据集微调时我通常降到0.001甚至更低因为预训练权重已经很好大学习率会把学到的特征冲掉。可以用这个公式粗估lr base_lr * batch_size / 64base_lr取0.01batch为16时lr约0.0025。训练轮数epochs。小数据集容易过拟合但也不能太少。我的做法是先设100轮观察验证集mAP曲线如果在60到80轮之间就平了甚至下降说明过拟合早停。如果还在涨加到200。# data.yaml path: ./pain_dataset train: images/train val: images/val test: images/test nc: 4 names: 0: no_pain 1: mild_pain 2: moderate_pain 3: severe_painyolo detect train \ modelyolov8s.pt \ datadata.yaml \ epochs150 \ imgsz640 \ batch16 \ lr00.001 \ patience30 \ device0 \ projectruns/pain \ nameexp1patience30表示30轮验证指标不提升就早停这个在小数据集上很实用能自动帮你省时间。3.3 数据增强的取舍YOLO默认开启mosaic、HSV抖动、随机翻转等增强。疼痛检测场景下这些增强要区别对待。mosaic增强把四张图拼成一张能提升小目标检测能力但它会改变人脸的空间上下文。疼痛表情的判断有时依赖整张脸的协调mosaic拼接后可能出现半张脸配另一张脸的诡异组合反而引入噪声。我的建议是训练前期开mosaic后期关掉让模型在接近真实的分布上收敛。YOLO里可以用close_mosaic参数控制最后多少轮关闭。HSV抖动对光照变化有帮助病房里白天黑夜灯光差异大这个可以保留。但色相H抖动幅度别太大人脸肤色被改得离谱反而不利于学习。随机翻转要小心。水平翻转对疼痛表情基本安全因为表情左右大致对称。但垂直翻转绝对不能用倒过来的人脸不是真实场景会污染数据。4. 训练过程监控与问题排查4.1 看懂训练输出的关键指标训练跑起来后控制台会刷一堆指标重点看这几个。box_loss和cls_loss。box_loss是边界框回归损失cls_loss是分类损失。正常情况下两者都应该是下降趋势。如果box_loss下降但cls_loss不降说明框能定位但类别分不清多半是类别定义模糊或者类别不平衡。如果cls_loss下降但box_loss震荡可能是学习率偏大或者标注框质量差。mAP50和mAP50-95。mAP50是IoU阈值0.5时的平均精度比较宽松mAP50-95是0.5到0.95多个阈值平均更严格。疼痛检测里如果mAP50能到0.8以上但mAP50-95只有0.4说明框的位置不够精准但对有没有疼痛这个判断已经够用。如果两个都低问题在特征学习层面。混淆矩阵。这个在验证阶段会生成是排查类别混淆的利器。疼痛检测最常见的混淆是mild和moderate互相错分以及no_pain被误判成mild。前者是类别边界问题后者是特征区分度不够。4.2 常见问题速查表问题现象可能原因排查与解决训练loss不下降学习率过大/过小、数据标签错误先降lr到0.0001试再抽查标签验证mAP远低于训练过拟合加数据增强、减epochs、加dropout某类别完全检测不到该类样本太少检查类别分布考虑过采样或合并类别框位置偏移严重标注坐标未归一化检查标签文件数值是否都在0-1训练中途loss变nan学习率过大、数据有异常值降lr检查是否有空标签或越界坐标小目标漏检多输入尺寸太小提高imgsz或改用更小的anchor推理速度慢模型太大、输入太大换n/s版本降imgsz4.3 我踩过的几个坑第一个坑是标签坐标越界。有些标注工具在框超出图片边界时会写出大于1或小于0的坐标YOLO训练时不会报错但会悄悄影响回归。我现在的习惯是训练前跑一遍清洗脚本把所有坐标clip到0到1之间。第二个坑是图片和标签不同步。数据集经过多次增删后经常出现有图无标签或有标签无图的情况。YOLO遇到有图无标签会当成负样本如果这种样本多了模型会学得保守倾向于不检测。训练前一定要做一次配对检查。第三个坑是验证集泄漏。有人图省事把训练集的一部分直接复制到验证集或者同一段视频的相邻帧分到训练和验证两边。疼痛表情在相邻帧里几乎一样这会导致验证mAP虚高实际部署时原形毕露。正确做法是按受试者或按视频片段划分确保同一个人不出现在训练和验证两边。import os img_dir pain_dataset/images/train lbl_dir pain_dataset/labels/train imgs {os.path.splitext(f)[0] for f in os.listdir(img_dir)} lbls {os.path.splitext(f)[0] for f in os.listdir(lbl_dir)} print(有图无标签:, imgs - lbls) print(有标签无图:, lbls - imgs)这段配对检查脚本很短但每次处理新数据集我都会跑一遍能挡掉不少低级错误。5. 模型评估与部署落地5.1 医疗场景下的评估指标选择通用检测任务看mAP就够了医疗场景要更细。疼痛检测我关注三个层面的指标。第一是分类层面的敏感度和特异度。把有痛mild及以上当成阳性无痛当成阴性敏感度是真正被识别出疼痛的比例特异度是无痛被正确排除的比例。临床辅助场景里敏感度比特异度更重要宁可多提醒几次让护士复核也不能漏掉真正疼痛的患者。第二是分级准确率。四级分类里相邻等级错分mild判成moderate比跨级错分no_pain判成severe危害小得多。可以引入加权混淆矩阵给跨级错误更高惩罚。第三是时序一致性。疼痛是连续状态单帧判断会抖动。实际部署时通常对连续多帧做平滑比如滑动窗口投票或者卡尔曼滤波。评估时也要看时序上的稳定性不能只看单帧。5.2 从训练到部署的转换训练完的.pt权重不能直接上生产需要转换。常见路径是导出ONNX再转TensorRT或者直接用YOLO的导出功能。# 导出ONNX yolo export modelruns/pain/exp1/weights/best.pt formatonnx opset12 # 导出TensorRT需要GPU环境 yolo export modelruns/pain/exp1/weights/best.pt formatengine halfTruehalfTrue表示用FP16半精度推理速度能提升接近一倍精度损失通常很小。医疗场景如果对精度极度敏感可以先用FP32对比一下确认差异可接受再上FP16。部署时的预处理要和训练时严格一致。训练用了letterbox填充推理时也要letterbox训练归一化到0-1推理也要。我见过推理结果全乱的情况最后查出来是训练用了RGB而推理喂了BGR这种低级错误在跨框架部署时特别容易发生。5.3 实际部署的工程考量疼痛检测模型落地有几个工程问题绕不开。隐私保护。病房画面涉及患者隐私模型最好部署在边缘设备上画面不出本地。如果必须上云人脸区域要做脱敏处理。这一点在方案设计阶段就要考虑不能等上线了再补。误报的处理。模型不可能100%准误报多了护士会烦最后直接关掉报警。我的做法是设置置信度阈值加时序确认单帧置信度超过0.7且连续5帧里有3帧以上判定为疼痛才触发提醒。这样能过滤掉大部分瞬时误报。模型更新。临床数据分布会变新病房、新设备、新人群都可能让模型性能下降。要建立定期评估和再训练机制把线上误判样本收集起来人工复核后加入训练集迭代。算力预算。如果用边缘盒子部署要算清楚算力。YOLOv8s在640输入下一张图推理大概几十毫秒如果要做多路视频实时分析得评估并发路数。算力不够就换n版本或者降输入尺寸别硬扛。6. 数据集扩展与模型改进方向6.1 数据层面的增强思路2200张是起点不是终点。如果项目要继续推进数据扩展有几个方向。一是多模态融合。单纯面部表情有局限有些人疼痛时表情不明显但会有生理信号变化。如果能结合心率、皮电、体动等信号判断会更准。数据集层面可以往多模态标注方向走给每张图或每段视频配上生理参数。二是场景多样化。现有数据如果集中在某几种拍摄条件下模型泛化会受限。补充不同光照、不同角度、不同肤色、不同年龄段、不同遮挡程度的样本能显著提升鲁棒性。特别是婴幼儿和失智老人这两个重点人群样本要专门补充。三是时序标注。疼痛是动态过程单帧标注丢失了时间信息。可以往视频片段标注方向扩展标注疼痛的起始、峰值、缓解过程这样能训练时序模型输出更符合临床的疼痛轨迹。6.2 模型层面的改进空间YOLO本身还有优化余地。针对疼痛检测的特点几个改进方向值得试。注意力机制。疼痛表情的关键区域集中在眉眼和嘴部引入注意力模块让模型聚焦这些区域能提升特征利用率。可以在backbone后加CBAM或者SE模块代价是少量推理开销。多尺度特征融合。人脸在画面里尺度变化大加强FPN或PANet的特征融合对小脸检测有帮助。如果数据里小目标多这个改进收益明显。损失函数调整。疼痛检测类别不平衡可以用focal loss缓解。如果相邻类别混淆严重可以引入类间距离惩罚让模型在特征空间里把mild和moderate拉开。知识蒸馏。如果部署端算力紧张可以用大模型蒸馏小模型在保持精度的同时压缩模型。医疗场景对精度敏感蒸馏时要注意别把关键的疼痛特征丢掉了。6.3 合规与伦理的边界最后必须强调医疗AI项目有明确的合规边界。这份数据集和训练出的模型用于科研、教学、产品原型验证都没问题但不能宣称能替代临床诊断。涉及患者数据的采集、存储、使用要符合相关数据保护要求做好脱敏和授权。模型输出的任何疼痛判断都应该定位为辅助提示最终决策权在专业人员手里。我在实际项目里养成的习惯是任何医疗相关的算法输出界面上都要明确标注仅供参考请以专业评估为准。这不是形式主义是保护用户也保护自己。7. 写在最后的一点实操体会这份2200张的疼痛检测数据集价值不在于规模而在于它把一个有临床意义的任务做成了可训练、可复现的工程形态。我处理过不少垂直领域数据集最大的感受是数据质量的决定性远大于数量标注一致性、类别定义清晰度、训练验证划分的严谨性这三点做到位2000张能跑出比8000张更好的效果。如果你正准备用这份数据训练模型我的建议是先花半天做数据自检把类别分布、框尺寸、图文配对、坐标合法性都过一遍再开始训练。训练时从小模型、小输入尺寸起步快速跑通流程确认没问题再往上加。遇到指标异常先怀疑数据再怀疑参数最后才怀疑模型结构——这个排查顺序能帮你少走很多弯路。疼痛检测这个方向技术上还有很大空间多模态、时序建模、个性化校准都是值得深挖的点。但无论技术怎么演进医疗场景的底线不变算法是辅助人是决策者。把这条守住项目才走得远。