MediaPipe 系列 19:条件分支 Calculator——动态路由完整指南

前言:为什么需要条件分支?

19.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
┌─────────────────────────────────────────────────────────────────────────┐
│ 条件执行的重要性 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ 问题:如何根据条件选择性地执行处理流程? │
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ IMS DMS 场景: │ │
│ │ │ │
│ │ • 没有检测到人脸 → 不执行关键点检测 │ │
│ │ • 白天场景 → 使用标准检测器 │ │
│ │ • 夜间场景 → 使用红外增强检测器 │ │
│ │ • 逆光场景 → 使用 HDR 处理 │ │
│ │ • 帧率低于 15 FPS → 跳过部分处理 │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ 解决方案:条件分支 Calculator │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ 1. Gate Calculator │ │
│ │ • 允许/阻塞数据流 │ │
│ │ • 条件为 true 通过,false 阻塞 │ │
│ │ │ │
│ │ 2. Switch Calculator │ │
│ │ • 多分支选择 │ │
│ │ • 根据索引选择输出 │ │
│ │ │ │
│ │ 3. Mux/Demux Calculator │ │
│ │ • 多路复用/解复用 │ │
│ │ • 动态路由 │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────┘

19.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
┌─────────────────────────────────────────────────────────────┐
│ 条件分支类型 │
├─────────────────────────────────────────────────────────────┤
│ │
1. Gate(门控) │
│ ┌─────────────────────────────────────────────┐ │
│ │ │ │
│ │ INPUT ──▶ [Gate] ──▶ OUTPUT (allow=true) │ │
│ │ │ │ │
│ │ └──▶ 阻塞 (allow=false) │ │
│ │ │ │
│ │ 用途:条件执行、性能优化 │ │
│ │ │ │
│ └─────────────────────────────────────────────┘ │
│ │
2. Switch(选择) │
│ ┌─────────────────────────────────────────────┐ │
│ │ │ │
│ │ INPUT ──▶ [Switch] ──▶ Branch A (sel=0) │ │
│ │ │──▶ Branch B (sel=1) │ │
│ │ └──▶ Branch C (sel=2) │ │
│ │ │ │
│ │ 用途:多分支选择、动态算法切换 │ │
│ │ │ │
│ └─────────────────────────────────────────────┘ │
│ │
3. Mux(多路复用) │
│ ┌─────────────────────────────────────────────┐ │
│ │ │ │
│ │ Input A ──┐ │ │
│ │ Input B ──┼──▶ [Mux] ──▶ Output │ │
│ │ Input C ──┘ │ │
│ │ │ │
│ │ 用途:合并多个流、选择输出 │ │
│ │ │ │
│ └─────────────────────────────────────────────┘ │
│ │
4. Demux(解复用) │
│ ┌─────────────────────────────────────────────┐ │
│ │ │ │
│ │ ┌──▶ Output A │ │
│ │ Input ──▶ [Demux] ──┼──▶ Output B │ │
│ │ └──▶ Output C │ │
│ │ │ │
│ │ 用途:分发数据到多个分支 │ │
│ │ │ │
│ └─────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘

二十、Gate Calculator

20.1 内置 GateCalculator

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
// ========== MediaPipe 内置 GateCalculator ==========
// mediapipe/calculators/core/gate_calculator.cc

// 功能:根据条件允许或阻塞数据流

// ========== Proto Options ==========
/*
syntax = "proto3";
package mediapipe;

message GateCalculatorOptions {
// 初始状态
enum State {
UNINITIALIZED = 0; // 未初始化
ALLOW = 1; // 允许通过
DISALLOW = 2; // 阻塞
}
optional State initial_state = 1 [default = UNINITIALIZED];

// 是否允许空条件
optional bool allow_empty_condition = 2 [default = false];

// 空条件时的行为
optional State empty_state = 3 [default = DISALLOW];
}
*/

// ========== Graph 配置示例 ==========

