MediaPipe 系列 42:IMS DMS 架构——分心检测流水线完整实现

一、分心检测业务背景

1.1 为什么需要分心检测?

分心驾驶是交通事故的主要诱因之一:

  • 美国 NHTSA 统计:分心驾驶导致 9% 的致命交通事故
  • 欧盟研究:驾驶员视线离开前方 2秒以上,事故风险增加 2倍
  • Euro NCAP 2025+ 要求:DMS 必须检测分心行为并发出告警

1.2 分心类型定义

分心类型 具体行为 风险等级
视觉分心 眼睛离开前方道路
认知分心 心不在焉、走神
物理分心 手离开方向盘操作物品
听觉分心 电话、音乐干扰

本篇聚焦视觉分心检测——通过视线追踪判断驾驶员是否在看前方道路。

1.3 检测指标体系

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
┌─────────────────────────────────────────────────────────────┐
│ 分心检测指标体系 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 一级指标(直接观测) │
│ ├── Gaze Zone:视线区域(前方/后视镜/中控/仪表盘) │
│ ├── Gaze Deviation:视线偏离角度 │
│ ├── Gaze Duration:在非前方区域的停留时间 │
│ └── Eye Closure:眼睛是否闭合 │
│ │
│ 二级指标(头部姿态) │
│ ├── Head Yaw:头部左右偏转角度 │
│ ├── Head Pitch:头部俯仰角度 │
│ ├── Head Roll:头部侧倾角度 │
│ └── Head Movement:头部运动速度 │
│ │
│ 三级指标(融合决策) │
│ ├── Distraction Score:分心得分 │
│ ├── Distraction Level:分心等级(0-3) │
│ └── Alert Decision:是否触发告警 │
│ │
└─────────────────────────────────────────────────────────────┘

二、视线估计原理

2.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
┌─────────────────────────────────────────────────────────────┐
│ 视线估计原理 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 眼睛坐标系 │
│ ┌─────────────────────────────────┐ │
│ │ ↑ │ │
│ │ │ Y │ │
│ │ │ │ │
│ │ └───────▶ X │ │
│ │ 外眼角 内眼角 │ │
│ │ │ │
│ │ ● 虹膜中心 │ │
│ │ ↓ │ │
│ │ 视线方向 = 虹膜偏移 / 眼睛尺寸 │ │
│ └─────────────────────────────────┘ │
│ │
│ Gaze X = (iris_x - eye_center_x) / eye_width │
│ Gaze Y = (iris_y - eye_center_y) / eye_height │
│ │
│ 取值范围: -11
│ - X < 0: 看向左边 │
│ - X > 0: 看向右边 │
│ - Y < 0: 看向上方 │
│ - Y > 0: 看向下方 │
│ │
└─────────────────────────────────────────────────────────────┘

2.2 Gaze Zone 映射

将视线方向映射到车内区域:

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
┌─────────────────────────────────────────────────────────────┐
│ 车内区域划分 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 前挡风玻璃 (Zone 0) │
│ ┌───────────────┐ │
│ │ │ │
│ 左后视镜 │ │ 右后视镜 │
│ (Zone 1) │ │ (Zone 2) │
│ ┌─────┐ │ │ ┌─────┐ │
│ │ │ │ │ │ │ │
│ └─────┘ │ │ └─────┘ │
│ │ │ │
│ │ 驾驶员位置 │ │
│ │ ● │ │
│ │ │ │
│ └───────────────┘ │
│ │
│ ┌─────────┐ ┌─────────┐ │
│ │ 仪表盘 │ │ 中控 │ │
│ │(Zone 3) │ │(Zone 4) │ │
│ └─────────┘ └─────────┘ │
│ │
Zone 0: 前挡风玻璃 - 正常驾驶区域 │
Zone 1: 左后视镜 - 偶尔查看,正常 │
Zone 2: 右后视镜 - 偶尔查看,正常 │
Zone 3: 仪表盘 - 偶尔查看,正常 │
Zone 4: 中控屏 - 分心风险,需要监控 │
Zone 5: 其他区域 - 分心,需要告警 │
│ │
└─────────────────────────────────────────────────────────────┘

2.3 Gaze Zone 判断规则

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
def determine_gaze_zone(gaze_x, gaze_y, head_yaw):
"""
根据视线和头部姿态判断区域

Args:
gaze_x: 视线水平偏移 (-1 到 1)
gaze_y: 视线垂直偏移 (-1 到 1)
head_yaw: 头部左右偏转角度 (度)

Returns:
zone: 区域编号 (0-5)
"""
# 前方道路:视线在前方,头部不偏转
if abs(gaze_x) < 0.3 and abs(gaze_y) < 0.3 and abs(head_yaw) < 15:
return 0 # 前挡风玻璃

