今天想聊聊一个医疗健康方向的检测项目:疼痛检测数据集,这套数据集一共2200张图像,已经整理成YOLO训练格式,专为医疗健康场景下的疼痛线索识别准备。很多朋友一听到“疼痛检测”,第一反应是疼痛明明是主观感受,机器怎么检测?其实这里检测的不是痛觉本身,而是疼痛引发的可观测表现——比如面部表情单元的变化、身体姿态的收紧、手脚的抓握动作,甚至皮肤或伤口区域的异常表现。用目标检测模型把这些区域定位出来,再结合评分规则,就能把传统依赖患者自评的量表变成可量化的图像指标。
这套东西能用在护理监测、康复训练评估、术后镇痛效果跟踪、临床试验筛查这些场景,对正在做医疗AI、或者想拿YOLO练手的人来说,处理好一份真实场景的小规模医疗数据集,比单纯在COCO上跑通一个模型有价值得多。下面我按数据准备、标注、训练、坑点、部署五个环节把整个过程讲透。
1. 项目整体构思与数据集定位
1.1 为什么盯上疼痛检测这个方向
临床疼痛评估一直是个老大难问题。传统量表比如VAS、NRS,都依赖患者自己打分,患者意识清醒、表达能力正常的时候还行,可一旦进了ICU、术后复苏期,或者遇到婴幼儿、认知障碍人群,主观评分基本就废了。护士只能靠经验观察患者表情、体动、生命体征变化来判断是否疼痛,这种方式不仅主观,而且很难标准化,尤其在夜班人员紧张的时候,漏判风险很高。
疼痛检测数据集要解决的,就是把“疼痛表现”变成模型可学习的目标。比如面部疼痛表情里的眉间收缩、眼周紧闭、口角下拉,这些在表情识别领域已经有大量研究支持;手部保护性动作里的握拳、紧抓床栏、按压伤口附近,也是疼痛行为的典型提示;还有躯体紧张、蜷缩、异常制动等姿态,同样可以作为线索。目标检测相比分类和关键点检测有一个很直接的优势:框能同时给出“哪里疼”的提示,比如左侧腹部区域的保护性体位,检测框落在哪个位置,本身就有定位信息。这对后续结合病史、护理记录做决策非常有帮助。
另外从项目可落地性来看,病房摄像头、陪护监控、康复训练摄像头都已经很普及,图像采集无创、无接触、成本低。YOLO这类单阶段检测器又足够快,完全可以在边缘设备上做实时分析。所以我把这个项目的技术路线定为:用YOLO做疼痛线索的目标检测,先定位“疑似疼痛表现”的区域,再配合后端评分规则输出风险等级。这个思路不追求直接给出“患者痛不痛”的结论,而是给临床人员提供一个客观、可追溯的观察辅助。
1.2 2200张数据的规模够不够用
我在各种群里被问得最多的问题就是:2200张够吗?大多数人被“深度学习需要海量数据”这句话吓住了,但实际上,数据需求量取决于任务复杂度和场景变化幅度。如果任务是COCO那种80类、场景千变万化的大规模通用检测,2200张远远不够;但对于医学场景里目标类别集中、背景相对固定的检测任务,2200张是一个很合理的起步量。
具体算一下:假设每张图包含1到4个标注目标,2200张图大约能产生3000到8000个标注框。YOLO做迁移学习时,预训练权重已经学到了丰富的底层纹理和边缘特征,我们需要训练的是医疗场景的语义和框回归能力,这个数据量足够把模型从“只会看通用物体”微调成“能认疼痛表现”的检测器。我习惯按8比1比1划分训练集、验证集、测试集,也就是大约1760张用于训练,220张用于调参,220张用于最终评估。220张测试图对于统计mAP和召回率已经能给出基本可信的数字。
当然,这个规模也有明确的上限。面部表情在低光、遮挡、侧脸角度下会剧烈变化,不同年龄段、不同人种、不同文化背景下的疼痛表达差异也很大,2200张不会覆盖全部长尾情况。所以我的定位是:把它当作第一个可运行的基线数据集,跑通整个YOLO训练部署流程,验证检测方案的有效性;后续再按场景缺口做定向扩充。直接拿这个规模去投标、去做大规模临床实验,那是不现实的,用来做技术验证和产品原型,完全够。
1.3 为什么选择YOLO作为检测底座
这个项目最终是要在病房、康复中心这类环境里跑的,不是所有地方都有强力GPU服务器。YOLO是单阶段检测器,一次前向直接输出边界框和类别,推理速度快,模型体积小,YOLOv8n的权重只有几MB,量化之后能轻松跑在Jetson、RK3588这类边缘设备上。这一点对医疗场景很重要,因为视频数据涉及隐私,很多医院希望摄像头画面不出内网,本地化推理是刚需,快模型比大模型更有实用价值。
YOLO的生态成熟也是一个重要原因。Ultralytics把训练、验证、导出、部署的工具链全部整合好了,标注格式就是一个txt文件,数据组织结构固定,甚至不需要自己写复杂的训练循环。医疗团队里如果有医生、护理人员参与,他们可以快速理解“每个txt文件对应一张图片里的框坐标”这种逻辑,沟通成本很低。对于小规模医疗数据集,YOLO的训练收敛速度和稳定性也要优于那些依赖海量数据的大Transformer模型,后者在小数据上很容易欠拟合,而且推理耗时高。
但我要强调一点:YOLO在这个项目里是定位“疑似疼痛线索”的前端,不是最终诊断工具。疼痛是主观体验,单靠一帧图像无法给出确定性结论。实际产品里,YOLO输出的每个检测框需要做时序滤波,并结合患者病历、心率、血压等多模态信息综合判断。模型的作用是把“人眼可能漏掉的表情和动作变化”及时标出来,让护士再有针对性地复核。这个定位想清楚后,YOLO的很多参数选择就都有了依据。
2. 数据处理与标注规范
2.1 数据来源与清洗策略
数据来源是医疗AI项目的第一道红线。疼痛表情相关的公开数据集有不少,包括学术机构发布的疼痛表情数据库、人脸表情数据集里带有疼痛标签的子集,以及部分公开的护理监测研究数据。这些数据使用时必须确认授权范围,看清楚是否允许用于模型训练和二次分发。如果和医院合作,涉及患者面部图像,必须有伦理审批和完整的脱敏流程。最忌讳的是从网上爬取各种来源不明的图片来做医疗数据,后续不但论文发表不了,还可能惹出一堆合规麻烦。
拿到原始图后,第一步是去重。疼痛表情视频抽帧经常会抽出大量高度相似的帧,比如一个持续两秒的表情,在30fps下就有60帧几乎一样的画面。如果全部进入训练集,模型会对这套重复特征过拟合,验证集指标虚高,真实场景一换人就垮。我的做法是先用感知哈希算法做相似度聚类,同一个视频片段里每隔一定间隔抽样一帧;更严谨一点还可以按视频片段分组,保证同一个人的同一个动作不会同时出现在训练集和验证集里。
清洗环节还要删掉明显不合格的图:分辨率过低导致五官模糊、严重过曝或欠曝、大面积遮挡、多个人脸混杂且面部极小等。这些图不是“增强样本”,而是噪声。我在第一轮清洗时,把2200张图里大概10%到15%的图剔掉了,再多引入一些补拍和公开数据源,最终保持总规模在2200左右。宁可总数少一点,也要保证每一张都有清晰的标注价值。
2.2 标注工具与YOLO格式转换细节
标注工具我用过LabelImg、AnyLabeling、CVAT,各有适用场景。单机单人标注的时候,LabelImg最轻量,打开一个文件夹,框一个存一个,不需要搭服务;多人协作标注时,CVAT更合适,它有任务分配、审核、冲突检测这些功能,还能导出YOLO格式。X-AnyLabeling的好处是带AI辅助预标注,可以先用一个初始模型自动生成一批框,人工再修改,效率能提高很多,适合数据集后续扩充。
标注前最重要的事是写一份标注规范文档。我给这个项目定义了三个大类:pain_face,覆盖面部疼痛表情区域,包括眉间收缩、眼周紧闭、口角下拉;clutch_hand,覆盖手部抓握、紧抓物品或按压痛处的动作;tension_body,覆盖躯体紧张、蜷缩、保护性体位等姿态表现。规范里明确要求,每个类别的最小框范围、遮挡超过50%是否标注、两个人脸重叠时如何处理,这些细节不提前定好,标到后面一定返工。
YOLO训练需要的是归一化后的txt标注:每一行是“类别ID x_center y_center width height”,所有坐标都除以图片宽高,所以范围在0到1之间。LabelMe这类工具默认输出是JSON多边形坐标,需要转一次。转换脚本核心逻辑就是取一个多边形外接矩形,换算成中心点坐标和宽高,然后写入txt文件。代码很常规,但我提醒几个容易踩的点:小数位数至少保留6位,不然小目标的框定位误差会被放大;注意检查是否有多边形坐标越界,尤其是手滑点到图片外面时,YOLO读取会报错;类别ID必须从0开始连续,不能跳号,否则训练时类别映射会错位。
2.3 数据集划分与类别平衡
划分数据集不能图省事直接random split。面部疼痛检测这种任务,同一个人的不同帧非常相似,如果同一人既出现在训练集又出现在验证集,模型其实是靠“认人”而不是“认表情”拿到高分的,换一个新患者效果立刻打回原形。我采用按人员ID和视频片段分组的方式,确保同一个人的所有帧只会落在训练、验证、测试三个集合中的一个,这样评估出的mAP才有参考价值。
类别平衡方面,疼痛面部表情通常是最容易采集的,手部抓握和躯体姿态次之,分泌物等异常区域更少。我做完首轮统计,pain_face的框数大约占六成,clutch_hand占两成,tension_body占两成。这种比例还凑合,但训练时仍然要给少数类别一点优待。最简单的做法是在采样器里提高少数类图像的权重;再进一步可以用复制粘贴增强,把少数类目标裁出来贴到其他背景图上,生成新训练样本;Mixup这类图像级增强对改善类别平衡也有帮助。要注意的是,验证集和测试集必须保持真实分布,不能做任何重采样,否则最终评估数字就失真了。
3. YOLO训练实操与参数解析
3.1 环境、预训练权重和训练命令
训练框架我直接用的Ultralytics YOLOv8,目前生态最省心,pip安装即可,不需要手动编译Darknet。项目目录结构建议这样:
- pain_yolo/
- images/
- train/
- val/
- test/
- labels/
- train/
- val/
- test/
- pain.yaml
- images/
痛苦经验之谈:图片和标签的目录名要一一对应,train的图片文件夹里有多少张图,labels/train里就要有多少个同名txt文件,任何一张图缺标签都会在训练时报错。pain.yaml内容也很简单:
path: /data/pain_yolo train: images/train val: images/val test: images/test names: 0: pain_face 1: clutch_hand 2: tension_body第一次训练我建议先用小模型跑通流程,比如yolov8n.pt或yolov8s.pt,把epochs设成5、imgsz设成320,先确认数据加载、标签读取、损失函数都正常,再上完整训练。有效规避“训练十几个小时后发现标签路径错了”这种惨剧。正式训练命令:
yolo detect train data=pain.yaml model=yolov8s.pt epochs=200 imgsz=640 batch=16 device=0model=yolov8s.pt表示加载COCO预训练权重,这是迁移学习的关键;imgsz=640是网络输入尺寸,对应多数显卡的性价比;batch=16需要根据显存调整,显存不够就降到8或4。24GB显存跑yolov8s基本没压力,8GB显卡建议用yolov8n,或者把imgsz降到512。
3.2 关键超参与训练曲线分析
很多新手把epochs拉到300就当甩手掌柜,这是不对的。医疗小数据集上,我最关心的三个参数是学习率、数据增强强度、输入尺寸。学习率:YOLOv8默认学习率对COCO这种大数据集是合理的,但2200张的医疗数据量小,预训练权重不应该被大学习率破坏,我会把lr0从默认的0.01降到0.005,如果有轻微过拟合再降到0.002。数据增强:Ultralytics默认开启Mosaic,但对小目标不友好,因为图像被切分后,一个小表情区域可能被切掉一半,尤其到了训练后期,Mosaic会让模型难收敛。我的做法是把Mosaic在前30个epoch关掉,使用close_mosaic=30参数,并在后面epoch只做轻度缩放和HSV增强。
另一个常用策略是冻结骨干网络。数据量小的时候,我会冻结前10层参数,只训练检测部分,这样能保留预训练模型学到的通用纹理特征,减少过度拟合风险。做法是在model参数里指定冻结层,或者直接修改train脚本里的freeze配置。训练过程中我一般盯着四张曲线图:训练loss、验证loss、mAP@0.5、mAP@0.5:0.95。理想情况是训练loss稳步下降,验证mAP同步上升。如果训练loss还在降、验证mAP却开始掉,说明过拟合了,优先减少增强强度、提高权重衰减,而不是盲目增加epochs。
3.3 评估指标怎么才算合格
疼痛检测和通用目标检测的评估侧重点不太一样。通用检测榜单上大家喜欢看mAP@0.5:0.95,这是一个综合了不同IoU阈值下的平均分,数值越接近1越好。但对医疗辅助场景,我强烈建议同时关注召回率,也就是“真实疼痛表现里到底找回了多少”。漏掉一个痛苦表情,可能意味着患者要多忍受几个小时的疼痛,而多框一个疑似区域,最多是让护士多看一眼,成本要低得多。所以在调阈值时,我会把置信度阈值放低到0.25甚至0.2,宁可输出多一些疑似框,再由后端规则或人去做二次判断。
在2200张这种规模的数据集上,我见过不少团队的mAP@0.5在0.7到0.85之间,mAP@0.5:0.95在0.4到0.6之间。这个数字本身不算惊艳,但放在医疗小数据场景里已经足够做产品原型验证。真正重要的是测试集要按患者维度分开,指标反映的是“面对没见过的病人”时的表现。我会额外看每个类别单独的平均精度,如果pain_face很高但clutch_hand很低,问题大概率出在数据或标注上,而不是模型结构上,下一步优先补那一类的数据。
4. 我踩过的坑:常见问题与排查技巧实录
4.1 小目标漏检,面部区域总是框不准
疼痛表情集中在眉眼、嘴角这些局部区域,在640输入尺寸下,一个眉毛紧缩的区域可能只有十几个像素宽,模型很难捕捉。我一开始训练完,发现验证集上漏检最严重的全是小目标。解决办法有几个方向:一是提高输入分辨率,imgsz从640提到960或1280,小目标特征明显变清晰,但显存占用涨得很快,只能用更小模型或更小batch找平衡;二是用切图策略,先跑一个通用的人脸检测器,把面部区域裁下来,再在裁切图上做疼痛表现检测,相当于把一张大图拆成若干小图分别检测;三是推理时用SAHI这类切片辅助推理框架,但部署复杂度也会增加。
我实测下来,对疼痛表情检测最有效的是“人脸裁切+主检测”双路方案。第一路人脸检测负责找到人脸位置,第二路在放大的面部区域上检测疼痛细节。代价是推理耗时翻倍,但在边缘设备上依然能跑到实时。小目标漏检还有可能是数据增强里的随机缩放造成的:训练时图像被缩得太小,原本就小的目标直接被压成噪点。我会在增广参数里限制缩放范围,保证小目标在训练过程中不会频繁消失。
4.2 类别不平衡引发的“选择性失明”
疼痛检测数据集里,pain_face样本量充足,模型学得很好;clutch_hand和tension_body因为样本少,经常被模型忽略。最典型的症状是:验证集里手部抓握动作明明很多,输出的检测结果却几乎全是pain_face。这不是模型笨,而是它在“模拟”训练集的先验分布:少数类样本太少,loss占比低,学了等于没学。
我的处理分两步。第一步是数据层面过采样,把包含clutch_hand和tension_body的图片复制几份放进训练集,复制时适当做上下翻转、亮度调整,避免完全重复的样本导致过拟合。第二步是使用复制粘贴增强,把少数类目标区域裁剪出来,随机粘贴到有疼痛表情但没有该目标的背景图上,并同步生成新的标注框。这一步对姿态类目标效果不错,因为它模拟了“同一个场景里出现多种疼痛线索”的实际情况。复制粘贴增强要注意目标的边缘融合和阴影处理,如果直接硬贴,模型会学到“目标旁边总有条裁剪线”这种伪特征,推理时反而更差。
4.3 BN崩溃与训练不收敛
我遇过最玄学的场景是训练跑到几十个epoch之后,loss突然变成NaN,验证mAP直接归零,在Ultralytics界面上输出一串warning,说batch normalization运行统计方差接近零。第一次遇到时我以为是数据有问题,排查了半天才发现是自己把batch从16改成1来省显存,BN层在batch=1的时候统计量根本算不准,数值崩掉是迟早的事。
BN崩溃的常见原因还包括初始学习率过大、脏数据里混了全黑全白图像、标注框坐标越界变成负数。我现在的排查顺序很固定:先检查标签文件有没有负数和越界坐标;再确认输入图像没有损坏文件;然后把batch调回8以上,或用梯度累积模拟大batch;最后把学习率降到原来的十分之一重新训练。数据量小的时候,加载预训练权重能大幅降低BN崩溃概率。另外建议训练脚本里打开resume功能,一旦发现loss异常有断点可以回滚。
4.4 混淆矩阵总和为什么不是100%
不少人在验证阶段看混淆矩阵,发现横着加竖着加都不是100%,就怀疑代码写错了,其实这是概念没理顺。YOLO输出的混淆矩阵默认是“真实样本计数”,矩阵里的数字表示的是“某个真实类别的样本被预测成了哪些类别”,每个类别的样本总数不一样,所以行和列都不是百分比。只有当矩阵做了归一化处理,每一行代表该真实类别下预测结果的分布比例,行和才会等于1或者100%。想查看比例,用normalize=True或在评估工具的设置里打开归一化混淆矩阵。
更重要的是怎么读混淆矩阵。我会看对角线以外的“混淆点”落在哪里,比如pain_face经常被预测成背景,说明面部表情区域特征不够强;tension_body被预测成clutch_hand,说明两类目标在画面位置或形态上太接近。这些信息比单独看mAP有用得多,能直接指导下一轮数据采集和标注优化。
4.5 标注缺陷引发的模型震荡
有一次我把验证集指标刷到很高,但一到新场景推理就疯狂误检,后来发现是标注阶段埋了雷:同一类目标,一位标注员习惯框得紧贴边缘,另一位习惯多留一圈背景,模型学到的目标边界风格一会儿紧一会儿松,输出框自然飘。解决方法是让两个人标注同一批图,计算IoU一致性,把不一致超过0.5的图全部打回重标。这种标注一致性校验在医疗项目里非常必要,因为框的边界直接影响框回归的收敛质量。
另一个隐蔽问题是数据泄露。我有一次把同一个视频流的相邻帧同时放进了训练集和验证集,结果验证mAP高得离谱,我一度以为模型已经能临床使用了,直到换了一个医院的视频测试,mAP直接掉了一半。从那以后我划分数据集都严格按“视频分组”,同一个人的所有帧只能出现在一个集合里。吃一堑长一智:评估指标只能信“跨患者”的评估,任何形式的同源数据混入都会骗人。
5. 从训练到部署的落地体会
5.1 轻量化导出与边缘部署
训练完成后,导出环节不能马虎。Ultralytics里一条命令就能导出ONNX和TensorRT引擎:
yolo export model=best.pt format=onnx imgsz=640 dynamic=True yolo export model=best.pt format=engine device=0ONNX格式适合先做跨平台验证,TensorRT引擎适合NVIDIA显卡上的高性能推理。我部署过的场景是病房摄像头分析盒,盒子上一块Jetson Orin Nano,跑yolov8s量化后大概能做到30到40ms一帧,同时分析一路视频完全没问题。如果目标是更低成本的嵌入式设备,可以进一步做INT8量化,但需要准备校准数据集,校准图尽量覆盖病房常见的亮度、角度,否则量化后精度损失会很明显。
部署层的代码不建议直接用Ultralytics的Python推理接口,太重了。我一般用ONNX Runtime或TensorRT的C++/Python API加载模型,输入的预处理只有resize、归一化、通道转换,后处理是解码边界框、做NMS、过滤低置信度框。这一套流程在多个平台间复用起来很干净。另外,我会在部署代码里加一个“灰度开关”:输出检测结果但默认不弹告警,只记录日志,等和护理团队确认准确率达标后再开启主动提醒。
5.2 时序滤波与多模态扩展思路
单帧检测结果直接抖起来没法用。疼痛表情往往持续几秒甚至更久,手部抓握也不是一帧就结束的,但单帧推理经常出现连续几帧有检测、下一帧突然消失的情况。最简单的解法是滑窗投票:记录最近15帧里每个位置区域的检测结果,只有出现频率超过一半才认为是一次有效事件。更平滑的做法是加一个卡尔曼滤波或EMA,对检测框中心和置信度做时间平滑,能明显降低误报,同时不会明显增加延迟。
2200张数据集只是一个基线。要真正在医院环境里用,后续还需要补三个方向的数据:一是不同年龄段和肤色人群的疼痛表达,现有公开数据集中年轻白人居多,适用性不够;二是遮挡场景,比如戴口罩的患者、侧脸朝向摄像头的患者,检测难度完全不同;三是低光照和夜间病房场景,这需要针对低光做专门的图像增强或训练数据增广。多模态方向上,将YOLO检测结果与心率、血压、体动传感器信号做特征融合,比纯图像判读更稳健,这也是我接下来打算扩展的方向。
最后再说一个我自己的习惯:每次训练结束,我会把预测得分在0.3到0.5之间的样本全部翻出来人工看一遍。这些低置信度样本往往最能暴露标注不一致和类别定义模糊的问题,比只盯mAP数字有用得多。疼痛检测这个方向,模型输出只是辅助,真正决定项目价值的,是能不能帮临床人员少漏掉一次痛苦的信号。