MediaPipe 系列 29:Pose Detection——BodyPose 架构完整指南

前言:为什么需要人体姿态检测?

29.1 Pose Detection 的重要性

人体姿态检测在 IMS/OMS 中的应用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
┌─────────────────────────────────────────────────────────────────────────┐
│ Pose Detection 在 IMS/OMS 中的应用 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ IMS/OMS 场景需求: │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ • 驾驶员姿态监控(疲劳、分心、身体倾斜) │ │
│ │ • 乘员姿态检测(OOP 异常姿态) │ │
│ │ • 儿童位置检测(CPD 儿童检测辅助) │ │
│ │ • 乘员分类(成人/儿童/宠物) │ │
│ │ • 安全带佩戴检测(配合安全带位置分析) │ │
│ │ • 气囊部署决策(基于乘员姿态调整气囊策略) │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ MediaPipe Pose Detection 特点: │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ • 333D 关键点 │ │
│ │ • 实时性能(~5ms GPU) │ │
│ │ • 支持人体分割(背景去除) │ │
│ │ • 轻量级模型(~9MB) │ │
│ │ • 全身/上半身模式可选 │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────┘

29.2 功能特点

特性 说明
关键点数量 33 个 3D 点
坐标系 归一化坐标 + 世界坐标
人体分割 可选输出分割 mask
速度 ~5ms (GPU), ~15ms (CPU)
模型大小 ~9MB (Full Body)

29.3 BlazePose 架构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
┌─────────────────────────────────────────────────────────────────────────┐
│ BlazePose 架构 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ 第一阶段:人体检测(Person Detection) │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ 输入:256×256 RGB 图像 │ │
│ │ 模型:BlazePose Detector │ │
│ │ 输出:人体边界框 + 关键区域 │ │
│ │ 速度:~2ms (GPU) │ │
│ │ │ │
│ │ 特点: │ │
│ │ • 基于轻量级 SSD 架构 │ │
│ │ • 多尺度特征金字塔 │ │
│ │ • 专为人体姿态优化 │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ 第二阶段:关键点检测(Pose Landmark) │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ 输入:256×256 裁剪的人体区域 │ │
│ │ 模型:Pose Landmark Model │ │
│ │ 输出:33 个 3D 关键点 + 分割 mask │ │
│ │ 速度:~3ms (GPU) │ │
│ │ │ │
│ │ 输出格式: │ │
│ │ • x, y: 归一化坐标 (0-1) │ │
│ │ • z: 相对深度 │ │
│ │ • visibility: 可见度 (0-1) │ │
│ │ • presence: 存在概率 (0-1) │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ 第三阶段:追踪(Tracking) │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ 输入:上一帧关键点 │ │
│ │ 方法:基于关键点预测下一帧 ROI │ │
│ │ 追踪失败时:重新检测 │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────┘

三十、关键点布局

30.1 33 个关键点详解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
┌─────────────────────────────────────────────────────────────────────────┐
│ Pose Landmarks (33 points) │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ 头部区域 (0-10) │
│ ┌─────────────────────────────────────────────┐ │
│ │ │ │
│ │ ●0 (nose) │ │
│ │ ╱ ╲ │ │
│ │ ●1───● ●───●2 (eyes) │ │
│ │ │ │ │ │
│ │ ●34 (ears) │ │
│ │ │ │
│ │ ●5-●6-●7-●8-●9-●10 (mouth) │ │
│ │ │ │
│ └─────────────────────────────────────────────┘ │
│ │
│ 上肢区域 (11-22) │
│ ┌─────────────────────────────────────────────┐ │
│ │ │ │
│ │ ●11 ●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●12 │ (shoulders) │
│ │ │ │ │ │
│ │ │ │ │ │
│ │ ●1314 │ (elbows) │
│ │ │ │ │ │
│ │ │ │ │ │
│ │ ●1516 │ (wrists) │
│ │ ╱│╲ ╱│╲│ │
│ │ ●171819202122│ (hands) │
│ │ pinky index thumb pinky index thumb │
│ │ │ │
│ └─────────────────────────────────────────────┘ │
│ │
│ 下肢区域 (23-32) │
│ ┌─────────────────────────────────────────────┐ │
│ │ │ │
│ │ ●2324 │ (hips) │
│ │ │ │ │ │
│ │ │ │ │ │
│ │ ●2526 │ (knees) │
│ │ │ │ │ │
│ │ │ │ │ │
│ │ ●2728 │ (ankles) │
│ │ │ │ │ │
│ │ ●2930 │ (heels) │
│ │ │ │ │ │
│ │ ●3132 │ (feet) │
│ │ │ │
│ └─────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────┘

