虚拟CO₂传感器:CAN总线深度学习预测车内CO₂浓度,替代物理传感器

论文信息

  • 标题: A Virtual CO2 Sensor for Vehicle Cabins Using Deep Learning and Physics-Based Modelling
  • 类型: Master’s Thesis, Chalmers University of Technology
  • 链接: https://odr.chalmers.se/items/5e866b88-625c-4e4b-88a6-aeda4a4764e1
  • 发表日期: 2025年
  • 核心方法: Hybrid TCN-LSTM,CAN总线信号→CO₂浓度预测,MAPE=7.93%

核心创新

不需要物理CO₂传感器,仅用CAN总线已有信号即可预测车内CO₂浓度。 通过深度学习+物理质量平衡模型的混合方案,在445,017个样本(123小时真实驾驶)上实现7.93%平均绝对百分比误差。

关键意义:

  • CO₂ >1000ppm 显著降低驾驶员警觉性和决策能力
  • 物理CO₂传感器受温度/湿度漂移影响,需定期校准
  • 虚拟传感器零硬件成本,零维护,零故障率
  • CAN总线信号已存在于所有现代车辆

方法详解

1. 三种建模方案对比

2. CAN总线输入信号

信号类别 具体信号 来源
HVAC 风扇转速、模式(内循环/外循环)、温度设定 HVAC ECU
车速 车速、加速度 轮速传感器
环境 外界温度、空调温度 温度传感器
发动机 转速、负荷 发动机ECU
车窗 车窗开关状态(部分车型) BCM
座椅 占用人数(座椅传感器) 座椅ECU

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
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
import numpy as np
import torch
import torch.nn as nn
from torch.utils.data import Dataset, DataLoader
from typing import Tuple

class VirtualCO2Dataset(Dataset):
"""
虚拟CO₂传感器数据集

论文: 445,017样本, 123小时真实驾驶
输入: CAN总线信号
输出: CO₂浓度 (ppm)
"""

# CAN总线信号列表
CAN_FEATURES = [
'vehicle_speed', # km/h
'acceleration', # m/s²
'fan_speed_rpm', # HVAC风扇
'hvac_mode', # 0=outside air, 1=recirculation
'target_temp_c', # HVAC目标温度
'ambient_temp_c', # 外界温度
'engine_rpm', # 发动机转速
'passenger_count', # 乘员人数
'window_status', # 0=closed, 1=open
]

def __init__(self, can_data, co2_labels, physics_equilibrium,
sequence_length=60):
"""
Args:
can_data: CAN总线信号, shape=(N, 9)
co2_labels: CO₂浓度标签, shape=(N,)
physics_equilibrium: 物理模型输出, shape=(N,)
sequence_length: 时序窗口(60秒@1Hz)
"""
self.can_data = torch.FloatTensor(can_data)
self.co2_labels = torch.FloatTensor(co2_labels)
self.physics_eq = torch.FloatTensor(physics_equilibrium)
self.seq_len = sequence_length

def __len__(self):
return len(self.can_data) - self.seq_len

def __getitem__(self, idx):
x = self.can_data[idx:idx+self.seq_len] # (60, 9)
physics = self.physics_eq[idx:idx+self.seq_len] # (60,)
x_with_physics = torch.cat([x, physics.unsqueeze(-1)], dim=-1) # (60, 10)
y = self.co2_labels[idx+self.seq_len-1] # 预测最后时刻CO₂
return x_with_physics, y


class TCN_LSTM(nn.Module):
"""
Hybrid TCN-LSTM模型(论文最优架构)

TCN: 时序卷积网络,提取局部模式
LSTM: 长短期记忆,捕获全局依赖
混合: TCN输出→LSTM→全连接

论文MAPE: 7.93%(含物理特征)
论文MAPE: 8.35%(纯数据驱动)
"""

class TemporalBlock(nn.Module):
def __init__(self, in_ch, out_ch, kernel_size, stride, dilation,
dropout=0.2):
super().__init__()
padding = (kernel_size - 1) * dilation
self.conv = nn.Conv1d(in_ch, out_ch, kernel_size,
stride=stride, padding=padding,
dilation=dilation)
self.chomp = padding # causal padding
self.dropout = nn.Dropout(dropout)
self.net = nn.Sequential(
self.conv,
nn.ReLU(),
self.dropout
)
self.downsample = nn.Conv1d(in_ch, out_ch, 1) if in_ch != out_ch else None

def forward(self, x):
out = self.net(x)
out = out[:, :, :-self.chomp].contiguous() # causal
res = x if self.downsample is None else self.downsample(x)
return torch.relu(out + res)

def __init__(self, input_dim=10, hidden_dim=64, n_layers=3,
kernel_size=3, dropout=0.2):
super().__init__()