# 基本 Gate 用法
node {
calculator: "GateCalculator"
input_stream: "INPUT:data"
input_stream: "ALLOW:condition" # bool 类型
output_stream: "OUTPUT:gated_data"
options {
[mediapipe.GateCalculatorOptions.ext] {
initial_state: UNINITIALIZED
allow_empty_condition: false
}
}
}

# 多输入 Gate
node {
calculator: "GateCalculator"
input_stream: "INPUT:0:data_a"
input_stream: "INPUT:1:data_b"
input_stream: "ALLOW:condition"
output_stream: "OUTPUT:0:gated_a"
output_stream: "OUTPUT:1:gated_b"
}

# 反向 Gate(条件为 false 时通过)
node {
calculator: "GateCalculator"
input_stream: "INPUT:data"
input_stream: "DISALLOW:condition" # 注意:使用 DISALLOW 标签
output_stream: "OUTPUT:gated_data"
}

20.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
// condition_generator_calculator.h
#ifndef MEDIAPIPE_CALCULATORS_CORE_CONDITION_GENERATOR_CALCULATOR_H_
#define MEDIAPIPE_CALCULATORS_CORE_CONDITION_GENERATOR_CALCULATOR_H_

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

namespace mediapipe {

// ========== 条件生成 Calculator ==========
class ConditionGeneratorCalculator : public CalculatorBase {
public:
static absl::Status GetContract(CalculatorContract* cc) {
// 多种条件输入
cc->Inputs().Tag("DETECTIONS").Set<std::vector<Detection>>().Optional();
cc->Inputs().Tag("POSE").Set<HeadPose>().Optional();
cc->Inputs().Tag("EAR").Set<float>().Optional();

// 输出多个条件
cc->Outputs().Tag("HAS_FACE").Set<bool>();
cc->Outputs().Tag("EYES_VISIBLE").Set<bool>();
cc->Outputs().Tag("HEAD_FRONT").Set<bool>();
cc->Outputs().Tag("QUALITY_OK").Set<bool>();

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

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

min_face_size_ = options.min_face_size();
max_head_yaw_ = options.max_head_yaw();
min_ear_ = options.min_ear();
min_quality_ = options.min_quality();

return absl::OkStatus();
}

absl::Status Process(CalculatorContext* cc) override {
// ========== 1. 检测是否有有效人脸 ==========
bool has_face = false;

if (!cc->Inputs().Tag("DETECTIONS").IsEmpty()) {
const auto& detections =
cc->Inputs().Tag("DETECTIONS").Get<std::vector<Detection>>();

// 检查是否有足够大的人脸
for (const auto& det : detections) {
float width = det.xmax() - det.xmin();
float height = det.ymax() - det.ymin();

if (width > min_face_size_ && height > min_face_size_) {
has_face = true;
break;
}
}
}

// ========== 2. 检查眼睛是否可见 ==========
bool eyes_visible = false;

if (!cc->Inputs().Tag("EAR").IsEmpty()) {
float ear = cc->Inputs().Tag("EAR").Get<float>();
eyes_visible = (ear > min_ear_);
}

// ========== 3. 检查头部是否正面 ==========
bool head_front = false;

if (!cc->Inputs().Tag("POSE").IsEmpty()) {
const HeadPose& pose = cc->Inputs().Tag("POSE").Get<HeadPose>();
head_front = (std::abs(pose.yaw()) < max_head_yaw_);
}

// ========== 4. 检查图像质量 ==========
bool quality_ok = has_face && eyes_visible;

// ========== 5. 输出条件 ==========
cc->Outputs().Tag("HAS_FACE").AddPacket(
MakePacket<bool>(has_face).At(cc->InputTimestamp()));

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

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

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

return absl::OkStatus();
}

private:
float min_face_size_ = 0.1f;
float max_head_yaw_ = 30.0f;
float min_ear_ = 0.15f;
float min_quality_ = 0.5f;
};

REGISTER_CALCULATOR(ConditionGeneratorCalculator);

} // namespace mediapipe

#endif // MEDIAPIPE_CALCULATORS_CORE_CONDITION_GENERATOR_CALCULATOR_H_