30.2 关键点索引定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
// ========== 人体关键点索引 ==========
// mediapipe/modules/pose_landmark/pose_landmark.h

namespace pose_landmark {
// ========== 面部区域 (0-10) ==========
constexpr int NOSE = 0;
constexpr int LEFT_EYE_INNER = 1;
constexpr int LEFT_EYE = 2;
constexpr int LEFT_EYE_OUTER = 3;
constexpr int RIGHT_EYE_INNER = 4;
constexpr int RIGHT_EYE = 5;
constexpr int RIGHT_EYE_OUTER = 6;
constexpr int LEFT_EAR = 7;
constexpr int RIGHT_EAR = 8;
constexpr int MOUTH_LEFT = 9;
constexpr int MOUTH_RIGHT = 10;

// ========== 上肢区域 (11-22) ==========
constexpr int LEFT_SHOULDER = 11;
constexpr int RIGHT_SHOULDER = 12;
constexpr int LEFT_ELBOW = 13;
constexpr int RIGHT_ELBOW = 14;
constexpr int LEFT_WRIST = 15;
constexpr int RIGHT_WRIST = 16;
constexpr int LEFT_PINKY = 17;
constexpr int RIGHT_PINKY = 18;
constexpr int LEFT_INDEX = 19;
constexpr int RIGHT_INDEX = 20;
constexpr int LEFT_THUMB = 21;
constexpr int RIGHT_THUMB = 22;

// ========== 下肢区域 (23-32) ==========
constexpr int LEFT_HIP = 23;
constexpr int RIGHT_HIP = 24;
constexpr int LEFT_KNEE = 25;
constexpr int RIGHT_KNEE = 26;
constexpr int LEFT_ANKLE = 27;
constexpr int RIGHT_ANKLE = 28;
constexpr int LEFT_HEEL = 29;
constexpr int RIGHT_HEEL = 30;
constexpr int LEFT_FOOT_INDEX = 31;
constexpr int RIGHT_FOOT_INDEX = 32;

// ========== 关键点分组 ==========
constexpr int UPPER_BODY[] = {
LEFT_SHOULDER, RIGHT_SHOULDER,
LEFT_ELBOW, RIGHT_ELBOW,
LEFT_WRIST, RIGHT_WRIST
};

constexpr int LOWER_BODY[] = {
LEFT_HIP, RIGHT_HIP,
LEFT_KNEE, RIGHT_KNEE,
LEFT_ANKLE, RIGHT_ANKLE
};

constexpr int FACE[] = {
NOSE, LEFT_EYE, RIGHT_EYE,
LEFT_EAR, RIGHT_EAR
};
}

三十一、Graph 配置

31.1 完整 Graph 配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
# ========== Pose Detection Graph 配置 ==========

# mediapipe/graphs/pose_tracking/pose_tracking_gpu.pbtxt

input_stream: "INPUT:Input"

output_stream: "LANDMARKS:pose_landmarks"
output_stream: "SEGMENTATION:segmentation_mask"
output_stream: "WORLD_LANDMARKS:pose_world_landmarks"

# ========== 1. 图像格式转换 ==========
node {
calculator: "ImageTransformationCalculator"
input_stream: "INPUT:Input"
output_stream: "IMAGE:converted_image"
options {
[mediapipe.ImageTransformationCalculatorOptions.ext] {
output_format: SRGB
}
}
}

