从e-scooter到汽车:运动学特征+熵分析检测酒精损伤(arXiv 2609.38276 论文解读+代码复现)

从e-scooter到汽车:运动学特征+熵分析检测酒精损伤

论文信息

项目 内容
标题 Kinematic signatures of impairment: Detecting alcohol intoxication in e-scooter riders using sensor data and machine learning
作者 Rahul Rajendra Pai et al.
arXiv 2609.38276
年份 2026
链接 https://arxiv.org/abs/2609.38276
被试 25人,清醒 + BAC 0.05% + BAC 0.08%
传感器 6轴IMU + 油门/刹车位置传感器,100Hz采样

1. 核心创新

1.1 关键发现

“Intoxicated riders shift from continuous, low-amplitude micro-corrections to fewer, high-amplitude reactive corrections.”

酒精损伤导致骑行者从连续低幅度微修正转变为少量高幅度反应性修正——这一模式转变可通过熵和标准差两个互补指标量化。

1.2 核心指标

指标 描述 酒精影响
置换熵 (PE) 信号时间复杂度 降低 (p<0.001)
标准差 (SD) 信号幅度 增加 (p<0.01)
转向率 方向修正频率 关键预测特征
横向加速度 侧向平衡 酒精导致横向平衡崩溃

1.3 分类性能

指标 数值
总体准确率 85%
加权 AuROC 0.94
清醒 vs 高BAC AuROC 1.00
分类器 熵基多类逻辑回归
验证 留一被试交叉验证 (LOPO)

2. 方法详解

2.1 实验设计

graph TD
    A[25名被试] --> B[三种条件]
    B --> B1[清醒 BAC=0%]
    B --> B2[低BAC 0.05%]
    B --> B3[高BAC 0.08%]
    
    B1 --> C[仪器化e-scooter<br/>6轴IMU + 油门/刹车]
    B2 --> C
    B3 --> C
    
    C --> D[100Hz采集]
    D --> E[特征提取]
    E --> E1[置换熵 PE]
    E --> E2[标准差 SD]
    E --> E3[重复测量相关性]
    
    E1 --> F[分类器]
    E2 --> F
    F --> G[多类逻辑回归]
    G --> H[85%准确率<br/>AuROC=0.94]

2.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
"""
Normalized Permutation Entropy (NPE)
归一化置换熵

原理:
1. 将时间序列嵌入 m 维延迟向量
2. 对每个向量排序,得到排列模式
3. 统计各排列模式出现频率
4. 计算香农熵

酒精影响:PE 降低 → 信号复杂度下降 → 控制能力减弱
"""

import numpy as np
from collections import Counter
from typing import Tuple

def permutation_entropy(signal: np.ndarray,
order: int = 3,
delay: int = 1,
normalize: bool = True) -> float:
"""
计算置换熵

Args:
signal: 一维时间序列
order: 嵌入维度(排列模式阶数)
delay: 延迟
normalize: 是否归一化到 [0, 1]

Returns:
pe: 置换熵值

Example:
>>> np.random.seed(42)
>>> s = np.random.randn(1000)
>>> pe = permutation_entropy(s, order=3, delay=1)
>>> print(f"PE = {pe:.4f}")
"""
n = len(signal)
if n < order * delay:
return 0.0

# 嵌入
embedded = np.array([
signal[i:i + order * delay:delay]
for i in range(n - (order - 1) * delay)
])

# 排列模式
patterns = np.argsort(embedd, axis=1)

# 统计频率
pattern_tuples = [tuple(p) for p in patterns]
counter = Counter(pattern_tuples)

# 概率分布
total = len(pattern_tuples)
probs = np.array(list(counter.values())) / total

# 香农熵
pe = -np.sum(probs * np.log2(probs + 1e-10))

if normalize:
max_pe = np.log2(np.math.factorial(order))
pe = pe / max_pe if max_pe > 0 else 0.0

return pe


def compute_kinematic_features(imu_data: np.ndarray,
window_size: int = 100,
order: int = 3) -> dict:
"""
计算运动学特征:PE + SD

Args:
imu_data: IMU 数据, shape=(N, 6) [ax, ay, az, gx, gy, gz]
window_size: 窗口大小(样本数)
order: 置换熵阶数

Returns:
features: dict with PE and SD for each axis
"""
axis_names = ['ax', 'ay', 'az', 'gx', 'gy', 'gz']
features = {}