# 左后视镜:视线向左,头部左转
if gaze_x < -0.4 and head_yaw < -15:
return 1 # 左后视镜

# 右后视镜:视线向右,头部右转
if gaze_x > 0.4 and head_yaw > 15:
return 2 # 右后视镜

# 仪表盘:视线向下
if gaze_y > 0.3 and abs(gaze_x) < 0.3:
return 3 # 仪表盘

# 中控屏:视线向右下
if gaze_x > 0.3 and gaze_y > 0.2:
return 4 # 中控屏

# 其他区域
return 5 # 其他区域

三、完整流水线架构

3.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
┌─────────────────────────────────────────────────────────────────────────┐
IMS DMS 分心检测完整流水线 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ 输入层 │
│ ┌─────────────┐ │
│ │ IR Camera │ → 640×480 @ 30fps
│ └─────────────┘ │
│ │ │
│ ▼ │
│ 检测层 │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │Face Mesh │────▶│Iris │────▶│Head Pose │ │
│ │(468) │ │Detection │ │Estimation │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │Face Landmarks│ │Iris Centers │ │Head Pose │ │
│ │[x,y,z]×468 │ │(Left/Right) │ │(Yaw/Pitch/ │ │
│ │ │ │ │ │ Roll) │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
│ │
│ 分析层 │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │Gaze │────▶│Gaze Zone │────▶│Duration │ │
│ │Estimation │ │Classifier │ │Tracker │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │Gaze Vector │ │Zone ID │ │Time in Zone │ │
│ │(x, y) │ │(0-5) │ │(seconds) │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
│ │
│ 融合层 │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Distraction Aggregator │ │
│ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ │
│ │ │Gaze Zone│ │Duration │ │Head Pose│ │Vehicle │ │ │
│ │ │ │ │ │ │ │ │State │ │ │
│ │ └─────────┘ └─────────┘ └─────────┘ └─────────┘ │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ ┌─────────────┐ │ │
│ │ │ Distraction │ │ │
│ │ │ Score/Level │ │ │
│ │ └─────────────┘ │ │
│ └─────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ 输出层 │ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ DistractionResult { │ │
│ │ gaze_zone: 4, │ │
│ │ gaze_duration_ms: 3500, │ │
│ │ distraction_score: 0.75, │ │
│ │ distraction_level: 2, │ │
│ │ alert: true │ │
│ │ } │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────┘

四、完整 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
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
# mediapipe/graphs/ims/dms_distraction_graph.pbtxt

# ============== 输入输出定义 ==============
input_stream: "IR_IMAGE:ir_image"
input_stream: "TIMESTAMP:timestamp"
input_stream: "VEHICLE_STATE:vehicle_state"

output_stream: "DISTRACTION_RESULT:distraction_result"
output_stream: "ALERT:alert"

# ============== 1. Face Mesh ==============
node {
calculator: "FaceMeshCalculator"
input_stream: "IMAGE:ir_image"
output_stream: "LANDMARKS:face_landmarks"
options {
[mediapipe.FaceMeshOptions.ext] {
model_path: "/models/face_landmark.tflite"
enable_attention: true
enable_iris: true # 启用虹膜检测
}
}
}

# ============== 2. 视线估计 ==============
node {
calculator: "GazeEstimationCalculator"
input_stream: "LANDMARKS:face_landmarks"
output_stream: "GAZE_VECTOR:gaze_vector"
output_stream: "LEFT_GAZE:left_gaze"
output_stream: "RIGHT_GAZE:right_gaze"
options {
[mediapipe.GazeEstimationOptions.ext] {
# Face Mesh 虹膜关键点索引
left_iris_center: 468
right_iris_center: 473
# 眼睛边界索引
left_eye_outer: 33
left_eye_inner: 133
right_eye_outer: 263
right_eye_inner: 362
}
}
}

# ============== 3. 头部姿态估计 ==============
node {
calculator: "HeadPoseCalculator"
input_stream: "LANDMARKS:face_landmarks"
output_stream: "HEAD_POSE:head_pose"
options {
[mediapipe.HeadPoseOptions.ext] {
# 6 个关键点用于 PnP
landmark_indices: [1, 152, 33, 263, 61, 291]
}
}
}

