☰
YOLO疼痛检测数据集训练实战:从数据清洗到边缘部署避坑指南
2026/9/29 18:24:00 网站建设 项目流程

最近手头有个项目要做自动疼痛评估,正好拿到一套2200张的医疗健康数据集,标注格式是YOLO。我带着这套疼痛检测数据集从数据清洗、标签校验到模型训练、评估、导出,完整跑了一遍,中间踩了不少坑,也沉淀了一些适合医疗场景的做法。这篇就按我实际操作的顺序,把能直接复用的命令、参数和避坑经验整理出来。

适合看这篇的人包括:准备用YOLO做医学图像检测的学生和研发、想做疼痛表情/疼痛行为自动识别的算法工程师、要把模型部署到边缘设备做监护的开发者。不太适合完全没接触过目标检测的读者,但如果你想入坑,照着命令敲一遍也能跑通。

1. 为什么用YOLO做疼痛检测

1.1 疼痛评估的痛点和视觉方案的价值

临床上,疼痛评估这件事比想象中麻烦得多。成人可以自己打分,但婴儿、ICU镇静患者、认知障碍的老人、术后麻醉未清醒的病人,他们没法准确描述疼痛程度,只能靠护士观察心率、血压、表情和行为来判断。传统量表如FLACC、CPOT、BPS等依赖医护人员的经验,观察间隔长、主观性强,很难做到连续、标准化。

视觉信号是解决这个问题的一个自然切入点。面部表情是公认的疼痛指标之一,很多疼痛编码体系比如FACS(面部动作编码系统)就是基于面部动作单元来判断疼痛相关表情。如果把病人脸部的疼痛表情区域检测出来,再结合帧间连续性,就能给医护人员提供一个相对客观的“疼痛指数”参考。这就是这套2200张疼痛检测数据集要做的事:训练一个YOLO目标检测模型,自动定位疼痛表情所在区域,输出边界框和类别。

1.2 为什么选择目标检测而不是图像分类

做疼痛表情识别,一个常见误区是以为用图像分类就够了。分类模型只能告诉你“这整张图里有没有疼痛表情”,但实际医疗场景里,图片里可能有多张脸、有遮挡、有陪护人员,疼痛特征也可能只出现在半边脸。用目标检测能同时解决“在哪里”和“是什么”两个问题,输出结果也更适合后续做跟踪和行为分析。

我在这套数据集上对比过纯分类模型和YOLO检测模型。分类模型在单人正脸、光照均匀的数据上准确率不差,但一旦出现多人、侧脸、局部遮挡,分类模型直接乱套。YOLO把这些情况当成一个个独立目标来检测,配合NMS处理重叠框,实用性高很多。而且YOLO模型体积小、推理速度快,后续放到树莓派或RK3588这类边缘设备上做实时分析,也留有性能余量。

2. 数据集准备是训练前最重要的一步

2.1 数据集构成与类别设计

先说这套数据集的构成。2200张图不算大,但对医疗类小场景来说是够用的起步量,关键在于类别怎么设计。我这次用的是二分类:no_pain和pain。no_pain指正常、放松或中性表情,pain指符合疼痛判定标准的表情,比如皱眉、眯眼、鼻唇沟加深、张口或面部扭曲。

有人会想把疼痛程度细分,比如mild、moderate、severe。但从实操角度我不建议一上来就做多分类,原因有三个:第一,疼痛等级判定的金标准在数据层面就很难统一,不同标注人员对等级的理解差异大,标签噪声高;第二,2200张图摊到多个等级后每个类别样本可能只有几百张,类别不平衡会让训练很难收敛;第三,YOLO的多分类只是框内分类,等级边界模糊时反而容易把相邻类互相误判。稳妥的做法是先做二分类把疼痛区域定位准,后面需要等级时再在框内加一个回归分支或单独训练一个分类头。

标注规范上我踩过一个坑:最初标注的人习惯把整个脸框进去,导致“pain”和“no_pain”的框几乎一样大,模型完全学不到疼痛特征。后来改成尽量贴近疼痛特征区,比如眼睛、眉毛、鼻部和口周,同时保留少量外部信息帮助模型理解上下文。框太紧会丢失表情肌肉运动的细节,框太松会把无关背景噪声带进来。我自己试下来,边界比疼痛特征外扩5%到10%效果比较合适。

