TAP3D:热成像辅助3D人体点云——隐私优先的座舱感知新范式

论文解读与代码复现

论文信息

  • 标题: TAP3D: Thermal-Assisted 3D Human Point Clouds
  • 作者: Xie Zhang, Chengxiao Li, Xuan Liu, Chenshu Wu
  • 会议: IEEE INDIN 2026(24th IEEE International Conference on Industrial Informatics)
  • 时间: 2026年10月8日
  • 链接: arXiv:2610.11241
  • 代码: GitHub: aiot-lab/TAP3D

核心创新

TAP3D 首次提出从人体热特征签名重建3D人体点云的系统方案。利用低成本热阵列传感器(thermal array sensor),通过物理信息驱动的设计,将体温热信号转化为稠密3D点云,在成本、密度、人体敏感性和隐私保护方面具有显著优势。

一句话总结: 用一个低成本热阵列传感器,通过物理模型+自监督学习,把人体热量变成3D点云,首次实现隐私优先的完全被动式人体感知。

方法详解

1. 问题定义

现有3D人体感知方法存在固有缺陷:

方法 成本 密度 隐私 人体敏感性
LiDAR 高($100+) 稀疏 中 低
mmWave雷达 中($30-80) 稀疏 高 中
深度摄像头 中($50-150) 稠密 低 高
TAP3D(热阵列) 低($5-15) 稠密 高 高

TAP3D 需要解决三大核心挑战:

  1. 深度估计 — 热阵列分辨率极低(如8×8或16×16),如何恢复3D深度?
  2. 热干扰 — 环境热源(阳光、暖风)干扰人体热信号
  3. 多人分离 — 多个乘员的热信号如何区分?

2. 核心方法:物理信息驱动架构

TAP3D 系统架构包含三个核心模块:

2.1 前向热物理模型

热阵列传感器测量的是物体表面温度的2D分布。前向模型描述了3D空间中人体热源如何映射到2D热阵列平面:

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
import numpy as np
from typing import Tuple

class ForwardThermalModel:
"""
前向热物理模型

描述3D人体热源到2D热阵列平面的映射关系。
基于热辐射物理方程:
I(d) = ε * σ * T^4 / (4 * π * d^2)

其中:
- I(d): 距离d处的辐射强度
- ε: 发射率(人体≈0.98)
- σ: Stefan-Boltzmann常数 (5.67e-8 W/m²K⁴)
- T: 表面温度(K)
- d: 传感器到热源的距离
"""

STEFAN_BOLTZMANN = 5.67e-8 # W/m²K⁴
HUMAN_EMISSIVITY = 0.98

def __init__(self, sensor_resolution: Tuple[int, int] = (16, 16),
fov: Tuple[float, float] = (60.0, 45.0),
ambient_temp: float = 22.0):
self.resolution = sensor_resolution
self.fov = np.radians(fov)
self.ambient_temp = ambient_temp
self.ambient_k = ambient_temp + 273.15

# 生成传感器像素方向向量
rows, cols = sensor_resolution
v_angles = np.linspace(-self.fov[1]/2, self.fov[1]/2, rows)
h_angles = np.linspace(-self.fov[0]/2, self.fov[0]/2, cols)
self.pixel_directions = np.array([
[np.cos(v) * np.sin(h), np.sin(v), np.cos(v) * np.cos(h)]
for v in v_angles for h in h_angles
]).reshape(rows, cols, 3)

def forward(self, heat_sources: np.ndarray, depths: np.ndarray) -> np.ndarray:
"""
前向模型:从3D热源到2D热阵列读数

Args:
heat_sources: 人体表面热源点 (N, 3) [x, y, z] 坐标
depths: 各热源点到传感器距离 (N,)

Returns:
thermal_image: 2D热阵列读数 (rows, cols) 温度值
"""
rows, cols = self.resolution
thermal_image = np.full((rows, cols), self.ambient_temp)

