做这个2200张YOLO医疗健康数据集的起因很直接。当时我接了一个护理场景的监测原型需求想用摄像头实时判断患者是否处于疼痛状态结果手里却找不到一套能直接扔进YOLO训练的标注数据。公开的疼痛识别数据集大多是实验室人脸表情图片要么只配了分类标签要么场景单一到换个病房就失效。后来我干脆自己整理并标注了一套以疼痛检测为核心目标的数据集把这件事从概念落到了可跑的算法验证。这套数据集既覆盖面部疼痛表情也覆盖手捂头部、腹部、胸部等常见疼痛行为目前已经用YOLOv8完整跑过训练和评估验证集mAP50在87%上下。如果你正好在找医疗健康方向的YOLO数据或者准备做护理监测、术后疼痛评估之类的原型这篇文章会把我标框的规范、训练参数、评估细节和踩过的坑全部摊开讲清楚。我不擅长绕弯子下面直接从方案选型讲起再逐步拆解数据集的构建和训练全流程。你不需要有医学背景只要用过标注工具、知道基本命令行就能把整套流程复现出来。1. 项目背景与方案选型1.1 为什么用视觉方式做疼痛检测医院和养老场景里疼痛评估最常见的做法是让患者自己打分比如0到10的数字评分量表。但问题很明显患者睡着、术后虚弱、意识模糊或者不愿意表达时这个数字就失真了护士定期巡视也有时间间隙疼痛发作没被看见就错过了。视觉检测提供了一种非接触、可连续观察的补充手段。摄像头固定在病房一角模型实时分析画面中的面部表情和身体动作——比如皱眉、嘴角下压、手反复按在某个部位——从而给出“疑似疼痛”的提示。它本质上不是诊断工具而是一个主动发现异常的辅助设备帮护理人员把有限的注意力优先放到需要的人身上。也正因为这样这套数据集的定位我很克制它做的是疼痛表观特征的检测不是疼痛程度的医学判断。后续部署时也建议把它当成“自动巡检员”而不是“诊断医生”。1.2 为什么选YOLO而不是其他模型选择YOLO最核心的理由是实时性和部署成本。疼痛检测要落地到病房或护理站摄像头往往不止一路普通边缘盒子或单卡GPU就得同时处理多路画面。YOLO系列经过这么多代迭代已经做到在精度不错的前提下跑出很高的帧率。相比之下两阶段的Faster R-CNN精度也许不差但推理速度天然吃亏基于Transformer的DETR类模型效果虽好对小算力设备却不太友好。YOLOv8是我这次使用的具体版本。它把分类损失和回归损失封装得比较完善损失函数主要由box_loss、cls_loss和dfl_loss组成边框回归用CIoU加DFL分布损失分类用BCE损失整体训练起来很省心。对“疼痛检测”这种目标类别不多、但对误报敏感的场景这套损失设计足够用。而且YOLO社区工具链非常成熟预训练模型下载、数据格式转换、模型导出都成体系。后面你想换YOLOv9、YOLO-NAS甚至加入注意力机制代码结构也基本兼容迁移成本低。1.3 公开数据集不够用为什么要自建最开始我也尝试使用公开的疼痛识别数据集比如实验室环境下面部疼痛表情的数据库。这类数据有学术价值但直接喂给YOLO会遇到几个麻烦一是数据格式偏分类模型不是目标检测需要的文本框格式二是类别大多数只有“疼痛”和“不疼痛”两个标签检测不了具体疼痛行为三是拍摄场景单一光照、机位、遮挡情况都和真实病房差异很大。更实际的问题是版权和隐私。很多医学图像数据有严格的授权限制不能直接拿来做工程模型更别说后续可能涉及商业化。所以我最后决定自建一套数据集志愿者演示拍摄为主辅以合规的公开图库素材和少量仿真渲染图统一按照YOLO格式标注。这样数据版权可控类别也能按场景自由设计。2. 数据集构建规范与核心细节2.1 类别定义先想清楚要检测什么很多人做数据集上来就标框标到一半才发现类别互相重叠模型学得一头雾水。我在动手前先把“疼痛检测”拆成两类信号面部表情信号和身体动作信号。最终定义了五个检测类别类别名含义标注要点face_pain面部疼痛表情脸歪、皱成一团、嘴角明显下压、眼睛挤成缝hand_head手捂头或抱头手掌或手臂接触头部区域hand_abdomen手捂腹部手按住腹部身体弯曲hand_chest手捂胸口手按压胸部区域hand_limb手捂四肢或其他部位手按住手臂、腿等部位有人会问为什么不单独设一个“no_pain”类别。我踩过这个坑目标检测里的负样本最好用“没有标注的纯背景图片”来体现而不是硬造一个“正常”类别否则模型会把“人站在那里”也学成一种目标推理时满屏误检。这套数据里我专门放了600张纯负样本图片画面里有人但没有任何疼痛动作让模型学习“有目标才输出没目标就闭嘴”。2.2 图像来源与合规处理数据来源我分成三块志愿者演示拍摄这是主要来源占总量的60%左右。拍摄前向志愿者说明用途录制各类疼痛动作和自然状态。发布前统一做脱敏处理只保留动作特征不保留可识别的面部身份信息。公开许可图库使用明确允许二次编辑的图片素材补足一些志愿者不方便演示的罕见姿态。仿真渲染图用三维人体姿态合成的图片补足极端角度、低光照、遮挡等训练盲区。仿真图占15%左右不宜再多否则域差异会让真实场景效果变差。所有图片统一处理后放入数据集目录。分辨率要求不低于640像素边长训练时模型会自动缩放但标注框必须在原始分辨率下完成不要先压缩再标注否则小目标的框会偏得厉害。还有一种很常见的失误是图片中出现了人脸特写面部识别信息没做处理就放进了公开数据集这个一旦泄露非常麻烦。我建议发布前过一遍人脸检测把无关面部区域打上不可逆的马赛克。2.3 标注工具与YOLO格式生成我标注使用的是CVAT支持多人协作、连续帧自动追踪插值比单张手工框高效很多。如果是小规模快速验证本地用labelImg也完全够用。标注完成后导出YOLO格式。YOLO的每个txt文件对应一张同名图片每一行格式是class_id x_center y_center width height坐标都归一化到0到1之间。比如一行0 0.512 0.418 0.246 0.382 1 0.731 0.622 0.158 0.215含义是第一个框是类别0中心点在图片相对位置(0.512, 0.418)宽度0.246高度0.382第二个框是类别1。没有目标的图片txt文件保留为空文件不要直接删除这样训练脚本能识别这是一张负样本。最终数据集目录结构如下pain_dataset/ ├── images/ │ ├── train/ 1700张 │ ├── val/ 300张 │ └── test/ 200张 ├── labels/ │ ├── train/ │ ├── val/ │ └── test/ └── pain.yamlpain.yaml里写模型要读的数据集信息path: /path/to/pain_dataset train: images/train val: images/val test: images/test nc: 5 names: [face_pain, hand_head, hand_abdomen, hand_chest, hand_limb]2.4 数据清洗与划分策略标注完成不等于能直接训练。我先写脚本做了一轮清洗规则包括删除整体模糊的图片可以用Laplacian算子响应值做粗筛检查每个txt是否有越界坐标、宽高为0、class id超出范围删除“有标注文件但图片已损坏”的脏数据统计每个类别的框数量低于合理阈值的类别要补充数据。清洗之后是划分数据集。这里有一个新手很容易犯的错误直接把所有图片随机打乱按比例分。如果同一组连续帧画面里的同一个动作同时出现在训练集和验证集模型等于提前看到了答案验证指标会虚高。我按下“拍摄场景”作为分组单位同一个人同一段时间拍的照片必须分到同一个集合里再做类别分层抽样保证pain_chest等少数类别在训练集、验证集、测试集中的比例大致一致。3. 训练流程与关键参数3.1 训练环境准备本次训练我用的是RTX 3060 12G显卡跑YOLOv8s批大小16没有压力。如果你显存比较小也可以跑后面会讲具体调整方法。环境用conda创建conda create -n pain python3.10 conda activate pain pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118 pip install ultralytics注意torch版本要和你本地CUDA版本匹配不能装成CPU版本否则后面训练慢得你想摔键盘。如果GPU驱动较新直接装官方默认版本也行。装完以后跑一句yolo version确认安装成功。3.2 模型选型与预训练权重我先用最快的YOLOv8n跑通全流程再换YOLOv8s提精度。推终点用YOLOv8n训练用YOLOv8s这样能快速验证数据没问题。选模型时直接指定预训练权重路径Ultralytics会自动下载yolov8s.pt。预训练权重很重要它让模型从一开始就具备通用目标特征提取能力而不是从零开始学边缘纹理。医疗健康数据集相对小众但底层的纹理、轮廓、上下文信息是通用的迁移学习能省下很多训练时间和数据量。3.3 训练命令与超参设置完整训练命令如下yolo detect train \ datapain.yaml \ modelyolov8s.pt \ epochs100 \ imgsz640 \ batch16 \ lr00.01 \ lrf0.01 \ momentum0.937 \ weight_decay0.0005 \ warmup_epochs3 \ patience15 \ saveTrue \ projectruns/detect参数解释一下epochs100配patience15如果连续15轮验证集指标不提升自动停止防止浪费时间。lr00.01是YOLOv8常用的初始学习率配合余弦退火到lrf0.01。batch16对于12G显存和YOLOv8s比较合适。如果你的显卡只有8G可以降到8但不要降到2否则BN崩溃会让你怀疑人生后面专门讲。warmup_epochs3让模型前几轮用较低学习率热身避免初始权重被大步长打乱。数据增强方面YOLOv8默认已经带了mosaic和HSV扰动。我在命令行里额外追加了几个yolo detect train ... flipud0.5 mixup0.2 degrees5flipud0.5是垂直翻转对躺姿护理场景很有效mixup0.2做样本混合增强degrees5只允许小角度旋转不设太大角度否则“头朝下”也会变成一种看似合理的增强实际引入错误语义。3.4 训练日志和结果观察训练过程中控制台会实时打印epoch、GPU显存、各loss值和验证集指标。我会重点看三类lossbox_loss、cls_loss、dfl_loss它们都应该整体下行而不是剧烈震荡。第一轮mAP可能接近0这正常YOLO的冷启动阶段就是在快速适应新数据。等到10轮以后验证集mAP50会逐渐抬升最终稳定在85%到90%。训练结束后的best.pt在runs/detect/train/weights/目录下。不要直接拿last.pt用因为early stop后last不一定是最佳权重。测试阶段跑一句yolo detect predict modelruns/detect/train/weights/best.pt sourcetest.jpg conf0.35 iou0.45conf是置信度阈值iou是NMS的IoU阈值。对疼痛检测这类漏报代价更高的任务我建议把conf调低让模型宁可多报也不漏报后续再用逻辑过滤。4. 评估指标与问题排查实录4.1 核心指标mAP50还是mAP50-95YOLO训练结束时常用指标有两个mAP50和mAP50-95。mAP50只看预测框和真实框IoU大于0.5算不算命中简单直观mAP50-95要计算多个IoU阈值下的平均精度更严格对边框回归的精细度要求更高。疼痛检测场景我认为mAP50是主要参考指标因为护理人员只需要知道“大概哪个部位出现了疑似疼痛”不需要框特别精确但如果要做后续自动跟踪mAP50-95也不能太低。我这次验证集mAP50在87%mAP50-95大概61%对单帧检测原型来说够用。还有precision和recall要一起看。我偏好更高的recall宁可多触发几个疑似事件交给后台二次确认也不希望漏掉真正的疼痛信号。所以最终部署时我把置信度阈值设成0.35而不是默认的0.25或0.5。4.2 混淆矩阵与“总合不唯一”问题训练结束会生成一张混淆矩阵图。很多人第一次看YOLO混淆矩阵会疑惑每一行每一列的百分比加起来怎么不等于100%这不是bug而是YOLO混淆矩阵把未实际预测出来的“背景”部分也算进了分母而且输出的是归一化百分比而不是计数。当你把全部行或列相加时当然不等于一个基准总数。更靠谱的做法是直接读val集统计脚本的数据。我习惯用r.boxes.cls拿到预测类别然后用统计模块对比真实标签计算每个类别的TP、FP、FN。混淆矩阵适合看相对分布不适合做精确计数。别为了“把总合凑成100%”去改统计方式没有意义。4.3 漏检、误检与样本不均衡排查我在训练过程中遇到最典型的误检是把“手摸头但不疼”识别成hand_head。这个从单帧图像上确实难分人挠头、扶额头和疼痛捂头动作差异很细微。我排查后的处理方法是增加普通“扶头”的负样本图片同时优化标注规则规定只有手掌或手臂明显按在头部、且持续时间较长可判断为疼痛行为时才标hand_head。即便如此单帧模型仍会误判所以最后部署时我加了时序滑动窗口来过滤跳单帧误报。另一个问题是类别不平衡。hand_abdomen标注了500多框但hand_limb只有150框导致后者的AP明显偏低。我的处理办法不是简单复制少数类图片而是把少数类图片在mosaic增强阶段提高出现概率让它以“被裁剪组合”的形式频繁参与训练这样既不会过拟合又能平衡类别。4.4 提高精度的几条实战经验如果你跑出来的效果不理想按我的排查顺序走一遍先抽查标注质量。框偏了半厘米可能不影响大目标但对手捂腹部这类小目标影响非常大重标一批问题样本往往比调参有效。再看负样本比例。纯背景负样本低于10%时背景误检会明显上升我控制在20%到30%效果稳定。然后尝试TTA预测。yolo detect predict modelbest.pt sourcetest.jpg ttaTrue会多尺度增强推理mAP能提升2个百分点左右代价是推理时间变长。最后才考虑换大模型。YOLOv8m通常比YOLOv8s高2到3个点mAP50但推理时间也高出一截。原型阶段s足够。4.5 BN崩溃与训练不收敛排查YOLO训练里“BN崩溃”是个高频问题症状是loss突然跳到NaN或者训练全程mAP为0。我这次就踩了坑第一次用batch2在8G卡上跑结果BN的均值方差统计完全失真前几轮loss就开始发散。解决方案很直接把batch提到8以上显存不够就降低imgsz到512或者用梯度累积等效模拟大batch。把初始学习率从0.01降到0.001让模型慢慢恢复稳定。检查数据里有没有全黑、全白、纯色图片这类极端输入会让BN统计崩坏直接删掉。如果用了混合精度且出现NaN改成ampFalse重试。在我换到batch16、lr00.01之后loss曲线立刻恢复正常。记住BN不是越大越安全但太小一定不安全。5. 模型部署与场景落地的延伸思考5.1 导出ONNX或TensorRT加速训练完成后要把模型导出成部署格式。我用的是ONNX和TensorRT两种yolo export modelbest.pt formatonnx opset12如果不追求极限速度ONNX Runtime就已经比PyTorch快一些。若手里有NVIDIA显卡可以进一步导出TensorRT engineyolo export modelbest.pt formatengine device0固定输入640x640后TensorRT在RTX 3060上的推理耗时大约3到4毫秒每帧ONNX约6到7毫秒PyTorch原生要8到10毫秒。当然这只是我这套数据模型上的相对表现不用当作官方基准但差距确实体感明显。5.2 与摄像头联动做实时监测部署时我用的是RTSP流接入。核心代码并不复杂from ultralytics import YOLO model YOLO(best.pt) names model.names for result in model.predict( sourcertsp://admin:password192.168.1.64:554/stream, conf0.35, imgsz640, streamTrue, ): labels [names[int(c)] for c in result.boxes.cls] if labels: print(当前帧检测到:, labels)但要直接用于告警不能只看一帧。我会加一个滑动窗口统计近10帧里有6帧以上检测到疼痛目标才推送一条告警。这样可以把偶然误报大幅过滤掉又不至于反应太慢recent [] for result in model.predict(...): labels [names[int(c)] for c in result.boxes.cls] hit any(pain in label or label.startswith(hand_) for label in labels) recent.append(hit) if len(recent) 10: recent.pop(0) if sum(recent) 6: print(疑似疼痛事件请人工复核)5.3 隐私与伦理边界医疗健康场景部署摄像头最敏感的永远是隐私。我在实验环境里明确定下几条红线没有书面知情同意不拍摄任何真实患者用于训练原始视频不落盘只保留检测框坐标和事件日志模型输出只叫“可疑疼痛事件”提醒护士本人复核绝不自动写进病历公开数据集必须脱敏去除可识别身份的面部信息。5.4 后续扩展方向2200张只是第一步。如果你想把这套数据做得更深可以考虑增加时序信息比如用YOLOTransformer或LSTM模型把连续几帧的表情和动作特征串起来判断疼痛持续事件融合人体关键点模型精确判断手是否接触特定身体区域压缩量化模型部署到Jetson Orin这类边缘设备用few-shot方式扩充新的疼痛相关行为类别。最后说几句实在话做完这套2200张的YOLO疼痛检测数据集我最深的体会是数据集的真正价值不在数量而在标注一致性和负样本设计。我花在整理负样本、统一标注边界、清洗脏数据上的时间对模型效果的提升比多标注300张图片还要明显。训练初期我图省事用了batch2结果BN统计直接崩掉后来老老实实改回batch16一轮就恢复正常这些细节都是官方文档里不会写的。如果你打算复制这套流程我的建议是先拿YOLOv8n跑通全链路确认数据格式没问题再上大模型。疼痛检测这件事离成熟还有很长距离但至少现在有一个可以持续迭代的起点后面每一步都是踩在前一步的干净数据上。