MediaPipe 系列 32:Iris Detection——虹膜定位完整指南

前言:为什么需要虹膜检测?

32.1 Iris Detection 的重要性

虹膜检测在 IMS/DMS 中的应用:

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
┌─────────────────────────────────────────────────────────────────────────┐
│ Iris Detection 在 IMS/DMS 中的应用 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ IMS/DMS 场景需求: │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ • 视线追踪(判断驾驶员是否看前方) │ │
│ │ • 分心检测(检测视线偏离道路) │ │
│ │ • 注意力分析(监控视线移动模式) │ │
│ │ • 认知分心检测(识别思考状态) │ │
│ │ • 驾驶员状态监控(疲劳前兆检测) │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ MediaPipe Iris Detection 特点: │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ • 像素级精度(误差 < 2%) │ │
│ │ • 实时性能(~1ms GPU) │ │
│ │ • 轻量级模型(~1MB) │ │
│ │ • 单目摄像头即可工作 │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────┘

32.2 功能特点

特性 说明
关键点数量 每眼 10 个点
精度 像素级误差 < 2%
速度 ~1ms (GPU)
依赖 需要 Face Mesh

32.3 虹膜关键点布局

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
┌─────────────────────────────────────────────────────────────────────────┐
│ 虹膜关键点布局 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ 左眼虹膜关键点(索引 468-477): │
│ ┌─────────────────────────────────────────────┐ │
│ │ │ │
│ │ ●469 (上边界) │ │
│ │ │ │ │
│ │ ●472───●468───●470 │ │
│ │ (左) (中心) (右) │ │
│ │ │ │ │
│ │ ●471 (下边界) │ │
│ │ │ │
│ │ 瞳孔边界点: 473-477 │ │
│ │ │ │
│ └─────────────────────────────────────────────┘ │
│ │
│ 右眼虹膜关键点(索引 478-487): │
│ ┌─────────────────────────────────────────────┐ │
│ │ │ │
│ │ ●479 (上边界) │ │
│ │ │ │ │
│ │ ●482───●478───●480 │ │
│ │ (左) (中心) (右) │ │
│ │ │ │ │
│ │ ●481 (下边界) │ │
│ │ │ │
│ │ 瞳孔边界点: 483-487 │ │
│ │ │ │
│ └─────────────────────────────────────────────┘ │
│ │
│ 关键点索引对照: │
│ ───────────────────────────────────────────────────────────────── │
│ 左眼: │
│ • 468: 虹膜中心 │
│ • 469: 上边界 │
│ • 470: 右边界 │
│ • 471: 下边界 │
│ • 472: 左边界 │
│ • 473-477: 瞳孔边界点 │
│ │
│ 右眼: │
│ • 478: 虹膜中心 │
│ • 479: 上边界 │
│ • 480: 右边界 │
│ • 481: 下边界 │
│ • 482: 左边界 │
│ • 483-487: 瞳孔边界点 │
│ ───────────────────────────────────────────────────────────────── │
│ │
└─────────────────────────────────────────────────────────────────────────┘

三十三、视线估计原理

33.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
┌─────────────────────────────────────────────────────────────────────────┐
│ 视线估计原理 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ 方法一:虹膜偏移法 │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ 原理: │ │
│ │ 虹膜中心相对于眼睛中心的偏移, │ │
│ │ 反映了视线的方向。 │ │
│ │ │ │
│ │ 步骤: │ │
│ │ 1. 计算眼睛中心(眼角中点) │ │
│ │ 2. 计算虹膜中心 │ │
│ │ 3. 计算归一化偏移 │ │
│ │ 4. 映射到视线方向 │ │
│ │ │ │
│ │ 优点: │ │
│ │ • 计算简单 │ │
│ │ • 实时性好 │ │
│ │ │ │
│ │ 局限: │ │
│ │ • 精度有限 │ │
│ │ • 受头部姿态影响 │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ 方法二:头眼融合法 │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ 原理: │ │
│ │ 结合头部姿态和眼动信息, │ │
│ │ 提高视线估计精度。 │ │
│ │ │ │
│ │ 步骤: │ │
│ │ 1. 计算头部姿态(yaw, pitch, roll) │ │
│ │ 2. 计算虹膜偏移 │ │
│ │ 3. 头眼融合模型 │ │
│ │ 4. 输出视线方向 │ │
│ │ │ │
│ │ 优点: │ │
│ │ • 精度更高 │ │
│ │ • 抗干扰能力强 │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────┘