# ========== 2. 人体检测 ==========
node {
calculator: "PoseDetectionCalculator"
input_stream: "IMAGE:converted_image"
output_stream: "DETECTIONS:detections"
options {
[mediapipe.PoseDetectionCalculatorOptions.ext] {
model_path: "/models/pose_detection.tflite"
score_threshold: 0.5
max_results: 1
}
}
}

# ========== 3. ROI 裁剪 ==========
node {
calculator: "DetectionToRectCalculator"
input_stream: "DETECTIONS:detections"
output_stream: "RECT:roi_rect"
}

node {
calculator: "RectTransformationCalculator"
input_stream: "RECT:roi_rect"
output_stream: "RECT:transformed_rect"
options {
[mediapipe.RectTransformationCalculatorOptions.ext] {
scale_x: 1.25
scale_y: 1.25
}
}
}

# ========== 4. 关键点检测 ==========
node {
calculator: "TfLiteInferenceCalculator"
input_stream: "IMAGE:converted_image"
input_stream: "RECT:transformed_rect"
output_stream: "TENSORS:landmark_tensors"
output_stream: "TENSORS:segmentation_tensors"
options {
[mediapipe.TfLiteInferenceCalculatorOptions.ext] {
model_path: "/models/pose_landmark.tflite"
delegate {
gpu {
use_advanced_gpu_api: true
}
}
}
}
}

# ========== 5. 关键点后处理 ==========
node {
calculator: "PoseLandmarksFromTensorCalculator"
input_stream: "TENSORS:landmark_tensors"
input_stream: "RECT:transformed_rect"
output_stream: "LANDMARKS:pose_landmarks"
output_stream: "WORLD_LANDMARKS:pose_world_landmarks"
options {
[mediapipe.PoseLandmarksFromTensorCalculatorOptions.ext] {
num_landmarks: 33
}
}
}

# ========== 6. 人体分割 ==========
node {
calculator: "SegmentationPostprocessorCalculator"
input_stream: "TENSORS:segmentation_tensors"
output_stream: "MASK:segmentation_mask"
options {
[mediapipe.SegmentationPostprocessorCalculatorOptions.ext] {
threshold: 0.5
}
}
}

三十二、姿态分析

32.1 身体朝向分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
// body_orientation_calculator.h
#ifndef MEDIAPIPE_CALCULATORS_IMS_BODY_ORIENTATION_CALCULATOR_H_
#define MEDIAPIPE_CALCULATORS_IMS_BODY_ORIENTATION_CALCULATOR_H_

#include "mediapipe/framework/calculator_framework.h"
#include "mediapipe/framework/formats/landmark.pb.h"

