NOVELIC ACAM:60GHz雷达座舱监测与生命体征检测一体化方案

NOVELIC ACAM:60GHz雷达座舱监测与生命体征检测一体化方案

来源: NOVELIC ACAM (Automotive In-Cabin Monitoring)
发布时间: 2025年5月
链接: https://www.novelic.com/acam-automotive-in-cabin-monitoring-radar/


核心技术

NOVELIC推出的ACAM系统,基于60GHz毫米波雷达,实现座舱乘员检测、CPD儿童存在检测、生命体征监测(心率/呼吸)一体化方案,为Euro NCAP 2026 CPD要求提供完整解决方案。

技术突破:

  1. 单芯片实现CPD+生命体征+乘员监测
  2. 非接触式心率检测(误差<3%)
  3. 呼吸率检测(误差<4%)
  4. 穿透遮挡检测(毯子/衣物)

系统架构

整体架构

graph TB
    A[60GHz FMCW雷达] --> B[信号处理]
    B --> C[人体检测]
    B --> D[生命体征提取]
    
    C --> E[CPD儿童检测]
    C --> F[乘员计数]
    
    D --> G[心率监测]
    D --> H[呼吸率监测]
    
    E --> I[安全联动]
    F --> I
    G --> J[健康评估]
    H --> J

技术参数

参数 规格 说明
频率 60-64 GHz ISM频段,FCC批准
带宽 4 GHz 距离分辨率3.75cm
发射通道 3 TX 支持3D角度估计
接收通道 4 RX MIMO虚拟孔径
刷新率 10 Hz 实时监测
功耗 <1.5W 低功耗设计
探测距离 0.5-3m 座舱全覆盖

核心技术实现

1. FMCW雷达原理

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
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
import numpy as np
from typing import Tuple, List
from dataclasses import dataclass

@dataclass
class FMCWRadarConfig:
"""FMCW雷达配置"""
start_freq: float = 60e9 # 60 GHz
bandwidth: float = 4e9 # 4 GHz
chirp_duration: float = 100e-6 # 100 μs
sample_rate: float = 10e6 # 10 MHz
num_chirps: int = 128
num_rx: int = 4
num_tx: int = 3

class FMCWRadarProcessor:
"""FMCW雷达信号处理器"""

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

# 距离分辨率
self.range_resolution = 3e8 / (2 * config.bandwidth) # 3.75 cm

# 速度分辨率
self.velocity_resolution = 3e8 / (2 * config.start_freq * config.num_chirps * config.chirp_duration)

def generate_chirp_signal(self) -> np.ndarray:
"""生成线性调频信号"""
t = np.linspace(0, self.config.chirp_duration, int(self.config.sample_rate * self.config.chirp_duration))

# 频率变化率
chirp_rate = self.config.bandwidth / self.config.chirp_duration

# 相位
phase = 2 * np.pi * (self.config.start_freq * t + 0.5 * chirp_rate * t**2)

# 发射信号
tx_signal = np.exp(1j * phase)

return tx_signal

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

Args:
adc_data: (num_chirps, num_samples, num_rx) ADC数据

Returns:
range_profile: (num_chirps, num_range_bins, num_rx) 距离剖面
"""
# 对每个chirp的采样点做FFT
range_fft = np.fft.fft(adc_data, axis=1)

# 取前半部分(有效距离)
range_profile = range_fft[:, :adc_data.shape[1]//2, :]

return range_profile

def doppler_fft(self, range_profile: np.ndarray) -> np.ndarray:
"""
多普勒FFT

Args:
range_profile: (num_chirps, num_range_bins, num_rx)

Returns:
range_doppler_map: (num_doppler_bins, num_range_bins, num_rx)
"""
# 对每个距离bin的chirp序列做FFT
doppler_fft = np.fft.fftshift(np.fft.fft(range_profile, axis=0), axes=0)

return np.abs(doppler_fft)

def detect_targets(self, range_doppler_map: np.ndarray, threshold: float = 0.1) -> List[dict]:
"""
检测目标

Args:
range_doppler_map: 距离-多普勒图
threshold: 检测阈值

