Skip to main content

· 11 min read

本文整理项目中接入 MediaPipe Pose Landmarker Lite,以及在其基础上使用轻量时序 ONNX 模型识别复杂动作的方案。

1. MediaPipe Pose Landmarker Lite 能做什么​

Pose Landmarker Lite 适合当前 CPU 环境,不依赖 PyTorch,并且可以与现有 MediaPipe 人脸分析流程共用视频帧。

模型负责检测人体姿态,默认输出一个人的 33 个身体关键点,覆盖:

  • 鼻子、眼睛、耳朵和嘴角
  • 肩、肘、腕及部分手部点
  • 髋、膝、踝、脚跟和脚尖

官方说明:MediaPipe Pose Landmarker

1.1 原始输出​

输出内容
图像坐标关键点每个点包含 x、y、z、visibility、presence,其中 x/y 是相对于图像宽高的归一化坐标
世界坐标关键点每个点包含 x、y、z、visibility,以髋部中心为原点,单位近似为米
人体分割掩码可选,输出每个像素属于人体的概率
多人姿态可通过 num_poses 配置,默认为 1 人

世界坐标适合进行关节角度和动作判断,但它是单目摄像头的估计值,不能当作精密人体尺寸测量结果。

1.2 可以派生的信息​

利用关键点可以进一步计算:

  • 肩、肘、髋和膝等关节角度
  • 身体中心、朝向和倾斜程度
  • 手是否高于肩膀或头部
  • 身体及四肢的移动速度
  • 站立、坐下、下蹲
  • 左手、右手或双手举起
  • 挥手、行走和跌倒疑似

需要注意,Pose Landmarker 只输出骨骼关键点,不会直接给出 waving、walking 等动作名称。简单动作可以通过规则判断,复杂动作则需要分析连续多帧。

1.3 模型位置​

建议把模型放在:

model/pose_landmarker/pose_landmarker_lite.task

官方下载地址:pose_landmarker_lite.task

1.4 当前项目的运行建议​

  • 使用 VIDEO 模式,利用 MediaPipe 的跨帧跟踪能力。
  • CPU 环境下先使用 8~10 FPS。
  • 第一版设置 num_poses=1。
  • 暂时关闭人体分割掩码以减少 CPU 开销。
  • 复用现有视觉进程解码出的 RGB 帧,不要再次连接和解码 RTSP。
  • 通过 /ws/pose 向前端推送关键点、动作、置信度和推理耗时。

2. 为什么需要时序模型​

单帧只能描述一个姿势。例如,手臂抬起的一帧既可能是“保持举手”,也可能是“挥手过程中的一帧”。复杂动作需要观察一段时间内关键点如何变化。

完整流程为:

RTSP 视频
-> MediaPipe Pose 提取每帧的 33 个关键点
-> 保存最近 1~3 秒的关键点序列
-> 轻量时序 ONNX 模型
-> 动作类别和置信度
-> WebSocket 推送前端

Pose Landmarker 回答“身体各关节在哪里”,时序模型回答“这段时间里人在做什么”。

3. 时序模型选型​

方案特点建议
TCN / 1D CNN模型小、CPU 推理快、ONNX 兼容好第一版首选
GRU结构直观,适合小数据集可选
ST-GCN显式学习人体骨骼连接及时间变化后续升级
RGB 视频动作模型能利用画面外观,但 CPU 开销较大当前不推荐

第一版推荐训练一个小型 TCN。训练阶段可以使用 PyTorch,但部署时只保留导出的 ONNX 文件,运行环境不需要安装 PyTorch。

4. 模型输入与输出​

推荐输入结构:

[batch, time, joints, features]
[1, 30, 33, 4]

其中:

  • 30:最近 30 帧;姿态检测为 10 FPS 时对应 3 秒。
  • 33:MediaPipe 的 33 个关键点。
  • 4:x、y、z、visibility。

也可以展平为:

[1, 30, 132]

为了增强动态动作识别,还可以加入相邻帧速度:

x, y, z, visibility, dx, dy, dz

模型输出为每种动作的分数或概率:

[1, action_count]

第一阶段可以定义以下动作类别:

[
"standing",
"sitting",
"walking",
"waving",
"drinking",
"using_phone",
"squatting",
"falling"
]

5. 关键点预处理​

原始关键点不能直接交给模型,否则模型可能把人物在画面中的位置和距离误认为动作特征。