33.2 Gaze Estimation 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
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
// 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"

namespace mediapipe {

// ========== 视线消息 ==========
message GazeVector {
float x = 1; // 水平方向 (-1 到 1)
float y = 2; // 垂直方向 (-1 到 1)
float confidence = 3;
uint64 timestamp_ms = 4;
}

// ========== 视线区域枚举 ==========
enum class GazeZone {
FRONT_WINDSHIELD = 0, // 前挡风玻璃
LEFT_MIRROR = 1, // 左后视镜
RIGHT_MIRROR = 2, // 右后视镜
DASHBOARD = 3, // 仪表盘
CENTER_CONSOLE = 4, // 中控台
LEFT_WINDOW = 5, // 左车窗
RIGHT_WINDOW = 6, // 右车窗
UNKNOWN = 7,
};

// ========== Gaze Estimation Calculator ==========
class GazeEstimationCalculator : public CalculatorBase {
public:
static absl::Status GetContract(CalculatorContract* cc) {
cc->Inputs().Tag("LANDMARKS").Set<std::vector<NormalizedLandmarkList>>();
cc->Outputs().Tag("GAZE").Set<GazeVector>();
cc->Outputs().Tag("ZONE").Set<int>();

return absl::OkStatus();
}

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

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

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

const auto& landmarks = face_landmarks[0];

// ========== 1. 计算左眼视线 ==========
float left_gaze_x, left_gaze_y;
std::tie(left_gaze_x, left_gaze_y) = CalculateGazeForEye(
landmarks,
/*iris_center_idx=*/468,
/*eye_left_idx=*/33,
/*eye_right_idx=*/133,
/*eye_top_idx=*/159,
/*eye_bottom_idx=*/145);

// ========== 2. 计算右眼视线 ==========
float right_gaze_x, right_gaze_y;
std::tie(right_gaze_x, right_gaze_y) = CalculateGazeForEye(
landmarks,
/*iris_center_idx=*/478,
/*eye_left_idx=*/362,
/*eye_right_idx=*/263,
/*eye_top_idx=*/386,
/*eye_bottom_idx=*/374);

// ========== 3. 融合双眼视线 ==========
float gaze_x = (left_gaze_x + right_gaze_x) / 2.0f;
float gaze_y = (left_gaze_y + right_gaze_y) / 2.0f;

// ========== 4. 判断视线区域 ==========
int zone = DetermineGazeZone(gaze_x, gaze_y);

// ========== 5. 输出 ==========
GazeVector gaze;
gaze.set_x(gaze_x);
gaze.set_y(gaze_y);
gaze.set_confidence(1.0f);
gaze.set_timestamp_ms(cc->InputTimestamp().Value() / 1000);

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

cc->Outputs().Tag("ZONE").AddPacket(
MakePacket<int>(zone).At(cc->InputTimestamp()));

VLOG(1) << "Gaze: (" << gaze_x << ", " << gaze_y << "), zone: " << zone;

return absl::OkStatus();
}

private:
std::pair<float, float> CalculateGazeForEye(
const NormalizedLandmarkList& landmarks,
int iris_center_idx,
int eye_left_idx,
int eye_right_idx,
int eye_top_idx,
int eye_bottom_idx) {

// 虹膜中心
float iris_x = landmarks.landmark(iris_center_idx).x();
float iris_y = landmarks.landmark(iris_center_idx).y();

// 眼睛中心
float eye_left_x = landmarks.landmark(eye_left_idx).x();
float eye_left_y = landmarks.landmark(eye_left_idx).y();
float eye_right_x = landmarks.landmark(eye_right_idx).x();
float eye_right_y = landmarks.landmark(eye_right_idx).y();

float eye_center_x = (eye_left_x + eye_right_x) / 2.0f;
float eye_center_y = (eye_left_y + eye_right_y) / 2.0f;

// 眼睛尺寸
float eye_width = std::abs(eye_right_x - eye_left_x);

float eye_top_y = landmarks.landmark(eye_top_idx).y();
float eye_bottom_y = landmarks.landmark(eye_bottom_idx).y();
float eye_height = std::abs(eye_bottom_y - eye_top_y);

// 归一化偏移
float offset_x = (iris_x - eye_center_x) / (eye_width / 2.0f);
float offset_y = (iris_y - eye_center_y) / (eye_height / 2.0f);

// 限制范围
offset_x = std::max(-1.0f, std::min(1.0f, offset_x));
offset_y = std::max(-1.0f, std::min(1.0f, offset_y));

return {offset_x, offset_y};
}

int DetermineGazeZone(float x, float y) {
// 前挡风玻璃(正前方)
if (y < -0.3f && std::abs(x) < 0.3f) {
return static_cast<int>(GazeZone::FRONT_WINDSHIELD);
}

// 左后视镜
if (x < -0.5f && y < 0.0f) {
return static_cast<int>(GazeZone::LEFT_MIRROR);
}

// 右后视镜
if (x > 0.5f && y < 0.0f) {
return static_cast<int>(GazeZone::RIGHT_MIRROR);
}

// 仪表盘
if (y > 0.3f && std::abs(x) < 0.3f) {
return static_cast<int>(GazeZone::DASHBOARD);
}

// 中控台
if (y > 0.3f && std::abs(x) >= 0.3f) {
return static_cast<int>(GazeZone::CENTER_CONSOLE);
}

// 左车窗
if (x < -0.3f && std::abs(y) < 0.3f) {
return static_cast<int>(GazeZone::LEFT_WINDOW);
}

// 右车窗
if (x > 0.3f && std::abs(y) < 0.3f) {
return static_cast<int>(GazeZone::RIGHT_WINDOW);
}

return static_cast<int>(GazeZone::UNKNOWN);
}
};

