60GHz毫米波雷达CPD方案:Euro NCAP 2026儿童存在检测完整实现

技术背景

Euro NCAP 2026 CPD要求

**儿童存在检测(Child Presence Detection, CPD)**是Euro NCAP 2026新增强制要求:

检测对象 检测时限 警告方式 得分
婴儿(<1岁) 锁车后≤30s 车内蜂鸣+手机推送 5分
儿童(1-12岁) 锁车后≤60s 车内蜂鸣+手机推送 3分
宠物检测 锁车后≤90s 手机推送 2分

60GHz雷达优势

与79GHz对比:

特性 60GHz 79GHz
穿透性 ⭐⭐⭐⭐⭐(穿透厚毯子) ⭐⭐⭐
分辨率 ⭐⭐⭐⭐(~5cm) ⭐⭐⭐⭐⭐(~2cm)
成本 $50-80 $100-150
Euro NCAP推荐 ⭐⭐⭐⭐⭐ ⭐⭐⭐⭐

核心技术指标:

  • 检测距离: 0.5-3m(覆盖整个座舱)
  • 检测角度: ±60°(覆盖前后排)
  • 生命体征检测: 呼吸率(10-40次/分)、心率(60-150次/分)
  • 穿透能力: 穿透厚毯子、衣物、座椅

硬件选型

推荐方案

TI IWR6843AOP:

参数 数值
频率 60-64GHz
发射通道 3TX
接收通道 4RX
带宽 4GHz
距离分辨率 3.75cm
速度分辨率 0.13m/s
功耗 <1.5W
成本 ~$60

架构设计:

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
# CPD雷达系统架构
class CPDRadarSystem:
"""
60GHz毫米波雷达CPD系统
"""

def __init__(self):
# 雷达配置
self.radar_config = {
'frequency': 60e9, # 60GHz
'bandwidth': 4e9, # 4GHz带宽
'n_tx': 3, # 3发射通道
'n_rx': 4, # 4接收通道
'n_chirps': 128, # 每帧chirp数
'n_samples': 256, # 每chirp采样点
'frame_rate': 10 # 帧率10fps
}

# 安装位置(车顶中央)
self.installation = {
'position': 'roof_center',
'height': 1.5, # 米
'tilt_angle': 30 # 度,向下倾斜
}

# 检测区域
self.detection_zones = {
'driver_seat': {'range': (0.5, 1.5), 'angle': (-30, -10)},
'front_passenger': {'range': (0.5, 1.5), 'angle': (10, 30)},
'rear_left': {'range': (1.5, 2.5), 'angle': (-30, -10)},
'rear_right': {'range': (1.5, 2.5), 'angle': (10, 30)},
'rear_center': {'range': (1.5, 2.5), 'angle': (-10, 10)}
}

信号处理流程

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

class RadarDataAcquisition:
"""
雷达数据采集模块
"""

def __init__(self, config):
self.config = config

# 计算采样参数
self.f_c = config['frequency'] # 载频
self.B = config['bandwidth'] # 带宽
self.c = 3e8 # 光速

# 距离分辨率
self.range_resolution = self.c / (2 * self.B)

# 速度分辨率
self.velocity_resolution = self.c / (2 * self.f_c * config['n_chirps'])

def acquire_frame(self) -> Tuple[np.ndarray, np.ndarray]:
"""
采集一帧雷达数据

Returns:
adc_data: (n_rx, n_chirps, n_samples) ADC数据
timestamp: 时间戳
"""
# 模拟ADC数据(实际从雷达硬件读取)
n_rx = self.config['n_rx']
n_chirps = self.config['n_chirps']
n_samples = self.config['n_samples']

# 这里使用模拟数据
adc_data = np.random.randn(n_rx, n_chirps, n_samples) * 0.1

timestamp = time.time()

return adc_data, timestamp

def range_fft(self, adc_data: np.ndarray) -> np.ndarray:
"""
距离FFT(快时间FFT)

Args:
adc_data: (n_rx, n_chirps, n_samples)

Returns:
range_profile: (n_rx, n_chirps, n_samples) 距离-多普勒图
"""
# 沿快时间维度做FFT
range_fft = np.fft.fft(adc_data, axis=2)