每一帧建议执行:

  1. 以左右髋中心作为坐标原点。
  2. 使用肩宽或躯干长度缩放坐标。
  3. 对低可见度关键点使用上一帧结果补充,或者置零并保留 visibility。
  4. 计算相邻帧坐标差,得到运动速度。
  5. 训练时加入水平翻转、轻微缩放、旋转和时间采样等数据增强。

如果某些动作需要区分左右方向,水平翻转时必须同步交换左右关键点和动作标签。

6. 滑动窗口实时推理​

建议配置:

Pose 检测频率:10 FPS
窗口长度:30 帧
窗口跨度:5 帧
动作推理频率:每 0.5 秒一次

伪代码:

pose_buffer.append(normalized_landmarks)

if len(pose_buffer) == 30 and frame_index % 5 == 0:
input_tensor = np.asarray(pose_buffer, dtype=np.float32)
input_tensor = input_tensor[None, ...]

logits = session.run(
[output_name],
{input_name: input_tensor},
)[0]

probabilities = softmax(logits[0])

ONNX Runtime 可以直接使用 NumPy 数据和 CPU 执行器:ONNX Runtime Python API

session = ort.InferenceSession(
"model/action_recognition/action_tcn.onnx",
providers=["CPUExecutionProvider"],
)

项目已经使用 OpenVINO,后续也可以尝试 OpenVINO 原生读取 ONNX,或者使用 ONNX Runtime 的 OpenVINO Execution Provider 对 Intel 硬件进行优化:OpenVINO Execution Provider

7. 动作结果稳定策略​

实时分类结果可能在相似动作之间跳动,因此需要增加稳定器:

  • 对最近 3~5 次预测概率做移动平均。
  • 连续 2~3 次超过阈值后才切换动作。
  • 最高置信度低于 0.6 时输出 unknown。
  • 动作至少保持 0.5~1 秒后再允许切换。
  • 对 falling 等重要事件设置独立阈值和冷却时间。

WebSocket 消息示例:

{
"type": "pose",
"detected": true,
"action": {
"label": "waving",
"confidence": 0.87,
"stable": true,
"duration_ms": 1800
},
"top_actions": [
{"label": "waving", "confidence": 0.87},
{"label": "standing", "confidence": 0.08}
],
"inference_ms": 2.7
}

8. 获取动作模型的两种方式​

8.1 训练项目专用的小型 TCN(推荐)​

按照业务需要准备视频:

data/
standing/
sitting/
walking/
waving/
drinking/
using_phone/
falling/

离线使用 MediaPipe 把视频转换为关键点序列,再训练 TCN,最后导出:

model/action_recognition/
action_tcn.onnx
labels.json
config.json

这种方式具有以下优势:

  • 摄像头角度和实际场景一致。
  • 可以自行定义动作标签。
  • 输入直接使用 MediaPipe 33 点,不需要关键点映射。
  • 模型通常只有几百 KB 到几 MB,CPU 推理速度快。

8.2 使用公开骨骼动作模型​

可以尝试 ST-GCN 等公开模型,但公开模型可能使用 COCO 17 点、OpenPose 18 点或 NTU RGB+D 25 点。因此接入前必须确认:

  • 关键点数量、顺序及连接关系。
  • MediaPipe 33 点到目标骨架的映射。
  • 坐标归一化方法。
  • 输入窗口长度和采样频率。
  • 模型支持的动作类别是否符合业务需求。

公开模型适合快速实验,但训练数据的摄像头视角、动作定义和使用场景可能与本项目不同,实际效果未必优于项目专用的小模型。

9. 项目代码组织建议​

model/
pose_landmarker/
pose_landmarker_lite.task
action_recognition/
action_tcn.onnx
labels.json
config.json

api/app/
pose.py
action_features.py
action_recognizer.py
action_stabilizer.py

视觉工作进程应当复用同一份解码帧:

一份 FFmpeg 解码帧
+-- Face Landmarker
+-- Emotion Recognition
+-- Pose Landmarker
+-- 关键点滑动窗口
+-- Action ONNX

不要为动作模型再创建一个 RTSP 连接,否则会增加视频解码开销,还可能造成各模型之间时间不同步。

10. 推荐实施顺序​

  1. 接入 Pose Landmarker Lite 和 /ws/pose。
  2. 在前端画出人体骨架。
  3. 实现举手、站立、坐下等规则动作。
  4. 增加关键点序列采集和保存工具。
  5. 收集项目实际环境中的动作样本。
  6. 训练小型 TCN 并导出 ONNX。
  7. 在后端接入 ONNX Runtime 和动作稳定器。
  8. 使用规则结果与模型结果融合,逐步替换不可靠的规则。