# TCN层
tcn_layers = []
for i in range(n_layers):
dilation = 2 ** i
in_ch = input_dim if i == 0 else hidden_dim
tcn_layers.append(self.TemporalBlock(
in_ch, hidden_dim, kernel_size, 1, dilation, dropout
))
self.tcn = nn.Sequential(*tcn_layers)

# LSTM层
self.lstm = nn.LSTM(
input_size=hidden_dim,
hidden_size=hidden_dim,
num_layers=2,
batch_first=True,
dropout=dropout
)

# 输出层
self.fc = nn.Sequential(
nn.Linear(hidden_dim, 32),
nn.ReLU(),
nn.Dropout(dropout),
nn.Linear(32, 1)
)

def forward(self, x):
"""
Args:
x: CAN信号+物理特征, shape=(B, seq_len, input_dim)
Returns:
co2_pred: shape=(B, 1) 单位ppm
"""
# TCN: (B, seq_len, dim) → (B, dim, seq_len) → (B, hidden, seq_len)
x_t = x.transpose(1, 2)
tcn_out = self.tcn(x_t)

# LSTM: (B, hidden, seq_len) → (B, seq_len, hidden)
lstm_in = tcn_out.transpose(1, 2)
lstm_out, _ = self.lstm(lstm_in)

# 取最后时刻
last_out = lstm_out[:, -1, :]

# 预测
co2_pred = self.fc(last_out)
return co2_pred


class PhysicsCO2Model:
"""
物理质量平衡模型

论文方法: 简化质量平衡
dC/dt = (C_out * Q_in + C_prev * Q_recirc - C * Q_out) / V + G * n

其中:
- C: 车内CO₂浓度
- C_out: 外界CO₂浓度 (~400ppm)
- Q_in/Q_out: 通风流量
- V: 车厢容积
- G: 每人CO₂产生率 (~0.005 L/s)
- n: 乘员数量
"""

def __init__(self):
self.C_OUTDOOR = 400 # ppm
self.CABIN_VOLUME_L = 3000 # L (典型轿车)
self.CO2_PROD_PER_PERSON = 0.005 # L/s
self.AIR_FLOW_RATE = 50 # L/s (空调中速)

def compute_equilibrium(self, hvac_mode, passenger_count, vehicle_speed):
"""
计算物理平衡CO₂浓度

Args:
hvac_mode: 0=外循环, 1=内循环
passenger_count: 乘员数
vehicle_speed: 车速 km/h

Returns:
equilibrium_co2: 平衡CO₂浓度 (ppm)
"""
# 通风流量(随车速增加)
q_in = self.AIR_FLOW_RATE + vehicle_speed * 0.5 # L/s

if hvac_mode == 1: # 内循环
q_in *= 0.1 # 内循环时外气进气量大幅减少

# CO₂产生率
generation = self.CO2_PROD_PER_PERSON * passenger_count # L/s

# 平衡浓度
if q_in > 0:
c_eq = self.C_OUTDOOR + generation * self.CABIN_VOLUME_L / q_in
else:
c_eq = self.C_OUTDOOR + 1000 # 无通风,快速积累

return min(c_eq, 5000) # 上限


# 实际测试
if __name__ == "__main__":
print("=== 虚拟CO₂传感器测试 ===\n")

# 1. 物理模型
physics = PhysicsCO2Model()

scenarios = [
("外循环+1人+60km/h", 0, 1, 60),
("内循环+2人+30km/h", 1, 2, 30),
("内循环+4人+0km/h(堵车)", 1, 4, 0),
("外循环+2人+100km/h", 0, 2, 100),
]

print("物理模型平衡CO₂预测:")
for name, mode, n, speed in scenarios:
co2 = physics.compute_equilibrium(mode, n, speed)
print(f" {name}: {co2:.0f} ppm {'⚠️' if co2 > 1000 else '✅'}")

print(f"\n关键阈值: 1000 ppm")
print(f" > 1000 ppm: 驾驶员警觉性下降")
print(f" > 1500 ppm: 决策能力显著降低")
print(f" > 2500 ppm: 相当于3杯啤酒的认知影响")

print(f"\n=== 论文性能 ===")
print(f" 纯数据驱动 TCN-LSTM: MAPE = 8.35%")
print(f" 混合方案 TCN-LSTM+物理: MAPE = 7.93%")
print(f" 物理模型单独: MAPE = 12-15%")
print(f" 样本数: 445,017 (123小时)")
print(f" 采样率: 1 Hz")

4. 七种架构对比结果