Returns:
targets: 目标列表
"""
# CFAR检测
cfar_mask = self.cfar_detection(range_doppler_map, threshold)

# 提取目标点
targets = []
doppler_bins, range_bins, _ = np.where(cfar_mask)

for d, r, rx in zip(doppler_bins, range_bins, [0] * len(range_bins)):
distance = r * self.range_resolution
velocity = (d - range_doppler_map.shape[0]//2) * self.velocity_resolution

targets.append({
'distance': distance,
'velocity': velocity,
'range_bin': r,
'doppler_bin': d
})

return targets

def cfar_detection(self, data: np.ndarray, threshold: float) -> np.ndarray:
"""CFAR恒虚警检测"""
# 简化实现:滑动窗口均值
from scipy.ndimage import uniform_filter

local_mean = uniform_filter(data, size=(16, 16), mode='constant')

return data > (local_mean * (1 + threshold))


# 生命体征提取
class VitalSignsExtractor:
"""生命体征提取器"""

def __init__(self):
# 心率频率范围 (0.8-2.0 Hz, 即48-120 bpm)
self.hr_freq_range = (0.8, 2.0)

# 呼吸率频率范围 (0.15-0.5 Hz, 即9-30 bpm)
self.rr_freq_range = (0.15, 0.5)

def extract_chest_displacement(self, range_profile: np.ndarray, target_bin: int) -> np.ndarray:
"""
提取胸腔位移

Args:
range_profile: (num_chirps, num_range_bins, num_rx)
target_bin: 目标距离bin

Returns:
displacement: (num_frames,) 胸腔位移序列
"""
# 提取目标bin的相位
phase = np.angle(range_profile[:, target_bin, 0])

# 相位解缠绕
unwrapped_phase = np.unwrap(phase)

# 相位到距离的转换
wavelength = 3e8 / 60e9 # 60 GHz波长
displacement = unwrapped_phase * wavelength / (4 * np.pi)

# 高通滤波(去除身体运动)
from scipy.signal import butter, filtfilt

nyq = 0.5 * 10 # 假设10 Hz采样
low = 0.1 / nyq
b, a = butter(4, low, btype='high')
displacement = filtfilt(b, a, displacement)

return displacement

def extract_heart_rate(self, displacement: np.ndarray, sample_rate: float = 10) -> Tuple[float, np.ndarray]:
"""
提取心率

Args:
displacement: 胸腔位移序列
sample_rate: 采样率

Returns:
hr: 心率 (bpm)
spectrum: 频谱
"""
# FFT
n = len(displacement)
freq = np.fft.rfftfreq(n, 1/sample_rate)
spectrum = np.abs(np.fft.rfft(displacement))

# 心率频段
hr_mask = (freq >= self.hr_freq_range[0]) & (freq <= self.hr_freq_range[1])

# 找峰值
hr_spectrum = spectrum[hr_mask]
hr_freq = freq[hr_mask]

peak_idx = np.argmax(hr_spectrum)
hr_freq_peak = hr_freq[peak_idx]

# 转换为bpm
hr = hr_freq_peak * 60

return hr, spectrum

def extract_respiration_rate(self, displacement: np.ndarray, sample_rate: float = 10) -> Tuple[float, np.ndarray]:
"""
提取呼吸率

Args:
displacement: 胸腔位移序列
sample_rate: 采样率