namespace mediapipe {

// ========== 身体朝向枚举 ==========
enum class BodyOrientation {
FORWARD = 0, // 正对
TURNING_LEFT = 1, // 左转
TURNING_RIGHT = 2, // 右转
LEANING_FORWARD = 3, // 前倾
LEANING_BACK = 4, // 后仰
UNKNOWN = 5,
};

// ========== 身体朝向消息 ==========
message BodyOrientationResult {
BodyOrientation orientation = 1;
float left_turn_angle = 2;
float right_turn_angle = 3;
float forward_lean_angle = 4;
float confidence = 5;
uint64 timestamp_ms = 6;
}

// ========== Body Orientation Calculator ==========
class BodyOrientationCalculator : public CalculatorBase {
public:
static absl::Status GetContract(CalculatorContract* cc) {
cc->Inputs().Tag("LANDMARKS").Set<std::vector<NormalizedLandmarkList>>();
cc->Outputs().Tag("ORIENTATION").Set<BodyOrientationResult>();

return absl::OkStatus();
}

absl::Status Process(CalculatorContext* cc) override {
if (cc->Inputs().Tag("LANDMARKS").IsEmpty()) {
return absl::OkStatus();
}

const auto& pose_landmarks =
cc->Inputs().Tag("LANDMARKS").Get<std::vector<NormalizedLandmarkList>>();

if (pose_landmarks.empty()) {
return absl::OkStatus();
}

const auto& landmarks = pose_landmarks[0];

// ========== 1. 计算肩膀中点 ==========
float left_shoulder_x = landmarks.landmark(pose_landmark::LEFT_SHOULDER).x();
float left_shoulder_y = landmarks.landmark(pose_landmark::LEFT_SHOULDER).y();
float right_shoulder_x = landmarks.landmark(pose_landmark::RIGHT_SHOULDER).x();
float right_shoulder_y = landmarks.landmark(pose_landmark::RIGHT_SHOULDER).y();

float shoulder_center_x = (left_shoulder_x + right_shoulder_x) / 2.0f;
float shoulder_center_y = (left_shoulder_y + right_shoulder_y) / 2.0f;

// ========== 2. 计算臀部中点 ==========
float left_hip_x = landmarks.landmark(pose_landmark::LEFT_HIP).x();
float left_hip_y = landmarks.landmark(pose_landmark::LEFT_HIP).y();
float right_hip_x = landmarks.landmark(pose_landmark::RIGHT_HIP).x();
float right_hip_y = landmarks.landmark(pose_landmark::RIGHT_HIP).y();

float hip_center_x = (left_hip_x + right_hip_x) / 2.0f;
float hip_center_y = (left_hip_y + right_hip_y) / 2.0f;

// ========== 3. 计算身体朝向 ==========
// 肩膀宽度变化表示身体转向
float shoulder_width = std::abs(right_shoulder_x - left_shoulder_x);

// 臀部宽度变化
float hip_width = std::abs(right_hip_x - left_hip_x);

// 肩膀中心相对于臀部的偏移
float body_offset = shoulder_center_x - hip_center_x;

// ========== 4. 计算前倾角度 ==========
// 肩膀相对于臀部的 Y 坐标差
float forward_lean = shoulder_center_y - hip_center_y;

// ========== 5. 判断朝向 ==========
BodyOrientation orientation = BodyOrientation::FORWARD;
float left_turn_angle = 0.0f;
float right_turn_angle = 0.0f;
float forward_lean_angle = 0.0f;

// 判断左转/右转
if (body_offset < -0.05f) {
orientation = BodyOrientation::TURNING_LEFT;
left_turn_angle = std::abs(body_offset) * 180.0f;
} else if (body_offset > 0.05f) {
orientation = BodyOrientation::TURNING_RIGHT;
right_turn_angle = body_offset * 180.0f;
}

// 判断前倾/后仰
if (forward_lean > 0.1f) {
orientation = BodyOrientation::LEANING_FORWARD;
forward_lean_angle = forward_lean * 90.0f;
} else if (forward_lean < -0.05f) {
orientation = BodyOrientation::LEANING_BACK;
forward_lean_angle = std::abs(forward_lean) * 90.0f;
}

// ========== 6. 输出 ==========
BodyOrientationResult result;
result.set_orientation(orientation);
result.set_left_turn_angle(left_turn_angle);
result.set_right_turn_angle(right_turn_angle);
result.set_forward_lean_angle(forward_lean_angle);
result.set_confidence(1.0f);
result.set_timestamp_ms(cc->InputTimestamp().Value() / 1000);

cc->Outputs().Tag("ORIENTATION").AddPacket(
MakePacket<BodyOrientationResult>(result).At(cc->InputTimestamp()));

VLOG(1) << "Body orientation: " << static_cast<int>(orientation);

return absl::OkStatus();
}
};

REGISTER_CALCULATOR(BodyOrientationCalculator);

} // namespace mediapipe

#endif

32.2 姿态评分

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
// posture_evaluation_calculator.h