二十一、Switch Calculator

21.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
// switch_calculator.h
#ifndef MEDIAPIPE_CALCULATORS_CORE_SWITCH_CALCULATOR_H_
#define MEDIAPIPE_CALCULATORS_CORE_SWITCH_CALCULATOR_H_

#include "mediapipe/framework/calculator_framework.h"

namespace mediapipe {

// ========== Proto Options ==========
/*
syntax = "proto3";
package mediapipe;

message SwitchCalculatorOptions {
// 输出模式
enum OutputMode {
SELECTED_ONLY = 0; // 只输出选中的
ALL_WITH_DEFAULT = 1; // 所有输出,未选中用默认值
}
optional OutputMode output_mode = 1 [default = SELECTED_ONLY];
}
*/

// ========== Switch Calculator ==========
template <typename T>
class SwitchCalculator : public CalculatorBase {
public:
static absl::Status GetContract(CalculatorContract* cc) {
cc->Inputs().Tag("SELECT").Set<int>();

// 可变数量输入
for (int i = 0; i < cc->Inputs().NumEntries("INPUT"); ++i) {
cc->Inputs().Get("INPUT", i).Set<T>();
}

cc->Outputs().Tag("OUTPUT").Set<T>();
cc->Options<SwitchCalculatorOptions>();
return absl::OkStatus();
}

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

num_inputs_ = cc->Inputs().NumEntries("INPUT");

LOG(INFO) << "SwitchCalculator initialized: num_inputs=" << num_inputs_;

return absl::OkStatus();
}

absl::Status Process(CalculatorContext* cc) override {
// ========== 1. 获取选择索引 ==========
if (cc->Inputs().Tag("SELECT").IsEmpty()) {
return absl::OkStatus();
}

int select = cc->Inputs().Tag("SELECT").Get<int>();

// ========== 2. 验证索引范围 ==========
if (select < 0 || select >= num_inputs_) {
LOG(WARNING) << "Invalid switch index: " << select
<< ", valid range: [0, " << num_inputs_ - 1 << "]";
return absl::OkStatus();
}

// ========== 3. 获取选中输入 ==========
if (cc->Inputs().Get("INPUT", select).IsEmpty()) {
return absl::OkStatus();
}

const T& selected = cc->Inputs().Get("INPUT", select).Get<T>();

// ========== 4. 输出 ==========
cc->Outputs().Tag("OUTPUT").AddPacket(
MakePacket<T>(selected).At(cc->InputTimestamp()));

return absl::OkStatus();
}

private:
int num_inputs_ = 0;
SwitchCalculatorOptions::OutputMode output_mode_ =
SwitchCalculatorOptions::SELECTED_ONLY;
};

REGISTER_CALCULATOR(SwitchCalculator);

// ========== 模板实例化 ==========
extern template class SwitchCalculator<std::vector<Detection>>;
extern template class SwitchCalculator<float>;

} // namespace mediapipe

#endif // MEDIAPIPE_CALCULATORS_CORE_SWITCH_CALCULATOR_H_

21.2 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
# switch_graph.pbtxt

# 场景分类
node {
calculator: "SceneClassifierCalculator"
input_stream: "IMAGE:image"
output_stream: "SCENE:scene_id" # 0=白天, 1=夜晚, 2=逆光
}

# 多种算法并行执行
node {
calculator: "DaytimeFaceDetector"
input_stream: "IMAGE:image"
output_stream: "DETECTIONS:detections_day"
}

node {
calculator: "NightFaceDetector"
input_stream: "IMAGE:image"
output_stream: "DETECTIONS:detections_night"
}

node {
calculator: "BacklightFaceDetector"
input_stream: "IMAGE:image"
output_stream: "DETECTIONS:detections_backlight"
}

# 动态选择
node {
calculator: "SwitchCalculator<std::vector<Detection>>"
input_stream: "SELECT:scene_id"
input_stream: "INPUT:0:detections_day"
input_stream: "INPUT:1:detections_night"
input_stream: "INPUT:2:detections_backlight"
output_stream: "OUTPUT:final_detections"
}