Returns:
rr: 呼吸率 (bpm)
spectrum: 频谱
"""
# FFT
n = len(displacement)
freq = np.fft.rfftfreq(n, 1/sample_rate)
spectrum = np.abs(np.fft.rfft(displacement))

# 呼吸率频段
rr_mask = (freq >= self.rr_freq_range[0]) & (freq <= self.rr_freq_range[1])

# 找峰值
rr_spectrum = spectrum[rr_mask]
rr_freq = freq[rr_mask]

peak_idx = np.argmax(rr_spectrum)
rr_freq_peak = rr_freq[peak_idx]

# 转换为bpm
rr = rr_freq_peak * 60

return rr, spectrum


# 完整测试代码
if __name__ == "__main__":
# 雷达配置
config = FMCWRadarConfig()
processor = FMCWRadarProcessor(config)
vital_extractor = VitalSignsExtractor()

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

# 模拟ADC数据
num_chirps = 128
num_samples = 256
num_rx = 4

# 模拟目标:距离1.5m,有呼吸运动
adc_data = np.random.randn(num_chirps, num_samples, num_rx) * 0.01

# 添加目标回波
target_bin = int(1.5 / processor.range_resolution)
for i in range(num_chirps):
# 模拟呼吸运动(0.3 Hz)
phase_shift = 2 * np.pi * 0.3 * i / num_chirps * 10
adc_data[i, target_bin, :] += np.exp(1j * phase_shift)

# 处理
range_profile = processor.range_fft(adc_data)
range_doppler = processor.doppler_fft(range_profile)

# 目标检测
targets = processor.detect_targets(range_doppler[:, :, 0])
print(f"\n检测到目标数: {len(targets)}")
for t in targets:
print(f" 距离: {t['distance']:.2f} m, 速度: {t['velocity']:.2f} m/s")

# 生命体征提取
displacement = vital_extractor.extract_chest_displacement(range_profile, target_bin)
hr, _ = vital_extractor.extract_heart_rate(displacement)
rr, _ = vital_extractor.extract_respiration_rate(displacement)

print(f"\n心率: {hr:.1f} bpm")
print(f"呼吸率: {rr:.1f} bpm")

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
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
class CPDDetector:
"""CPD儿童存在检测器"""

def __init__(self, config: Dict = None):
self.config = config or {}

# 儿童特征阈值
self.child_thresholds = {
'size_range': (0.2, 0.8), # 米,身体尺寸
'vital_signs_presence': True,
'breathing_amplitude': (0.5, 5.0), # mm,呼吸幅度
'heart_rate_range': (80, 160) # bpm,儿童心率范围
}

def detect_child_presence(self, range_doppler_map: np.ndarray,
vital_signs: Dict) -> Dict:
"""
检测儿童存在

Args:
range_doppler_map: 距离-多普勒图
vital_signs: 生命体征数据

Returns:
result: CPD检测结果
"""
# 1. 人体检测
person_detected = self._detect_person(range_doppler_map)

# 2. 生命体征验证
vital_signs_detected = self._detect_vital_signs(vital_signs)

# 3. 儿童特征判断
child_features = self._classify_child(vital_signs)

# 综合判断
is_child_present = person_detected and vital_signs_detected and child_features['is_child']

return {
'child_present': is_child_present,
'confidence': self._calculate_confidence(person_detected, vital_signs_detected, child_features),
'location': child_features.get('location'),
'vital_signs': vital_signs
}

def _detect_person(self, range_doppler_map: np.ndarray) -> bool:
"""检测人体存在"""
# 简化:能量检测
total_energy = np.sum(np.abs(range_doppler_map))
return total_energy > 1e-3 # 阈值

def _detect_vital_signs(self, vital_signs: Dict) -> bool:
"""检测生命体征"""
hr = vital_signs.get('heart_rate', 0)
rr = vital_signs.get('respiration_rate', 0)

# 心率和呼吸率在合理范围
hr_valid = 40 < hr < 200
rr_valid = 5 < rr < 40

return hr_valid and rr_valid

def _classify_child(self, vital_signs: Dict) -> Dict:
"""分类儿童"""
hr = vital_signs.get('heart_rate', 0)

# 儿童心率通常较高
is_child = self.child_thresholds['heart_rate_range'][0] < hr < self.child_thresholds['heart_rate_range'][1]

return {
'is_child': is_child,
'age_estimate': self._estimate_age(hr)
}

def _estimate_age(self, heart_rate: float) -> str:
"""基于心率估计年龄范围"""
if heart_rate > 140:
return "Infant (0-1岁)"
elif heart_rate > 120:
return "Toddler (1-3岁)"
elif heart_rate > 100:
return "Child (3-12岁)"
else:
return "Adult (>12岁)"

def _calculate_confidence(self, person: bool, vital: bool, child: Dict) -> float:
"""计算置信度"""
confidence = 0.0
if person:
confidence += 0.3
if vital:
confidence += 0.4
if child['is_child']:
confidence += 0.3

return confidence


# 座舱监测系统
class CabinMonitoringSystem:
"""座舱监测系统"""

def __init__(self):
self.radar_processor = FMCWRadarProcessor(FMCWRadarConfig())
self.vital_extractor = VitalSignsExtractor()
self.cpd_detector = CPDDetector()

def monitor_frame(self, adc_data: np.ndarray) -> Dict:
"""
单帧监测

Args:
adc_data: ADC数据

