车载人脸活体检测:防止身份欺骗的安全屏障

核心价值

随着车载生物识别认证的普及,人脸活体检测(Face Anti-Spoofing) 成为防止身份欺骗的关键技术。本文档分析主流方案及车载场景的特殊挑战。


问题定义

车载生物识别应用场景

应用 功能 安全需求
无钥匙进入 面部识别解锁车辆 防止照片/视频攻击
个性化配置 自动调节座椅/空调 中等安全需求
支付认证 车载支付、加油 高安全需求(ISO 30107)
驾驶授权 确认驾驶员身份 防止无证驾驶

常见攻击方式

攻击类型 描述 难度 检测难度
照片攻击 打印照片/手机屏幕展示 ⭐⭐
视频攻击 播放预录视频 ⭐⭐⭐
面具攻击 3D 打印面具/硅胶面具 ⭐⭐⭐⭐
头模攻击 高仿真人偶头部 极高 ⭐⭐⭐⭐⭐

技术方案

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
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
import cv2
import numpy as np
from skimage.feature import local_binary_pattern

class TextureBasedAntiSpoofing:
"""基于纹理的活体检测

原理:
- 真实人脸:纹理细腻、自然
- 攻击样本:纹理异常(屏幕摩尔纹、打印网点)

方法:
- LBP(Local Binary Pattern)
- 傅里叶频谱分析
"""

def __init__(self):
# LBP 参数
self.radius = 3
self.n_points = 24 * self.radius**2

def extract_lbp_features(self, face_img):
"""提取 LBP 特征

Args:
face_img: 面部图像(灰度), shape=(H, W)

Returns:
hist: LBP 直方图, shape=(256,)
"""
# 计算 LBP
lbp = local_binary_pattern(face_img, self.n_points, self.radius, method='uniform')

# 直方图
hist, _ = np.histogram(lbp.ravel(), bins=np.arange(0, self.n_points + 3),
range=(0, self.n_points + 2))

# 归一化
hist = hist.astype(np.float32)
hist /= hist.sum() + 1e-7

return hist

def analyze_frequency(self, face_img):
"""傅里叶频谱分析

检测:
- 屏幕刷新率特征(60/120 Hz)
- 打印网点频率
"""
# FFT
f = np.fft.fft2(face_img)
fshift = np.fft.fftshift(f)
magnitude = np.abs(fshift)

# 对数变换
magnitude_log = np.log(magnitude + 1)

# 分析高频区域
h, w = magnitude_log.shape
center_h, center_w = h // 2, w // 2

# 高频能量
high_freq_mask = np.ones((h, w), dtype=np.uint8)
high_freq_mask[center_h-50:center_h+50, center_w-50:center_w+50] = 0

high_freq_energy = np.sum(magnitude_log * high_freq_mask)
total_energy = np.sum(magnitude_log)

high_freq_ratio = high_freq_energy / (total_energy + 1e-7)

return high_freq_ratio

def detect_spoof(self, face_img, threshold=0.3):
"""检测是否为攻击

Args:
face_img: 面部图像
threshold: 高频比例阈值

Returns:
is_live: bool, 是否为真人
confidence: float, 置信度
"""
# 灰度化
if len(face_img.shape) == 3:
gray = cv2.cvtColor(face_img, cv2.COLOR_RGB2GRAY)
else:
gray = face_img

# 频谱分析
high_freq_ratio = self.analyze_frequency(gray)

# 判断
is_live = high_freq_ratio > threshold
confidence = min(high_freq_ratio / threshold, 1.0)

return is_live, confidence


# 实际测试
if __name__ == "__main__":
detector = TextureBasedAntiSpoofing()

# 模拟真实人脸
real_face = np.random.randint(50, 200, (112, 112), dtype=np.uint8)
is_live, conf = detector.detect_spoof(real_face)
print(f"真实人脸检测: {is_live}, 置信度: {conf:.2f}")