这条路线可以让项目先快速获得可见的动作识别能力,同时逐步积累训练数据,最终支持更复杂、更符合业务场景的动作。

· 14 min read

本文为 Live ASR Pose 项目规划一套不依赖 PyTorch、可以在本地 CPU 环境运行,并能接通现有 RTSP、FastAPI、WebSocket 和 React 前端的视觉分析方案。

目标​

在现有实时语音识别系统上增加:

  • 面部关键点检测
  • 面部细微动作检测
  • 基础情绪分类
  • 人体姿态估计
  • 常用动作识别
  • 统一的实时分析事件协议

当前首先考虑 CPU 实时性和完整链路跑通,未来再迁移到独立 GPU 推理服务器。

推荐模型组合​

功能推荐模型运行方式主要输出
面部细微动作MediaPipe Face LandmarkerMediaPipe CPU Runtime478 个面部关键点、52 个 Blendshape
基础情绪OpenVINO emotions-recognition-retail-0003OpenVINO CPU平静、开心、悲伤、惊讶、愤怒
人体姿态MediaPipe Pose Landmarker LiteMediaPipe CPU Runtime33 个二维及三维人体关键点
动作分类关键点规则 + 小型时序分类器Python / ONNX Runtime站立、坐下、挥手、行走等

MediaPipe 使用 TFLite 模型包,不是 ONNX,但同样不需要 PyTorch,并且专门针对端侧 CPU 实时推理进行了优化。

为什么选择 MediaPipe​

Face Landmarker​

Face Landmarker 一次推理可以输出:

  • 478 个面部关键点
  • 52 个面部 Blendshape
  • 面部变换矩阵

常用 Blendshape 包括:

  • eyeBlinkLeft
  • eyeBlinkRight
  • browInnerUp
  • browDownLeft
  • browDownRight
  • mouthSmileLeft
  • mouthSmileRight
  • mouthFrownLeft
  • mouthFrownRight
  • mouthPressLeft
  • mouthPressRight
  • jawOpen

利用这些数据可以检测:

  • 眨眼
  • 皱眉
  • 抬眉
  • 嘴角上扬
  • 嘴角下压
  • 抿嘴
  • 张嘴
  • 左右表情不对称
  • 头部转动

相比直接输出一个“开心”或“悲伤”标签,Blendshape 更容易解释,也更适合展示面部的细微变化。

官方文档:MediaPipe Face Landmarker for Python

Pose Landmarker Lite​

Pose Landmarker Lite 输出 33 个身体关键点,包括:

  • 肩膀
  • 手肘
  • 手腕
  • 手指
  • 髋部
  • 膝盖
  • 脚踝
  • 脚跟

每个关键点包含:

  • 归一化二维坐标
  • 三维世界坐标
  • 可见度
  • 存在置信度

这些信息足以实现常见姿态和动作的第一版识别。

官方文档:MediaPipe Pose Landmarker

关于“微表情”的定义​

严格意义上的微表情不是普通的单帧情绪分类。

真正的微表情通常具有以下特点:

  • 持续时间很短
  • 依赖表情开始、峰值和结束的连续变化
  • 对摄像头帧率、脸部清晰度和光线要求较高
  • 需要专门的微表情数据集训练
  • 普通 25 FPS 视频可能漏掉关键变化

因此,项目第一版建议在界面和接口中使用:

面部状态 / 面部动作

而不是直接声称识别出了人的真实情绪。

推荐输出两层信息:

面部动作:皱眉、眨眼、嘴角上扬
推测情绪:开心,置信度 0.72

其中“面部动作”来自 Blendshape,“推测情绪”来自情绪模型和连续帧融合。

基础情绪模型​

推荐使用 OpenVINO Open Model Zoo 的:

emotions-recognition-retail-0003

模型特点:

  • 输入为 1 × 3 × 64 × 64 的 BGR 人脸图像
  • 约 0.126 GFLOPs
  • 约 2.48M 参数
  • 提供 FP32、FP16 和 FP16-INT8 版本
  • 输出五类基础情绪

输出类别:

索引类别
0neutral
1happy
2sad
3surprise
4anger

官方在其 AffectNet 子集上报告的准确率为 70.2%。模型更适合接近正面的人脸,头部偏转最好控制在约 ±15° 内。

官方文档:emotions-recognition-retail-0003

这个模型适合作为辅助判断,不应单独当作严格的微表情识别模型。

面部结果融合​