二十二、Mux Calculator

22.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
// mux_calculator.h
#ifndef MEDIAPIPE_CALCULATORS_CORE_MUX_CALCULATOR_H_
#define MEDIAPIPE_CALCULATORS_CORE_MUX_CALCULATOR_H_

#include "mediapipe/framework/calculator_framework.h"

namespace mediapipe {

// ========== Mux Calculator ==========
// 从多个输入中选择一个输出

template <typename T>
class MuxCalculator : public CalculatorBase {
public:
static absl::Status GetContract(CalculatorContract* cc) {
// 可变数量输入
for (int i = 0; i < cc->Inputs().NumEntries("INPUT"); ++i) {
cc->Inputs().Get("INPUT", i).Set<T>();
}

cc->Outputs().Tag("OUTPUT").Set<T>();
cc->Options<MuxCalculatorOptions>();
return absl::OkStatus();
}

absl::Status Open(CalculatorContext* cc) override {
num_inputs_ = cc->Inputs().NumEntries("INPUT");
return absl::OkStatus();
}

absl::Status Process(CalculatorContext* cc) override {
// 遍历所有输入,选择第一个非空的
for (int i = 0; i < num_inputs_; ++i) {
if (!cc->Inputs().Get("INPUT", i).IsEmpty()) {
const T& data = cc->Inputs().Get("INPUT", i).Get<T>();

cc->Outputs().Tag("OUTPUT").AddPacket(
MakePacket<T>(data).At(cc->InputTimestamp()));

return absl::OkStatus();
}
}

return absl::OkStatus();
}

private:
int num_inputs_ = 0;
};

REGISTER_CALCULATOR(MuxCalculator);

} // namespace mediapipe

#endif // MEDIAPIPE_CALCULATORS_CORE_MUX_CALCULATOR_H_

二十三、IMS 实战:动态算法选择

23.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
# ims_adaptive_detection_graph.pbtxt

input_stream: "IR_IMAGE:ir_image"
output_stream: "DETECTIONS:detections"
output_stream: "SCENE:scene_info"

# ========== 步骤 1:场景分析 ==========
node {
calculator: "SceneAnalyzerCalculator"
input_stream: "IMAGE:ir_image"
output_stream: "SCENE_ID:scene_id"
output_stream: "SCENE_INFO:scene_info"
output_stream: "BRIGHTNESS:brightness"
output_stream: "CONTRAST:contrast"
}

# ========== 步骤 2:多种检测器并行 ==========
# 白天模式(标准检测)
node {
calculator: "StandardFaceDetector"
input_stream: "IMAGE:ir_image"
output_stream: "DETECTIONS:detections_standard"
executor: "gpu_executor"
}

# 夜间模式(增强检测)
node {
calculator: "EnhancedFaceDetector"
input_stream: "IMAGE:ir_image"
output_stream: "DETECTIONS:detections_enhanced"
options {
[mediapipe.FaceDetectorOptions.ext] {
enhance_contrast: true
adaptive_threshold: true
}
}
executor: "gpu_executor"
}

# 极端模式(HDR 检测)
node {
calculator: "HDRFaceDetector"
input_stream: "IMAGE:ir_image"
output_stream: "DETECTIONS:detections_hdr"
options {
[mediapipe.FaceDetectorOptions.ext] {
hdr_mode: true
multi_scale: true
}
}
executor: "gpu_executor"
}

# ========== 步骤 3:动态选择 ==========
node {
calculator: "SwitchCalculator<std::vector<Detection>>"
input_stream: "SELECT:scene_id"
input_stream: "INPUT:0:detections_standard"
input_stream: "INPUT:1:detections_enhanced"
input_stream: "INPUT:2:detections_hdr"
output_stream: "OUTPUT:detections"
}

# ========== 步骤 4:Gate 控制 ==========
# 只有检测到人脸才执行后续处理
node {
calculator: "ConditionGeneratorCalculator"
input_stream: "DETECTIONS:detections"
output_stream: "HAS_FACE:has_face"
}