for i, (source, depth) in enumerate(zip(heat_sources, depths)):
# 计算热源方向
direction = source / (np.linalg.norm(source) + 1e-8)

# 找到最近的传感器像素
dots = self.pixel_directions.reshape(-1, 3) @ direction
pixel_idx = np.argmax(dots)
r, c = pixel_idx // cols, pixel_idx % cols

# 热辐射强度衰减(距离平方)
source_temp_k = 34.0 + 273.15 # 人体表面~34°C
intensity = (self.HUMAN_EMISSIVITY * self.STEFAN_BOLTZMANN *
source_temp_k**4 / (4 * np.pi * max(depth**2, 0.01)))

# 转换为温度增量
temp_delta = (intensity / (self.STEFAN_BOLTZMANN * self.ambient_k**3 + 1e-8)) * 100
thermal_image[r, c] = max(thermal_image[r, c], self.ambient_temp + temp_delta)

return thermal_image

def inverse_prior(self, thermal_image: np.ndarray) -> np.ndarray:
"""
粗略逆模型:从2D热阵列读数估计深度

Args:
thermal_image: (rows, cols) 温度矩阵

Returns:
depth_map: (rows, cols) 粗略深度图
"""
# 基于温度差异估算距离
temp_diff = thermal_image - self.ambient_temp
temp_diff = np.maximum(temp_diff, 0.01)

# 逆辐射模型:d ∝ sqrt(1/ΔT)
depth_map = np.sqrt(12.0 / temp_diff) # 12.0为校准常数
depth_map = np.clip(depth_map, 0.3, 5.0)

return depth_map


# 实际测试
if __name__ == "__main__":
model = ForwardThermalModel(sensor_resolution=(16, 16))

# 模拟人体热源(简化为5个关键点)
heat_sources = np.array([
[0.0, 0.2, 1.0], # 头部
[-0.2, 0.0, 1.1], # 左肩
[0.2, 0.0, 1.1], # 右肩
[-0.15, -0.3, 1.2],# 左膝
[0.15, -0.3, 1.2], # 右膝
])
depths = np.array([1.0, 1.1, 1.1, 1.2, 1.2])

thermal_img = model.forward(heat_sources, depths)
depth_map = model.inverse_prior(thermal_img)

print(f"热阵列分辨率: {thermal_img.shape}")
print(f"最大温度: {np.max(thermal_img):.2f}°C")
print(f"环境温度: {model.ambient_temp}°C")
print(f"深度估计范围: {depth_map.min():.2f}m ~ {depth_map.max():.2f}m")

2.2 多原语估计模块(Multi-Primitive Estimation)

该模块用于自监督联合恢复深度和其他热属性。核心思想是利用热阵列读数的时空特征,通过多原语分解来恢复3D信息:

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
import torch
import torch.nn as nn

class MultiPrimitiveEstimator(nn.Module):
"""
多原语估计模块

从2D热阵列帧序列中联合估计:
- 深度图 (depth map)
- 热辐射强度 (thermal intensity)
- 运动场 (motion field)

采用自监督训练策略:
1. 前向渲染损失:重建的热图像应匹配原始输入
2. 时间一致性:相邻帧的深度估计应平滑变化
3. 稀疏性约束:深度图应在人体区域集中
"""

def __init__(self, input_channels: int = 1, hidden_dim: int = 64):
super().__init__()

# 编码器:从热阵列帧提取特征
self.encoder = nn.Sequential(
nn.Conv2d(input_channels, 32, 3, padding=1),
nn.ReLU(),
nn.Conv2d(32, hidden_dim, 3, padding=1),
nn.ReLU(),
nn.AdaptiveAvgPool2d((8, 8))
)

# 深度解码头
self.depth_head = nn.Sequential(
nn.ConvTranspose2d(hidden_dim, 32, 3, stride=2, padding=1),
nn.ReLU(),
nn.Conv2d(32, 1, 3, padding=1),
nn.Sigmoid() # 归一化到 [0, 1]
)