# ============== 4. Gaze Zone 分类 ==============
node {
calculator: "GazeZoneClassifierCalculator"
input_stream: "GAZE_VECTOR:gaze_vector"
input_stream: "HEAD_POSE:head_pose"
output_stream: "GAZE_ZONE:gaze_zone"
output_stream: "ZONE_CONFIDENCE:confidence"
options {
[mediapipe.GazeZoneOptions.ext] {
# 区域定义
zone_0_threshold_x: 0.3 # 前方 X 阈值
zone_0_threshold_y: 0.3 # 前方 Y 阈值
mirror_threshold_x: 0.4 # 后视镜阈值
mirror_head_yaw: 15.0 # 后视镜头部偏转阈值
dashboard_threshold_y: 0.3 # 仪表盘阈值
console_threshold_x: 0.3 # 中控阈值
}
}
}

# ============== 5. 区域停留时间追踪 ==============
node {
calculator: "ZoneDurationTrackerCalculator"
input_stream: "GAZE_ZONE:gaze_zone"
output_stream: "ZONE_DURATION:zone_duration"
output_stream: "CURRENT_ZONE_TIME:current_zone_time"
options {
[mediapipe.ZoneDurationOptions.ext] {
# 超时阈值(毫秒)
zone_0_timeout_ms: 0 # 前方不超时
zone_1_timeout_ms: 3000 # 左后视镜 3
zone_2_timeout_ms: 3000 # 右后视镜 3
zone_3_timeout_ms: 2000 # 仪表盘 2
zone_4_timeout_ms: 2000 # 中控屏 2
zone_5_timeout_ms: 1000 # 其他区域 1
}
}
}

# ============== 6. 分心融合决策 ==============
node {
calculator: "DistractionAggregatorCalculator"
input_stream: "GAZE_ZONE:gaze_zone"
input_stream: "ZONE_DURATION:zone_duration"
input_stream: "HEAD_POSE:head_pose"
input_stream: "VEHICLE_STATE:vehicle_state"
output_stream: "DISTRACTION_RESULT:distraction_result"
output_stream: "ALERT:alert"
options {
[mediapipe.DistractionAggregatorOptions.ext] {
# 权重配置
gaze_zone_weight: 0.5
duration_weight: 0.3
head_pose_weight: 0.2

# 分心阈值
low_threshold: 0.3
medium_threshold: 0.6
high_threshold: 0.8

# 速度因子
speed_factor_enabled: true
speed_factor_threshold: 80.0 # km/h

# 告警配置
alert_cooldown_seconds: 10
}
}
}

五、核心 Calculator 实现

5.1 GazeEstimationCalculator

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
// mediapipe/calculators/ims/gaze_estimation_calculator.h
#ifndef MEDIAPIPE_CALCULATORS_IMS_GAZE_ESTIMATION_CALCULATOR_H_
#define MEDIAPIPE_CALCULATORS_IMS_GAZE_ESTIMATION_CALCULATOR_H_

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

namespace mediapipe {

// 视线向量
struct GazeVector {
float x; // 水平分量 (-1 到 1)
float y; // 垂直分量 (-1 到 1)
float confidence;
int64_t timestamp;
};

class GazeEstimationCalculator : public CalculatorBase {
public:
static absl::Status GetContract(CalculatorContract* cc);

absl::Status Open(CalculatorContext* cc) override;
absl::Status Process(CalculatorContext* cc) override;

private:
// 计算单眼视线
GazeVector CalculateEyeGaze(
const NormalizedLandmarkList& landmarks,
int iris_center_idx,
int eye_outer_idx,
int eye_inner_idx,
int eye_top_idx,
int eye_bottom_idx);

// 平滑滤波
GazeVector SmoothGaze(const GazeVector& current);

// 配置
int left_iris_center_;
int right_iris_center_;
int left_eye_outer_;
int left_eye_inner_;
int right_eye_outer_;
int right_eye_inner_;

// 历史用于平滑
std::deque<GazeVector> gaze_history_;
int smooth_window_ = 5;
};

} // namespace mediapipe

#endif // MEDIAPIPE_CALCULATORS_IMS_GAZE_ESTIMATION_CALCULATOR_H_
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
152
153
154
155
156
157
158
159
160
161
// mediapipe/calculators/ims/gaze_estimation_calculator.cc
#include "mediapipe/calculators/ims/gaze_estimation_calculator.h"
#include "mediapipe/framework/port/logging.h"