for i, name in enumerate(axis_names):
signal = imu_data[:, i]

# 滑动窗口
num_windows = len(signal) // window_size
pe_values = []
sd_values = []

for w in range(num_windows):
window = signal[w * window_size:(w + 1) * window_size]
pe = permutation_entropy(window, order=order)
sd = np.std(window)
pe_values.append(pe)
sd_values.append(sd)

features[f'{name}_pe'] = np.mean(pe_values)
features[f'{name}_sd'] = np.mean(sd_values)

return features


# 测试
if __name__ == "__main__":
np.random.seed(42)

# 模拟清醒骑行:连续低幅度微修正
sober_signal = np.random.randn(1000) * 0.1 + 0.5 * np.sin(
2 * np.pi * 5 * np.arange(1000) / 100
)

# 模拟醉酒骑行:少量高幅度反应性修正
drunk_signal = np.zeros(1000)
for i in range(0, 1000, 50): # 每50个样本一次大修正
drunk_signal[i:i+10] = np.random.randn() * 2.0

pe_sober = permutation_entropy(sober_signal, order=3)
pe_drunk = permutation_entropy(drunk_signal, order=3)

sd_sober = np.std(sober_signal)
sd_drunk = np.std(drunk_signal)

print("=== 置换熵对比 ===")
print(f"清醒: PE={pe_sober:.4f}, SD={sd_sober:.4f}")
print(f"醉酒: PE={pe_drunk:.4f}, SD={sd_drunk:.4f}")
print(f"PE 变化: {(pe_drunk - pe_sober)/pe_sober*100:.1f}%")
print(f"SD 变化: {(sd_drunk - sd_sober)/sd_sober*100:.1f}%")
print(f"→ 醉酒 PE 降低 + SD 增大 = 预期的损伤模式")

2.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
"""
熵基多类逻辑回归分类器
检测清醒/低BAC/高BAC三分类
"""

import numpy as np
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import LeaveOneOut, cross_val_score
from sklearn.metrics import roc_auc_score, classification_report
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import Pipeline

class AlcoholImpairmentClassifier:
"""
酒精损伤运动学分类器

特征:7个关键运动学特征的 PE 和 SD
分类器:多类逻辑回归
"""

def __init__(self):
# 关键特征(论文识别的7个)
self.key_features = [
'ax_pe', 'ax_sd', # 横向加速度
'ay_pe', 'ay_sd', # 纵向加速度
'gx_pe', 'gx_sd', # 转向角速度
'throttle_pe', # 油门
]

# 分类器 pipeline
self.pipeline = Pipeline([
('scaler', StandardScaler()),
('clf', LogisticRegression(
multi_class='multinomial',
solver='lbfgs',
C=1.0,
max_iter=1000
))
])

def extract_features(self, imu_data: np.ndarray,
throttle_data: np.ndarray,
window_size: int = 100) -> np.ndarray:
"""
提取特征向量

Args:
imu_data: (N, 6) IMU数据
throttle_data: (N,) 油门位置
window_size: 窗口大小
"""
# 计算6轴IMU的PE和SD
features = compute_kinematic_features(imu_data, window_size)

# 添加油门特征
num_windows = len(throttle_data) // window_size
pe_values = []
sd_values = []
for w in range(num_windows):
window = throttle_data[w * window_size:(w + 1) * window_size]
pe_values.append(permutation_entropy(window, order=3))
sd_values.append(np.std(window))

features['throttle_pe'] = np.mean(pe_values)
features['throttle_sd'] = np.mean(sd_values)

# 构建特征向量
feat_vector = np.array([
features.get(f, 0.0) for f in self.key_features
])

return feat_vector

def train(self, X: np.ndarray, y: np.ndarray) -> dict:
"""
训练分类器(留一被试交叉验证)

Args:
X: (n_samples, n_features)
y: (n_samples,) 0=清醒, 1=低BAC, 2=高BAC

Returns:
metrics: 准确率和AuROC
"""
# LOPO 交叉验证
loo = LeaveOneOut()
y_pred = np.zeros(len(y))
y_proba = np.zeros((len(y), 3))

for train_idx, test_idx in loo.split(X):
X_train, X_test = X[train_idx], X[test_idx]
y_train, y_test = y[train_idx], y[test_idx]