# 热强度解码头
self.intensity_head = nn.Sequential(
nn.ConvTranspose2d(hidden_dim, 32, 3, stride=2, padding=1),
nn.ReLU(),
nn.Conv2d(32, 1, 3, padding=1),
nn.Sigmoid()
)

# 运动场解码头(从相邻帧差分)
self.motion_head = nn.Sequential(
nn.ConvTranspose2d(hidden_dim * 2, 32, 3, stride=2, padding=1),
nn.ReLU(),
nn.Conv2d(32, 2, 3, padding=1) # 2D运动场
)

def forward(self, thermal_frame: torch.Tensor,
prev_frame: torch.Tensor = None) -> dict:
"""
Args:
thermal_frame: (B, 1, H, W) 当前热阵列帧
prev_frame: (B, 1, H, W) 上一帧(可选)

Returns:
dict containing:
- depth: (B, 1, H, W) 深度图
- intensity: (B, 1, H, W) 热辐射强度
- motion: (B, 2, H, W) 运动场
"""
feat = self.encoder(thermal_frame)

depth = self.depth_head(feat)
intensity = self.intensity_head(feat)

result = {'depth': depth, 'intensity': intensity}

if prev_frame is not None:
prev_feat = self.encoder(prev_frame)
combined = torch.cat([feat, prev_feat], dim=1)
motion = self.motion_head(combined)
result['motion'] = motion

return result


class TAP3DLoss(nn.Module):
"""
TAP3D 自监督损失函数

L = α * L_render + β * L_temporal + γ * L_sparse
"""

def __init__(self, alpha: float = 1.0, beta: float = 0.3, gamma: float = 0.1):
super().__init__()
self.alpha = alpha
self.beta = beta
self.gamma = gamma

def forward(self, predictions: dict, targets: dict) -> torch.Tensor:
# 渲染损失:重建的热图像应匹配原始输入
l_render = F.mse_loss(predictions['rendered'], targets['thermal'])

# 时间一致性损失
l_temporal = torch.tensor(0.0, device=l_render.device)
if 'prev_depth' in predictions:
l_temporal = F.smooth_l1_loss(
predictions['depth'],
predictions['prev_depth'].detach()
)

# 稀疏性约束
l_sparse = torch.norm(predictions['depth'], p=1) / predictions['depth'].numel()

return (self.alpha * l_render +
self.beta * l_temporal +
self.gamma * l_sparse)


import torch.nn.functional as F

# 测试
if __name__ == "__main__":
model = MultiPrimitiveEstimator(input_channels=1, hidden_dim=64)

# 模拟热阵列输入 (batch=1, channel=1, 16x16)
thermal = torch.randn(1, 1, 16, 16) + 22.0 # ~22°C ambient
prev_thermal = torch.randn(1, 1, 16, 16) + 22.0

output = model(thermal, prev_thermal)

print(f"输入热阵列: {thermal.shape}")
print(f"深度图: {output['depth'].shape}, 范围: [{output['depth'].min():.3f}, {output['depth'].max():.3f}]")
print(f"热辐射强度: {output['intensity'].shape}")
print(f"运动场: {output['motion'].shape}")

2.3 几何透视融合模块(Geometric Perspective Fusion)

用于抑制热干扰和分离多人:

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
class GeometricPerspectiveFusion:
"""
几何透视融合模块

通过多视角几何约束实现:
1. 热干扰抑制:利用空间几何一致性过滤环境热源
2. 多人分离:基于热特征聚类和3D空间分离

核心思想:
- 人体热源在3D空间中应形成连通的骨架结构
- 环境热源(阳光、暖风)不符合人体几何先验
- 多人可通过空间聚类分离
"""

def __init__(self, human_temp_range: tuple = (30.0, 37.0)):
self.human_temp_range = human_temp_range