namespace mediapipe {

using mediapipe::NormalizedLandmarkList;
using mediapipe::NormalizedLandmark;

absl::Status GazeEstimationCalculator::GetContract(CalculatorContract* cc) {
cc->Inputs().Tag("LANDMARKS").Set<NormalizedLandmarkList>();

cc->Outputs().Tag("GAZE_VECTOR").Set<GazeVector>();
cc->Outputs().Tag("LEFT_GAZE").Set<GazeVector>();
cc->Outputs().Tag("RIGHT_GAZE").Set<GazeVector>();

cc->Options<GazeEstimationOptions>();

return absl::OkStatus();
}

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

left_iris_center_ = options.left_iris_center();
right_iris_center_ = options.right_iris_center();
left_eye_outer_ = options.left_eye_outer();
left_eye_inner_ = options.left_eye_inner();
right_eye_outer_ = options.right_eye_outer();
right_eye_inner_ = options.right_eye_inner();

LOG(INFO) << "GazeEstimationCalculator initialized";

return absl::OkStatus();
}

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

const auto& landmarks = cc->Inputs().Tag("LANDMARKS").Get<NormalizedLandmarkList>();

// 验证关键点数量
if (landmarks.landmark_size() < 478) {
LOG(WARNING) << "Insufficient landmarks for iris detection";
return absl::OkStatus();
}

// 计算左眼视线
GazeVector left_gaze = CalculateEyeGaze(
landmarks,
left_iris_center_,
left_eye_outer_,
left_eye_inner_,
159, // 上眼睑
145 // 下眼睑
);

// 计算右眼视线
GazeVector right_gaze = CalculateEyeGaze(
landmarks,
right_iris_center_,
right_eye_outer_,
right_eye_inner_,
386, // 上眼睑
374 // 下眼睑
);

// 平均视线
GazeVector avg_gaze;
avg_gaze.x = (left_gaze.x + right_gaze.x) / 2.0f;
avg_gaze.y = (left_gaze.y + right_gaze.y) / 2.0f;
avg_gaze.confidence = (left_gaze.confidence + right_gaze.confidence) / 2.0f;
avg_gaze.timestamp = cc->InputTimestamp().Value();

// 平滑
avg_gaze = SmoothGaze(avg_gaze);

// 输出
cc->Outputs().Tag("GAZE_VECTOR").AddPacket(
MakePacket<GazeVector>(avg_gaze).At(cc->InputTimestamp()));

cc->Outputs().Tag("LEFT_GAZE").AddPacket(
MakePacket<GazeVector>(left_gaze).At(cc->InputTimestamp()));

cc->Outputs().Tag("RIGHT_GAZE").AddPacket(
MakePacket<GazeVector>(right_gaze).At(cc->InputTimestamp()));

VLOG(1) << "Gaze: x=" << avg_gaze.x << ", y=" << avg_gaze.y;

return absl::OkStatus();
}

GazeVector GazeEstimationCalculator::CalculateEyeGaze(
const NormalizedLandmarkList& landmarks,
int iris_center_idx,
int eye_outer_idx,
int eye_inner_idx,
int eye_top_idx,
int eye_bottom_idx) {

GazeVector gaze;

// 获取关键点
const auto& iris = landmarks.landmark(iris_center_idx);
const auto& outer = landmarks.landmark(eye_outer_idx);
const auto& inner = landmarks.landmark(eye_inner_idx);
const auto& top = landmarks.landmark(eye_top_idx);
const auto& bottom = landmarks.landmark(eye_bottom_idx);

// 计算眼睛中心和尺寸
float eye_center_x = (outer.x() + inner.x()) / 2.0f;
float eye_center_y = (top.y() + bottom.y()) / 2.0f;
float eye_width = std::abs(inner.x() - outer.x());
float eye_height = std::abs(bottom.y() - top.y());

// 归一化视线偏移
if (eye_width > 1e-6f && eye_height > 1e-6f) {
gaze.x = (iris.x() - eye_center_x) / eye_width * 2.0f;
gaze.y = (iris.y() - eye_center_y) / eye_height * 2.0f;
} else {
gaze.x = 0.0f;
gaze.y = 0.0f;
}

// 置信度基于关键点可见性
gaze.confidence = (iris.visibility() + outer.visibility() + inner.visibility()) / 3.0f;

return gaze;
}

