ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

疼痛检测YOLO数据集构建与训练全流程实践解析

疼痛检测YOLO数据集构建与训练全流程实践解析 做医疗健康方向的视觉项目一个很微妙的经验是很多人上来就调 YOLO却很少有人先把数据集这件事真正想明白。最近整理完一套 2200 张的疼痛检测数据集格式直接对齐 YOLO 检测流程从图像收集、标签体系设计、标注格式转换到训练调参、结果判读、踩坑排查整个流程都走了一遍。这篇内容就把这个项目里最关键的思路和实操细节全部摊开讲给后面准备做医疗健康检测数据集、或者想在 YOLO 框架下跑医疗图像检测的朋友当一份可复用的参考。这套数据集的体量不算大但对疼痛检测这类细分医疗场景来说2200 张已经足够训练一个可用的 baseline前提是标注规范、划分合理、训练策略得当。下面从项目最开始的设计决策说起。1. 为什么疼痛检测要用 YOLO这 2200 张数据价值在哪1.1 疼痛自动检测要解决什么实际问题先聊一个容易被忽略的临床背景。疼痛评估在医院里非常依赖患者的主观报告护士拿量表让患者自己打分比如 0 到 10 分选一个数字。但有一大批患者根本没法准确表达术后麻醉还没完全清醒的人、ICU 里插管镇静的病人、不会说话的婴幼儿、中晚期阿尔茨海默症老人他们没法告诉医生我现在疼得厉害。这时候如果有一套系统能通过摄像头画面自动判断患者是否处于疼痛状态、疼痛信号出现在哪个区域就能给医护人员提供一个持续的客观参考而不只是靠护士每隔一段时间去看一眼。疼痛会反映在面部表情上这是有心理学和生理学依据的。基于面部动作编码系统FACS的研究早已证实某些面部动作单元在疼痛状态下会出现稳定的激活比如眉毛下压、眼睑收紧、鼻梁皱起、上唇提升、眼睛闭合等。这些表情信号在普通人眼里可能只是脸皱起来了但对计算机视觉模型来说恰恰是可以学习的稳定模式。疼痛检测数据集的价值就是把这种临床观察转化为机器可读的标注数据让模型学会从一帧图像里找出疼痛信号出现的区域和类别。这 2200 张图像的定位就是给这类任务提供一个直接可用的训练起点。它不像 ImageNet 那样追求海量规模而是追求在特定医疗场景下够用、能用、格式标准。对于目标检测任务来说2200 张图像配合预训练权重和合理的数据增强完全能训练出一个验证可行的雏形。更重要的是这个量级的数据量正好暴露了小数据集训练过程中会遇到的各类问题过拟合、类别不平衡、标注噪声每一个都是后面做大规模版本时注定要面对的提前踩一遍反而是好事。1.2 YOLO 与技术选型的取舍做疼痛检测技术路线不是只有 YOLO 一种。我见过有人用纯分类网络把整张图判断成疼痛或不疼痛但很快发现这个方案在实际场景里很难用。因为监控画面里可能同时出现患者、护士、家属和陪护设备分类网络只能告诉你这张图有疼痛特征却没法告诉你疼痛信号在画面中的什么位置也没法同时处理多个人的情况。一旦画面里有两个人分类逻辑就彻底失灵。也有人倾向用关键点检测比如把眉毛、眼睛、嘴角的坐标点全部标出来再去算动作单元的组合。这个方案精度高能输出精细的面部结构信息但代价也非常直接标注成本比框标注贵得多一个框只要点两下一组关键点要标十几二十个点2200 张图全标关键点工期和人力成本翻好几倍。而且很多下游需求其实只需要知道疼痛信号出现在哪些区域并不需要精确到像素级的关键点坐标用关键点属于过度设计。YOLO 在这两者之间是平衡点。它用矩形框把疼痛信号区域或者受检人员框出来同时输出类别和坐标既能定位多人也能区分不同的疼痛表情类别而且工程生态非常成熟。Ultralytics 框架把训练、验证、导出、部署的链路都打通了训练完可以直接导出 ONNX 或 TensorRT 格式方便接到边缘计算设备上。对于医疗场景这种部署环境相对固定的场景来说YOLO 的推理速度和部署便利性都很关键这也是我最终选择 YOLO 路线的主要原因。2. 数据集构建从原始图像到 YOLO 标准格式2.1 检测目标定义与标签体系设计动手标注之前第一件事不是打开标注工具而是把检测目标到底是什么定义清楚。同样是疼痛检测数据集有两种完全不同的任务路线。路线 A 是人物级检测模型检测画面中的每个人输出人形框并给每个人打上疼痛或正常的类别标签。这种方式实现简单适合做单人护理场景的粗筛但信息量有限只能知道这个人疼不疼不知道模型的判断依据是什么。路线 B 是区域级疼痛信号检测模型直接检测疼痛表情相关的脸部区域比如眼睑收紧区域、眉间皱纹区域、口周区域每一类对应一个 bbox。这种方式输出的结果包含空间位置信息可以给出可解释的判断证据比如眉间区域出现持续收缩眼睛闭合时间异常对于医疗场景来说可解释性非常重要医生和护士不会只凭一个疼的结论就采信他们需要知道模型看到了什么。我最后采用的是路线 B 的简化版本。参考 FACS 中疼痛相关的常见动作单元把标签体系设计成六类眉下压、眼睑收紧、鼻翼扩张、上唇提升、闭眼、嘴角横拉。为了控制标注难度和模型负担也可以再简化为三类比如眼睛区域、眉间区域、口周区域配合一个疼痛总类别。但有一点要记住2200 张图的体量撑不起太细的标签体系类别越多每类的正样本越稀疏模型就越容易顾此失彼。我的建议是控制在 2 到 6 类之间每类至少要有 200 个正样本框低于这个数就要考虑合并类别。2.2 数据来源、标注工具与 YOLO 格式转换数据来源这块需要先强调一件事真实患者的图像涉及隐私和伦理审批不是想用就能用的。我这次项目的图像主要来自三个途径一是公开的面部表情数据库中筛选的合作许可图像二是志愿者按标准疼痛表情指令模拟的标准化表情三是与临床场景合作采集的脱敏图像所有数据在使用前都经过严格的隐私处理。这样做既保证了数据多样性也能守住合规底线。标注工具我建议小团队用开源的 AnyLabeling它支持手动框选也能加载 YOLOv8 或 SAM 模型做预标注先让模型标一遍人工再修一遍效率比纯手动标注高不少。如果是多人协作标注用 CVAT 更合适有任务分配和冲突处理机制。个人单机操作用 LabelImg 也行但功能相对基础。标注完成后如果原始标注格式不是 YOLO 的 txt就需要做格式转换。这里以最常见的 VOC XML 格式转换为例核心代码逻辑如下import os import xml.etree.ElementTree as ET def voc2yolo(xml_path, img_w, img_h, class_map, out_txt_path): tree ET.parse(xml_path) root tree.getroot() lines [] for obj in root.findall(object): cls_name obj.find(name).text if cls_name not in class_map: continue cls_id class_map[cls_name] bbox obj.find(bndbox) xmin float(bbox.find(xmin).text) ymin float(bbox.find(ymin).text) xmax float(bbox.find(xmax).text) ymax float(bbox.find(ymax).text) x_center (xmin xmax) / 2.0 / img_w y_center (ymin ymax) / 2.0 / img_h w (xmax - xmin) / img_w h (ymax - ymin) / img_h w max(w, 1e-6) h max(h, 1e-6) lines.append(f{cls_id} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}) with open(out_txt_path, w, encodingutf-8) as f: f.write(\n.join(lines))这里最需要注意的是坐标归一化。YOLO 格式要求 x_center、y_center、width、height 全部除以图像宽高范围在 0 到 1 之间而且类别 id 从 0 开始。很多新手第一次转换就会在这里出错比如忘了除以图像宽高或者类别 id 写成了从 1 开始导致训练时类别错位却浑然不知。另外如果 xml 里存在空框、坐标翻转xmax xmin或者宽高为 0 的异常标注转换时一定要过滤掉否则训练过程会在数据加载阶段直接报错或者静默引入脏数据。转换完成后images 目录和 labels 目录要一一对应同名不同后缀这一步严格做对后面训练会省很多事。2.3 数据划分、质量检查与可视化验证数据划分是很多人会随手处理的一步但恰恰是这一块最容易埋雷。常规做法是随机按比例把图片分成训练集、验证集和测试集比例可以选 8:1:1 或者 7:2:1。但对于人物相关的数据集我强烈建议按人来分组而不是按图片随机分。什么意思呢就是同一批志愿者的所有图像要整体放入同一个集合不能这一半在训练集那一半在验证集。如果同一张脸以高度相似的姿态同时出现在训练集和验证集里验证集的结果会虚高看起来 mAP 很高换一批新人一测立刻现原形。这个坑我踩过重新划分之后指标掉了将近 10 个百分点但反而更真实了。质量检查是标注完成后的必经环节不能只看数量就开训。我通常会做三件事。第一可视化验证把标注框画到原图上生成一批预览图人眼过一遍重点看框是否贴合目标区域有没有框偏、框大、漏框的问题。第二统计所有 bbox 的面积分布把面积占整图超过 90% 的候选框和小于 32x32 像素的极小框筛出来单独检查前者往往是误标后者 YOLO 很难学会需要调整标注策略。第三检查类别分布表确认每一类的样本量看看有没有明显偏向某个类别的失衡情况。这套检查做完再进入训练环节心里就有底了。3. 基于 2200 张数据的 YOLO 训练实操3.1 环境、模型选择与预训练权重训练环境方面我用的是 Ultralytics 生态具体是 YOLOv8 这一代。它相较早期版本的优点是训练流程非常标准化配置、监控、导出都是一套 API 走完出错率低社区资料也多。如果电脑没有 NVIDIA GPUCPU 也能训练但速度慢很多2200 张图可能要跑数小时甚至更久有条件还是建议用 GPU。模型规模的选择上2200 张数据对应的模型不能太大。YOLOv8 系列从 n、s、m、l、x 依次增大我最终选用的是 YOLOv8n。这个模型参数量约 300 万训练速度快、显存占用小在小数据集上反而不容易过拟合。相比之下YOLOv8x 这样的大模型在没有海量数据支撑的情况下很容易陷入严重的过拟合训练集表现很好验证集却一塌糊涂。先拿小模型跑通全流程验证数据没有问题再考虑要不要升级模型规模这是比较稳妥的项目节奏。预训练权重这块一定要用 COCO 预训练模型开始而不是从零初始化。在医学图像这类数据量稀缺的领域迁移学习的作用非常明显。COCO 预训练模型已经学会了边缘、纹理、颜色等通用视觉特征这些特征对疼痛表情区域检测同样有效我们只需要在预训练基础上做领域微调就行。如果从零开始训练想要达到同样的效果往往需要至少十倍以上的数据量。安装环境非常简单直接执行 pip 安装即可pip install ultralytics需要确认 PyTorch 版本和 CUDA 版本匹配。如果只是 CPU 训练则不需要 CUDA。装完之后可以先把预训练权重文件拉下来Ultralytics 会在首次使用时自动下载也可以提前手动下载 yolov8n.pt 放到工程目录避免训练中途断网等待。3.2 data.yaml 配置与关键超参调优训练之前要先把数据集描述文件写好。这个文件告诉 YOLO 去哪里找训练集、验证集、有几个类别、类别叫什么名字是一个标准的 YAML 格式path: /path/to/pain_dataset train: images/train val: images/val nc: 6 names: 0: brow_lower 1: eye_squeeze 2: nose_wrinkle 3: lip_raise 4: eye_close 5: mouth_stretch写好 YAML 之后训练命令的形式非常简洁yolo detect train \ datapain.yaml \ modelyolov8n.pt \ epochs150 \ imgsz640 \ batch16 \ patience30 \ lr00.01如果更喜欢在 Python 脚本里控制参数可以这样写from ultralytics import YOLO model YOLO(yolov8n.pt) results model.train( datapain.yaml, epochs150, imgsz640, batch16, patience30, lr00.01, optimizerAdamW )关键超参的选择逻辑值得单独说几句。imgsz 是输入图像尺寸。默认 640 对大多数情况都够用但医疗图像往往带有较大的无效背景区域疼痛表情区域可能只占图像的很小一部分。如果标注框整体偏小可以尝试 imgsz960让小目标映射到更大的特征图上有更好的表现。代价是显存占用和训练时间显著增加需要根据 GPU 显存权衡。epochs 在小规模数据集上建议给到 150 到 200配合早停机制使用。patience 设置为 30意思是如果验证集指标连续 30 个 epoch 没有提升就自动停止训练避免无效等待。优化器用 AdamW 在大多数小数据集场景下收敛更平稳SGD 在数据充足时效果更好但小数据下容易震荡。数据增强参数的调整这个容易被忽略但对医疗图像极其关键。默认增强规则里包含上下翻转这在自然场景里问题不大但人脸图像一旦上下翻转就变成了完全不可能的视角给训练注入噪声。疼痛检测数据集在训练时要显式关闭 flipud把 degrees 限制在 5 度以内只保留轻微旋转。fliplr 水平翻转可以保留疼痛表情在左右对称性上基本没有差异。以下是我实际使用的增强参数配置可以直接参考mosaic: 0.8 fliplr: 0.5 flipud: 0.0 degrees: 5.0 scale: 0.4 translate: 0.1 hsv_h: 0.015 hsv_s: 0.3 hsv_v: 0.3hsv 色调扰动也要比默认值小因为医疗影像对颜色失真比较敏感过强的颜色扰动会让模型学到错误的色彩关联。3.3 训练过程监控与指标判读训练启动之后需要在终端里实时盯着几个关键指标的走向。train/box_loss 和 train/cls_loss 逐 epoch 下降是正常的val 指标会呈现先降后稳的趋势。如果 train loss 持续下降而 val loss 在某个节点后开始回升这就是过拟合的典型信号早停机制会在这种情况下帮你自动终止训练但最好还是自己也能看懂这些曲线而不是把一切都交给工具。训练结束后真正要看的核心指标是这几个mAP50、mAP50-95、Precision、Recall。mAP50 代表 IoU 阈值 0.5 下的平均精度对于疼痛检测这种任务框的定位不需要特别精细mAP50 是主要参考。mAP50-95 则更加严格它要考虑不同 IoU 阈值下的综合表现如果这个值偏低往往意味着框的位置不够精准或者类别置信度分布不合理。下面是训练结束后终端通常会输出的结果样式可以对照理解epoch gpu_mem box_loss cls_loss dfl_loss precision recall mAP50 mAP50-95 120 3.82G 0.821 0.452 1.022 0.923 0.886 0.934 0.612mAP50 在 0.9 以上看起来很漂亮但如果 mAP50-95 只有 0.6 左右说明模型对精确位置和尺度变化的泛化仍不够稳。更重要的检验方式是找一段完全没有参与训练的视频喂给模型做实时检测观察它在真实光线、遮挡、运动模糊下的表现。视频测试往往比任何指标都能更早暴露问题。训练完成后导出模型也很方便yolo export modelbest.pt formatonnx yolo export modelbest.pt formattensorrt导出的 ONNX 或 TensorRT 模型可以直接部署到边缘设备上。在医疗场景里视频流的实时性要求不像自动驾驶那么高每秒处理 2 到 5 帧就足够满足护理监控的需求用 TensorRT FP16 精度已经绰绰有余。4. 小数据集常见问题与排查实录4.1 过拟合信号识别与对策2200 张数据集训练过程中出现过拟合几乎是必然的只是程度不同。要能准确识别过拟合信号而不是等到训练结束后才发现验证集表现很差。第一个信号是训练集 mAP 一路涨到 0.99但验证集 mAP 在某个 epoch 后停滞甚至下降两者之间的 gap 越拉越大。第二个信号是模型的检测结果在训练集图片上完美但换到没有参与训练的同场景图像上漏检率陡然升高。我当时遇到的情况是训练集上 mAP50 达到 0.97验证集上只有 0.71。排查下来的核心原因有两个一是数据量太少二是模型过深。换用更小的 yolov8n 之后验证集表现立刻上升到了 0.82。这说明对于小数据集模型容量不是越大越好合适的容量才能平衡拟合能力和泛化能力。还有一个有效对策是增加正则化强度。把 weight_decay 提高到默认值的两倍比如从 0.0005 提到 0.001能有效抑制模型对训练集中噪声模式的过度记忆。如果再配合强一点的数据增强比如提高 mosaic 概率、增加轻微的遮挡模拟等于免费扩充了数据规模。组合拳打完即使数据量没有变化验证集指标也能有明显改善。4.2 混淆矩阵异常与标注隐患很多人在训练后都会打开混淆矩阵看类别间的误检情况但如果发现混淆矩阵的总和不为 1 或者行和列对不上别急着怀疑模型出了问题。YOLO 的混淆矩阵默认包含背景类而且每一行的归一化是独立进行的背景列会把被漏检的目标归入其中加上有些目标可能同时被多个类别竞争行和列加起来自然不等于 1这是正常现象不是 bug。更值得关注的是混淆矩阵里某两个类别长期互相混淆的情况。我在这套数据集里就遇到过一次eye_squeeze 和 eye_close 这两类的混淆程度特别高。回头看标注数据才发现一部分志愿者在做出疼痛表情时眼睑收紧和眼睛闭合的边界本来就很模糊不同标注员对同一张图的判断甚至都不能达成一致。这种情况说明类别定义本身需要调整把这类模糊样本统一归入更粗的类别或者重新制定标注规范比强行调模型的损失函数更管用。顺带说一句如果 val 集指标虚高也可以先检查是否同一个人物反复出现在不同集合里。这类问题时少但一旦中招整个指标体系都会失真。4.3 类别不平衡与泛化能力在疼痛检测数据集中类别不均衡是很常见的。疼痛表情发生时闭眼和眼睑收紧的出现频率通常远高于鼻翼扩张和上唇提升这就导致某些类别的正样本数量差距悬殊。我统计过这套数据集最多的类别有接近 700 个框最少的一个类别只有 180 个框差距接近四倍。解决类别不平衡的思路要分层。YOLOv8 自带类别损失权重平衡机制它会在计算分类损失时自动给样本少的类别更高的权重所以原则上不需要手动做特别重的重采样。但如果某个类别的样本量实在太少可以针对这类样本单独做离线增强比如旋转、缩放、局部截取后重新标注把每类的框数量拉得更均衡一些。不建议过于激进地复制粘贴同一张图的不同增强版本那样只会让模型记住噪声模式而不是学到真正的类内多样性。泛化能力才是最终要检验的东西。我建议训练完成后额外收集一小批行为习惯完全不同的受试者图像做测试比如戴眼镜的、有大胡子的、面部有遮挡的。如果模型对这部分样本的表现明显滑坡说明训练集本身的覆盖度还需要补充这类信息比任何单一指标都有说服力。4.4 医疗场景部署的合规与工程细节数据集和模型完成之后部署环节也有一些医疗场景特有的注意点。隐私合规是第一位的。疼痛检测系统涉及对患者面部的持续拍摄和分析图像数据必须在本地的边缘设备完成推理不能把原始视频上传到云端否则会触碰医疗数据管理的红线。我在实际部署时把所有模型推理放在病房网关设备上只外传检测结果和脱敏后的统计信息不传原始图像这个架构从源头规避了隐私风险。第二个注意点是对模型的定位。它输出的结果只能作为护理评估的辅助参考不能直接替代医护人员的专业判断更不能基于模型结果自动启动治疗流程。这个边界在设计系统时就要明确界面展示上要保留参考之类的提示属性避免使用者过度依赖模型结果。第三个是工程细节。医疗场景里的摄像头视角、光线条件、遮挡情况都跟训练数据有差异部署后要做一段时间的灰度测试持续收集误检和漏检样本不断迭代标注和训练。另外模型输入分辨率要结合实际摄像头分辨率选择不建议把 200 万像素的画面强行缩放到 320 再推理适当提高输入尺寸可以显著改善小疼痛表情区域的检出率。实际做下来我觉得这套 2200 张的疼痛检测数据集最宝贵的价值不只是模型本身而是把从零到一的完整链路走通了。数据收集、标注规范、格式转换、训练调参、部署验证每一步都有积累。后续如果想继续扩展可以往两个方向走一是引入视频时序信息用多帧画面的表情变化趋势代替单帧判断能够过滤掉瞬时表情带来的误检二是把检测框和面部关键点检测结合输出更精细的疼痛信号解释。这两个方向都建立在这套数据集已经打好的地基之上接下去每一步都有清晰的路可走。最后再分享一个我的个人体会数据集的构建在整个项目里占的时间精力远超过模型训练本身而恰恰是这部分工作的质量决定了模型的真实上限。我会在第一版数据上快速跑出一个模型不是为了追求指标而是为了用它反向审视数据的问题标注错了什么、类别设计哪里不合理、分布上缺了什么场景模型的误差会告诉你答案。数据是模型的天花板这句话在 2200 张的医疗数据集上体现得格外明显。
返回列表