推荐将情绪模型和 Blendshape 结合:

情绪模型概率
+
Blendshape 的连续变化
+
多帧时间平滑
=
最终面部状态

例如:

happy 概率较高
+ mouthSmileLeft/Right 持续上升
+ browDown 较低
= 开心,置信度 0.81

动作识别方案​

第一阶段:关键点规则​

从 33 个姿态关键点计算:

  • 肩、肘、腕夹角
  • 髋、膝、踝夹角
  • 手腕是否高于肩膀
  • 肩膀和髋部的移动速度
  • 身体包围框宽高比
  • 连续帧关键点位移
  • 身体主轴与水平线夹角

可以先实现以下动作:

  • 站立
  • 坐下
  • 下蹲
  • 左手举起
  • 右手举起
  • 双手举起
  • 挥手
  • 行走
  • 身体倾斜
  • 疑似跌倒

示例规则:

右手举起:
right_wrist.y < right_shoulder.y

双手举起:
left_wrist.y < left_shoulder.y
and right_wrist.y < right_shoulder.y

坐下:
膝关节角度较小
and 髋部高度在一段时间内保持稳定

挥手:
手腕高于肩膀
and 手腕在连续帧中多次左右移动

疑似跌倒:
身体主轴快速从垂直转向水平
and 髋部高度快速下降

规则方案的优点:

  • 不需要训练模型
  • 不依赖 PyTorch
  • CPU 消耗低
  • 结果容易解释
  • 可以快速接通完整前后端链路
  • 动作类别可以完全按业务定义

第二阶段:小型 ONNX 时序分类器​

规则方案跑通后,可以收集最近 1~2 秒的姿态数据:

30 帧 × 33 个关键点 × (x, y, z, visibility)

然后训练一个小型时序模型:

  • MLP
  • 1D CNN
  • TCN
  • GRU

训练可以在其他机器或 GPU 服务器上完成,最终导出为 ONNX。本地环境只安装 ONNX Runtime,不需要 PyTorch。

它可以进一步识别:

  • 挥手
  • 行走
  • 起立
  • 坐下
  • 转身
  • 跌倒
  • 鼓掌

关键点时序模型只处理少量数值,不处理完整图像,因此通常比视频动作模型轻很多。

为什么暂不使用通用视频动作模型​

OpenVINO 提供 action-recognition-0001,它可以识别 Kinetics-400 数据集中的 400 类动作。

模型由编码器和解码器组成:

  • 编码器单帧约 7.34 GFLOPs
  • 编码器约 21.28M 参数
  • 解码器接收 16 帧的特征序列
  • 每次处理约 1 秒的视频片段

官方文档:action-recognition-0001

第一版不推荐使用它,原因是:

  • CPU 开销明显更大
  • 与本地 ASR 同时运行时容易争抢 CPU
  • Kinetics-400 类别未必符合项目业务
  • 许多结果依赖场景背景
  • 输出动作类别难以控制和解释

推荐后端架构​

视觉推理不应直接运行在 FastAPI 事件循环里,建议使用独立进程:

flowchart LR
A[MediaMTX RTSP] --> B[ASR Worker]
B --> C[sherpa-onnx]
A --> D[Vision Worker]
D --> E[FFmpeg 视频解码]
E --> F[Face Landmarker]
E --> G[Pose Landmarker]
F --> H[Emotion Model]
G --> I[动作规则 / ONNX 分类器]
C --> J[Event Broker]
H --> J
I --> J
J --> K[WebSocket]
K --> L[React 前端]

建议让 MediaMTX 分别为 ASR 和 Vision Worker 提供 RTSP 读取连接。两个 Worker 相互独立,某一个推理任务变慢时不会阻塞另一个任务和 FastAPI。

Vision Worker 处理流程​

RTSP
│
▼
FFmpeg 解码并缩放
│
▼
有界最新帧队列
├── Face Landmarker
│ ├── Blendshape 分析
│ └── Emotion Model
│
└── Pose Landmarker
└── 动作规则 / 时序模型
│
▼
多帧平滑
│
▼
WebSocket Event

视频解码​

建议由 FFmpeg 从 RTSP 解码视频,缩放后通过管道传入 Vision Worker:

fps=10,scale=640:-2

视觉分析不需要处理原始 25 或 30 FPS 的每一帧。降低分析帧率可以显著节省 CPU。

最新帧优先​

队列长度建议设置为 1~2:

生产速度 > 推理速度时:
丢弃旧帧,始终处理最新帧

实时分析系统不应该积压所有帧,否则结果会越来越滞后。