absl::Status PostureEvaluationCalculator::Process(CalculatorContext* cc) override {
const auto& landmarks =
cc->Inputs().Tag("LANDMARKS").Get<std::vector<NormalizedLandmarkList>>()[0];

// ========== 1. 计算关键点 ==========
float nose_y = landmarks.landmark(pose_landmark::NOSE).y();
float left_shoulder_y = landmarks.landmark(pose_landmark::LEFT_SHOULDER).y();
float right_shoulder_y = landmarks.landmark(pose_landmark::RIGHT_SHOULDER).y();
float shoulder_center_y = (left_shoulder_y + right_shoulder_y) / 2.0f;

float left_hip_y = landmarks.landmark(pose_landmark::LEFT_HIP).y();
float right_hip_y = landmarks.landmark(pose_landmark::RIGHT_HIP).y();
float hip_center_y = (left_hip_y + right_hip_y) / 2.0f;

// ========== 2. 计算姿态指标 ==========

// 头部位置(相对于肩膀)
float head_position = nose_y - shoulder_center_y;

// 肩膀高度差(判断高低肩)
float shoulder_asymmetry = std::abs(left_shoulder_y - right_shoulder_y);

// 前倾程度
float forward_lean = shoulder_center_y - hip_center_y;

// ========== 3. 计算姿态评分 ==========
float score = 100.0f;

// 头部位置异常扣分
if (head_position < -0.05f) {
score -= 20.0f; // 头部过低
} else if (head_position > 0.15f) {
score -= 15.0f; // 头部抬高
}

// 高低肩扣分
if (shoulder_asymmetry > 0.03f) {
score -= shoulder_asymmetry * 300.0f; // 最高扣 15 分
}

// 前倾扣分
if (forward_lean > 0.05f) {
score -= forward_lean * 200.0f; // 最高扣 20 分
}

// 限制分数范围
score = std::max(0.0f, std::min(100.0f, score));

// ========== 4. 输出 ==========
PostureEvaluationResult result;
result.set_score(score);
result.set_head_position(head_position);
result.set_shoulder_asymmetry(shoulder_asymmetry);
result.set_forward_lean(forward_lean);

cc->Outputs().Tag("EVALUATION").AddPacket(
MakePacket<PostureEvaluationResult>(result).At(cc->InputTimestamp()));

return absl::OkStatus();
}

三十三、IMS 实战:OOP 检测

33.1 OOP (Out-of-Position) 检测

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
┌─────────────────────────────────────────────────────────────────────────┐
│ OOP 检测原理 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ OOP (Out-of-Position) 定义: │
│ 乘员位置异常,可能导致安全气囊部署时造成伤害。 │
│ │
│ 常见 OOP 情况: │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ 1. 身体前倾过度(距离仪表盘/方向盘太近) │ │
│ │ 2. 身体侧倾(靠在车门或中控台上) │ │
│ │ 3. 腿部异常位置(脚放在仪表盘上) │ │
│ │ 4. 儿童坐姿异常(跪在座椅上) │ │
│ │ 5. 头部位置异常(靠近侧窗或挡风玻璃) │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ OOP 检测目标: │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ • 调整气囊部署策略(延迟/禁用/降级) │ │
│ │ • 发出警告提示乘员调整位置 │ │
│ │ • 记录乘员状态用于事故分析 │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────┘

33.2 OOP Detection Graph

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
# ims_oop_detection_graph.pbtxt

input_stream: "OMS_IMAGE:oms_image"
output_stream: "OOP_RESULT:oop_result"
output_stream: "ALERT:alert"

# ========== 1. Pose Detection ==========
node {
calculator: "PoseTrackingGpu"
input_stream: "IMAGE:oms_image"
output_stream: "LANDMARKS:pose_landmarks"
output_stream: "SEGMENTATION:segmentation_mask"
options {
[mediapipe.PoseTrackingOptions.ext] {
model_complexity: 0 # 0: Lite, 1: Full, 2: Heavy
enable_segmentation: false
smooth_landmarks: true
}
}
}