数据来源和合规也要先说清楚。医疗类数据涉及个人隐私,不能随便从网上抓图用。建议优先使用公开的疼痛表情库,比如基于受试者知情同意拍摄的疼痛表情数据集,或者自建数据时做严格的脱敏和伦理审查。无论哪种来源,都要保证数据授权允许用于算法训练和论文发表,否则后面写论文和上线都会被卡住。

2.2 YOLO标签格式与目录结构

YOLO格式的标签和VOC、COCO不同,它不是用XML或JSON,而是每个图片对应一个同名TXT文件,每行标注一个目标:

class_id x_center y_center width height

这里的x_center、y_center、width、height都是相对图片宽高的归一化值,范围是0到1。比如一张宽度为1920、高度为1080的图片,某个目标左上角坐标为(960, 540),中心点归一化坐标就是(0.5, 0.5)。

目录结构建议这样组织:

pain_dataset/ ├── images/ │ ├── train/ │ ├── val/ │ └── test/ └── labels/ ├── train/ ├── val/ └── test/

训练时直接给一个数据集YAML文件,指向这两个目录就行。图片名和标签名必须完全对应,包括不分大小写后缀。我自己就遇到过Windows系统下文件名带空格导致训练时标签读不到的问题,后面统一用脚本把所有文件名做了一遍清理。

2.3 标签质量检查不能省

标签质量是医疗数据集的生死线。YOLO训练对标签格式极敏感,一个越界坐标就能让训练loss直接炸掉或出现NaN。我在训练前写了个简单脚本扫了一遍全部标签:

import os from pathlib import Path from PIL import Image label_dir = Path("pain_dataset/labels/train") img_dir = Path("pain_dataset/images/train") for label_path in label_dir.glob("*.txt"): with open(label_path) as f: lines = f.readlines() for line in lines: data = line.strip().split() if len(data) != 5: print(f"格式错误: {label_path}: {line}") continue cls, x, y, w, h = map(float, data) if not (0 <= x <= 1 and 0 <= y <= 1): print(f"中心坐标越界: {label_path}: {line}") if w <= 0 or h <= 0: print(f"宽高小于等于0: {label_path}: {line}") if x - w / 2 < 0 or y - h / 2 < 0 or x + w / 2 > 1 or y + h / 2 > 1: print(f"边界越界: {label_path}: {line}")

除了脚本检查,我还会把标注框可视化画到图上,随机抽几百张人工过一遍。这一步虽然费时间,但能发现很多程序检查不出来的问题:框明明应该是左眼区域,实际标到了右眼;同一个脸标了两个重叠框;这个脸都模糊到看不清了标注员还硬标了个“pain”。这些脏数据对训练结果的破坏力比模型结构选型大得多。

另外一个容易被忽略的问题是数据划分。医疗数据划分不能直接随机按图片分,而应该按受试者或视频片段分。如果同一个人的不同帧分别出现在训练集和验证集,模型相当于提前见过了验证集的人脸,mAP会虚高,部署到新病人身上就露馅。我这次就是先按人物ID分组,再按组划分train、val、test,比例大约7:1.5:1.5,这样才能反应真实泛化能力。

3. 训练流程与关键参数

3.1 环境配置与依赖安装

训练环境我用的是Python 3.10 + PyTorch 2.1 + Ultralytics YOLO框架。不用自己手写损失函数和训练循环,Ultralytics把数据加载、增强、训练、评估、导出都封装好了,非常适合快速验证。

安装命令很简单:

pip install ultralytics

如果机器上有NVIDIA GPU,建议同时确认CUDA和cuDNN环境正常,跑下面这条命令验证一下:

python -c "import torch; print(torch.cuda.is_available())"

返回True就说明GPU可用。没有GPU也能训练,但2200张图、100个epoch用CPU可能要跑到天荒地老,最好还是租个云GPU或者用在线训练平台。

3.2 数据集YAML配置

训练前先创建数据集的YAML描述文件,命名为pain.yaml:

path: ./pain_dataset train: images/train val: images/val test: images/test nc: 2 names: 0: no_pain 1: pain

nc是类别数量,names里的顺序要和标注TXT里的class_id对应。如果顺序写反,训练时所有标签都错位,模型学的完全是噪声。

3.3 模型选型:从YOLOv8n开始