GazeVector GazeEstimationCalculator::SmoothGaze(const GazeVector& current) {
gaze_history_.push_back(current);

while (gaze_history_.size() > smooth_window_) {
gaze_history_.pop_front();
}

GazeVector smoothed;
smoothed.x = 0.0f;
smoothed.y = 0.0f;
smoothed.confidence = 0.0f;

for (const auto& g : gaze_history_) {
smoothed.x += g.x;
smoothed.y += g.y;
smoothed.confidence += g.confidence;
}

smoothed.x /= gaze_history_.size();
smoothed.y /= gaze_history_.size();
smoothed.confidence /= gaze_history_.size();
smoothed.timestamp = current.timestamp;

return smoothed;
}

REGISTER_CALCULATOR(GazeEstimationCalculator);

} // namespace mediapipe

5.2 GazeZoneClassifierCalculator

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
// mediapipe/calculators/ims/gaze_zone_classifier_calculator.h
#ifndef MEDIAPIPE_CALCULATORS_IMS_GAZE_ZONE_CLASSIFIER_CALCULATOR_H_
#define MEDIAPIPE_CALCULATORS_IMS_GAZE_ZONE_CLASSIFIER_CALCULATOR_H_

#include "mediapipe/framework/calculator_framework.h"
#include "mediapipe/calculators/ims/gaze_estimation_calculator.h"
#include "mediapipe/calculators/ims/head_pose_calculator.h"

namespace mediapipe {

// Gaze Zone 定义
enum GazeZone {
ZONE_FRONT_WINDSHIELD = 0, // 前挡风玻璃
ZONE_LEFT_MIRROR = 1, // 左后视镜
ZONE_RIGHT_MIRROR = 2, // 右后视镜
ZONE_DASHBOARD = 3, // 仪表盘
ZONE_CENTER_CONSOLE = 4, // 中控屏
ZONE_OTHER = 5, // 其他区域
};

class GazeZoneClassifierCalculator : public CalculatorBase {
public:
static absl::Status GetContract(CalculatorContract* cc);

absl::Status Open(CalculatorContext* cc) override;
absl::Status Process(CalculatorContext* cc) override;

private:
GazeZone ClassifyZone(const GazeVector& gaze, const HeadPose& head_pose);

// 阈值配置
float zone_0_threshold_x_ = 0.3f;
float zone_0_threshold_y_ = 0.3f;
float mirror_threshold_x_ = 0.4f;
float mirror_head_yaw_ = 15.0f;
float dashboard_threshold_y_ = 0.3f;
float console_threshold_x_ = 0.3f;
};

} // namespace mediapipe

#endif // MEDIAPIPE_CALCULATORS_IMS_GAZE_ZONE_CLASSIFIER_CALCULATOR_H_
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
// mediapipe/calculators/ims/gaze_zone_classifier_calculator.cc
#include "mediapipe/calculators/ims/gaze_zone_classifier_calculator.h"
#include "mediapipe/framework/port/logging.h"

namespace mediapipe {

absl::Status GazeZoneClassifierCalculator::GetContract(CalculatorContract* cc) {
cc->Inputs().Tag("GAZE_VECTOR").Set<GazeVector>();
cc->Inputs().Tag("HEAD_POSE").Set<HeadPose>();

cc->Outputs().Tag("GAZE_ZONE").Set<int>();
cc->Outputs().Tag("ZONE_CONFIDENCE").Set<float>();

cc->Options<GazeZoneOptions>();

return absl::OkStatus();
}

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

zone_0_threshold_x_ = options.zone_0_threshold_x();
zone_0_threshold_y_ = options.zone_0_threshold_y();
mirror_threshold_x_ = options.mirror_threshold_x();
mirror_head_yaw_ = options.mirror_head_yaw();
dashboard_threshold_y_ = options.dashboard_threshold_y();
console_threshold_x_ = options.console_threshold_x();

LOG(INFO) << "GazeZoneClassifierCalculator initialized";

return absl::OkStatus();
}

absl::Status GazeZoneClassifierCalculator::Process(CalculatorContext* cc) {
if (cc->Inputs().Tag("GAZE_VECTOR").IsEmpty() ||
cc->Inputs().Tag("HEAD_POSE").IsEmpty()) {
return absl::OkStatus();
}

const auto& gaze = cc->Inputs().Tag("GAZE_VECTOR").Get<GazeVector>();
const auto& head_pose = cc->Inputs().Tag("HEAD_POSE").Get<HeadPose>();

// 分类区域
GazeZone zone = ClassifyZone(gaze, head_pose);

// 置信度
float confidence = gaze.confidence;

// 输出
cc->Outputs().Tag("GAZE_ZONE").AddPacket(
MakePacket<int>(static_cast<int>(zone)).At(cc->InputTimestamp()));

cc->Outputs().Tag("ZONE_CONFIDENCE").AddPacket(
MakePacket<float>(confidence).At(cc->InputTimestamp()));

VLOG(1) << "Gaze Zone: " << static_cast<int>(zone)
<< " (gaze_x=" << gaze.x << ", gaze_y=" << gaze.y
<< ", head_yaw=" << head_pose.yaw << ")";

return absl::OkStatus();
}

