MediaPipe 系列 48:IMS OMS 架构——遗留物检测完整指南

前言:遗留物检测的重要性

48.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
┌─────────────────────────────────────────────────────────────────────────┐
遗留物检测应用场景
├─────────────────────────────────────────────────────────────────────────┤

场景一:贵重物品提醒
├── 用户下车时,提醒车内有手机/钱包
├── 避免财产损失
└── 提升用户满意度

场景二:儿童座椅检测
├── 检测后排是否有儿童座椅
├── 结合 CPD 功能提醒检查儿童
└── Euro NCAP 2026 要求

场景三:危险物品检测
├── 检测打火机、充电宝等易燃易爆物品
├── 高温天气安全提醒
└── 预防安全隐患

场景四:宠物检测
├── 检测车内是否有宠物遗留
├── 高温天气宠物安全
└── 动物保护法规合规

└─────────────────────────────────────────────────────────────────────────┘

48.2 检测目标分类

物品类型 优先级 典型物品 检测难度
贵重物品 🔴 高 手机、钱包、电脑
儿童座椅 🔴 高 婴儿座椅、安全座椅
易燃易爆 🟡 中 打火机、充电宝
宠物 🟡 中 狗、猫
日常物品 🟢 低 购物袋、衣物、水杯

四十九、检测方法概述

49.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
┌─────────────────────────────────────────────────────────────────────────┐
│ 遗留物检测方法对比 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ 方法一:单帧物体检测 │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 原理:每帧独立检测物体 │ │
│ │ 优点:简单、实时 │ │
│ │ 缺点:无法区分"新遗留""一直存在" │ │
│ │ 适用:快速检测车内物品 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 方法二:时序变化检测 │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 原理:对比乘员进出前后的物品变化 │ │
│ │ 优点:能准确识别"遗留" │ │
│ │ 缺点:需要乘员状态检测 │ │
│ │ 适用:下车提醒 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 方法三:多摄像头融合 │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 原理:多个摄像头协同检测 │ │
│ │ 优点:覆盖更广、遮挡少 │ │
│ │ 缺点:成本高、标定复杂 │ │
│ │ 适用:高端车型 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 推荐方案:时序变化检测 + 多摄像头(可选) │
│ │
└─────────────────────────────────────────────────────────────────────────┘

49.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
┌─────────────────────────────────────────────────────────────────────────┐
│ 遗留物检测流程 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ 输入:OMS Camera(后排摄像头) │
│ │ │
│ ▼ │
│ ┌─────────────────┐ │
│ │ Occupancy │ → 检测乘员状态 │
│ │ Detection │ 有人/无人/进出 │
│ └─────────────────┘ │
│ │ │
│ ├──────────────────────────────────────┐ │
│ │ │ │
│ ▼ ▼ │
│ ┌─────────────────┐ ┌─────────────────┐ │
│ │ Object │ │ 记录当前物品 │ │
│ │ Detection │ │ (乘员进入时) │ │
│ │ (持续检测) │ └─────────────────┘ │
│ └─────────────────┘ │ │
│ │ │ │
│ ▼ ▼ │
│ ┌─────────────────┐ ┌─────────────────┐ │
│ │ 物品列表 │ │ 比较物品变化 │ │
│ │ (当前帧) │ │ (乘员离开时) │ │
│ └─────────────────┘ └─────────────────┘ │
│ │ │ │
│ └──────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────┐ │
│ │ Left Item │ → 输出遗留物列表 │
│ │ Detection │ 发送告警 │
│ └─────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────┘

五十、物体检测模型

50.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
// cabin_object_detection.h
// 车内物体检测模型配置

namespace mediapipe {
namespace oms {

// ========== 车内物体类别 ==========
enum CabinObjectClass {
UNKNOWN = 0,

// 贵重物品
PHONE = 1,
WALLET = 2,
LAPTOP = 3,
BAG = 4,

// 儿童相关
CHILD_SEAT = 10,
TOY = 11,

// 日常物品
BOTTLE = 20,
CUP = 21,
UMBRELLA = 22,

// 危险物品
LIGHTER = 30,
POWER_BANK = 31,

// 宠物
DOG = 40,
CAT = 41,

// 最大类别数
MAX_CLASSES = 50
};

// ========== 类别配置 ==========
struct ObjectClassConfig {
CabinObjectClass class_id;
std::string name;
float priority; // 优先级 [0, 1]
float min_confidence; // 最小置信度
bool alert_enabled; // 是否启用告警
};

const std::vector<ObjectClassConfig> kCabinObjectClasses = {
// 贵重物品
{PHONE, "phone", 1.0f, 0.5f, true},
{WALLET, "wallet", 1.0f, 0.5f, true},
{LAPTOP, "laptop", 1.0f, 0.6f, true},
{BAG, "bag", 0.8f, 0.5f, true},

// 儿童相关
{CHILD_SEAT, "child_seat", 1.0f, 0.7f, true},
{TOY, "toy", 0.5f, 0.5f, false},

// 日常物品
{BOTTLE, "bottle", 0.6f, 0.5f, false},
{CUP, "cup", 0.6f, 0.5f, false},
{UMBRELLA, "umbrella", 0.5f, 0.5f, false},

// 危险物品
{LIGHTER, "lighter", 0.9f, 0.6f, true},
{POWER_BANK, "power_bank", 0.8f, 0.5f, true},

// 宠物
{DOG, "dog", 1.0f, 0.7f, true},
{CAT, "cat", 1.0f, 0.7f, true},
};

} // namespace oms
} // namespace mediapipe