def suppress_interference(self, depth_map: np.ndarray,
thermal_image: np.ndarray) -> np.ndarray:
"""
抑制环境热干扰

Args:
depth_map: (H, W) 深度图
thermal_image: (H, W) 原始热阵列读数

Returns:
filtered_depth: (H, W) 过滤后的深度图
"""
# 1. 人体温度范围过滤
human_mask = ((thermal_image >= self.human_temp_range[0]) &
(thermal_image <= self.human_temp_range[1]))

# 2. 深度一致性过滤:人体深度应在合理范围
depth_mask = (depth_map > 0.3) & (depth_map < 3.0)

# 3. 形态学操作去除孤立热源
from scipy.ndimage import binary_opening, binary_closing

combined_mask = human_mask & depth_mask
combined_mask = binary_opening(combined_mask, iterations=1)
combined_mask = binary_closing(combined_mask, iterations=2)

filtered_depth = depth_map.copy()
filtered_depth[~combined_mask] = 0.0

return filtered_depth

def separate_persons(self, point_cloud: np.ndarray,
min_cluster_size: int = 50) -> list:
"""
分离多个人体点云

Args:
point_cloud: (N, 3) 3D点云
min_cluster_size: 最小聚类大小

Returns:
clusters: list of (M, 3) arrays, 每个元素为一个人的点云
"""
from sklearn.cluster import DBSCAN

if len(point_cloud) < min_cluster_size:
return [point_cloud] if len(point_cloud) > 0 else []

# DBSCAN 聚类
clustering = DBSCAN(
eps=0.15, # 15cm邻域
min_samples=min_cluster_size // 5
).fit(point_cloud)

clusters = []
for label in set(clustering.labels_):
if label == -1: # 噪声
continue
mask = clustering.labels_ == label
clusters.append(point_cloud[mask])

return clusters


# 测试
if __name__ == "__main__":
fusion = GeometricPerspectiveFusion()

# 模拟深度图和热图像(16x16)
depth_map = np.random.uniform(0.3, 2.0, (16, 16))
thermal = np.random.uniform(20, 35, (16, 16))
thermal[5:10, 5:10] = 34.0 # 人体区域

filtered = fusion.suppress_interference(depth_map, thermal)
print(f"过滤前有效像素: {(depth_map > 0).sum()}")
print(f"过滤后有效像素: {(filtered > 0).sum()}")

# 模拟多人点云
person1 = np.random.randn(100, 3) + np.array([0, 0, 1.0])
person2 = np.random.randn(100, 3) + np.array([0.5, 0, 1.5])
point_cloud = np.vstack([person1, person2])

clusters = fusion.separate_persons(point_cloud)
print(f"检测到 {len(clusters)} 个人体聚类")
for i, c in enumerate(clusters):
print(f" 人物 {i+1}: {len(c)} 个点, 中心: {c.mean(axis=0)}")

3. 数据集与评估

TAP3D 构建了大规模热阵列3D人体数据集:

属性 数值
样本数 160,000
环境数 8
用户数 11
传感器 单个商用热阵列传感器
传感分辨率 16×16 ~ 32×24
帧率 10 fps
标注 3D点云真值(来自同步LiDAR)

4. 实验结果

下游任务 指标 TAP3D 结果 对比基线
跌倒检测 准确率 91.46% 雷达法 ~85%
室内追踪 MAE 21.86 cm mmWave ~30 cm
人体网格恢复 误差 4.87 cm 深度摄像头 ~3 cm(但有隐私问题)
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
# 下游任务:跌倒检测示例
class FallDetector:
"""
基于 TAP3D 点云的跌倒检测

通过分析人体点云的高度变化率和水平面积变化判断跌倒
"""

def __init__(self, fall_threshold: float = 0.3,
time_window: int = 10):
self.fall_threshold = fall_threshold # 高度变化阈值(m/s)
self.time_window = time_window