YOLO系列现在模型很多,从v5到v8再到更新版本都有,但我不建议盲目追新。对医疗这种样本量不大的任务,先用成熟稳定、文档齐全的版本最稳妥。我这次以YOLOv8为主,具体选型参考下表:

模型参数量推理速度适合场景
YOLOv8n3.2M极快边缘设备、实时监控、初期验证
YOLOv8s11.2M快本机GPU、精度要求适中
YOLOv8m25.9M中等离线分析、精度优先

我的习惯是先跑YOLOv8n,把数据清洗、参数流程跑通,再根据mAP决定要不要换更大的模型。直接上大模型如果数据脏,等于在加速学习错误模式,浪费时间。

训练命令这样写:

yolo task=detect mode=train \ model=yolov8n.pt \ data=pain.yaml \ epochs=100 \ imgsz=640 \ batch=16 \ patience=15 \ lr0=0.01 \ device=0

这里model=yolov8n.pt是加载COCO预训练权重做迁移学习,这一点在医疗数据集上特别重要。2200张图从零训练很容易学不充分,预训练模型已经掌握了通用特征,比如边缘、纹理、形状,我们只需要让它适应“疼痛表情”这个新任务,收敛更快也更稳。数据量越少,迁移学习收益越明显。

3.4 数据增强参数的特殊处理

Ultralytics默认开启Mosaic、HSV扰动、随机翻转等增强策略,但对医疗表情识别,默认参数不能直接拿来用。

最典型的是垂直翻转flipud。默认数据增强里flipud是0.5,意思是有50%概率把图片上下翻转。人脸上下翻转后,嘴巴跑到眼睛上面,眉毛跑到下巴位置,这种违反生理结构的样本会让模型学到荒谬的特征。我在训练时有意识地把flipud关掉或调成0:

flipud: 0.0

水平翻转fliplr可以保留,因为左右脸出现疼痛表情是对称的,水平翻转还能让左脸右脸的样本更均衡。但这里也有个坑:如果标注框带有左右语义,比如“左眼区域”,水平翻转后标签会变成右眼区域,语义就错了。如果类别只是“pain区域”这种中性描述,那水平翻转是安全的。

另外,旋转角度不要开太大。默认degrees是0.0,我调到了5度,应对轻微的头部倾斜可以,但超过10度的旋转会让表情失真。HSV色域增强可以保留默认,因为医院环境里光源色温差异大,色彩抖动有助于提升鲁棒性。

Mosaic增强默认是启用的,它把四张图拼成一张图来训练,对小目标检测提升很明显。但医疗场景里有个问题:拼图会产生很多半张脸、截断脸,训练后期模型容易对截断区域产生幻觉检测。我建议前50个epoch保持Mosaic,后面把mosaic调小或关闭,让模型适应真实完整图片的分布:

mosaic: 0.5

3.5 训练过程与损失函数

YOLOv8的损失函数由三部分构成:分类损失(BCE)、边界框回归损失(CIoU)和DFL损失。分类损失管“框里是什么”,回归损失管“框得准不准”,DFL管边界框的分布表达。训练时终端会输出这三类loss以及总的box loss、cls loss、dfl loss。正常情况下它们都应该整体下降,中间有波动是正常的,但如果某个loss从某个epoch开始变成NaN,就要马上停。

我这次跑了前30个epoch时遇到过一次NaN loss,排查下来是某个标签文件里的坐标写了负数,导致回归分支算梯度时爆掉。清洗数据时脚本虽然检查了格式,但没检查数值是否在图片范围内,后来补上了范围检查才解决。这类问题属于典型的“标签污染”,不是模型问题。

BN崩溃也是训练中常见的坑,表现为某个epoch后loss突然从0.3跳到几十甚至NaN,整个模型输出全部失效。原因多数是batch size太小且学习率过大,或者是标签里出现极端坐标导致的梯度异常。解决办法是降低学习率到0.005或0.001、增大batch size到32或64,严重时用amp=False关闭混合精度训练再试。如果关闭混合精度后能正常训练,则说明是半精度数值稳定性问题,那就在保证batch size的前提下保持amp=False就好。

3.6 训练监控与中断恢复

训练过程中我一般开两个监控窗口:一个看终端日志里的loss曲线,另一个用TensorBoard看更完整的曲线:

yolo task=detect mode=train \ model=yolov8n.pt \ data=pain.yaml \ epochs=100 \ imgsz=640 \ batch=16 \ patience=15 \ device=0

如果要续跑中断的训练,直接用:

yolo task=detect mode=train \ model=runs/detect/train/weights/last.pt \ data=pain.yaml \ epochs=100 \ imgsz=640 \ batch=16 \ device=0

注意epochs要写剩余需要的epoch数,Ultralytics会接着last.pt继续而不是重头开始。我因为忘记这个参数,曾经把同一个中断的模型从头又训了一遍,白等了4个小时。

4. 评估结果怎么看

4.1 验证与测试命令

训练完成后,用验证集和测试集分别评估。验证集可以帮我们调阈值、比较模型,测试集只做一次,否则测试集也会变成调参集,导致指标失真。

yolo task=detect mode=val \ model=runs/detect/train/weights/best.pt \ data=pain.yaml \ split=test \ conf=0.25 \ iou=0.5

输出结果里包含mAP50、mAP50-95、Precision、Recall,以及每个类别的单独指标。对疼痛检测这种医疗场景,我会重点关注Recall而不是只盯mAP。因为漏报一个疼痛表情的代价远高于误报一次,宁可让护士多看一次被系统标记的视频片段,也不能让疼痛病人被漏掉。

4.2 mAP和混淆矩阵的正确理解

mAP50指的是IoU阈值为0.5时计算的平均精度,mAP50-95是IoU从0.5到0.95每间隔0.05取一个阈值再求平均。后者更严格,也更接近实际定位质量。医疗目标如果位置精度要求高,比如需要后续做手术区域分析,那mAP50-95更重要;如果只是辅助护士观察,mAP50够用。

混淆矩阵的“总合不唯一”问题,很多人第一次看到YOLO输出的混淆矩阵都会疑惑:为什么每一行的百分比加起来不等于100%?原因在于YOLO的混淆矩阵里,除背景类别之外,一个真实目标如果和一个预测框匹配成功,它会被计作TP;如果没有匹配到任何预测框,会被算进“background”那一列。但可能出现一个预测框同时匹配多个真实目标的情况,此时一个GT被算作TP,另一个GT可能落到background。加上背景类别的存在,行归一化后总和自然不是100%。所以看混淆矩阵时,重点看类与类之间的错误流向,而不是纠结行和是否等于100%。

4.3 阈值怎么定

YOLO的conf阈值控制“多少置信度的框才保留”。默认0.25,对医疗场景来说如果追求高召回,可以降到0.1,再用后处理过滤掉帧间闪烁的误检。具体做法是先跑一遍验证集,画出Precision-Recall曲线和F1曲线,找到F1最高的阈值点,再结合场景需求手动调整。

我在这个项目里最后把conf定在0.2,IoU定在0.5。原因是实际测试中发现,疼痛表情如果比较轻微,模型置信度普遍在0.2到0.4之间,把阈值提到0.5会漏掉大量真值。代价是误检多一些,但后处理按时间窗口去噪后,实际效果还可接受。

5. 常见问题与排查实录

5.1 问题速查表

以下是我在训练疼痛检测数据集过程中遇到的典型问题以及解决方向:

现象可能原因排查与解决
loss一开始就是NaN标签坐标越界、图片损坏重跑标签清洗脚本,检查图片文件完整性
训练正常但mAP很低标签类别错误、标注框与目标不贴合可视化抽查100张图,检查框的位置和类别
训练loss下降但验证loss上升过拟合增加数据增强、减小模型、加weight_decay
验证集某个类别AP为0该类别样本太少或标注错误统计每类样本数,过采样/降采样,检查该类可视化样本
小目标漏检严重输入分辨率太低、目标过小将imgsz从640提到960,开启Mosaic增强
推理时误检很多conf阈值过低、训练样本背景复杂提高conf、按帧间稳定性过滤,或增加负样本
BN崩溃学习率过高、batch太小、含NaN梯度降低lr、增大batch、关闭AMP

5.2 最容易中招的三个数据坑

第一个坑是标注不一致。医学数据集往往多人标注,每个人对“疼痛表情”的主观判断不同,导致同一类别的框标注差异很大。我有个解决办法:训练前对训练集做一个简单聚类,把标注框的宽高比和面积分布画出来,如果发现明显的双峰分布,大概率是标注风格不统一,需要返工统一标注规范。