REGISTER_CALCULATOR(GazeEstimationCalculator);

} // namespace mediapipe

#endif

三十四、Graph 配置

34.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
# ========== Iris Detection Graph 配置 ==========

# mediapipe/graphs/iris/iris_tracking_gpu.pbtxt

input_stream: "INPUT:Input"

output_stream: "LANDMARKS:face_landmarks_with_iris"
output_stream: "GAZE:gaze_vector"
output_stream: "ZONE:gaze_zone"

# ========== 1. Face Mesh (包含 Iris) ==========
node {
calculator: "FaceMeshGpu"
input_stream: "IMAGE:Input"
output_stream: "LANDMARKS:face_landmarks"
options {
[mediapipe.FaceMeshOptions.ext] {
max_num_faces: 1
refine_landmarks: true # 启用 Iris 检测
min_detection_confidence: 0.5
min_tracking_confidence: 0.5
}
}
}

# ========== 2. 视线估计 ==========
node {
calculator: "GazeEstimationCalculator"
input_stream: "LANDMARKS:face_landmarks"
output_stream: "GAZE:gaze_vector"
output_stream: "ZONE:gaze_zone"
}

三十五、IMS 实战:分心检测

35.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
# ims_distraction_detection_graph.pbtxt

input_stream: "IR_IMAGE:ir_image"
output_stream: "DISTRACTION_RESULT:distraction_result"
output_stream: "ALERT:alert"