def detect(self, point_clouds: list) -> dict:
"""
Args:
point_clouds: 时间序列点云列表,每个 (N, 3)

Returns:
result: {'is_fall': bool, 'confidence': float}
"""
if len(point_clouds) < self.time_window:
return {'is_fall': False, 'confidence': 0.0}

heights = []
widths = []
for pc in point_clouds[-self.time_window:]:
if len(pc) > 0:
heights.append(pc[:, 1].max() - pc[:, 1].min()) # 垂直高度
widths.append(np.sqrt(pc[:, 0].max()**2 + pc[:, 2].max()**2)) # 水平宽度

if len(heights) < 2:
return {'is_fall': False, 'confidence': 0.0}

# 高度变化率
height_rate = np.diff(heights) / 0.1 # 假设10fps

# 跌倒特征:高度快速下降 + 水平面积增加
fall_score = np.mean(np.abs(height_rate) if len(height_rate) > 0 else [0])
is_fall = fall_score > self.fall_threshold

return {
'is_fall': is_fall,
'confidence': min(fall_score / self.fall_threshold, 1.0),
'height_change_rate': float(fall_score)
}


# 测试
if __name__ == "__main__":
detector = FallDetector()

# 模拟正常站立点云序列
normal_pcs = [np.random.randn(50, 3) * 0.2 + np.array([0, 1.0, 1.0])
for _ in range(10)]

# 模拟跌倒点云序列(高度逐渐下降)
fall_pcs = []
for i in range(10):
h = 1.0 - i * 0.08 # 高度逐渐下降
fall_pcs.append(np.random.randn(50, 3) * 0.2 + np.array([0, h, 1.0]))

print("正常活动:", detector.detect(normal_pcs))
print("跌倒事件:", detector.detect(fall_pcs))

IMS应用启示

1. OOP异常姿态检测的新方案

TAP3D 的热阵列方案直接适用于座舱 OOP 检测:

OOP场景 当前方案 TAP3D 方案优势
乘员前倾 摄像头+深度估计 隐私保护,无需红外补光
乘员侧倾 摄像头+姿态估计 穿透衣物遮挡(热信号不受衣物影响)
儿童异常位置 mmWave雷达 成本降低90%($5 vs $50+)
婴儿被毯覆盖 mmWave雷达+摄像头 热信号穿透毯子,无需多传感器融合

2. CPD 儿童检测的补充方案

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
# 座舱 CPD 应用示例
class CabinCPD_Detector:
"""
基于 TAP3D 的座舱儿童遗留检测

优势:
- 完全被动式:无辐射、无摄像头(隐私友好)
- 成本极低:$5-15 热阵列传感器
- 穿透能力强:可检测毯下/座椅后的儿童

部署方案:
- 顶棚安装:1个 32×24 热阵列
- 覆盖范围:前排+后排座椅
- 功耗:<0.5W(适合停车后持续监测)
"""

def __init__(self, sensor_resolution=(32, 24)):
self.thermal_model = ForwardThermalModel(sensor_resolution=sensor_resolution)
self.fusion = GeometricPerspectiveFusion()
self.fall_detector = FallDetector()

def detect_child_presence(self, thermal_frame: np.ndarray) -> dict:
"""
检测座舱内是否有儿童遗留

Args:
thermal_frame: (32, 24) 热阵列读数

Returns:
result: {'has_child': bool, 'location': tuple, 'confidence': float}
"""
# 环境温度基线
ambient = np.median(thermal_frame)

# 人体温度区域
human_mask = (thermal_frame > ambient + 2.0) & (thermal_frame < 40.0)

# 如果检测到人体热源
if human_mask.sum() < 5:
return {'has_child': False, 'location': None, 'confidence': 0.0}

# 估计热源位置
rows, cols = np.where(human_mask)
center_r = int(np.mean(rows))
center_c = int(np.mean(cols))

# 估计尺寸(区分成人/儿童)
area = human_mask.sum()
is_child = area < (self.thermal_model.resolution[0] *
self.thermal_model.resolution[1] * 0.05) # <5%面积