# ========== 2. 身体朝向分析 ==========
node {
calculator: "BodyOrientationCalculator"
input_stream: "LANDMARKS:pose_landmarks"
output_stream: "ORIENTATION:orientation"
}

# ========== 3. 姿态评分 ==========
node {
calculator: "PostureEvaluationCalculator"
input_stream: "LANDMARKS:pose_landmarks"
output_stream: "EVALUATION:evaluation"
}

# ========== 4. OOP 检测 ==========
node {
calculator: "OOPDetectionCalculator"
input_stream: "LANDMARKS:pose_landmarks"
input_stream: "ORIENTATION:orientation"
input_stream: "EVALUATION:evaluation"
output_stream: "OOP_RESULT:oop_result"
output_stream: "ALERT:alert"
options {
[mediapipe.OOPDetectionOptions.ext] {
forward_lean_threshold: 0.15
side_lean_threshold: 0.10
head_deviation_threshold: 0.08
}
}
}

33.3 OOP Detection Calculator

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
// oop_detection_calculator.h
#ifndef MEDIAPIPE_CALCULATORS_IMS_OOP_DETECTION_CALCULATOR_H_
#define MEDIAPIPE_CALCULATORS_IMS_OOP_DETECTION_CALCULATOR_H_

#include "mediapipe/framework/calculator_framework.h"
#include "mediapipe/framework/formats/landmark.pb.h"