Returns:
result: 监测结果
"""
# 1. 雷达信号处理
range_profile = self.radar_processor.range_fft(adc_data)
range_doppler = self.radar_processor.doppler_fft(range_profile)

# 2. 目标检测
targets = self.radar_processor.detect_targets(range_doppler[:, :, 0])

# 3. 生命体征提取
vital_signs_list = []
for target in targets[:3]: # 最多处理3个目标
displacement = self.vital_extractor.extract_chest_displacement(
range_profile, target['range_bin']
)
hr, _ = self.vital_extractor.extract_heart_rate(displacement)
rr, _ = self.vital_extractor.extract_respiration_rate(displacement)

vital_signs_list.append({
'heart_rate': hr,
'respiration_rate': rr,
'distance': target['distance']
})

# 4. CPD检测
cpd_results = []
for vs in vital_signs_list:
cpd_result = self.cpd_detector.detect_child_presence(range_doppler, vs)
cpd_results.append(cpd_result)

return {
'occupants': len(targets),
'vital_signs': vital_signs_list,
'cpd_results': cpd_results,
'timestamp': __import__('time').time()
}


# 测试
if __name__ == "__main__":
system = CabinMonitoringSystem()

# 模拟ADC数据(包含1个目标)
adc_data = np.random.randn(128, 256, 4) * 0.01
adc_data[:, 40, :] += np.exp(1j * np.linspace(0, 2*np.pi*3, 128)[:, np.newaxis])

# 监测
result = system.monitor_frame(adc_data)

print(f"乘员数: {result['occupants']}")
print(f"生命体征: {result['vital_signs']}")
print(f"CPD结果: {result['cpd_results']}")

Euro NCAP 2026 CPD合规

CPD场景要求

场景 目标 遮挡条件 检测时限
CPD-01 婴儿座椅 无遮挡 ≤60秒
CPD-02 婴儿座椅 毯子覆盖 ≤90秒
CPD-03 儿童(无座椅) 无遮挡 ≤60秒
CPD-04 儿童 睡眠状态 ≤90秒

ACAM优势

特性 ACAM 摄像头 超声波
穿透遮挡 ✅ 毯子/衣物
生命体征验证 ✅ 心率/呼吸
夜间工作 ⚠️ 需补光
隐私友好 ✅ 无图像
成本 $20 $15 $10

IMS开发启示

1. 系统集成方案

graph LR
    A[ACAM雷达] --> B[MCU]
    B --> C[DMS控制器]
    
    D[摄像头DMS] --> C
    E[座椅传感器] --> C
    
    C --> F{数据融合}
    
    F --> G[CPD报告]
    F --> H[生命体征]
    F --> I[乘员计数]
    
    G --> J[安全联动]
    H --> K[健康评估]

2. 硬件选型

组件 型号 功能 成本
雷达芯片 TI IWR6843AOP 60GHz 3TX4RX $15
MCU STM32H7 雷达信号处理 $5
电源管理 TPS7A88 低噪声电源 $2
PCB 4层板 天线集成 $3

总成本:约$25/车

3. 部署优化

优化项 方法 性能提升
FFT加速 硬件FFT 速度+5x
多目标分离 MIMO 角度分辨率+2x
低功耗模式 占空比控制 功耗-50%

4. 与Euro NCAP对接

ENCAP要求 ACAM功能 覆盖度
CPD检测 雷达+生命体征 ✅ 完全覆盖
穿透遮挡 60GHz穿透性 ✅ 完全覆盖
误报控制 心率验证 ✅ 超出要求
隐私保护 无图像采集 ✅ 原生支持

竞品对比

方案 厂商 频段 生命体征 CPD
ACAM NOVELIC 60GHz
AWRL6432 TI 60GHz
S3D Infineon 60GHz ⚠️
PMIC NXP 79GHz

ACAM优势:

  1. 完整的生命体征提取(心率+呼吸)
  2. 儿童年龄估计(基于心率)
  3. 低功耗设计(<1.5W)
  4. 量产验证(已供货欧洲OEM)

参考文献

  1. NOVELIC, “ACAM Automotive In-Cabin Monitoring Radar”, 2025
  2. TI, “AWRL6432 60GHz mmWave Radar”, 2026
  3. Nature Scientific Reports, “Vital Signs Detection Using mmWave Radar”, 2025

本文为NOVELIC ACAM雷达的详细技术解读,面向Euro NCAP 2026 CPD要求提供完整的雷达+生命体征方案。


NOVELIC ACAM:60GHz雷达座舱监测与生命体征检测一体化方案
https://dapalm.com/2026/07/27/2026-07-27-novelic-acam-60ghz-radar-cpd-vital-signs/
作者
Mars
发布于
2026年7月27日
许可协议