return {
'has_child': True,
'location': (center_r, center_c),
'is_child': is_child,
'confidence': min(area / 50.0, 1.0),
'estimated_area_cm2': area * 4 # 粗略估计
}


# 测试
if __name__ == "__main__":
detector = CabinCPD_Detector(sensor_resolution=(32, 24))

# 模拟空座舱
empty_cabin = np.full((32, 24), 25.0) # 25°C环境温度
print("空座舱:", detector.detect_child_presence(empty_cabin))

# 模拟有儿童
child_cabin = np.full((32, 24), 25.0)
child_cabin[15:20, 10:14] = 34.0 # 儿童热源(小面积)
print("有儿童:", detector.detect_child_presence(child_cabin))

# 模拟成人
adult_cabin = np.full((32, 24), 25.0)
adult_cabin[10:22, 8:16] = 33.0 # 成人热源(大面积)
print("有成人:", detector.detect_child_presence(adult_cabin))

3. 部署建议

部署位置 传感器型号 分辨率 成本 覆盖范围
顶棚中央 MLX90640 32×24 ~$8 前排+部分后排
后排顶棚 AMG8833 8×8 ~$5 后排专用
B柱内侧 MLX90641 16×12 ~$6 侧向乘员

推荐架构:

4. 与现有方案对比

维度 摄像头方案 mmWave雷达 TAP3D热阵列
成本 $15-30 $30-80 $5-15
隐私 ❌ 有图像 ✅ 无图像 ✅ 无图像
穿透遮挡 ❌ 无法穿透 ✅ 穿透毯子 ✅ 穿透毯子
分辨率 高(1080p+) 低(点云) 中(16×16~32×24)
功耗 1-3W 2-5W <0.5W
停车后监测 不适合(隐私) 适合 适合
多人区分 好 中 中
3D姿态精度 高 中 中低

性能对比

指标 TAP3D mmWave雷达 深度摄像头
点云密度 稠密 稀疏 稠密
深度精度 4.87cm ~5-10cm ~1-3cm
跌倒检测 91.46% ~85% ~95%
追踪精度 21.86cm ~30cm ~10cm
隐私保护 ✅ ✅ ❌
成本 $5-15 $30-80 $50-150

技术路线图

短期(3-6个月)

  1. 原型搭建: 使用 MLX90640 (32×24) 搭建座舱原型
  2. 数据采集: 在实车内采集不同乘员、不同姿态的热阵列数据
  3. 模型适配: 将 TAP3D 开源代码适配到座舱场景

中期(6-12个月)

  1. 多传感器融合: 热阵列 + 单摄像头融合,热阵列提供粗深度和存在检测,摄像头提供精细姿态
  2. CPD场景验证: 验证毯下儿童、安全座椅婴儿等场景
  3. OOP场景验证: 前倾、侧倾、跪立等异常姿态

长期(12-24个月)

  1. 量产集成: 热阵列集成到顶灯/后视镜,成本<$10
  2. 停车后监测: 利用极低功耗特性实现24h停车监测
  3. 与ADAS联动: OOP检测结果与安全气囊抑制联动

开发优先级建议

优先级 功能 技术路径 预期周期
P0 CPD 儿童检测 TAP3D + 温度阈值 3个月
P1 乘员分类(成人/儿童/空座) TAP3D + 面积分析 4个月
P2 OOP 前倾检测 TAP3D + 深度变化 6个月
P3 OOP 侧倾/跪立 TAP3D + 姿态分类 8个月
P4 多人追踪 TAP3D + DBSCAN聚类 12个月

参考资源


总结: TAP3D 开创了从体热重建3D点云的新范式。对于 IMS 座舱感知,它提供了一个隐私友好、低成本、低功耗的补充方案,特别适合 CPD 和基础 OOP 检测。虽然精度不及摄像头或深度传感器,但在隐私约束场景和停车后持续监测场景中具有不可替代的优势。