第二个坑是类别不平衡。2200张图里如果pain只有300张,no_pain有1900张,模型会偏向预测no_pain,因为就算全预测no_pain也能有80%以上准确率。我这次通过过采样pain样本让两类比例接近2:3,同时给pain类别在损失函数里设置更高的权重,有效提升了对疼痛样本的召回。Ultralytics里没有直接改损失权重暴露在命令行,但可以通过sample策略和自定义数据增强来实现。

第三个坑是过拟合。2200张医疗图像说多不多,说少不少,我把YOLOv8n在100个epoch下训练后,训练集的mAP50能到0.98,验证集只有0.78,明显过拟合。解决办法一是加大Mosaic、HSV等增强强度,二是把模型从YOLOv8n降到更小或增加早停阈值patience,三是用验证集指标而不是训练集指标选最优模型。Ultralytics的patience=15意思是15个epoch验证指标不提升就停,我实际用下来挺有效。

6. 模型部署与落地经验

6.1 导出ONNX和TensorRT

训练得到best.pt之后,要落地到边缘设备通常要转成推理引擎格式。导出ONNX的命令:

yolo task=detect mode=export \ model=runs/detect/train/weights/best.pt \ format=onnx \ opset=12 \ imgsz=640

导出的ONNX模型可以用ONNX Runtime在CPU上跑,也可以用TensorRT在NVIDIA设备上加速。如果目标平台是NVIDIA Jetson系列,可以导出TensorRT格式:

yolo task=detect mode=export \ model=runs/detect/train/weights/best.pt \ format=engine \ imgsz=640

导出engine模型前要注意:导出的imgsz必须和部署时的输入尺寸一致,否则推理时一张图被resize到两种尺寸,检测框坐标会错位。我就在树莓派上调过这个问题,界面里显示640,后端engine输入是512,结果框全部飘到左上角。

6.2 边缘设备上的监控误检处理

把疼痛检测模型放到树莓派或RK3588这类设备上做实时监控时,最常见的问题是误检率比本地离线高。原因是现场光照复杂、摄像头晃动、人物走动干扰多。我总结了一套降误检的组合拳:

第一,单帧检测结果不做最终判断,按时间窗口平滑。比如连续30帧里同一位置至少检测出15次pain,才触发一次预警,能过滤掉大量闪烁误检。

第二,限制检测区域。如果画面里有固定的病床区域,可以给模型传入ROI掩码,只对ROI内的检测框做后处理,ROI外的直接丢弃。这个在护士站摄像头场景下特别有效。

第三,把置信度阈值从离线测试的值往上抬一点。边缘部署时模型输入尺寸通常降低到320或416提升速度,精度会下降,但阈值抬高反而能把那些“半信半疑”的误检压掉。

6.3 医疗场景的使用边界

疼痛检测模型无论如何都只是辅助工具,不能替代专业医护人员的评估。实际部署时必须向使用方明确:模型输出的是一个候选区域和置信度,真实疼痛等级依然需要医护人员确认。这也是医疗AI的一条红线,任何声称能独立诊断疼痛的算法展示都要慎重。

在数据合规上,训练集中的图像必须做脱敏处理,不能保留可识别身份的信息。如果涉及患者数据,需要遵守相应伦理和隐私规定。我在整理这套数据时,所有原始图片只保留标注所需区域,脸部其他身份特征尽量模糊化,也不在模型日志里记录图片路径。

最后说点实操体会

这套2200张的疼痛检测数据集,我用YOLOv8n训练出来的测试集mAP50在0.86左右,mAP50-95在0.52左右,看起来不算特别亮眼,但考虑到医疗标注的模糊性和数据量,这个结果在辅助筛选场景里已经能用了。整个过程走下来,我最深的体会是:医疗数据集的瓶颈永远在数据侧而不在模型侧。花在清洗、可视化、防数据泄漏上的时间越多,后面训练越省心。另一个很有用的习惯是每轮实验前固定一个“小样本快速验证集”,用20张图跑30个epoch,先证明标签和训练流程没毛病,再上全量数据。这比直接跑100个epoch然后发现数据有问题高效得多。如果你也在做类似的任务,先别急着换模型改结构,把数据质量打扎实,收益会超出预期。

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

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

立即咨询