推荐推理频率​

任务建议频率
Pose Landmarker8~12 FPS
Face Landmarker5~10 FPS
Emotion Model3~5 FPS
前端结果推送5~10 次/秒

情绪变化速度通常慢于姿态变化,因此不需要每一帧都运行情绪模型。

多帧平滑​

单帧预测容易抖动。推荐使用:

  • 最近 5~10 帧滑动平均
  • 指数移动平均
  • 连续 N 帧超过阈值后再切换状态
  • 动作进入和退出使用不同阈值

例如:

连续 3 帧检测到右手高于肩膀 → 进入“右手举起”
连续 5 帧不满足条件 → 退出“右手举起”

这种滞回策略可以避免结果在临界位置快速跳变。

WebSocket 协议​

建议新增:

/ws/analysis

统一视觉事件格式:

{
"type": "vision",
"timestamp": "2026-10-02T08:00:00.000Z",
"face": {
"detected": true,
"emotion": {
"label": "happy",
"confidence": 0.81
},
"expressions": [
{
"label": "smile",
"confidence": 0.88
},
{
"label": "brow_raise",
"confidence": 0.63
}
]
},
"pose": {
"detected": true,
"action": {
"label": "waving",
"confidence": 0.86
}
},
"performance": {
"vision_fps": 9.7,
"latency_ms": 82
}
}

前端展示:

动作识别
当前动作:挥手
置信度:86%

面部状态
推测情绪:开心
面部动作:微笑、抬眉
置信度:81%

建议依赖​

mediapipe
opencv-python-headless
openvino
numpy
fastapi

第二阶段增加:

onnxruntime

项目不需要安装 PyTorch。

模型目录建议​

model/
├── sherpa-onnx-streaming-zipformer-bilingual-zh-en-2023-02-20/
├── face_landmarker/
│ └── face_landmarker.task
├── pose_landmarker/
│ └── pose_landmarker_lite.task
└── emotion/
└── emotions-recognition-retail-0003/
├── model.xml
└── model.bin

未来的小型动作分类器可以放在:

model/action/action_classifier.onnx

实施顺序​

阶段一:姿态与规则动作​

  1. 接入 MediaPipe Pose Landmarker Lite。
  2. 从 RTSP 解码 8~12 FPS 视频帧。
  3. 实现举手、站立、坐下和挥手规则。
  4. 新增 /ws/analysis。
  5. 接通前端动作展示区域。

阶段二:面部动作​

  1. 接入 MediaPipe Face Landmarker。
  2. 输出 Blendshape 原始结果。
  3. 实现眨眼、微笑、皱眉、抬眉和张嘴事件。
  4. 接通前端面部状态区域。

阶段三:基础情绪​

  1. 接入 OpenVINO 情绪模型。
  2. 对检测到的人脸进行裁剪和对齐。
  3. 融合情绪概率与 Blendshape。
  4. 增加多帧平滑和置信度阈值。

阶段四:ONNX 时序动作模型​

  1. 保存姿态关键点序列和动作标签。
  2. 使用收集的数据训练小型时序模型。
  3. 导出 ONNX。
  4. 使用 ONNX Runtime 替换或补充规则分类器。

风险与限制​

面部分析​

  • 光线不足会降低人脸关键点稳定性。
  • 侧脸和遮挡会降低情绪模型准确率。
  • 眼镜、口罩会影响部分 Blendshape。
  • 模型输出不能证明用户真实的心理状态。

姿态分析​

  • 人体没有完整进入画面时,部分动作规则会失效。
  • Pose Landmarker 更适合单人场景。
  • 摄像头角度变化会影响基于二维坐标的规则。
  • 跌倒检测需要较长的时间窗口,不能只看单帧。

性能​

  • ASR、H.264 转码和视觉推理会竞争 CPU。
  • 应使用独立 Worker 进程隔离推理任务。
  • 必须限制视觉分析帧率。
  • 必须使用最新帧优先策略,避免推理积压。

最终建议​

第一版采用:

MediaPipe Pose Landmarker Lite
+ MediaPipe Face Landmarker
+ OpenVINO Emotion Model
+ 规则动作分类器
+ 独立 Vision Worker

这套方案的优势是:

  • 不需要 PyTorch
  • 可以在本地 CPU 运行
  • 模型体积较小
  • 能快速接通完整链路
  • 结果容易解释和调试
  • 可以逐步替换为 ONNX 时序分类器
  • 未来迁移 GPU 服务器时无需修改前端协议