namespace mediapipe {

// ========== OOP 类型枚举 ==========
enum class OOPType {
NONE = 0,
FORWARD_LEAN = 1, // 前倾过度
SIDE_LEAN = 2, // 侧倾
HEAD_DEVIATION = 3, // 头部位置异常
LEG_ABNORMAL = 4, // 腿部位置异常
UNKNOWN = 5,
};

// ========== OOP 检测结果 ==========
message OOPResult {
OOPType oop_type = 1;
float severity = 2; // 0-1,越大约严重
float forward_lean = 3;
float side_lean = 4;
float head_deviation = 5;
bool alert_required = 6;
uint64 timestamp_ms = 7;
}

// ========== OOP Detection Calculator ==========
class OOPDetectionCalculator : public CalculatorBase {
public:
static absl::Status GetContract(CalculatorContract* cc) {
cc->Inputs().Tag("LANDMARKS").Set<std::vector<NormalizedLandmarkList>>();
cc->Inputs().Tag("ORIENTATION").Set<BodyOrientationResult>();
cc->Inputs().Tag("EVALUATION").Set<PostureEvaluationResult>();
cc->Outputs().Tag("OOP_RESULT").Set<OOPResult>();
cc->Outputs().Tag("ALERT").Set<bool>();

cc->Options<OOPDetectionOptions>();
return absl::OkStatus();
}

absl::Status Open(CalculatorContext* cc) override {
const auto& options = cc->Options<OOPDetectionOptions>();

forward_lean_threshold_ = options.forward_lean_threshold();
side_lean_threshold_ = options.side_lean_threshold();
head_deviation_threshold_ = options.head_deviation_threshold();

return absl::OkStatus();
}

absl::Status Process(CalculatorContext* cc) override {
if (cc->Inputs().Tag("LANDMARKS").IsEmpty()) {
return absl::OkStatus();
}

const auto& pose_landmarks =
cc->Inputs().Tag("LANDMARKS").Get<std::vector<NormalizedLandmarkList>>();

if (pose_landmarks.empty()) {
return absl::OkStatus();
}

const auto& landmarks = pose_landmarks[0];

// ========== 1. 计算关键指标 ==========

// 前倾程度
float shoulder_center_y = (landmarks.landmark(pose_landmark::LEFT_SHOULDER).y() +
landmarks.landmark(pose_landmark::RIGHT_SHOULDER).y()) / 2.0f;
float hip_center_y = (landmarks.landmark(pose_landmark::LEFT_HIP).y() +
landmarks.landmark(pose_landmark::RIGHT_HIP).y()) / 2.0f;
float forward_lean = shoulder_center_y - hip_center_y;

// 侧倾程度
float shoulder_center_x = (landmarks.landmark(pose_landmark::LEFT_SHOULDER).x() +
landmarks.landmark(pose_landmark::RIGHT_SHOULDER).x()) / 2.0f;
float hip_center_x = (landmarks.landmark(pose_landmark::LEFT_HIP).x() +
landmarks.landmark(pose_landmark::RIGHT_HIP).x()) / 2.0f;
float side_lean = std::abs(shoulder_center_x - hip_center_x);

// 头部偏移
float nose_x = landmarks.landmark(pose_landmark::NOSE).x();
float head_deviation = std::abs(nose_x - shoulder_center_x);

// ========== 2. 判断 OOP 类型 ==========
OOPType oop_type = OOPType::NONE;
float severity = 0.0f;

// 前倾检测
if (forward_lean > forward_lean_threshold_) {
oop_type = OOPType::FORWARD_LEAN;
severity = (forward_lean - forward_lean_threshold_) / (1.0f - forward_lean_threshold_);
}

// 侧倾检测
if (side_lean > side_lean_threshold_) {
if (severity < (side_lean - side_lean_threshold_) / (1.0f - side_lean_threshold_)) {
oop_type = OOPType::SIDE_LEAN;
severity = (side_lean - side_lean_threshold_) / (1.0f - side_lean_threshold_);
}
}

// 头部偏移检测
if (head_deviation > head_deviation_threshold_) {
if (severity < (head_deviation - head_deviation_threshold_) / (1.0f - head_deviation_threshold_)) {
oop_type = OOPType::HEAD_DEVIATION;
severity = (head_deviation - head_deviation_threshold_) / (1.0f - head_deviation_threshold_);
}
}

// ========== 3. 输出 ==========
bool alert_required = severity > 0.5f;

OOPResult result;
result.set_oop_type(oop_type);
result.set_severity(std::min(1.0f, severity));
result.set_forward_lean(forward_lean);
result.set_side_lean(side_lean);
result.set_head_deviation(head_deviation);
result.set_alert_required(alert_required);
result.set_timestamp_ms(cc->InputTimestamp().Value() / 1000);

cc->Outputs().Tag("OOP_RESULT").AddPacket(
MakePacket<OOPResult>(result).At(cc->InputTimestamp()));

cc->Outputs().Tag("ALERT").AddPacket(
MakePacket<bool>(alert_required).At(cc->InputTimestamp()));

if (oop_type != OOPType::NONE) {
LOG(INFO) << "OOP detected: type=" << static_cast<int>(oop_type)
<< ", severity=" << severity;
}

return absl::OkStatus();
}

private:
float forward_lean_threshold_ = 0.15f;
float side_lean_threshold_ = 0.10f;
float head_deviation_threshold_ = 0.08f;
};

REGISTER_CALCULATOR(OOPDetectionCalculator);

} // namespace mediapipe

#endif

三十四、总结

要点 说明
关键点数 33 个 3D 点
身体区域 面部、上肢、下肢、脚部
人体分割 可选背景去除
姿态分析 身体朝向、前倾、侧倾
IMS 应用 OOP 检测、乘员分类、安全带检测

下篇预告

MediaPipe 系列 30:Object Detection——通用目标检测

深入讲解通用目标检测、SSD 架构、IMS 车内物品检测应用。


参考资料

  1. Google AI Edge. Pose Detection
  2. MediaPipe. BlazePose Paper
  3. V. Bazarevsky et al. “BlazePose: On-device Real-time Body Pose tracking”

系列进度: 29/55
更新时间: 2026-03-12


MediaPipe 系列 29:Pose Detection——BodyPose 架构完整指南
https://dapalm.com/2026/03/13/MediaPipe系列29-Pose-Detection:BodyPose架构/
作者
Mars
发布于
2026年3月13日
许可协议