node {
calculator: "GateCalculator"
input_stream: "INPUT:detections"
input_stream: "ALLOW:has_face"
output_stream: "OUTPUT:valid_detections"
}

# ========== 步骤 5:后续处理 ==========
# 关键点检测(仅当有人脸时)
node {
calculator: "LandmarkDetectorCalculator"
input_stream: "IMAGE:ir_image"
input_stream: "DETECTIONS:valid_detections"
output_stream: "LANDMARKS:landmarks"
}

23.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
// scene_analyzer_calculator.h
#ifndef MEDIAPIPE_CALCULATORS_IMS_SCENE_ANALYZER_CALCULATOR_H_
#define MEDIAPIPE_CALCULATORS_IMS_SCENE_ANALYZER_CALCULATOR_H_

#include "mediapipe/framework/calculator_framework.h"
#include "mediapipe/framework/formats/image_frame.h"
#include "mediapipe/framework/formats/image_frame_opencv.h"

namespace mediapipe {

// ========== Proto Options ==========
/*
syntax = "proto3";
package mediapipe;

message SceneAnalyzerOptions {
// 场景阈值
optional float dark_threshold = 1 [default = 50.0];
optional float bright_threshold = 2 [default = 200.0];
optional float low_contrast_threshold = 3 [default = 30.0];
optional float high_contrast_threshold = 4 [default = 100.0];
}
*/

// ========== 场景信息 ==========
message SceneInfo {
int32 scene_id = 1; // 0=白天, 1=夜晚, 2=逆光, 3=极端
float brightness = 2; // 平均亮度
float contrast = 3; // 对比度
string scene_name = 4; // 场景名称
}

// ========== 场景分析 Calculator ==========
class SceneAnalyzerCalculator : public CalculatorBase {
public:
static absl::Status GetContract(CalculatorContract* cc) {
cc->Inputs().Tag("IMAGE").Set<ImageFrame>();

cc->Outputs().Tag("SCENE_ID").Set<int>();
cc->Outputs().Tag("SCENE_INFO").Set<SceneInfo>();
cc->Outputs().Tag("BRIGHTNESS").Set<float>();
cc->Outputs().Tag("CONTRAST").Set<float>();

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

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

dark_threshold_ = options.dark_threshold();
bright_threshold_ = options.bright_threshold();
low_contrast_threshold_ = options.low_contrast_threshold();
high_contrast_threshold_ = options.high_contrast_threshold();

return absl::OkStatus();
}

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

const ImageFrame& image = cc->Inputs().Tag("IMAGE").Get<ImageFrame>();
cv::Mat mat = formats::MatView(&image);

// ========== 1. 计算亮度 ==========
cv::Mat gray;
if (mat.channels() == 3) {
cv::cvtColor(mat, gray, cv::COLOR_RGB2GRAY);
} else {
gray = mat;
}

double brightness = cv::mean(gray)[0];

// ========== 2. 计算对比度 ==========
cv::Mat std_dev;
cv::meanStdDev(gray, cv::noArray(), std_dev);
double contrast = std_dev.at<double>(0);

// ========== 3. 判断场景 ==========
int scene_id = 0; // 默认白天
std::string scene_name = "daytime";

if (brightness < dark_threshold_) {
scene_id = 1;
scene_name = "night";
} else if (brightness > bright_threshold_) {
scene_id = 2;
scene_name = "backlight";
} else if (contrast < low_contrast_threshold_) {
scene_id = 3;
scene_name = "low_contrast";
} else if (contrast > high_contrast_threshold_) {
scene_id = 4;
scene_name = "high_contrast";
}

// ========== 4. 创建场景信息 ==========
SceneInfo scene_info;
scene_info.set_scene_id(scene_id);
scene_info.set_brightness(static_cast<float>(brightness));
scene_info.set_contrast(static_cast<float>(contrast));
scene_info.set_scene_name(scene_name);

// ========== 5. 输出 ==========
cc->Outputs().Tag("SCENE_ID").AddPacket(
MakePacket<int>(scene_id).At(cc->InputTimestamp()));

cc->Outputs().Tag("SCENE_INFO").AddPacket(
MakePacket<SceneInfo>(scene_info).At(cc->InputTimestamp()));

cc->Outputs().Tag("BRIGHTNESS").AddPacket(
MakePacket<float>(static_cast<float>(brightness)).At(cc->InputTimestamp()));

cc->Outputs().Tag("CONTRAST").AddPacket(
MakePacket<float>(static_cast<float>(contrast)).At(cc->InputTimestamp()));

return absl::OkStatus();
}

private:
float dark_threshold_ = 50.0f;
float bright_threshold_ = 200.0f;
float low_contrast_threshold_ = 30.0f;
float high_contrast_threshold_ = 100.0f;
};