50.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
// cabin_object_detection_calculator.cc

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

namespace mediapipe {

class CabinObjectDetectionCalculator : public CalculatorBase {
public:
static absl::Status GetContract(CalculatorContract* cc) {
cc->Inputs().Tag("IMAGE").Set<ImageFrame>();
cc->Outputs().Tag("DETECTIONS").Set<std::vector<Detection>>();
cc->Options<CabinObjectDetectionOptions>();
return absl::OkStatus();
}

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

// 加载模型
model_ = tflite::FlatBufferModel::BuildFromFile(
options.model_path().c_str());

interpreter_ = std::make_unique<tflite::Interpreter>();
tflite::InterpreterBuilder(*model_)(*interpreter_);
interpreter_->AllocateTensors();

// 配置
score_threshold_ = options.score_threshold();
max_results_ = options.max_results();

return absl::OkStatus();
}

absl::Status Process(CalculatorContext* cc) override {
const auto& image = cc->Inputs().Tag("IMAGE").Get<ImageFrame>();

// ========== 预处理 ==========
// 缩放到模型输入尺寸
cv::Mat input_mat = ConvertImageFrameToMat(image);
cv::Mat resized;
cv::resize(input_mat, resized, cv::Size(320, 320));

// 归一化
resized.convertTo(resized, CV_32FC3, 1.0 / 255.0);

// 复制到输入 tensor
float* input_data = interpreter_->input_tensor(0)->data.f;
std::memcpy(input_data, resized.data, 320 * 320 * 3 * sizeof(float));

// ========== 推理 ==========
TfLiteStatus status = interpreter_->Invoke();
if (status != kTfLiteOk) {
LOG(ERROR) << "Inference failed";
return absl::InternalError("Inference failed");
}

// ========== 后处理 ==========
auto detections = std::make_unique<std::vector<Detection>>();

// 输出格式: [num_boxes, boxes, classes, scores]
int num_boxes = interpreter_->output_tensor(0)->data.f[0];
const float* boxes = interpreter_->output_tensor(1)->data.f;
const float* classes = interpreter_->output_tensor(2)->data.f;
const float* scores = interpreter_->output_tensor(3)->data.f;

for (int i = 0; i < num_boxes; ++i) {
float score = scores[i];

if (score < score_threshold_) {
continue;
}

int class_id = static_cast<int>(classes[i]);

// 检查是否为关注类别
const auto& config = oms::kCabinObjectClasses[class_id];
if (score < config.min_confidence) {
continue;
}

Detection detection;
detection.set_score(score);
detection.set_label_id(class_id);
detection.set_label(config.name);

// 边界框(归一化坐标)
auto* bbox = detection.mutable_location_data()->mutable_relative_bounding_box();
bbox->set_ymin(boxes[i * 4 + 0]);
bbox->set_xmin(boxes[i * 4 + 1]);
bbox->set_ymax(boxes[i * 4 + 2]);
bbox->set_xmax(boxes[i * 4 + 3]);
bbox->set_width(bbox->xmax() - bbox->xmin());
bbox->set_height(bbox->ymax() - bbox->ymin());

detections->push_back(detection);
}

// 按分数排序
std::sort(detections->begin(), detections->end(),
[](const Detection& a, const Detection& b) {
return a.score() > b.score();
});

// 限制数量
if (detections->size() > max_results_) {
detections->resize(max_results_);
}

cc->Outputs().Tag("DETECTIONS").Add(detections.release(),
cc->InputTimestamp());

return absl::OkStatus();
}

private:
std::unique_ptr<tflite::FlatBufferModel> model_;
std::unique_ptr<tflite::Interpreter> interpreter_;
float score_threshold_ = 0.5f;
int max_results_ = 20;
};

REGISTER_CALCULATOR(CabinObjectDetectionCalculator);

} // namespace mediapipe

五十一、遗留物检测 Calculator

51.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
// occupancy_state.proto

syntax = "proto2";

package mediapipe;