GazeZone GazeZoneClassifierCalculator::ClassifyZone(
const GazeVector& gaze,
const HeadPose& head_pose) {

// 前挡风玻璃
if (std::abs(gaze.x) < zone_0_threshold_x_ &&
std::abs(gaze.y) < zone_0_threshold_y_ &&
std::abs(head_pose.yaw) < mirror_head_yaw_) {
return ZONE_FRONT_WINDSHIELD;
}

// 左后视镜
if (gaze.x < -mirror_threshold_x_ && head_pose.yaw < -mirror_head_yaw_) {
return ZONE_LEFT_MIRROR;
}

// 右后视镜
if (gaze.x > mirror_threshold_x_ && head_pose.yaw > mirror_head_yaw_) {
return ZONE_RIGHT_MIRROR;
}

// 仪表盘
if (gaze.y > dashboard_threshold_y_ && std::abs(gaze.x) < zone_0_threshold_x_) {
return ZONE_DASHBOARD;
}

// 中控屏
if (gaze.x > console_threshold_x_ && gaze.y > 0.2f) {
return ZONE_CENTER_CONSOLE;
}

// 其他区域
return ZONE_OTHER;
}

REGISTER_CALCULATOR(GazeZoneClassifierCalculator);

} // namespace mediapipe

六、分心告警逻辑

6.1 告警规则

区域 正常停留时间 告警阈值 风险等级
前挡风玻璃 无限制
左/右后视镜 < 1秒 > 3秒
仪表盘 < 1秒 > 2秒
中控屏 不应查看 > 2秒
其他区域 不应查看 > 1秒

6.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
// 高速时分心更危险
float CalculateDistractionScore(int zone, float duration_ms, float speed_kmh) {
float base_score = 0.0f;

// 基础分数
switch (zone) {
case ZONE_FRONT_WINDSHIELD:
base_score = 0.0f;
break;
case ZONE_LEFT_MIRROR:
case ZONE_RIGHT_MIRROR:
base_score = 0.3f * (duration_ms / 3000.0f);
break;
case ZONE_DASHBOARD:
base_score = 0.5f * (duration_ms / 2000.0f);
break;
case ZONE_CENTER_CONSOLE:
base_score = 0.8f * (duration_ms / 2000.0f);
break;
case ZONE_OTHER:
base_score = 1.0f * (duration_ms / 1000.0f);
break;
}

// 速度因子
float speed_factor = 1.0f;
if (speed_kmh > 80.0f) {
speed_factor = 1.5f; // 高速时分心更严重
} else if (speed_kmh > 120.0f) {
speed_factor = 2.0f;
}

return std::min(base_score * speed_factor, 1.0f);
}

七、测试与验证

7.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
TEST(GazeZoneClassifierTest, ClassifiesFrontCorrectly) {
GazeVector gaze;
gaze.x = 0.0f;
gaze.y = 0.0f;
gaze.confidence = 0.9f;

HeadPose pose;
pose.yaw = 0.0f;
pose.pitch = 0.0f;
pose.roll = 0.0f;

GazeZone zone = ClassifyZone(gaze, pose);
EXPECT_EQ(zone, ZONE_FRONT_WINDSHIELD);
}

TEST(GazeZoneClassifierTest, ClassifiesLeftMirrorCorrectly) {
GazeVector gaze;
gaze.x = -0.5f;
gaze.y = 0.0f;
gaze.confidence = 0.9f;

HeadPose pose;
pose.yaw = -20.0f; // 头左转

GazeZone zone = ClassifyZone(gaze, pose);
EXPECT_EQ(zone, ZONE_LEFT_MIRROR);
}

八、总结

要点 说明
Gaze Estimation 基于虹膜位置计算视线方向
Gaze Zone 映射视线到车内区域
Duration Tracking 追踪区域停留时间
Speed Factor 高速时分心告警更严格

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


MediaPipe 系列 42:IMS DMS 架构——分心检测流水线完整实现
https://dapalm.com/2026/03/12/MediaPipe系列42-IMS-DMS架构:分心检测流水线/
作者
Mars
发布于
2026年3月12日
许可协议