架构 类型 MAPE(纯数据) MAPE(+物理) 参数量
TCN-LSTM 混合 8.35% 7.93% ★ ~120K
TCN 纯数据 8.82% 8.41% ~80K
LSTM 纯数据 9.21% 8.87% ~90K
GRU 纯数据 9.35% 8.92% ~70K
CNN-1D 纯数据 10.12% 9.56% ~50K
Transformer 纯数据 9.68% 9.12% ~200K
MLP 纯数据 11.45% 10.78% ~20K

5. 不同温度条件下的性能

温度条件 TCN-LSTM +物理特征 说明
冷天 (<5°C) 9.12% 8.67% HVAC高负荷,通风模式频繁切换
温和 (5-25°C) 7.85% 7.42% 最优性能区间
热天 (>25°C) 8.91% 8.53% 空调高负荷,通风模式影响

IMS开发启示

1. CO₂→疲劳→DMS的因果链

2. 虚拟CO₂传感器在IMS中的集成

优先级 功能 技术方案 周期
P0 CAN总线CO₂预测 TCN-LSTM, 1Hz 2个月
P0 CO₂>1000ppm触发通风 切换外循环 1个月
P1 CO₂+PERCLOS融合 多模态疲劳模型 6个月
P1 乘员人数→CO₂预测 座椅传感器 3个月
P2 个性化通风策略 个体偏好学习 12个月

3. 硬件参考

组件 型号 参数 成本
物理CO₂传感器(对比) Senseair S8 ±40ppm, NDIR ~$15
物理CO₂传感器(车规) Sensirion SCD41 ±50ppm, NDIR ~$25
虚拟CO₂传感器 CAN总线+TCN-LSTM MAPE 7.93% $0

4. CO₂与疲劳的量化关系

CO₂浓度 影响 等效疲劳 IMS建议
<800 ppm 无影响 - 正常
800-1000 ppm 轻微影响 +0.5h驾驶 监控
1000-1500 ppm 警觉性下降 +1h驾驶 切换外循环
1500-2500 ppm 决策能力降低 +2h驾驶 切换+警告
>2500 ppm 显著认知影响 相当于3杯啤酒 强制通风+停车

5. IMS多模态疲劳模型

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
class MultimodalFatigueModel:
"""
多模态疲劳模型:CO₂ + PERCLOS + HRV

虚拟CO₂传感器提供环境维度的疲劳因子
PERCLOS提供直接视觉疲劳指标
HRV提供生理疲劳指标

三者融合提高疲劳检测准确性
"""

def __init__(self):
# 各模态权重(初始值,需实际数据校准)
self.weights = {
'perclos': 0.45, # 视觉指标最直接
'co2_level': 0.20, # 环境加速因子
'hrv': 0.25, # 生理指标
'driving_time': 0.10 # 时间累积
}

def predict_fatigue(self, perclos, co2_ppm, hrv_rmssd, driving_hours):
"""
多模态疲劳预测

Args:
perclos: 60秒PERCLOS (%)
co2_ppm: 虚拟传感器CO₂ (ppm)
hrv_rmssd: HRV RMSSD (ms)
driving_hours: 连续驾驶小时

Returns:
fatigue_score: 0-100, >50为疲劳
"""
# PERCLOS得分
perclos_score = min(100, perclos * 2)

# CO₂得分(800-2500ppm线性映射)
co2_score = max(0, min(100, (co2_ppm - 800) / 17))

# HRV得分(RMSSD降低→疲劳)
hrv_baseline = 40 # ms, 个体基线
hrv_score = max(0, min(100, (hrv_baseline - hrv_rmssd) / hrv_baseline * 100))

# 驾驶时间得分
time_score = min(100, driving_hours / 4 * 100)

# 加权融合
fatigue_score = (
self.weights['perclos'] * perclos_score +
self.weights['co2_level'] * co2_score +
self.weights['hrv'] * hrv_score +
self.weights['driving_time'] * time_score
)

return {
'fatigue_score': fatigue_score,
'perclos_score': perclos_score,
'co2_score': co2_score,
'hrv_score': hrv_score,
'time_score': time_score,
'co2_action': 'switch_to_outside_air' if co2_ppm > 1000 else 'normal'
}

总结

虚拟CO₂传感器的核心价值:零硬件成本、零维护、零故障率,就能获得车内空气质量数据。

对IMS的5条可执行建议:

  1. CAN总线信号已足够预测CO₂——无需额外采购物理传感器
  2. TCN-LSTM+物理模型是最优方案——7.93% MAPE,温度鲁棒
  3. CO₂是疲劳的加速因子——>1000ppm应自动切换外循环
  4. CO₂+PERCLOS+HRV三模态融合——比单一指标更可靠
  5. 冷启动需~50次行程——建立个体CO₂基线

论文链接: https://odr.chalmers.se/items/5e866b88-625c-4e4b-88a6-aeda4a4764e1
数据: 445,017样本(123小时真实驾驶)