message OccupancyState {
enum State {
UNKNOWN = 0;
EMPTY = 1; // 车内无人
OCCUPIED = 2; // 车内有人
ENTERING = 3; // 乘员进入中
EXITING = 4; // 乘员离开中
}

State state = 1;

// 各座位状态
repeated bool seat_occupied = 2; // [FL, FR, RL, RR]

// 置信度
float confidence = 3;

uint64 timestamp_ms = 4;
}

51.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
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
// left_item_detector_calculator.h
#ifndef MEDIAPIPE_CALCULATORS_OMS_LEFT_ITEM_DETECTOR_H_
#define MEDIAPIPE_CALCULATORS_OMS_LEFT_ITEM_DETECTOR_H_

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

namespace mediapipe {

// ========== 遗留物消息 ==========
message LeftItem {
Detection item = 1;
uint64 first_detected_ms = 2;
uint64 duration_ms = 3;
}

message LeftItemResult {
repeated LeftItem items = 1;
bool has_high_priority_items = 2;
bool has_pet = 3;
bool has_child_seat = 4;
uint64 timestamp_ms = 5;
}

// ========== Left Item Detector Calculator ==========
class LeftItemDetectorCalculator : public CalculatorBase {
public:
static absl::Status GetContract(CalculatorContract* cc) {
cc->Inputs().Tag("DETECTIONS").Set<std::vector<Detection>>();
cc->Inputs().Tag("OCCUPANCY").Set<OccupancyState>();
cc->Outputs().Tag("LEFT_ITEMS").Set<LeftItemResult>();
cc->Outputs().Tag("ALERT").Set<bool>();
cc->Options<LeftItemDetectorOptions>();
return absl::OkStatus();
}

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

// 配置
min_duration_ms_ = options.min_duration_ms();
enable_pet_detection_ = options.enable_pet_detection();
enable_child_seat_detection_ = options.enable_child_seat_detection();

return absl::OkStatus();
}

absl::Status Process(CalculatorContext* cc) override {
uint64 current_time = cc->InputTimestamp().Value() / 1000;

// ========== 获取输入 ==========
std::vector<Detection> current_detections;
if (!cc->Inputs().Tag("DETECTIONS").IsEmpty()) {
current_detections =
cc->Inputs().Tag("DETECTIONS").Get<std::vector<Detection>>();
}

OccupancyState occupancy;
if (!cc->Inputs().Tag("OCCUPANCY").IsEmpty()) {
occupancy = cc->Inputs().Tag("OCCUPANCY").Get<OccupancyState>();
}

// ========== 状态机逻辑 ==========
switch (occupancy.state()) {
case OccupancyState::OCCUPIED:
// 乘员在车内,记录当前物品(作为基准)
UpdateItemBaseline(current_detections, current_time);
in_exit_phase_ = false;
break;

case OccupancyState::EXITING:
// 乘员正在离开,进入检测阶段
in_exit_phase_ = true;
exit_start_time_ = current_time;
break;

case OccupancyState::EMPTY:
// 车内无人,检测遗留物
if (in_exit_phase_) {
DetectLeftItems(current_detections, current_time);
}
break;

default:
break;
}

// ========== 输出结果 ==========
LeftItemResult result;
result.set_timestamp_ms(current_time);

for (const auto& item : left_items_) {
*result.add_items() = item;

// 检查高优先级物品
int class_id = item.item().label_id();
if (class_id == oms::PHONE || class_id == oms::WALLET ||
class_id == oms::LAPTOP) {
result.set_has_high_priority_items(true);
}

// 检查宠物
if (class_id == oms::DOG || class_id == oms::CAT) {
result.set_has_pet(true);
}

// 检查儿童座椅
if (class_id == oms::CHILD_SEAT) {
result.set_has_child_seat(true);
}
}

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

// 告警判断
bool alert = !left_items_.empty() &&
(result.has_high_priority_items() ||
result.has_pet() ||
(result.has_child_seat() && enable_child_seat_detection_));

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

return absl::OkStatus();
}

private:
// 配置
uint64 min_duration_ms_ = 5000;
bool enable_pet_detection_ = true;
bool enable_child_seat_detection_ = true;

// 状态
bool in_exit_phase_ = false;
uint64 exit_start_time_ = 0;

// 物品基准(乘员进入时的物品列表)
std::vector<Detection> baseline_items_;
uint64 baseline_time_ = 0;

// 遗留物列表
std::vector<LeftItem> left_items_;

void UpdateItemBaseline(const std::vector<Detection>& detections,
uint64 timestamp) {
// 更新基准物品列表
baseline_items_ = detections;
baseline_time_ = timestamp;
}

void DetectLeftItems(const std::vector<Detection>& current_detections,
uint64 timestamp) {
// 检测遗留物
// 遗留物定义:在基准中存在,且在当前帧仍然存在的物品

left_items_.clear();

for (const auto& baseline_item : baseline_items_) {
// 在当前帧中查找匹配
for (const auto& current_item : current_detections) {
if (IsSameObject(baseline_item, current_item)) {
// 物品仍然存在,可能是遗留物
LeftItem left_item;
*left_item.mutable_item() = current_item;
left_item.set_first_detected_ms(baseline_time_);
left_item.set_duration_ms(timestamp - exit_start_time_);

left_items_.push_back(left_item);
break;
}
}
}

// 过滤短期物品(可能是误检)
left_items_.erase(
std::remove_if(left_items_.begin(), left_items_.end(),
[this, timestamp](const LeftItem& item) {
return item.duration_ms() < min_duration_ms_;
}),
left_items_.end());
}

bool IsSameObject(const Detection& a, const Detection& b) {
// IoU 匹配
const auto& bbox_a = a.location_data().relative_bounding_box();
const auto& bbox_b = b.location_data().relative_bounding_box();

// 类别相同
if (a.label_id() != b.label_id()) {
return false;
}

// 计算 IoU
float x1 = std::max(bbox_a.xmin(), bbox_b.xmin());
float y1 = std::max(bbox_a.ymin(), bbox_b.ymin());
float x2 = std::min(bbox_a.xmin() + bbox_a.width(),
bbox_b.xmin() + bbox_b.width());
float y2 = std::min(bbox_a.ymin() + bbox_a.height(),
bbox_b.ymin() + bbox_b.height());

if (x2 <= x1 || y2 <= y1) {
return false;
}

float intersection = (x2 - x1) * (y2 - y1);
float area_a = bbox_a.width() * bbox_a.height();
float area_b = bbox_b.width() * bbox_b.height();
float union_area = area_a + area_b - intersection;

float iou = intersection / union_area;

return iou > 0.5f;
}
};