REGISTER_CALCULATOR(SceneAnalyzerCalculator);

} // namespace mediapipe

#endif // MEDIAPIPE_CALCULATORS_IMS_SCENE_ANALYZER_CALCULATOR_H_

二十四、性能优化

24.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
┌─────────────────────────────────────────────────────────────┐
│ 懒执行优化 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 问题:上游 Calculator 仍会执行,浪费资源 │
│ │
│ 传统 Gate: │
│ ┌─────────────────────────────────────────────┐ │
│ │ [Detector] ──▶ [Gate] ──▶ [Process] │ │
│ │ ↑ │ │
│ │ 仍会执行(浪费) │ │
│ └─────────────────────────────────────────────┘ │
│ │
│ 解决方案:FlowLimiter + 反向边 │
│ ┌─────────────────────────────────────────────┐ │
│ │ [FlowLimiter] ──▶ [Detector] ──▶ [Gate] │ │
│ │ ↑ │ │ │
│ │ └─────────────────────────┘ │ │
│ │ (back edge) │ │
│ │ │ │
│ │ Gate 阻塞时,FlowLimiter 也停止输出 │ │
│ └─────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘

24.2 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
# 懒执行 Graph

# FlowLimiter + Gate 实现懒执行
node {
calculator: "FlowLimiterCalculator"
input_stream: "image"
input_stream: "processed"
input_stream_info: { tag_index: "processed" back_edge: true }
output_stream: "throttled_image"
}

# 条件判断
node {
calculator: "ConditionGeneratorCalculator"
input_stream: "DETECTIONS:detections"
output_stream: "HAS_FACE:has_face"
}

# 检测器(受 FlowLimiter 控制)
node {
calculator: "FaceDetector"
input_stream: "IMAGE:throttled_image"
output_stream: "DETECTIONS:detections"
}

# Gate
node {
calculator: "GateCalculator"
input_stream: "INPUT:detections"
input_stream: "ALLOW:has_face"
output_stream: "OUTPUT:gated_detections"
}

# 后处理
node {
calculator: "LandmarkDetector"
input_stream: "IMAGE:throttled_image"
input_stream: "DETECTIONS:gated_detections"
output_stream: "LANDMARKS:landmarks"
output_stream: "PROCESSED:processed"
}

二十五、总结

Calculator 功能 用途
GateCalculator 允许/阻塞 条件执行
SwitchCalculator 多分支选择 动态算法
MuxCalculator 多路复用 输入选择
ConditionGenerator 条件生成 状态判断
FlowLimiter 限流 懒执行

下篇预告

MediaPipe 系列 20:渲染 Calculator——可视化输出

深入讲解如何在 Calculator 中渲染检测结果、关键点、热力图。


参考资料

  1. Google AI Edge. Gate Calculator
  2. Google AI Edge. Flow Control
  3. Google AI Edge. Conditional Execution

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


MediaPipe 系列 19:条件分支 Calculator——动态路由完整指南
https://dapalm.com/2026/03/13/MediaPipe系列19-条件分支Calculator:动态路由/
作者
Mars
发布于
2026年3月13日
许可协议