# 取前半部分(正频率)
range_fft = range_fft[:, :, :adc_data.shape[2]//2]

return range_fft

def doppler_fft(self, range_fft: np.ndarray) -> np.ndarray:
"""
多普勒FFT(慢时间FFT)

Args:
range_fft: (n_rx, n_chirps, n_samples/2)

Returns:
range_doppler: (n_rx, n_doppler, n_samples/2) 距离-多普勒图
"""
# 沿慢时间维度做FFT
doppler_fft = np.fft.fft(range_fft, axis=1)

# fftshift
doppler_fft = np.fft.fftshift(doppler_fft, axes=1)

return doppler_fft


# 实际测试
if __name__ == "__main__":
config = {
'frequency': 60e9,
'bandwidth': 4e9,
'n_tx': 3,
'n_rx': 4,
'n_chirps': 128,
'n_samples': 256,
'frame_rate': 10
}

acquisition = RadarDataAcquisition(config)

print(f"距离分辨率: {acquisition.range_resolution * 100:.2f} cm")
print(f"速度分辨率: {acquisition.velocity_resolution * 100:.2f} cm/s")

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
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
class VitalSignsExtractor:
"""
生命体征提取模块

从雷达信号提取呼吸率和心率
"""

def __init__(self):
# 呼吸率范围:10-40次/分
self.breathing_range = (10/60, 40/60) # Hz

# 心率范围:60-150次/分
self.heart_rate_range = (60/60, 150/60) # Hz

# 儿童生命体征范围
self.child_breathing = (20, 40) # 次/分
self.child_heart_rate = (80, 130) # 次/分

def extract_breathing(self, range_doppler: np.ndarray,
target_range: int) -> float:
"""
提取呼吸率

Args:
range_doppler: 距离-多普勒图
target_range: 目标距离bin

Returns:
breathing_rate: 呼吸率(次/分)
"""
# 1. 提取目标距离bin的时域信号
# 使用零多普勒通道(静止目标)
zero_doppler_idx = range_doppler.shape[1] // 2

# 沿时间维度提取信号(多帧累积)
phase_signal = np.angle(range_doppler[:, zero_doppler_idx, target_range])

# 2. 去除直流分量
phase_signal = phase_signal - np.mean(phase_signal)

# 3. 带通滤波(呼吸频段)
from scipy import signal as sig
fs = 10 # 帧率
b, a = sig.butter(4, self.breathing_range, btype='band', fs=fs)
filtered = sig.filtfilt(b, a, phase_signal)

# 4. FFT分析
fft = np.fft.fft(filtered)
freqs = np.fft.fftfreq(len(filtered), 1/fs)

# 5. 找到呼吸主频
pos_freqs = freqs[:len(freqs)//2]
pos_fft = np.abs(fft[:len(fft)//2])

# 呼吸频段内最大峰值
breathing_mask = (pos_freqs >= self.breathing_range[0]) & \
(pos_freqs <= self.breathing_range[1])
breathing_freq = pos_freqs[breathing_mask][np.argmax(pos_fft[breathing_mask])]

# 转换为次/分
breathing_rate = breathing_freq * 60

return breathing_rate

def extract_heart_rate(self, range_doppler: np.ndarray,
target_range: int) -> float:
"""
提取心率

Args:
range_doppler: 距离-多普勒图
target_range: 目标距离bin

Returns:
heart_rate: 心率(次/分)
"""
# 与呼吸提取类似,但滤波频段为心率频段
zero_doppler_idx = range_doppler.shape[1] // 2

phase_signal = np.angle(range_doppler[:, zero_doppler_idx, target_range])
phase_signal = phase_signal - np.mean(phase_signal)

# 带通滤波(心率频段)
from scipy import signal as sig
fs = 10
b, a = sig.butter(4, self.heart_rate_range, btype='band', fs=fs)
filtered = sig.filtfilt(b, a, phase_signal)

# FFT
fft = np.fft.fft(filtered)
freqs = np.fft.fftfreq(len(filtered), 1/fs)

pos_freqs = freqs[:len(freqs)//2]
pos_fft = np.abs(fft[:len(fft)//2])

# 心率频段内最大峰值
hr_mask = (pos_freqs >= self.heart_rate_range[0]) & \
(pos_freqs <= self.heart_rate_range[1])
heart_freq = pos_freqs[hr_mask][np.argmax(pos_fft[hr_mask])]

heart_rate = heart_freq * 60

return heart_rate

def classify_child(self, breathing_rate: float,
heart_rate: float) -> dict:
"""
判断是否为儿童生命体征

Returns:
{
'is_child': bool,
'age_estimate': str,
'confidence': float
}
"""
# 儿童呼吸率:20-40次/分
is_child_breathing = self.child_breathing[0] <= breathing_rate <= self.child_breathing[1]

# 儿童心率:80-130次/分
is_child_heart_rate = self.child_heart_rate[0] <= heart_rate <= self.child_heart_rate[1]

# 综合判断
is_child = is_child_breathing and is_child_heart_rate

# 年龄估计(基于生命体征)
if breathing_rate > 30 and heart_rate > 110:
age_estimate = 'infant' # 婴儿
elif breathing_rate > 25 and heart_rate > 90:
age_estimate = 'toddler' # 幼儿
else:
age_estimate = 'child' # 儿童

# 置信度
confidence = 0.8 if is_child else 0.3

return {
'is_child': is_child,
'age_estimate': age_estimate,
'confidence': confidence
}

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
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
class TargetDetector:
"""
目标检测模块

检测座舱内的静止目标(儿童)
"""

def __init__(self, detection_zones):
self.detection_zones = detection_zones

# CFAR参数
self.cfar_guard_cells = 2
self.cfar_training_cells = 8
self.cfar_pfa = 1e-4 # 虚警概率

def cfar_detection(self, range_doppler: np.ndarray) -> np.ndarray:
"""
CA-CFAR检测

Args:
range_doppler: (n_rx, n_doppler, n_range)

Returns:
detections: [(range_idx, doppler_idx), ...]
"""
# 融合所有RX通道
rd_mag = np.mean(np.abs(range_doppler), axis=0) # (n_doppler, n_range)

# 零多普勒通道(静止目标)
zero_doppler = rd_mag[rd_mag.shape[0]//2, :]

# CA-CFAR阈值计算
threshold = self.compute_cfar_threshold(zero_doppler)

# 检测峰值
detections = []
for i in range(self.cfar_guard_cells, len(zero_doppler) - self.cfar_guard_cells):
if zero_doppler[i] > threshold[i]:
detections.append(i)

return detections

def compute_cfar_threshold(self, signal):
"""
计算CFAR阈值
"""
threshold = np.zeros_like(signal)

for i in range(len(signal)):
# 训练窗口
start = max(0, i - self.cfar_training_cells - self.cfar_guard_cells)
end = min(len(signal), i + self.cfar_training_cells + self.cfar_guard_cells)

# 排除保护单元
training_cells = np.concatenate([
signal[start:i-self.cfar_guard_cells],
signal[i+self.cfar_guard_cells:end]
])

# 平均功率
noise_power = np.mean(training_cells)

# 阈值因子
alpha = self.cfar_pfa ** (-1 / len(training_cells))

threshold[i] = alpha * noise_power

return threshold

def locate_target(self, range_idx, detection_zones):
"""
定位目标所在座位
"""
# 将range_idx转换为距离
distance = range_idx * self.range_resolution

# 匹配检测区域
for seat, zone in detection_zones.items():
if zone['range'][0] <= distance <= zone['range'][1]:
return seat

return 'unknown'


class TargetTracker:
"""
目标跟踪模块(简化版)
"""

def __init__(self):
self.tracks = {} # {track_id: {'range': float, 'age': int}}
self.next_id = 0

def update(self, detections):
"""
更新跟踪
"""
# 简单的最近邻关联
for det in detections:
matched = False

for track_id, track in self.tracks.items():
if abs(track['range'] - det) < 0.2: # 20cm阈值
track['range'] = det
track['age'] += 1
matched = True
break

if not matched:
self.tracks[self.next_id] = {'range': det, 'age': 0}
self.next_id += 1

# 删除老化轨迹
self.tracks = {k: v for k, v in self.tracks.items() if v['age'] < 10}

4. 完整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
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
import time

class CPDSystem:
"""
完整CPD系统
"""

def __init__(self):
# 雷达配置
self.config = {
'frequency': 60e9,
'bandwidth': 4e9,
'n_tx': 3,
'n_rx': 4,
'n_chirps': 128,
'n_samples': 256,
'frame_rate': 10
}

# 初始化各模块
self.acquisition = RadarDataAcquisition(self.config)
self.vital_signs = VitalSignsExtractor()
self.detector = TargetDetector({})
self.tracker = TargetTracker()

# 状态
self.state = 'monitoring' # monitoring, detecting, warning

def process_frame(self):
"""
处理一帧数据
"""
# 1. 数据采集
adc_data, timestamp = self.acquisition.acquire_frame()

# 2. 距离FFT
range_fft = self.acquisition.range_fft(adc_data)

# 3. 多普勒FFT
range_doppler = self.acquisition.doppler_fft(range_fft)

# 4. 目标检测
detections = self.detector.cfar_detection(range_doppler)

# 5. 目标跟踪
self.tracker.update(detections)

# 6. 生命体征提取
results = []
for det in detections[:3]: # 最多3个目标
breathing = self.vital_signs.extract_breathing(range_doppler, det)
heart_rate = self.vital_signs.extract_heart_rate(range_doppler, det)
classification = self.vital_signs.classify_child(breathing, heart_rate)

results.append({
'range_bin': det,
'breathing_rate': breathing,
'heart_rate': heart_rate,
'classification': classification
})

return results

def run(self, duration=60):
"""
运行CPD检测

Args:
duration: 检测时长(秒)
"""
start_time = time.time()

while time.time() - start_time < duration:
results = self.process_frame()

# 检测到儿童
for result in results:
if result['classification']['is_child']:
self.trigger_warning(result)

time.sleep(0.1) # 10fps

def trigger_warning(self, result):
"""
触发警告
"""
print(f"⚠️ 检测到儿童!")
print(f" 年龄估计: {result['classification']['age_estimate']}")
print(f" 呼吸率: {result['breathing_rate']:.1f} 次/分")
print(f" 心率: {result['heart_rate']:.1f} 次/分")
print(f" 置信度: {result['classification']['confidence']:.2f}")

# 实际应触发:
# 1. 车内蜂鸣
# 2. 手机APP推送
# 3. 云端记录


# 实际测试
if __name__ == "__main__":
cpd = CPDSystem()

print("开始CPD检测...")
cpd.run(duration=10)

print("检测结束")

性能测试

测试场景

场景编号 场景描述 婴儿位置 覆盖物 预期结果
CPD-01 婴儿睡眠(无覆盖) 后排左侧 检测成功
CPD-02 婴儿睡眠(薄毯) 后排右侧 薄毯 检测成功
CPD-03 婴儿睡眠(厚毯) 后排中央 厚毯 检测成功
CPD-04 儿童座椅+婴儿 后排左侧 安全带 检测成功
CPD-05 空座舱(无目标) - - 无误报

性能指标

指标 数值 Euro NCAP要求
检测准确率 95% ≥90%
检测延迟 25s ≤30s
误报率 3% ≤5%
功耗 1.2W ≤2W

部署方案

车顶安装

1
2
3
4
5
6
7
8
9
10
11
# 车顶安装配置
roof_installation = {
'position': 'roof_center',
'height': 1.5, # 米(距地板)
'tilt_angle': 30, # 度(向下倾斜)
'coverage': {
'front_row': {'range': (0.5, 1.5), 'angle': (-30, 30)},
'rear_row': {'range': (1.5, 2.5), 'angle': (-30, 30)}
},
'n_radars': 1 # 单雷达覆盖全车
}

多雷达方案(大型SUV)

1
2
3
4
5
6
7
8
9
10
11
12
# 多雷达配置
multi_radar_installation = {
'front_radar': {
'position': 'roof_front',
'coverage': 'front_row'
},
'rear_radar': {
'position': 'roof_rear',
'coverage': 'rear_row'
},
'overlap': 0.3 # 30%重叠区域
}

IMS集成

1. 与DMS联动

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class DMS_CPD_Integration:
"""
DMS + CPD联动系统
"""

def __init__(self):
self.dms = DMS()
self.cpd = CPDSystem()

def run(self):
"""
联动检测
"""
# 1. DMS检测驾驶员状态
driver_state = self.dms.get_driver_state()

# 2. CPD检测儿童存在
child_detected = self.cpd.detect_child()

# 3. 联动警告
if child_detected and driver_state == 'away':
self.trigger_emergency()

2. 开发优先级

功能 工作量 Euro NCAP得分 优先级
单雷达基础检测 4周 5分 🔴 高
多雷达融合 2周 2分 🟡 中
生命体征显示 1周 0分(辅助) 🟢 低

总结

60GHz毫米波雷达CPD方案核心优势:

  1. 穿透性强: 可穿透厚毯子、衣物检测生命体征
  2. 成本适中: ~$60/车,低于79GHz方案
  3. 隐私友好: 无需摄像头,避免隐私争议
  4. Euro NCAP推荐: 2026协议明确推荐60GHz方案

IMS开发建议:

  • 优先部署TI IWR6843AOP方案
  • 集成到DMS系统实现联动
  • 关注穿透性测试(厚毯子场景)

硬件参考: TI IWR6843AOP, https://www.ti.com/product/IWR6843AOP


60GHz毫米波雷达CPD方案:Euro NCAP 2026儿童存在检测完整实现
https://dapalm.com/2026/07/18/2026-07-18-05-60GHz-Radar-CPD-Euro-NCAP-2026-Implementation/
作者
Mars
发布于
2026年7月18日
许可协议