# 模拟攻击(添加周期性噪声)
attack_face = real_face.copy()
x = np.arange(112)
attack_face += np.sin(x * 0.5) * 30 # 摩尔纹
attack_face = np.clip(attack_face, 0, 255).astype(np.uint8)
is_live, conf = detector.detect_spoof(attack_face)
print(f"攻击样本检测: {is_live}, 置信度: {conf:.2f}")

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
142
143
144
145
146
class MotionBasedAntiSpoofing:
"""基于运动的活体检测

方法:
1. 挑战-响应(Challenge-Response)
2. 自发运动检测(眨眼、点头)
3. 光流一致性分析
"""

def __init__(self):
self.blink_detector = None # 眨眼检测器
self.optical_flow = cv2.optflow.DualTVL1OpticalFlow_create()

def challenge_response(self, challenge_type='blink'):
"""挑战-响应检测

Args:
challenge_type: 挑战类型
- 'blink': 眨眼
- 'turn_head_left': 左转头
- 'turn_head_right': 右转头
- 'smile': 微笑

Returns:
instruction: str, 提示语
expected_motion: str, 预期运动
"""
challenges = {
'blink': ('请眨眼', 'eye_blink'),
'turn_head_left': ('请向左转头', 'head_yaw_negative'),
'turn_head_right': ('请向右转头', 'head_yaw_positive'),
'smile': ('请微笑', 'smile')
}

return challenges.get(challenge_type, challenges['blink'])

def detect_blink(self, eye_landmarks, frame_history):
"""眨眼检测

使用 Eye Aspect Ratio (EAR)

Args:
eye_landmarks: 眼睛关键点
frame_history: 历史帧序列

Returns:
blink_detected: bool, 是否检测到眨眼
blink_count: int, 眨眼次数
"""
# 计算 EAR
def calculate_ear(eye):
# 眼睛纵横比
v1 = np.linalg.norm(eye[1] - eye[5])
v2 = np.linalg.norm(eye[2] - eye[4])
h = np.linalg.norm(eye[0] - eye[3])

ear = (v1 + v2) / (2.0 * h + 1e-7)
return ear

# 历史 EAR 序列
ear_history = []
for landmarks in frame_history:
left_ear = calculate_ear(landmarks['left_eye'])
right_ear = calculate_ear(landmarks['right_eye'])
avg_ear = (left_ear + right_ear) / 2.0
ear_history.append(avg_ear)

# 检测眨眼(EAR 低于阈值)
ear_history = np.array(ear_history)
blink_threshold = 0.2
blink_frames = np.where(ear_history < blink_threshold)[0]

# 统计眨眼次数(连续低值算一次)
blink_count = 0
if len(blink_frames) > 0:
# 简化:至少连续 2 帧
blink_count = 1

blink_detected = blink_count > 0

return blink_detected, blink_count

def analyze_optical_flow(self, frame1, frame2):
"""光流一致性分析

真实人脸:运动一致
攻击样本:屏幕边界运动不一致

Args:
frame1: 第一帧
frame2: 第二帧

Returns:
consistency: float, 一致性分数 (0-1)
"""
# 灰度化
gray1 = cv2.cvtColor(frame1, cv2.COLOR_RGB2GRAY)
gray2 = cv2.cvtColor(frame2, cv2.COLOR_RGB2GRAY)

# 计算光流
flow = self.optical_flow.calc(gray1, gray2, None)

# 分析一致性
# 面部区域应该整体运动,而不是局部
magnitude = np.sqrt(flow[..., 0]**2 + flow[..., 1]**2)

# 计算方向一致性
angle = np.arctan2(flow[..., 1], flow[..., 0])
angle_std = np.std(angle[magnitude > 1]) # 只看有运动的区域

# 方向标准差越小,一致性越高
consistency = 1.0 / (1.0 + angle_std)

return consistency

def detect_spoof(self, frame_history, landmarks_history):
"""综合运动检测

Args:
frame_history: 历史帧序列(至少 30 帧)
landmarks_history: 历史关键点序列

Returns:
is_live: bool
confidence: float
"""
if len(frame_history) < 10:
# 帧数不足,无法判断
return True, 0.5