# 克隆 pipeline
from sklearn.base import clone
clf = clone(self.pipeline)
clf.fit(X_train, y_train)

y_pred[test_idx] = clf.predict(X_test)
y_proba[test_idx] = clf.predict_proba(X_test)

# 评估
accuracy = np.mean(y_pred == y)

# 多类AuROC(one-vs-rest加权)
from sklearn.preprocessing import label_binarize
y_bin = label_binarize(y, classes=[0, 1, 2])
auc = roc_auc_score(y_bin, y_proba, multi_class='ovr', average='weighted')

# 清醒vs高BAC AuROC
mask = (y == 0) | (y == 2)
if np.sum(mask) > 1:
auc_sober_high = roc_auc_score(
y[mask] // 2, # 0 or 1
y_proba[mask, 2] # 高BAC概率
)
else:
auc_sober_high = 0.0

self.pipeline.fit(X, y) # 全量训练

return {
'accuracy': accuracy,
'auc_weighted': auc,
'auc_sober_vs_high': auc_sober_high,
'classification_report': classification_report(y, y_pred)
}


# 测试
if __name__ == "__main__":
classifier = AlcoholImpairmentClassifier()

# 模拟数据:25被试 × 3条件 = 75样本
np.random.seed(42)
n_samples = 75
n_features = len(classifier.key_features)

# 清醒:高PE低SD
sober_X = np.random.randn(25, n_features) * 0.5 + np.array([0.8, 0.3] * 3 + [0.7])
# 低BAC:中等
low_X = np.random.randn(25, n_features) * 0.5 + np.array([0.65, 0.5] * 3 + [0.6])
# 高BAC:低PE高SD
high_X = np.random.randn(25, n_features) * 0.5 + np.array([0.45, 0.8] * 3 + [0.45])

X = np.vstack([sober_X, low_X, high_X])
y = np.array([0]*25 + [1]*25 + [2]*25)

results = classifier.train(X, y)

print("=== 分类结果 ===")
print(f"准确率: {results['accuracy']:.1%}")
print(f"加权AuROC: {results['auc_weighted']:.2f}")
print(f"清醒vs高BAC AuROC: {results['auc_sober_vs_high']:.2f}")
print(f"\n{results['classification_report']}")

3. 从e-scooter到汽车的迁移

3.1 共同原理

特征 e-scooter 汽车
微修正模式 转把微修正 方向盘微修正
横向平衡 车身侧倾 车道偏离
纵向控制 油门/刹车 加速踏板/制动踏板
酒精影响 PE↓ SD↑ PE↓ SD↑(相同模式)

3.2 迁移可行性分析

维度 e-scooter 汽车 迁移难度
传感器 IMU + 油门/刹车 CAN: 方向盘角度 + IMU + 加速度 ✅ 低(已有传感器)
信号频率 100Hz 50-100Hz(CAN) ✅ 低
运动模式 转把 + 身体倾斜 方向盘 + 踏板 ⚠️ 中(需适配)
噪声环境 户外路面振动 发动机振动 + 路面 ⚠️ 中
个体差异 大(骑行姿势) 中(座椅位置固定) ✅ 汽车更可控

3.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
"""
汽车版运动学酒精损伤检测
从e-scooter迁移到汽车的传感器配置
"""

class AutomotiveImpairmentDetector:
"""
汽车版运动学酒精损伤检测器

传感器来源(全部已存在于车辆中):
1. 方向盘角度(CAN信号,通常≤10ms采样)
2. 横向加速度(ESC/IMU,100Hz)
3. 纵向加速度(ESC/IMU,100Hz)
4. 加速踏板位置(CAN信号)
5. 制动踏板状态(CAN信号)
6. 车道偏离(LDW摄像头)
"""

def __init__(self):
self.features = {
'steering_angle': {'pe': True, 'sd': True},
'lateral_accel': {'pe': True, 'sd': True},
'longitudinal_accel': {'pe': True, 'sd': True},
'throttle_pos': {'pe': True, 'sd': True},
}

# 窗口大小:6秒@50Hz = 300样本
self.window_size = 300
self.pe_order = 3

# 分类器
from sklearn.linear_model import LogisticRegression
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import Pipeline