REGISTER_CALCULATOR(LeftItemDetectorCalculator);

} // namespace mediapipe

#endif // MEDIAPIPE_CALCULATORS_OMS_LEFT_ITEM_DETECTOR_H_

五十二、Graph 配置

52.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
# ims_oms_left_item_detection_graph.pbtxt

input_stream: "OMS_IMAGE:oms_image"
output_stream: "LEFT_ITEMS:left_items"
output_stream: "ALERT:alert"

# ========== 1. 乘员检测 ==========
node {
calculator: "OccupancyDetectionCalculator"
input_stream: "IMAGE:oms_image"
output_stream: "OCCUPANCY:occupancy"
options {
[mediapipe.OccupancyDetectionOptions.ext] {
min_confidence: 0.7
}
}
}

# ========== 2. 物体检测 ==========
node {
calculator: "CabinObjectDetectionCalculator"
input_stream: "IMAGE:oms_image"
output_stream: "DETECTIONS:detections"
options {
[mediapipe.CabinObjectDetectionOptions.ext] {
model_path: "/models/cabin_objects.tflite"
score_threshold: 0.5
max_results: 20
}
}
}

# ========== 3. 遗留物检测 ==========
node {
calculator: "LeftItemDetectorCalculator"
input_stream: "DETECTIONS:detections"
input_stream: "OCCUPANCY:occupancy"
output_stream: "LEFT_ITEMS:left_items"
output_stream: "ALERT:alert"
options {
[mediapipe.LeftItemDetectorOptions.ext] {
min_duration_ms: 3000
enable_pet_detection: true
enable_child_seat_detection: true
}
}
}

# ========== 4. 告警管理 ==========
node {
calculator: "LeftItemAlertManagerCalculator"
input_stream: "LEFT_ITEMS:left_items"
input_stream: "ALERT:raw_alert"
output_stream: "MANAGED_ALERT:final_alert"
options {
[mediapipe.LeftItemAlertManagerOptions.ext] {
# 温度相关告警(宠物/儿童)
temperature_threshold_celsius: 25.0
# 冷却时间
cooldown_ms: 30000
}
}
}

五十三、总结

要点 说明
检测目标 贵重物品、儿童座椅、宠物、危险物品
检测方法 时序变化检测 + 物体识别
状态关联 结合乘员进出状态判断遗留
告警策略 分优先级、温度相关告警

下篇预告

MediaPipe 系列 49:IMS OMS 架构——安全带检测

深入讲解安全带检测、错误佩戴检测、Euro NCAP 2026 要求。


参考资料

  1. Euro NCAP. “Child Presence Detection” (2026)
  2. MediaPipe. Object Detection

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


MediaPipe 系列 48:IMS OMS 架构——遗留物检测完整指南
https://dapalm.com/2026/03/13/MediaPipe系列48-IMS-OMS架构:遗留物检测/
作者
Mars
发布于
2026年3月13日
许可协议