# 1. 眨眼检测
blink_detected, _ = self.detect_blink(None, landmarks_history[-30:])

# 2. 光流一致性
consistency_scores = []
for i in range(len(frame_history) - 1):
cons = self.analyze_optical_flow(frame_history[i], frame_history[i+1])
consistency_scores.append(cons)

avg_consistency = np.mean(consistency_scores)

# 3. 综合判断
is_live = blink_detected and avg_consistency > 0.7
confidence = (0.5 * float(blink_detected) + 0.5 * avg_consistency)

return is_live, 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
class DepthBasedAntiSpoofing:
"""基于深度的活体检测

原理:
- 真实人脸:3D 深度分布
- 攻击样本:平面(照片/屏幕)

传感器:
- 结构光(Structured Light)
- 飞行时间(ToF)
- 双目立体视觉
"""

def __init__(self):
# 深度阈值
self.depth_threshold = 0.3 # 米(真实人脸深度变化)

def analyze_depth_distribution(self, depth_map, face_region):
"""分析深度分布

Args:
depth_map: 深度图, shape=(H, W)
face_region: 面部区域 (x, y, w, h)

Returns:
depth_stats: dict
- range: 深度范围
- variance: 深度方差
- is_3d: 是否为3D(真人)
"""
# 提取面部深度
x, y, w, h = face_region
face_depth = depth_map[y:y+h, x:x+w]

# 过滤无效值
valid_depth = face_depth[face_depth > 0]

if len(valid_depth) == 0:
return {'range': 0, 'variance': 0, 'is_3d': False}

# 统计
depth_range = valid_depth.max() - valid_depth.min()
depth_variance = np.var(valid_depth)

# 判断
is_3d = depth_range > self.depth_threshold

return {
'range': depth_range,
'variance': depth_variance,
'is_3d': is_3d
}

def detect_depth_edges(self, depth_map):
"""检测深度边缘

攻击样本在边界处有深度突变
"""
# 深度边缘检测
depth_edges = cv2.Canny(
(depth_map * 255).astype(np.uint8),
50, 150
)

# 分析边缘分布
edge_density = np.sum(depth_edges > 0) / depth_edges.size

return edge_density

def detect_spoof(self, depth_map, face_region):
"""检测是否为攻击

Args:
depth_map: 深度图(米)
face_region: 面部区域

Returns:
is_live: bool
confidence: float
"""
# 深度分布分析
depth_stats = self.analyze_depth_distribution(depth_map, face_region)

# 深度边缘分析
edge_density = self.detect_depth_edges(depth_map)

# 综合判断
is_live = depth_stats['is_3d'] and edge_density < 0.3

confidence = 0.7 * float(depth_stats['is_3d']) + 0.3 * (1 - edge_density)

return is_live, confidence

4. 多模态融合

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
class MultiModalAntiSpoofing:
"""多模态融合活体检测

融合:
- 纹理(RGB)
- 运动(视频序列)
- 深度(ToF/结构光)
- 红外(IR)
"""

def __init__(self):
self.texture_detector = TextureBasedAntiSpoofing()
self.motion_detector = MotionBasedAntiSpoofing()
self.depth_detector = DepthBasedAntiSpoofing()

# 模态权重
self.weights = {
'texture': 0.25,
'motion': 0.25,
'depth': 0.30,
'ir': 0.20
}

def detect_spoof(self, rgb_frame, ir_frame, depth_map,
frame_history, landmarks_history, face_region):
"""多模态检测

Args:
rgb_frame: RGB 图像
ir_frame: 红外图像
depth_map: 深度图
frame_history: 历史帧序列
landmarks_history: 历史关键点
face_region: 面部区域

Returns:
is_live: bool
confidence: float
details: dict, 各模态详情
"""
scores = {}

# 1. 纹理检测
texture_live, texture_conf = self.texture_detector.detect_spoof(rgb_frame)
scores['texture'] = texture_conf if texture_live else 0.0