self.model = Pipeline([
('scaler', StandardScaler()),
('clf', LogisticRegression(
multi_class='multinomial',
solver='lbfgs',
C=1.0
))
])

def extract_steering_features(self,
steering_angle: np.ndarray) -> dict:
"""
从方向盘角度信号提取特征

关键发现(来自论文):
- 清醒:连续低幅度微修正(高PE, 低SD)
- 醉酒:少量高幅度反应性修正(低PE, 高SD)
"""
# 计算方向盘角速度(一阶差分)
steering_rate = np.diff(steering_angle)

# 计算方向盘微修正频率
# 微修正定义:方向变化且幅度 < 5°
reversals = 0
for i in range(1, len(steering_rate)):
if (steering_rate[i] * steering_rate[i-1] < 0 and # 方向变化
abs(steering_rate[i]) < 5): # 小幅度
reversals += 1

reversals_per_min = reversals / (len(steering_rate) / 50 / 60)

# 窗口化PE和SD
num_windows = len(steering_rate) // self.window_size
pe_values = []
sd_values = []

for w in range(num_windows):
window = steering_rate[w * self.window_size:(w + 1) * self.window_size]
pe = permutation_entropy(window, order=self.pe_order)
sd = np.std(window)
pe_values.append(pe)
sd_values.append(sd)

return {
'steering_pe': np.mean(pe_values),
'steering_sd': np.mean(sd_values),
'reversals_per_min': reversals_per_min,
'steering_rate_mean': np.mean(np.abs(steering_rate)),
'steering_rate_max': np.max(np.abs(steering_rate))
}

def assess_impairment(self, can_signals: dict) -> dict:
"""
综合评估驾驶员损伤状态

Args:
can_signals: {
'steering_angle': np.ndarray,
'lateral_accel': np.ndarray,
'longitudinal_accel': np.ndarray,
'throttle_pos': np.ndarray,
'speed': np.ndarray
}

Returns:
{
'impairment_score': 0-1,
'classification': 'sober'|'mild'|'impaired',
'key_indicators': dict
}
"""
indicators = {}

# 方向盘特征
steering_feat = self.extract_steering_features(
can_signals['steering_angle']
)
indicators.update(steering_feat)

# 横向加速度特征
lat_accel = can_signals['lateral_accel']
num_windows = len(lat_accel) // self.window_size
lat_pe = np.mean([
permutation_entropy(
lat_accel[w*self.window_size:(w+1)*self.window_size],
order=self.pe_order
) for w in range(num_windows)
])
lat_sd = np.std(lat_accel)
indicators['lateral_accel_pe'] = lat_pe
indicators['lateral_accel_sd'] = lat_sd

# 综合评分
# PE降低 + SD增加 = 损伤信号
# 方向盘微修正 > 12次/分钟 = 高风险(来自IDD研究)

score = 0.0
if steering_feat['reversals_per_min'] > 12:
score += 0.3 * min(
(steering_feat['reversals_per_min'] - 12) / 8, 1.0
)
if steering_feat['steering_pe'] < 0.6:
score += 0.3 * (0.6 - steering_feat['steering_pe']) / 0.2
if steering_feat['steering_sd'] > 2.0:
score += 0.2 * min(steering_feat['steering_sd'] / 5.0, 1.0)
if lat_pe < 0.6:
score += 0.2 * (0.6 - lat_pe) / 0.2

score = min(score, 1.0)

if score < 0.2:
classification = 'sober'
elif score < 0.5:
classification = 'mild'
else:
classification = 'impaired'

return {
'impairment_score': round(score, 3),
'classification': classification,
'key_indicators': {k: round(v, 3) for k, v in indicators.items()}
}


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

# 模拟CAN信号:60秒@50Hz = 3000样本
n = 3000
t = np.arange(n) / 50

# 清醒:连续低幅度微修正
sober_steering = 0.5 * np.sin(0.3 * t) + 0.1 * np.random.randn(n)
sober_lat = 0.1 * np.random.randn(n)

# 醉酒:少量高幅度反应性修正
drunk_steering = np.zeros(n)
for i in range(0, n, 200): # 每4秒一次大修正
drunk_steering[i:i+20] = 3.0 * np.random.randn()
drunk_lat = 0.5 * np.random.randn(n)