# ========== 1. Face Mesh + Iris ==========
node {
calculator: "FaceMeshGpu"
input_stream: "IMAGE:ir_image"
output_stream: "LANDMARKS:face_landmarks"
options {
[mediapipe.FaceMeshOptions.ext] {
max_num_faces: 1
refine_landmarks: true
}
}
}

# ========== 2. 视线估计 ==========
node {
calculator: "GazeEstimationCalculator"
input_stream: "LANDMARKS:face_landmarks"
output_stream: "GAZE:gaze_vector"
output_stream: "ZONE:gaze_zone"
}

# ========== 3. 头部姿态估计 ==========
node {
calculator: "HeadPoseCalculator"
input_stream: "LANDMARKS:face_landmarks"
output_stream: "POSE:head_pose"
}

# ========== 4. 分心检测 ==========
node {
calculator: "DistractionDetectorCalculator"
input_stream: "GAZE:gaze_vector"
input_stream: "ZONE:gaze_zone"
input_stream: "POSE:head_pose"
output_stream: "DISTRACTION_RESULT:distraction_result"
output_stream: "ALERT:alert"
options {
[mediapipe.DistractionDetectorOptions.ext] {
off_road_threshold_seconds: 2.0
alert_cooldown_seconds: 5.0
}
}
}

35.2 分心检测 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
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
// distraction_detector_calculator.h
#ifndef MEDIAPIPE_CALCULATORS_IMS_DISTRACTION_DETECTOR_CALCULATOR_H_
#define MEDIAPIPE_CALCULATORS_IMS_DISTRACTION_DETECTOR_CALCULATOR_H_

#include "mediapipe/framework/calculator_framework.h"