# 2. 运动检测
if len(frame_history) >= 10:
motion_live, motion_conf = self.motion_detector.detect_spoof(
frame_history, landmarks_history
)
else:
motion_live, motion_conf = True, 0.5 # 数据不足,中性值
scores['motion'] = motion_conf if motion_live else 0.0

# 3. 深度检测
if depth_map is not None:
depth_live, depth_conf = self.depth_detector.detect_spoof(depth_map, face_region)
else:
depth_live, depth_conf = True, 0.5
scores['depth'] = depth_conf if depth_live else 0.0

# 4. 红外检测(简化)
if ir_frame is not None:
# 真实人脸:热辐射特征
ir_brightness = ir_frame.mean()
ir_live = ir_brightness > 100 # 阈值
scores['ir'] = 0.8 if ir_live else 0.2
else:
scores['ir'] = 0.5

# 加权融合
total_score = sum(
scores[k] * self.weights[k] for k in self.weights
)

# 决策
is_live = total_score > 0.6

return is_live, total_score, scores

ISO 30107 标准

测试指标

指标 定义 要求
Attack Presentation Classification Error Rate (APCER) 攻击样本误判为真人的比例 < 5%
Bona Fide Presentation Classification Error Rate (BPCER) 真人误判为攻击的比例 < 5%
Impostor Attack Presentation Match Rate (IAPMR) 攻击样本成功匹配的比例 < 1%

测试场景

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# ISO 30107 测试场景
TEST_SCENARIOS = {
'photo_attack': {
'description': '打印照片攻击',
'variants': ['A4_print', 'photo_paper', 'glossy_print'],
'difficulty': 'low',
'apcer_requirement': '< 5%'
},
'video_attack': {
'description': '视频播放攻击',
'variants': ['smartphone', 'tablet', 'laptop', 'TV'],
'difficulty': 'medium',
'apcer_requirement': '< 5%'
},
'mask_attack': {
'description': '3D面具攻击',
'variants': ['paper_mask', 'plastic_mask', 'silicone_mask'],
'difficulty': 'high',
'apcer_requirement': '< 10%'
}
}

IMS 开发启示

1. 传感器选型

传感器 用途 成本 推荐度
RGB 摄像头 纹理、运动检测 $50 ⭐⭐⭐⭐⭐
红外摄像头 红外活体检测 $80 ⭐⭐⭐⭐
ToF 深度摄像头 深度检测 $150 ⭐⭐⭐
结构光 高精度深度 $200 ⭐⭐

2. 安全等级配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 不同场景的安全配置
SECURITY_CONFIGS = {
'keyless_entry': {
'level': 'medium',
'methods': ['texture', 'motion'],
'apcer_target': 0.1, # 10%
'latency_target': '500ms'
},
'payment': {
'level': 'high',
'methods': ['texture', 'motion', 'depth'],
'apcer_target': 0.01, # 1%
'latency_target': '1000ms'
},
'driver_authorization': {
'level': 'high',
'methods': ['texture', 'motion', 'depth', 'ir'],
'apcer_target': 0.01,
'latency_target': '2000ms'
}
}

参考文献

  1. ISO/IEC 30107-3:2017. Biometric presentation attack detection - Part 3: Testing and reporting.

  2. Liu et al. (2025). Robust Face Liveness Detection for Biometric Authentication using Single Image. arXiv.


总结

车载人脸活体检测需要融合纹理、运动、深度、红外等多模态信息,达到 ISO 30107 标准要求。

IMS 开发启示

  1. 基础方案:纹理 + 运动(成本低,精度中等)
  2. 高安全方案:纹理 + 运动 + 深度 + 红外(成本高,精度高)
  3. 部署优化:INT8 量化,延迟 < 500ms
  4. ISO 30107 合规:APCER < 5%,BPCER < 5%

技术来源:ISO 30107-3:2017 | arXiv 2025 | IMS 研究笔记


https://dapalm.com/2026/08/09/2026-08-09-Face-Anti-Spoofing-Automotive-Security/
作者
Mars
发布于
2026年8月9日
许可协议