sober_signals = {
'steering_angle': sober_steering,
'lateral_accel': sober_lat,
'longitudinal_accel': np.random.randn(n) * 0.1,
'throttle_pos': 0.3 + 0.05 * np.random.randn(n),
'speed': 60 + np.random.randn(n) * 0.5
}

drunk_signals = {
'steering_angle': drunk_steering,
'lateral_accel': drunk_lat,
'longitudinal_accel': np.random.randn(n) * 0.3,
'throttle_pos': 0.3 + 0.15 * np.random.randn(n),
'speed': 60 + np.random.randn(n) * 2.0
}

sober_result = detector.assess_impairment(sober_signals)
drunk_result = detector.assess_impairment(drunk_signals)

print("=== 清醒驾驶 ===")
print(f"损伤评分: {sober_result['impairment_score']}")
print(f"分类: {sober_result['classification']}")
print(f"关键指标: {sober_result['key_indicators']}")

print("\n=== 醉酒驾驶 ===")
print(f"损伤评分: {drunk_result['impairment_score']}")
print(f"分类: {drunk_result['classification']}")
print(f"关键指标: {drunk_result['key_indicators']}")

4. 对 IMS 开发的启示

4.1 可落地的开发建议

优先级 建议 具体行动
🔴 P0 从CAN提取方向盘微修正频率 方向盘角度信号 → 一阶差分 → 方向变化计数,>12次/分钟为高风险
🔴 P0 计算方向盘角速度的置换熵 6秒窗口,3阶PE,清醒>0.7,醉酒<0.5
🟡 P1 横向加速度PE分析 ESC IMU数据,6秒窗口PE,酒精导致横向平衡崩溃
🟡 P1 综合损伤评分 PE↓ + SD↑ + 微修正↑ → 综合评分
🟢 P2 与视觉DFR融合 运动学评分 + 视觉评分 → 双模态决策

4.2 传感器需求

传感器 车辆已有 采样率 用途
方向盘角度 ✅ CAN 50-100Hz 微修正频率 + PE
横向加速度 ✅ ESC/IMU 50-100Hz 横向平衡PE
纵向加速度 ✅ ESC/IMU 50-100Hz 纵向控制PE
加速踏板 ✅ CAN 50Hz 油门操作PE
制动踏板 ✅ CAN 50Hz 制动操作PE
车道偏离 ✅ LDW摄像头 10-20Hz 横向偏差

无需新增任何传感器——全部使用已有车辆信号。

4.3 性能预期

指标 e-scooter 论文 汽车预期
准确率 85% 75-85%
AuROC 0.94 0.85-0.92
清醒vs醉酒AuROC 1.00 0.90-0.95
误报率 ~10% <5%(可调阈值)
检测延迟 实时 6秒窗口

5. 技术路线判断

5.1 核心洞察

这篇论文的核心贡献在于证明了运动学特征可以可靠地检测酒精损伤——不需要摄像头、不需要EEG、不需要呼气传感器,只需要车辆已有的IMU和方向盘信号。

5.2 方案互补性

检测方案 检测维度 误报率 成本 与运动学方案互补
运动学(本文) 行为控制 ~10% 零(已有传感器) —
视觉DFR 面部+眼动 ~15% 摄像头 ✅ 互补
方向盘生物传感 汗液酒精 ~5% 传感器 ✅ 互补
呼气传感器 BAC直接 ~2% 传感器 ✅ 确认

推荐融合策略:

1
2
3
4
5
运动学初筛(零成本)
├── 高风险 → 触发视觉DFR检测
│ └── 高风险 → 触发呼气/生物传感确认
│ └── 确认 → 禁止启动/自动驾驶接管
└── 低风险 → 正常驾驶

5.3 对 Euro NCAP 的影响

Euro NCAP 2026 DFR 评估中,运动学方案可以作为零成本基线方案:

  • 无需额外硬件 — 降低OEM集成门槛
  • 实时检测 — 6秒窗口满足实时性要求
  • 隐私保护 — 不涉及摄像头数据
  • 误报可控 — 通过多特征融合降低误报率

6. 参考


从e-scooter到汽车:运动学特征+熵分析检测酒精损伤(arXiv 2609.38276 论文解读+代码复现)
https://dapalm.com/2026/10/06/2026-10-06-019-kinematic-alcohol-impairment-escooter-to-auto-arxiv2026/
作者
Mars
发布于
2026年10月6日
许可协议