namespace mediapipe {

// ========== 分心类型枚举 ==========
enum class DistractionType {
NONE = 0,
LOOKING_LEFT = 1, // 向左看
LOOKING_RIGHT = 2, // 向右看
LOOKING_DOWN = 3, // 低头
LOOKING_UP = 4, // 抬头
PHONE_USE = 5, // 使用手机
UNKNOWN = 6,
};

// ========== 分心检测结果 ==========
message DistractionResult {
DistractionType type = 1;
float duration_seconds = 2;
bool is_distracted = 3;
int gaze_zone = 4;
float head_yaw = 5;
float head_pitch = 6;
uint64 timestamp_ms = 7;
}

// ========== Distraction Detector Calculator ==========
class DistractionDetectorCalculator : public CalculatorBase {
public:
static absl::Status GetContract(CalculatorContract* cc) {
cc->Inputs().Tag("GAZE").Set<GazeVector>();
cc->Inputs().Tag("ZONE").Set<int>();
cc->Inputs().Tag("POSE").Set<HeadPose>();
cc->Outputs().Tag("DISTRACTION_RESULT").Set<DistractionResult>();
cc->Outputs().Tag("ALERT").Set<bool>();

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

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

off_road_threshold_ms_ = static_cast<uint64>(options.off_road_threshold_seconds() * 1000);
alert_cooldown_ms_ = static_cast<uint64>(options.alert_cooldown_seconds() * 1000);

return absl::OkStatus();
}

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

const auto& gaze = cc->Inputs().Tag("GAZE").Get<GazeVector>();
int zone = cc->Inputs().Tag("ZONE").Get<int>();
const auto& pose = cc->Inputs().Tag("POSE").Get<HeadPose>();

uint64 current_time = cc->InputTimestamp().Value() / 1000;

// ========== 1. 判断是否在看前方 ==========
bool looking_forward = IsLookingForward(gaze, pose);

// ========== 2. 更新分心计时 ==========
if (looking_forward) {
// 重置计时
distraction_start_time_ = 0;
current_distraction_type_ = DistractionType::NONE;
} else {
// 开始计时
if (distraction_start_time_ == 0) {
distraction_start_time_ = current_time;
current_distraction_type_ = DetermineDistractionType(gaze, zone, pose);
}
}

// ========== 3. 计算分心持续时间 ==========
float duration_seconds = 0.0f;
if (distraction_start_time_ > 0) {
duration_seconds = static_cast<float>(current_time - distraction_start_time_) / 1000.0f;
}

// ========== 4. 判断是否需要告警 ==========
bool is_distracted = duration_seconds * 1000 >= off_road_threshold_ms_;

// 检查告警冷却
bool should_alert = is_distracted;
if (last_alert_time_ > 0 &&
(current_time - last_alert_time_) < alert_cooldown_ms_) {
should_alert = false;
}

if (should_alert) {
last_alert_time_ = current_time;
}

// ========== 5. 输出 ==========
DistractionResult result;
result.set_type(current_distraction_type_);
result.set_duration_seconds(duration_seconds);
result.set_is_distracted(is_distracted);
result.set_gaze_zone(zone);
result.set_head_yaw(pose.yaw());
result.set_head_pitch(pose.pitch());
result.set_timestamp_ms(current_time);

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

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

if (is_distracted) {
LOG(INFO) << "Distraction detected: type="
<< static_cast<int>(current_distraction_type_)
<< ", duration=" << duration_seconds << "s";
}

return absl::OkStatus();
}

private:
uint64 off_road_threshold_ms_ = 2000;
uint64 alert_cooldown_ms_ = 5000;
uint64 distraction_start_time_ = 0;
uint64 last_alert_time_ = 0;
DistractionType current_distraction_type_ = DistractionType::NONE;

bool IsLookingForward(const GazeVector& gaze, const HeadPose& pose) {
// 视线接近正中
bool gaze_forward = std::abs(gaze.x()) < 0.3f && std::abs(gaze.y()) < 0.3f;

// 头部姿态接近正中
bool head_forward = std::abs(pose.yaw()) < 15.0f && std::abs(pose.pitch()) < 15.0f;

return gaze_forward && head_forward;
}

DistractionType DetermineDistractionType(
const GazeVector& gaze, int zone, const HeadPose& pose) {
// 使用手机(低头 + 看下方)
if (gaze.y() > 0.5f && pose.pitch() > 20.0f) {
return DistractionType::PHONE_USE;
}

// 向左看
if (gaze.x() < -0.3f || pose.yaw() < -15.0f) {
return DistractionType::LOOKING_LEFT;
}

// 向右看
if (gaze.x() > 0.3f || pose.yaw() > 15.0f) {
return DistractionType::LOOKING_RIGHT;
}

// 低头
if (gaze.y() > 0.3f || pose.pitch() > 15.0f) {
return DistractionType::LOOKING_DOWN;
}

// 抬头
if (gaze.y() < -0.3f || pose.pitch() < -15.0f) {
return DistractionType::LOOKING_UP;
}

return DistractionType::UNKNOWN;
}
};

REGISTER_CALCULATOR(DistractionDetectorCalculator);

} // namespace mediapipe

#endif

三十六、总结

要点 说明
关键点 每眼 10 个点(虹膜中心 + 边界)
精度 像素级误差 < 2%
视线估计 虹膜偏移法
视线区域 7 个区域(前挡风玻璃、后视镜等)
IMS 应用 分心检测、注意力分析

下篇预告

MediaPipe 系列 33:Holistic——全身感知融合

深入讲解 Holistic 解决方案、多模态融合、IMS 全场景应用。


参考资料

  1. Google AI Edge. Face Mesh with Iris
  2. A. Kar et al. “One Millisecond Face Alignment with an Ensemble of Regression Trees”
  3. E. Wood et al. “Gaze Estimation for Human-Computer Interaction”

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


MediaPipe 系列 32:Iris Detection——虹膜定位完整指南
https://dapalm.com/2026/03/13/MediaPipe系列32-Iris-Detection:虹膜定位/
作者
Mars
发布于
2026年3月13日
许可协议