@sssxyd/face-liveness-detector 0.4.0-alpha.6 → 0.4.0-alpha.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.en.md +844 -0
- package/README.md +544 -393
- package/dist/index.esm.js +37 -18
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +37 -18
- package/dist/index.js.map +1 -1
- package/dist/types/motion-liveness-detector.d.ts +8 -3
- package/dist/types/motion-liveness-detector.d.ts.map +1 -1
- package/dist/types/types.d.ts +82 -0
- package/dist/types/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/README.zh-Hans.md +0 -695
package/README.md
CHANGED
|
@@ -1,445 +1,609 @@
|
|
|
1
|
-
>
|
|
1
|
+
<div align="center">
|
|
2
2
|
|
|
3
|
-
#
|
|
3
|
+
> **语言 / Languages:** [中文](#) · [English](./README.en.md)
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
# 人脸活体检测引擎
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
<p>
|
|
8
|
+
<strong>基于 TensorFlow + OpenCV 的纯前端实时人脸活体检测解决方案</strong>
|
|
9
|
+
</p>
|
|
8
10
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
<p>
|
|
12
|
+
<img alt="TypeScript" src="https://img.shields.io/badge/TypeScript-5.0+-3178c6?logo=typescript">
|
|
13
|
+
<img alt="NPM Package" src="https://img.shields.io/npm/v/@sssxyd/face-liveness-detector?label=npm&color=cb3837">
|
|
14
|
+
<img alt="License" src="https://img.shields.io/badge/license-MIT-green">
|
|
15
|
+
</p>
|
|
14
16
|
|
|
15
|
-
|
|
17
|
+
</div>
|
|
16
18
|
|
|
17
|
-
|
|
19
|
+
---
|
|
18
20
|
|
|
19
|
-
|
|
21
|
+
## ✨ 功能特性
|
|
20
22
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
+
<table>
|
|
24
|
+
<tr>
|
|
25
|
+
<td>💯 <strong>纯前端实现</strong><br/>零后端依赖,所有处理在浏览器中本地运行</td>
|
|
26
|
+
<td>🔬 <strong>混合 AI 方案</strong><br/>TensorFlow + OpenCV 深度融合</td>
|
|
27
|
+
</tr>
|
|
28
|
+
<tr>
|
|
29
|
+
<td>🧠 <strong>双重活体验证</strong><br/>静默检测 + 动作识别(眨眼、张嘴、点头)</td>
|
|
30
|
+
<td>⚡ <strong>事件驱动架构</strong><br/>100% TypeScript,与任何框架无缝集成</td>
|
|
31
|
+
</tr>
|
|
32
|
+
<tr>
|
|
33
|
+
<td>🎯 <strong>全维度分析</strong><br/>质量、正对度、运动分数、屏幕检测</td>
|
|
34
|
+
<td>🛡️ <strong>多维反欺骗</strong><br/>照片、屏幕视频、莫尔纹、RGB发光检测</td>
|
|
35
|
+
</tr>
|
|
36
|
+
</table>
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## 🚀 在线演示
|
|
41
|
+
|
|
42
|
+
<div align="center">
|
|
43
|
+
|
|
44
|
+
**[👉 实时体验演示](https://face.lowtechsoft.com/) | 用手机扫码快速测试**
|
|
45
|
+
|
|
46
|
+
[](https://face.lowtechsoft.com/)
|
|
47
|
+
|
|
48
|
+
</div>
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## 🧬 核心算法设计
|
|
53
|
+
|
|
54
|
+
| 检测模块 | 技术方案 | 说明文档 |
|
|
55
|
+
|---------|--------|--------|
|
|
56
|
+
| **人脸识别** | Human.js BlazeFace + FaceMesh | 468个面部特征点 + 表情识别 |
|
|
57
|
+
| **动作检测** | 多维度运动分析 | [运动检测算法](./docs/MOTION_DETECTION_ALGORITHM.md) - 光流、关键点方差、面部区域变化 |
|
|
58
|
+
| **屏幕检测** | 三维特征融合 | [屏幕捕捉检测](./docs/SCREEN_CAPTURE_DETECTION_ALGORITHM.md) - 莫尔纹、RGB发光、色彩特征 |
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
## 📦 安装指南
|
|
63
|
+
|
|
64
|
+
### 快速安装(3 个包)
|
|
23
65
|
|
|
24
66
|
```bash
|
|
25
67
|
npm install @sssxyd/face-liveness-detector @vladmandic/human @techstark/opencv-js
|
|
26
68
|
```
|
|
27
69
|
|
|
28
|
-
|
|
70
|
+
<details>
|
|
71
|
+
<summary><strong>其他包管理器</strong></summary>
|
|
72
|
+
|
|
29
73
|
```bash
|
|
74
|
+
# Yarn
|
|
30
75
|
yarn add @sssxyd/face-liveness-detector @vladmandic/human @techstark/opencv-js
|
|
31
|
-
```
|
|
32
76
|
|
|
33
|
-
|
|
34
|
-
```bash
|
|
77
|
+
# pnpm
|
|
35
78
|
pnpm add @sssxyd/face-liveness-detector @vladmandic/human @techstark/opencv-js
|
|
36
79
|
```
|
|
37
80
|
|
|
38
|
-
>
|
|
81
|
+
</details>
|
|
82
|
+
|
|
83
|
+
> 📝 **为什么需要三个包?**
|
|
84
|
+
> `@vladmandic/human` 和 `@techstark/opencv-js` 是对等依赖(peer dependencies),需要单独安装以避免捆绑大型库,减小最终的打包体积。
|
|
85
|
+
|
|
86
|
+
---
|
|
87
|
+
|
|
88
|
+
## ⚠️ 必要配置步骤
|
|
39
89
|
|
|
40
|
-
|
|
90
|
+
### 1️⃣ 修复 OpenCV.js ESM 兼容性问题
|
|
41
91
|
|
|
42
|
-
|
|
43
|
-
> - **Issue**: https://github.com/TechStark/opencv-js/issues/44
|
|
44
|
-
> - **Patch Script**: [patch-opencv.js](https://github.com/sssxyd/face-liveness-detector/tree/main/demos/vue-demo/scripts/patch-opencv.js)
|
|
45
|
-
> - **Setup**: Add to your `package.json` scripts as a `postinstall` hook to auto-apply after dependencies install
|
|
92
|
+
`@techstark/opencv-js` 包含不兼容的 UMD 格式,**必须应用补丁脚本**。
|
|
46
93
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
94
|
+
**参考:**
|
|
95
|
+
- 问题详情:[TechStark/opencv-js#44](https://github.com/TechStark/opencv-js/issues/44)
|
|
96
|
+
- 补丁脚本:[patch-opencv.js](https://github.com/sssxyd/face-liveness-detector/tree/main/demos/vue-demo/scripts/patch-opencv.js)
|
|
97
|
+
|
|
98
|
+
**设置方法(推荐):** 添加到 `package.json` 的 `postinstall` 钩子
|
|
99
|
+
|
|
100
|
+
```json
|
|
101
|
+
{
|
|
102
|
+
"scripts": {
|
|
103
|
+
"postinstall": "node patch-opencv.cjs"
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
```
|
|
51
107
|
|
|
108
|
+
### 2️⃣ 下载 Human.js 模型文件
|
|
109
|
+
|
|
110
|
+
`@vladmandic/human` 需要模型文件和 TensorFlow WASM 后端,**否则无法加载**。
|
|
111
|
+
|
|
112
|
+
**下载脚本:**
|
|
113
|
+
- 模型复制:[copy-models.js](https://github.com/sssxyd/face-liveness-detector/tree/main/demos/vue-demo/scripts/copy-models.js)
|
|
114
|
+
- WASM 下载:[download-wasm.js](https://github.com/sssxyd/face-liveness-detector/tree/main/demos/vue-demo/scripts/download-wasm.js)
|
|
115
|
+
|
|
116
|
+
**设置方法(推荐):** 配置为 `postinstall` 钩子
|
|
117
|
+
|
|
118
|
+
```json
|
|
119
|
+
{
|
|
120
|
+
"scripts": {
|
|
121
|
+
"postinstall": "node scripts/copy-models.js && node scripts/download-wasm.js"
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
---
|
|
127
|
+
|
|
128
|
+
## 🎯 快速开始
|
|
129
|
+
|
|
130
|
+
### 基础示例
|
|
52
131
|
|
|
53
132
|
```typescript
|
|
54
133
|
import FaceDetectionEngine, { LivenessAction } from '@sssxyd/face-liveness-detector'
|
|
55
134
|
|
|
56
|
-
//
|
|
135
|
+
// 初始化引擎
|
|
57
136
|
const engine = new FaceDetectionEngine({
|
|
58
|
-
//
|
|
137
|
+
// 资源路径配置
|
|
59
138
|
human_model_path: '/models',
|
|
60
139
|
tensorflow_wasm_path: '/wasm',
|
|
140
|
+
tensorflow_backend: 'auto',
|
|
61
141
|
|
|
62
|
-
//
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
//
|
|
71
|
-
|
|
72
|
-
|
|
142
|
+
// 检测设置(建议 ≥720p,否则屏幕检测准确率下降)
|
|
143
|
+
detect_video_ideal_width: 1920,
|
|
144
|
+
detect_video_ideal_height: 1080,
|
|
145
|
+
detect_video_mirror: true,
|
|
146
|
+
detect_video_load_timeout: 5000,
|
|
147
|
+
detect_frame_delay: 100,
|
|
148
|
+
|
|
149
|
+
// 采集质量要求
|
|
150
|
+
collect_min_collect_count: 3, // 最少采集 3 张人脸
|
|
151
|
+
collect_min_face_ratio: 0.5, // 人脸占比 50%+
|
|
152
|
+
collect_max_face_ratio: 0.9, // 人脸占比 90% 以下
|
|
153
|
+
collect_min_face_frontal: 0.9, // 人脸正对度 90%
|
|
154
|
+
collect_min_image_quality: 0.5, // 图像质量 50%+
|
|
155
|
+
|
|
156
|
+
// 活体检测设置
|
|
157
|
+
action_liveness_action_count: 1, // 需要 1 个动作
|
|
158
|
+
action_liveness_action_list: [LivenessAction.BLINK, LivenessAction.MOUTH_OPEN, LivenessAction.NOD],
|
|
159
|
+
action_liveness_action_randomize: true,
|
|
160
|
+
action_liveness_verify_timeout: 60000,
|
|
161
|
+
|
|
162
|
+
// 防欺骗设置
|
|
163
|
+
motion_liveness_min_motion_score: 0.15,
|
|
164
|
+
motion_liveness_strict_photo_detection: false,
|
|
165
|
+
screen_capture_confidence_threshold: 0.7,
|
|
73
166
|
})
|
|
74
167
|
|
|
75
|
-
//
|
|
168
|
+
// 监听核心事件
|
|
76
169
|
engine.on('detector-loaded', (data) => {
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
170
|
+
if (data.success) {
|
|
171
|
+
console.log('✅ 引擎就绪', {
|
|
172
|
+
opencv: data.opencv_version,
|
|
173
|
+
human: data.human_version
|
|
174
|
+
})
|
|
175
|
+
}
|
|
80
176
|
})
|
|
81
177
|
|
|
82
178
|
engine.on('detector-info', (data) => {
|
|
83
|
-
//
|
|
179
|
+
// 每帧实时数据
|
|
84
180
|
console.log({
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
181
|
+
status: data.code,
|
|
182
|
+
quality: (data.imageQuality * 100).toFixed(1) + '%',
|
|
183
|
+
frontal: (data.faceFrontal * 100).toFixed(1) + '%',
|
|
184
|
+
motion: (data.motionScore * 100).toFixed(1) + '%',
|
|
185
|
+
screen: (data.screenConfidence * 100).toFixed(1) + '%'
|
|
89
186
|
})
|
|
90
187
|
})
|
|
91
188
|
|
|
92
189
|
engine.on('detector-action', (data) => {
|
|
93
|
-
//
|
|
94
|
-
|
|
95
|
-
console.log(`Please perform: ${data.action}`)
|
|
96
|
-
} else if (data.status === 'completed') {
|
|
97
|
-
console.log(`✅ Action recognized: ${data.action}`)
|
|
98
|
-
}
|
|
190
|
+
// 动作提示
|
|
191
|
+
console.log(`请执行动作: ${data.action} (${data.status})`)
|
|
99
192
|
})
|
|
100
193
|
|
|
101
194
|
engine.on('detector-finish', (data) => {
|
|
195
|
+
// 检测完成
|
|
102
196
|
if (data.success) {
|
|
103
|
-
console.log('✅
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
totalTime: (data.totalTime / 1000).toFixed(2) + 's',
|
|
109
|
-
bestFrame: data.bestFrameImage, // Base64 encoded
|
|
110
|
-
bestFace: data.bestFaceImage // Base64 encoded
|
|
197
|
+
console.log('✅ 活体验证通过!', {
|
|
198
|
+
静默通过: data.silentPassedCount,
|
|
199
|
+
动作完成: data.actionPassedCount,
|
|
200
|
+
最佳质量: (data.bestQualityScore * 100).toFixed(1) + '%',
|
|
201
|
+
总耗时: (data.totalTime / 1000).toFixed(2) + 's'
|
|
111
202
|
})
|
|
112
203
|
} else {
|
|
113
|
-
console.log('❌
|
|
204
|
+
console.log('❌ 活体验证失败')
|
|
114
205
|
}
|
|
115
206
|
})
|
|
116
207
|
|
|
117
208
|
engine.on('detector-error', (error) => {
|
|
118
|
-
console.error(
|
|
209
|
+
console.error(`❌ 错误 [${error.code}]: ${error.message}`)
|
|
119
210
|
})
|
|
120
211
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
})
|
|
124
|
-
|
|
125
|
-
// Initialize and start detection
|
|
126
|
-
async function runDetection() {
|
|
212
|
+
// 启动检测
|
|
213
|
+
async function startLivenessDetection() {
|
|
127
214
|
try {
|
|
128
|
-
//
|
|
215
|
+
// 初始化库
|
|
129
216
|
await engine.initialize()
|
|
130
217
|
|
|
131
|
-
//
|
|
132
|
-
const
|
|
218
|
+
// 获取视频元素并开始检测
|
|
219
|
+
const videoEl = document.getElementById('video') as HTMLVideoElement
|
|
220
|
+
await engine.startDetection(videoEl)
|
|
133
221
|
|
|
134
|
-
//
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
// Detection runs until completion or error
|
|
138
|
-
// Stop manually if needed:
|
|
139
|
-
// engine.stopDetection(true) // true to display best image
|
|
222
|
+
// 检测自动运行到完成或手动停止
|
|
223
|
+
// engine.stopDetection(true) // 停止并显示最佳图像
|
|
140
224
|
} catch (error) {
|
|
141
|
-
console.error('
|
|
225
|
+
console.error('检测启动失败:', error)
|
|
142
226
|
}
|
|
143
227
|
}
|
|
144
228
|
|
|
145
|
-
//
|
|
146
|
-
|
|
147
|
-
```
|
|
229
|
+
// 就绪时启动
|
|
230
|
+
startLivenessDetection()
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
---
|
|
234
|
+
|
|
235
|
+
## ⚙️ 详细配置参考
|
|
236
|
+
|
|
237
|
+
### 资源路径配置
|
|
238
|
+
|
|
239
|
+
| 选项 | 类型 | 说明 | 默认值 |
|
|
240
|
+
|-----|------|------|--------|
|
|
241
|
+
| `human_model_path` | `string` | Human.js 模型文件目录 | `undefined` |
|
|
242
|
+
| `tensorflow_wasm_path` | `string` | TensorFlow WASM 文件目录 | `undefined` |
|
|
243
|
+
| `tensorflow_backend` | `'auto' \| 'webgl' \| 'wasm'` | TensorFlow 后端引擎 | `'auto'` |
|
|
148
244
|
|
|
149
|
-
|
|
245
|
+
### 视频检测设置
|
|
150
246
|
|
|
151
|
-
|
|
247
|
+
| 选项 | 类型 | 说明 | 默认值 |
|
|
248
|
+
|-----|------|------|--------|
|
|
249
|
+
| `detect_video_ideal_width` | `number` | 视频宽度(像素) | `1920` |
|
|
250
|
+
| `detect_video_ideal_height` | `number` | 视频高度(像素) | `1080` |
|
|
251
|
+
| `detect_video_mirror` | `boolean` | 水平翻转视频 | `true` |
|
|
252
|
+
| `detect_video_load_timeout` | `number` | 加载超时(ms) | `5000` |
|
|
253
|
+
| `detect_frame_delay` | `number` | 帧间延迟(ms) | `100` |
|
|
254
|
+
| `detect_error_retry_delay` | `number` | 错误重试延迟(ms) | `200` |
|
|
152
255
|
|
|
153
|
-
|
|
256
|
+
### 人脸采集质量要求
|
|
154
257
|
|
|
155
|
-
|
|
|
156
|
-
|
|
157
|
-
| `
|
|
158
|
-
| `
|
|
159
|
-
| `
|
|
258
|
+
| 选项 | 类型 | 说明 | 默认值 |
|
|
259
|
+
|-----|------|------|--------|
|
|
260
|
+
| `collect_min_collect_count` | `number` | 最少采集数量 | `3` |
|
|
261
|
+
| `collect_min_face_ratio` | `number` | 最小人脸占比 (0-1) | `0.5` |
|
|
262
|
+
| `collect_max_face_ratio` | `number` | 最大人脸占比 (0-1) | `0.9` |
|
|
263
|
+
| `collect_min_face_frontal` | `number` | 最小正对度 (0-1) | `0.9` |
|
|
264
|
+
| `collect_min_image_quality` | `number` | 最小图像质量 (0-1) | `0.5` |
|
|
160
265
|
|
|
161
|
-
|
|
266
|
+
### 人脸正对度参数
|
|
162
267
|
|
|
163
|
-
|
|
|
164
|
-
|
|
165
|
-
| `
|
|
166
|
-
| `
|
|
167
|
-
| `
|
|
168
|
-
| `video_load_timeout` | `number` | Video stream loading timeout in ms | `5000` |
|
|
169
|
-
| `detection_frame_delay` | `number` | Delay between detection frames in ms | `100` |
|
|
170
|
-
| `error_retry_delay` | `number` | Error retry delay in ms | `200` |
|
|
268
|
+
| 选项 | 类型 | 说明 | 默认值 |
|
|
269
|
+
|-----|------|------|--------|
|
|
270
|
+
| `yaw_threshold` | `number` | 偏航角阈值(度) | `3` |
|
|
271
|
+
| `pitch_threshold` | `number` | 俯仰角阈值(度) | `4` |
|
|
272
|
+
| `roll_threshold` | `number` | 翻滚角阈值(度) | `2` |
|
|
171
273
|
|
|
172
|
-
|
|
274
|
+
### 图像质量参数
|
|
173
275
|
|
|
174
|
-
|
|
|
175
|
-
|
|
176
|
-
| `
|
|
177
|
-
| `
|
|
178
|
-
| `
|
|
179
|
-
| `
|
|
180
|
-
| `min_image_quality` | `number` | Minimum image quality score (0-1) | `0.5` |
|
|
181
|
-
| `min_live_score` | `number` | Minimum liveness score (0-1) | `0.5` |
|
|
182
|
-
| `min_real_score` | `number` | Minimum anti-spoofing score (0-1) | `0.85` |
|
|
183
|
-
| `suspected_frauds_count` | `number` | Number of frauds to detect before fail | `3` |
|
|
276
|
+
| 选项 | 类型 | 说明 | 默认值 |
|
|
277
|
+
|-----|------|------|--------|
|
|
278
|
+
| `require_full_face_in_bounds` | `boolean` | 人脸完全在边界内 | `false` |
|
|
279
|
+
| `min_laplacian_variance` | `number` | 最小模糊检测值 | `40` |
|
|
280
|
+
| `min_gradient_sharpness` | `number` | 最小清晰度 | `0.15` |
|
|
281
|
+
| `min_blur_score` | `number` | 最小模糊分数 | `0.6` |
|
|
184
282
|
|
|
185
|
-
|
|
283
|
+
### 活体检测设置
|
|
186
284
|
|
|
187
|
-
|
|
|
188
|
-
|
|
189
|
-
| `
|
|
190
|
-
| `
|
|
191
|
-
| `
|
|
285
|
+
| 选项 | 类型 | 说明 | 默认值 |
|
|
286
|
+
|-----|------|------|--------|
|
|
287
|
+
| `action_liveness_action_list` | `LivenessAction[]` | 动作列表 | `[BLINK, MOUTH_OPEN, NOD]` |
|
|
288
|
+
| `action_liveness_action_count` | `number` | 需要完成的动作数 | `1` |
|
|
289
|
+
| `action_liveness_action_randomize` | `boolean` | 随机化动作顺序 | `true` |
|
|
290
|
+
| `action_liveness_verify_timeout` | `number` | 超时时间(ms) | `60000` |
|
|
291
|
+
| `action_liveness_min_mouth_open_percent` | `number` | 最小张嘴比例 (0-1) | `0.2` |
|
|
192
292
|
|
|
193
|
-
|
|
293
|
+
### 动作活体检测(防照片攻击)
|
|
194
294
|
|
|
195
|
-
|
|
|
196
|
-
|
|
197
|
-
| `
|
|
198
|
-
| `
|
|
199
|
-
| `
|
|
200
|
-
| `
|
|
201
|
-
| `
|
|
295
|
+
| 选项 | 类型 | 说明 | 默认值 |
|
|
296
|
+
|-----|------|------|--------|
|
|
297
|
+
| `motion_liveness_min_motion_score` | `number` | 最小运动分数 (0-1) | `0.15` |
|
|
298
|
+
| `motion_liveness_min_keypoint_variance` | `number` | 最小关键点方差 (0-1) | `0.02` |
|
|
299
|
+
| `motion_liveness_frame_buffer_size` | `number` | 帧缓冲区大小 | `5` |
|
|
300
|
+
| `motion_liveness_eye_aspect_ratio_threshold` | `number` | 眨眼阈值 | `0.15` |
|
|
301
|
+
| `motion_liveness_motion_consistency_threshold` | `number` | 一致性阈值 (0-1) | `0.3` |
|
|
302
|
+
| `motion_liveness_min_optical_flow_threshold` | `number` | 最小光流幅度 (0-1) | `0.02` |
|
|
303
|
+
| `motion_liveness_strict_photo_detection` | `boolean` | 严格照片检测模式 | `false` |
|
|
202
304
|
|
|
203
|
-
|
|
305
|
+
### 屏幕采集检测
|
|
204
306
|
|
|
205
|
-
|
|
|
206
|
-
|
|
207
|
-
| `
|
|
208
|
-
| `
|
|
209
|
-
| `
|
|
210
|
-
| `
|
|
211
|
-
| `
|
|
307
|
+
| 选项 | 类型 | 说明 | 默认值 |
|
|
308
|
+
|-----|------|------|--------|
|
|
309
|
+
| `screen_capture_confidence_threshold` | `number` | 置信度阈值 (0-1) | `0.7` |
|
|
310
|
+
| `screen_capture_detection_strategy` | `string` | 检测策略 | `'adaptive'` |
|
|
311
|
+
| `screen_moire_pattern_threshold` | `number` | 莫尔纹阈值 (0-1) | `0.65` |
|
|
312
|
+
| `screen_moire_pattern_enable_dct` | `boolean` | 启用 DCT 分析 | `true` |
|
|
313
|
+
| `screen_moire_pattern_enable_edge_detection` | `boolean` | 启用边缘检测 | `true` |
|
|
212
314
|
|
|
315
|
+
### 屏幕色彩特征
|
|
213
316
|
|
|
214
|
-
|
|
317
|
+
| 选项 | 类型 | 说明 | 默认值 |
|
|
318
|
+
|-----|------|------|--------|
|
|
319
|
+
| `screen_color_saturation_threshold` | `number` | 饱和度阈值 (%) | `40` |
|
|
320
|
+
| `screen_color_rgb_correlation_threshold` | `number` | RGB相关性阈值 (0-1) | `0.75` |
|
|
321
|
+
| `screen_color_pixel_entropy_threshold` | `number` | 熵值阈值 (0-8) | `6.5` |
|
|
322
|
+
| `screen_color_gradient_smoothness_threshold` | `number` | 平滑性阈值 (0-1) | `0.7` |
|
|
323
|
+
| `screen_color_confidence_threshold` | `number` | 置信度阈值 (0-1) | `0.65` |
|
|
215
324
|
|
|
216
|
-
###
|
|
325
|
+
### 屏幕 RGB 发光检测
|
|
326
|
+
|
|
327
|
+
| 选项 | 类型 | 说明 | 默认值 |
|
|
328
|
+
|-----|------|------|--------|
|
|
329
|
+
| `screen_rgb_low_freq_start_percent` | `number` | 低频段开始 (0-1) | `0.15` |
|
|
330
|
+
| `screen_rgb_low_freq_end_percent` | `number` | 低频段结束 (0-1) | `0.35` |
|
|
331
|
+
| `screen_rgb_energy_score_weight` | `number` | 能量权重 | `0.40` |
|
|
332
|
+
| `screen_rgb_asymmetry_score_weight` | `number` | 不同步权重 | `0.40` |
|
|
333
|
+
| `screen_rgb_difference_factor_weight` | `number` | 差异权重 | `0.20` |
|
|
334
|
+
| `screen_rgb_confidence_threshold` | `number` | 置信度阈值 (0-1) | `0.65` |
|
|
335
|
+
|
|
336
|
+
---
|
|
337
|
+
|
|
338
|
+
## 🛠️ API 方法参考
|
|
339
|
+
|
|
340
|
+
### 核心方法
|
|
217
341
|
|
|
218
342
|
#### `initialize(): Promise<void>`
|
|
219
|
-
|
|
343
|
+
加载并初始化检测库。**必须在使用其他功能前调用。**
|
|
220
344
|
|
|
221
345
|
```typescript
|
|
222
346
|
await engine.initialize()
|
|
223
347
|
```
|
|
224
348
|
|
|
225
349
|
#### `startDetection(videoElement): Promise<void>`
|
|
226
|
-
|
|
350
|
+
在视频元素上开始人脸检测。
|
|
227
351
|
|
|
228
352
|
```typescript
|
|
229
|
-
const
|
|
230
|
-
await engine.startDetection(
|
|
353
|
+
const videoEl = document.getElementById('video') as HTMLVideoElement
|
|
354
|
+
await engine.startDetection(videoEl)
|
|
231
355
|
```
|
|
232
356
|
|
|
233
357
|
#### `stopDetection(success?: boolean): void`
|
|
234
|
-
|
|
358
|
+
停止检测过程。
|
|
235
359
|
|
|
236
360
|
```typescript
|
|
237
|
-
engine.stopDetection(true) // true
|
|
361
|
+
engine.stopDetection(true) // true: 显示最佳检测图像
|
|
238
362
|
```
|
|
239
363
|
|
|
240
364
|
#### `updateConfig(config): void`
|
|
241
|
-
|
|
365
|
+
运行时动态更新配置。
|
|
242
366
|
|
|
243
367
|
```typescript
|
|
244
368
|
engine.updateConfig({
|
|
245
|
-
|
|
246
|
-
|
|
369
|
+
collect_min_face_ratio: 0.6,
|
|
370
|
+
action_liveness_action_count: 0
|
|
247
371
|
})
|
|
248
372
|
```
|
|
249
373
|
|
|
250
|
-
#### `
|
|
251
|
-
|
|
374
|
+
#### `getOptions(): FaceDetectionEngineOptions`
|
|
375
|
+
获取当前配置对象。
|
|
252
376
|
|
|
253
377
|
```typescript
|
|
254
|
-
const config = engine.
|
|
378
|
+
const config = engine.getOptions()
|
|
255
379
|
```
|
|
256
380
|
|
|
257
|
-
#### `
|
|
258
|
-
|
|
381
|
+
#### `getEngineState(): EngineState`
|
|
382
|
+
获取引擎当前状态。
|
|
259
383
|
|
|
260
384
|
```typescript
|
|
261
|
-
const
|
|
262
|
-
```
|
|
263
|
-
|
|
264
|
-
|
|
385
|
+
const state = engine.getEngineState()
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
---
|
|
389
|
+
|
|
390
|
+
## 📡 事件系统
|
|
391
|
+
|
|
392
|
+
引擎采用 **TypeScript 事件发射器模式**,所有事件都是类型安全的。
|
|
393
|
+
|
|
394
|
+
### 事件列表
|
|
395
|
+
|
|
396
|
+
<table>
|
|
397
|
+
<tr>
|
|
398
|
+
<td><strong>detector-loaded</strong></td>
|
|
399
|
+
<td>引擎初始化完成</td>
|
|
400
|
+
</tr>
|
|
401
|
+
<tr>
|
|
402
|
+
<td><strong>detector-info</strong></td>
|
|
403
|
+
<td>每帧实时检测数据</td>
|
|
404
|
+
</tr>
|
|
405
|
+
<tr>
|
|
406
|
+
<td><strong>detector-action</strong></td>
|
|
407
|
+
<td>动作活体提示与状态</td>
|
|
408
|
+
</tr>
|
|
409
|
+
<tr>
|
|
410
|
+
<td><strong>detector-finish</strong></td>
|
|
411
|
+
<td>检测完成(成功/失败)</td>
|
|
412
|
+
</tr>
|
|
413
|
+
<tr>
|
|
414
|
+
<td><strong>detector-error</strong></td>
|
|
415
|
+
<td>错误发生时触发</td>
|
|
416
|
+
</tr>
|
|
417
|
+
<tr>
|
|
418
|
+
<td><strong>detector-debug</strong></td>
|
|
419
|
+
<td>调试信息(开发用)</td>
|
|
420
|
+
</tr>
|
|
421
|
+
</table>
|
|
422
|
+
|
|
423
|
+
---
|
|
424
|
+
|
|
425
|
+
### 📋 detector-loaded
|
|
426
|
+
|
|
427
|
+
**引擎初始化完成时触发**
|
|
265
428
|
|
|
266
|
-
The engine uses a TypeScript event emitter pattern. All events are type-safe:
|
|
267
|
-
|
|
268
|
-
#### `detector-loaded`
|
|
269
|
-
Fired when the engine finishes initialization.
|
|
270
|
-
|
|
271
|
-
**Data:**
|
|
272
429
|
```typescript
|
|
273
430
|
interface DetectorLoadedEventData {
|
|
274
|
-
success: boolean //
|
|
275
|
-
error?: string //
|
|
276
|
-
opencv_version?: string // OpenCV.js
|
|
277
|
-
human_version?: string // Human.js
|
|
431
|
+
success: boolean // 初始化是否成功
|
|
432
|
+
error?: string // 错误信息(失败时)
|
|
433
|
+
opencv_version?: string // OpenCV.js 版本号
|
|
434
|
+
human_version?: string // Human.js 版本号
|
|
278
435
|
}
|
|
279
436
|
```
|
|
280
437
|
|
|
281
|
-
|
|
438
|
+
**示例:**
|
|
282
439
|
```typescript
|
|
283
440
|
engine.on('detector-loaded', (data) => {
|
|
284
441
|
if (data.success) {
|
|
285
|
-
console.log('✅
|
|
286
|
-
console.log(`OpenCV
|
|
287
|
-
console.log(`Human.js: ${data.human_version}`)
|
|
442
|
+
console.log('✅ 引擎就绪')
|
|
443
|
+
console.log(`OpenCV ${data.opencv_version} | Human.js ${data.human_version}`)
|
|
288
444
|
} else {
|
|
289
|
-
console.error('
|
|
445
|
+
console.error('❌ 初始化失败:', data.error)
|
|
290
446
|
}
|
|
291
447
|
})
|
|
292
448
|
```
|
|
293
449
|
|
|
294
|
-
|
|
295
|
-
|
|
450
|
+
---
|
|
451
|
+
|
|
452
|
+
### 📊 detector-info
|
|
453
|
+
|
|
454
|
+
**每帧返回实时检测数据(高频事件)**
|
|
296
455
|
|
|
297
|
-
**Data:**
|
|
298
456
|
```typescript
|
|
299
457
|
interface DetectorInfoEventData {
|
|
300
|
-
passed: boolean
|
|
301
|
-
code: DetectionCode
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
458
|
+
passed: boolean // 是否通过静默检测
|
|
459
|
+
code: DetectionCode // 检测状态码
|
|
460
|
+
message: string // 状态消息
|
|
461
|
+
faceCount: number // 检测到的人脸数
|
|
462
|
+
faceRatio: number // 人脸占比 (0-1)
|
|
463
|
+
faceFrontal: number // 人脸正对度 (0-1)
|
|
464
|
+
imageQuality: number // 图像质量分数 (0-1)
|
|
465
|
+
motionScore: number // 运动分数 (0-1)
|
|
466
|
+
keypointVariance: number // 关键点方差 (0-1)
|
|
467
|
+
motionType: string // 检测到的运动类型
|
|
468
|
+
screenConfidence: number // 屏幕采集置信度 (0-1)
|
|
307
469
|
}
|
|
308
470
|
```
|
|
309
471
|
|
|
310
|
-
|
|
472
|
+
**检测状态码:**
|
|
311
473
|
```typescript
|
|
312
474
|
enum DetectionCode {
|
|
313
|
-
VIDEO_NO_FACE = 'VIDEO_NO_FACE',
|
|
314
|
-
MULTIPLE_FACE = 'MULTIPLE_FACE',
|
|
315
|
-
FACE_TOO_SMALL = 'FACE_TOO_SMALL',
|
|
316
|
-
FACE_TOO_LARGE = 'FACE_TOO_LARGE',
|
|
317
|
-
FACE_NOT_FRONTAL = 'FACE_NOT_FRONTAL',
|
|
318
|
-
FACE_NOT_REAL = 'FACE_NOT_REAL',
|
|
319
|
-
FACE_NOT_LIVE = 'FACE_NOT_LIVE',
|
|
320
|
-
FACE_LOW_QUALITY = 'FACE_LOW_QUALITY',
|
|
321
|
-
FACE_CHECK_PASS = 'FACE_CHECK_PASS'
|
|
475
|
+
VIDEO_NO_FACE = 'VIDEO_NO_FACE', // 未检测到人脸
|
|
476
|
+
MULTIPLE_FACE = 'MULTIPLE_FACE', // 检测到多张人脸
|
|
477
|
+
FACE_TOO_SMALL = 'FACE_TOO_SMALL', // 人脸太小
|
|
478
|
+
FACE_TOO_LARGE = 'FACE_TOO_LARGE', // 人脸太大
|
|
479
|
+
FACE_NOT_FRONTAL = 'FACE_NOT_FRONTAL', // 人脸不够正面
|
|
480
|
+
FACE_NOT_REAL = 'FACE_NOT_REAL', // 疑似欺骗
|
|
481
|
+
FACE_NOT_LIVE = 'FACE_NOT_LIVE', // 活体分数过低
|
|
482
|
+
FACE_LOW_QUALITY = 'FACE_LOW_QUALITY', // 图像质量过低
|
|
483
|
+
FACE_CHECK_PASS = 'FACE_CHECK_PASS' // 所有检查通过 ✅
|
|
322
484
|
}
|
|
323
485
|
```
|
|
324
486
|
|
|
325
|
-
|
|
487
|
+
**示例:**
|
|
326
488
|
```typescript
|
|
327
489
|
engine.on('detector-info', (data) => {
|
|
328
490
|
console.log({
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
491
|
+
检测状态: data.code,
|
|
492
|
+
静默通过: data.passed ? '✅' : '❌',
|
|
493
|
+
图像质量: `${(data.imageQuality * 100).toFixed(1)}%`,
|
|
494
|
+
人脸正对度: `${(data.faceFrontal * 100).toFixed(1)}%`,
|
|
495
|
+
运动分数: `${(data.motionScore * 100).toFixed(1)}%`,
|
|
496
|
+
屏幕采集: `${(data.screenConfidence * 100).toFixed(1)}%`
|
|
335
497
|
})
|
|
336
498
|
})
|
|
337
499
|
```
|
|
338
500
|
|
|
339
|
-
|
|
340
|
-
|
|
501
|
+
---
|
|
502
|
+
|
|
503
|
+
### 👤 detector-action
|
|
504
|
+
|
|
505
|
+
**动作活体提示与识别状态**
|
|
341
506
|
|
|
342
|
-
**Data:**
|
|
343
507
|
```typescript
|
|
344
508
|
interface DetectorActionEventData {
|
|
345
|
-
action: LivenessAction
|
|
346
|
-
status: LivenessActionStatus
|
|
509
|
+
action: LivenessAction // 要执行的动作
|
|
510
|
+
status: LivenessActionStatus // 动作状态
|
|
347
511
|
}
|
|
348
|
-
```
|
|
349
512
|
|
|
350
|
-
**Action Types:**
|
|
351
|
-
```typescript
|
|
352
513
|
enum LivenessAction {
|
|
353
|
-
BLINK = 'blink',
|
|
354
|
-
MOUTH_OPEN = 'mouth_open',
|
|
355
|
-
NOD = 'nod'
|
|
514
|
+
BLINK = 'blink', // 眨眼
|
|
515
|
+
MOUTH_OPEN = 'mouth_open', // 张嘴
|
|
516
|
+
NOD = 'nod' // 点头
|
|
356
517
|
}
|
|
357
|
-
```
|
|
358
518
|
|
|
359
|
-
**Action Status:**
|
|
360
|
-
```typescript
|
|
361
519
|
enum LivenessActionStatus {
|
|
362
|
-
STARTED = 'started', //
|
|
363
|
-
COMPLETED = 'completed', //
|
|
364
|
-
TIMEOUT = 'timeout' //
|
|
520
|
+
STARTED = 'started', // 提示已开始
|
|
521
|
+
COMPLETED = 'completed', // 成功识别
|
|
522
|
+
TIMEOUT = 'timeout' // 识别超时
|
|
365
523
|
}
|
|
366
524
|
```
|
|
367
525
|
|
|
368
|
-
|
|
526
|
+
**示例:**
|
|
369
527
|
```typescript
|
|
370
528
|
engine.on('detector-action', (data) => {
|
|
529
|
+
const actionLabels = {
|
|
530
|
+
'blink': '眨眼',
|
|
531
|
+
'mouth_open': '张嘴',
|
|
532
|
+
'nod': '点头'
|
|
533
|
+
}
|
|
534
|
+
|
|
371
535
|
switch (data.status) {
|
|
372
536
|
case 'started':
|
|
373
|
-
console.log(`👤
|
|
374
|
-
//
|
|
537
|
+
console.log(`👤 请执行: ${actionLabels[data.action]}`)
|
|
538
|
+
// 显示 UI 提示
|
|
375
539
|
break
|
|
376
540
|
case 'completed':
|
|
377
|
-
console.log(`✅
|
|
378
|
-
//
|
|
541
|
+
console.log(`✅ 已识别: ${actionLabels[data.action]}`)
|
|
542
|
+
// 更新进度条
|
|
379
543
|
break
|
|
380
544
|
case 'timeout':
|
|
381
|
-
console.log(`⏱️
|
|
545
|
+
console.log(`⏱️ 超时: ${actionLabels[data.action]}`)
|
|
546
|
+
// 显示重试提示
|
|
382
547
|
break
|
|
383
548
|
}
|
|
384
549
|
})
|
|
385
550
|
```
|
|
386
551
|
|
|
387
|
-
|
|
388
|
-
|
|
552
|
+
---
|
|
553
|
+
|
|
554
|
+
### ✅ detector-finish
|
|
555
|
+
|
|
556
|
+
**检测流程完成(成功或失败)**
|
|
389
557
|
|
|
390
|
-
**Data:**
|
|
391
558
|
```typescript
|
|
392
559
|
interface DetectorFinishEventData {
|
|
393
|
-
success: boolean //
|
|
394
|
-
silentPassedCount: number //
|
|
395
|
-
actionPassedCount: number //
|
|
396
|
-
totalTime: number //
|
|
397
|
-
bestQualityScore: number //
|
|
398
|
-
bestFrameImage: string | null // Base64
|
|
399
|
-
bestFaceImage: string | null // Base64
|
|
560
|
+
success: boolean // 是否通过验证
|
|
561
|
+
silentPassedCount: number // 静默检测通过数
|
|
562
|
+
actionPassedCount: number // 动作完成数
|
|
563
|
+
totalTime: number // 总耗时(毫秒)
|
|
564
|
+
bestQualityScore: number // 最佳图像质量 (0-1)
|
|
565
|
+
bestFrameImage: string | null // Base64 帧图像
|
|
566
|
+
bestFaceImage: string | null // Base64 人脸图像
|
|
400
567
|
}
|
|
401
568
|
```
|
|
402
569
|
|
|
403
|
-
|
|
570
|
+
**示例:**
|
|
404
571
|
```typescript
|
|
405
572
|
engine.on('detector-finish', (data) => {
|
|
406
573
|
if (data.success) {
|
|
407
|
-
console.log('
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
time: (data.totalTime / 1000).toFixed(2) + 's'
|
|
574
|
+
console.log('🎉 活体验证成功!', {
|
|
575
|
+
静默通过: `${data.silentPassedCount} 次`,
|
|
576
|
+
动作完成: `${data.actionPassedCount} 次`,
|
|
577
|
+
最佳质量: `${(data.bestQualityScore * 100).toFixed(1)}%`,
|
|
578
|
+
总耗时: `${(data.totalTime / 1000).toFixed(2)}s`
|
|
413
579
|
})
|
|
414
580
|
|
|
415
|
-
//
|
|
581
|
+
// 上传结果到服务器
|
|
416
582
|
if (data.bestFrameImage) {
|
|
417
|
-
|
|
583
|
+
uploadToServer({
|
|
418
584
|
image: data.bestFrameImage,
|
|
419
585
|
quality: data.bestQualityScore,
|
|
420
586
|
timestamp: new Date()
|
|
421
587
|
})
|
|
422
588
|
}
|
|
423
589
|
} else {
|
|
424
|
-
console.log('❌
|
|
425
|
-
// Prompt user to try again
|
|
590
|
+
console.log('❌ 验证失败,请重试')
|
|
426
591
|
}
|
|
427
592
|
})
|
|
428
593
|
```
|
|
429
594
|
|
|
430
|
-
|
|
431
|
-
|
|
595
|
+
---
|
|
596
|
+
|
|
597
|
+
### ⚠️ detector-error
|
|
598
|
+
|
|
599
|
+
**检测过程中发生错误**
|
|
432
600
|
|
|
433
|
-
**Data:**
|
|
434
601
|
```typescript
|
|
435
602
|
interface DetectorErrorEventData {
|
|
436
|
-
code: ErrorCode
|
|
437
|
-
message: string
|
|
603
|
+
code: ErrorCode // 错误代码
|
|
604
|
+
message: string // 错误信息
|
|
438
605
|
}
|
|
439
|
-
```
|
|
440
606
|
|
|
441
|
-
**Error Codes:**
|
|
442
|
-
```typescript
|
|
443
607
|
enum ErrorCode {
|
|
444
608
|
DETECTOR_NOT_INITIALIZED = 'DETECTOR_NOT_INITIALIZED',
|
|
445
609
|
CAMERA_ACCESS_DENIED = 'CAMERA_ACCESS_DENIED',
|
|
@@ -448,124 +612,118 @@ enum ErrorCode {
|
|
|
448
612
|
}
|
|
449
613
|
```
|
|
450
614
|
|
|
451
|
-
|
|
615
|
+
**示例:**
|
|
452
616
|
```typescript
|
|
453
617
|
engine.on('detector-error', (error) => {
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
break
|
|
460
|
-
case 'STREAM_ACQUISITION_FAILED':
|
|
461
|
-
showErrorMessage('Failed to access camera')
|
|
462
|
-
break
|
|
463
|
-
case 'SUSPECTED_FRAUDS_DETECTED':
|
|
464
|
-
showErrorMessage('Spoofing detected - please try again')
|
|
465
|
-
break
|
|
466
|
-
default:
|
|
467
|
-
showErrorMessage('Detection failed: ' + error.message)
|
|
618
|
+
const errorMessages: Record<string, string> = {
|
|
619
|
+
'DETECTOR_NOT_INITIALIZED': '引擎未初始化',
|
|
620
|
+
'CAMERA_ACCESS_DENIED': '摄像头权限被拒绝',
|
|
621
|
+
'STREAM_ACQUISITION_FAILED': '无法获取摄像头数据流',
|
|
622
|
+
'SUSPECTED_FRAUDS_DETECTED': '检测到欺骗行为'
|
|
468
623
|
}
|
|
624
|
+
|
|
625
|
+
console.error(`❌ 错误 [${error.code}]: ${errorMessages[error.code] || error.message}`)
|
|
626
|
+
showUserErrorPrompt(errorMessages[error.code])
|
|
469
627
|
})
|
|
470
628
|
```
|
|
471
629
|
|
|
472
|
-
|
|
473
|
-
|
|
630
|
+
---
|
|
631
|
+
|
|
632
|
+
### 🐛 detector-debug
|
|
633
|
+
|
|
634
|
+
**开发和故障排除的调试信息**
|
|
474
635
|
|
|
475
|
-
**Data:**
|
|
476
636
|
```typescript
|
|
477
637
|
interface DetectorDebugEventData {
|
|
478
|
-
level: 'info' | 'warn' | 'error' //
|
|
479
|
-
stage: string //
|
|
480
|
-
message: string //
|
|
481
|
-
details?: Record<string, any> //
|
|
482
|
-
timestamp: number // Unix
|
|
638
|
+
level: 'info' | 'warn' | 'error' // 日志级别
|
|
639
|
+
stage: string // 处理阶段
|
|
640
|
+
message: string // 调试信息
|
|
641
|
+
details?: Record<string, any> // 额外详情
|
|
642
|
+
timestamp: number // Unix 时间戳
|
|
483
643
|
}
|
|
484
644
|
```
|
|
485
645
|
|
|
486
|
-
|
|
646
|
+
**示例:**
|
|
487
647
|
```typescript
|
|
488
648
|
engine.on('detector-debug', (debug) => {
|
|
489
649
|
const time = new Date(debug.timestamp).toLocaleTimeString()
|
|
490
|
-
|
|
650
|
+
const prefix = `[${time}] [${debug.stage}]`
|
|
491
651
|
|
|
492
|
-
if (debug.details) {
|
|
493
|
-
console.log('Details:', debug.details)
|
|
494
|
-
}
|
|
495
|
-
|
|
496
|
-
// Log errors for troubleshooting
|
|
497
652
|
if (debug.level === 'error') {
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
details: debug.details
|
|
502
|
-
})
|
|
653
|
+
console.error(`${prefix} ❌ ${debug.message}`, debug.details)
|
|
654
|
+
} else {
|
|
655
|
+
console.log(`${prefix} ℹ️ ${debug.message}`)
|
|
503
656
|
}
|
|
504
657
|
})
|
|
505
658
|
```
|
|
506
659
|
|
|
507
|
-
|
|
660
|
+
---
|
|
661
|
+
|
|
662
|
+
## 📖 类型定义
|
|
508
663
|
|
|
509
664
|
### LivenessAction
|
|
510
665
|
```typescript
|
|
511
666
|
enum LivenessAction {
|
|
512
|
-
BLINK = 'blink',
|
|
513
|
-
MOUTH_OPEN = 'mouth_open',
|
|
514
|
-
NOD = 'nod'
|
|
667
|
+
BLINK = 'blink', // 眨眼
|
|
668
|
+
MOUTH_OPEN = 'mouth_open', // 张嘴
|
|
669
|
+
NOD = 'nod' // 点头
|
|
515
670
|
}
|
|
516
671
|
```
|
|
517
672
|
|
|
518
673
|
### LivenessActionStatus
|
|
519
674
|
```typescript
|
|
520
675
|
enum LivenessActionStatus {
|
|
521
|
-
STARTED = 'started', //
|
|
522
|
-
COMPLETED = 'completed', //
|
|
523
|
-
TIMEOUT = 'timeout' //
|
|
676
|
+
STARTED = 'started', // 动作提示已开始
|
|
677
|
+
COMPLETED = 'completed', // 动作成功识别
|
|
678
|
+
TIMEOUT = 'timeout' // 动作识别超时
|
|
524
679
|
}
|
|
525
680
|
```
|
|
526
681
|
|
|
527
682
|
### DetectionCode
|
|
528
683
|
```typescript
|
|
529
684
|
enum DetectionCode {
|
|
530
|
-
VIDEO_NO_FACE = 'VIDEO_NO_FACE',
|
|
531
|
-
MULTIPLE_FACE = 'MULTIPLE_FACE',
|
|
532
|
-
FACE_TOO_SMALL = 'FACE_TOO_SMALL',
|
|
533
|
-
FACE_TOO_LARGE = 'FACE_TOO_LARGE',
|
|
534
|
-
FACE_NOT_FRONTAL = 'FACE_NOT_FRONTAL',
|
|
535
|
-
FACE_NOT_REAL = 'FACE_NOT_REAL',
|
|
536
|
-
FACE_NOT_LIVE = 'FACE_NOT_LIVE',
|
|
537
|
-
FACE_LOW_QUALITY = 'FACE_LOW_QUALITY',
|
|
538
|
-
FACE_CHECK_PASS = 'FACE_CHECK_PASS'
|
|
685
|
+
VIDEO_NO_FACE = 'VIDEO_NO_FACE', // 视频中未检测到人脸
|
|
686
|
+
MULTIPLE_FACE = 'MULTIPLE_FACE', // 检测到多张人脸
|
|
687
|
+
FACE_TOO_SMALL = 'FACE_TOO_SMALL', // 人脸尺寸小于最小阈值
|
|
688
|
+
FACE_TOO_LARGE = 'FACE_TOO_LARGE', // 人脸尺寸大于最大阈值
|
|
689
|
+
FACE_NOT_FRONTAL = 'FACE_NOT_FRONTAL', // 人脸角度不够正面
|
|
690
|
+
FACE_NOT_REAL = 'FACE_NOT_REAL', // 检测到疑似欺骗
|
|
691
|
+
FACE_NOT_LIVE = 'FACE_NOT_LIVE', // 活体分数低于阈值
|
|
692
|
+
FACE_LOW_QUALITY = 'FACE_LOW_QUALITY', // 图像质量低于最小值
|
|
693
|
+
FACE_CHECK_PASS = 'FACE_CHECK_PASS' // 所有检测检查通过 ✅
|
|
539
694
|
}
|
|
540
695
|
```
|
|
541
696
|
|
|
542
697
|
### ErrorCode
|
|
543
698
|
```typescript
|
|
544
699
|
enum ErrorCode {
|
|
545
|
-
DETECTOR_NOT_INITIALIZED = 'DETECTOR_NOT_INITIALIZED', //
|
|
546
|
-
CAMERA_ACCESS_DENIED = 'CAMERA_ACCESS_DENIED', //
|
|
547
|
-
STREAM_ACQUISITION_FAILED = 'STREAM_ACQUISITION_FAILED', //
|
|
548
|
-
SUSPECTED_FRAUDS_DETECTED = 'SUSPECTED_FRAUDS_DETECTED' //
|
|
700
|
+
DETECTOR_NOT_INITIALIZED = 'DETECTOR_NOT_INITIALIZED', // 引擎未初始化
|
|
701
|
+
CAMERA_ACCESS_DENIED = 'CAMERA_ACCESS_DENIED', // 摄像头权限被拒
|
|
702
|
+
STREAM_ACQUISITION_FAILED = 'STREAM_ACQUISITION_FAILED', // 获取视频流失败
|
|
703
|
+
SUSPECTED_FRAUDS_DETECTED = 'SUSPECTED_FRAUDS_DETECTED' // 检测到欺骗/欺诈
|
|
549
704
|
}
|
|
550
705
|
```
|
|
551
706
|
|
|
552
|
-
|
|
707
|
+
---
|
|
553
708
|
|
|
554
|
-
|
|
709
|
+
## 🎓 高级用法与示例
|
|
555
710
|
|
|
556
|
-
|
|
711
|
+
### 完整的 Vue 3 演示项目
|
|
557
712
|
|
|
558
|
-
|
|
559
|
-
- Complete Vue 3 integration with TypeScript
|
|
560
|
-
- Real-time detection visualization
|
|
561
|
-
- Configuration panel for experimenting with different settings
|
|
562
|
-
- Event handling examples for all engine events
|
|
563
|
-
- Debug panel showing detailed detection information
|
|
564
|
-
- Responsive UI design for mobile and desktop
|
|
565
|
-
- Error handling and user feedback patterns
|
|
566
|
-
- Result export and image capture examples
|
|
713
|
+
有关全面的示例和高级使用模式,请参考官方演示项目:
|
|
567
714
|
|
|
568
|
-
|
|
715
|
+
**[Vue 演示项目](https://github.com/sssxyd/face-liveness-detector/tree/main/demos/vue-demo/)** 包括:
|
|
716
|
+
|
|
717
|
+
- ✅ 完整的 Vue 3 + TypeScript 集成
|
|
718
|
+
- ✅ 实时检测结果可视化
|
|
719
|
+
- ✅ 动态配置面板
|
|
720
|
+
- ✅ 所有引擎事件的完整处理
|
|
721
|
+
- ✅ 实时调试面板
|
|
722
|
+
- ✅ 响应式移动端 + 桌面端 UI
|
|
723
|
+
- ✅ 错误处理和用户反馈
|
|
724
|
+
- ✅ 结果导出和图像捕获
|
|
725
|
+
|
|
726
|
+
**快速启动演示:**
|
|
569
727
|
|
|
570
728
|
```bash
|
|
571
729
|
cd demos/vue-demo
|
|
@@ -573,123 +731,116 @@ npm install
|
|
|
573
731
|
npm run dev
|
|
574
732
|
```
|
|
575
733
|
|
|
576
|
-
|
|
734
|
+
然后在浏览器中打开显示的本地 URL。
|
|
735
|
+
|
|
736
|
+
---
|
|
577
737
|
|
|
578
|
-
##
|
|
738
|
+
## 📥 本地部署模型文件
|
|
579
739
|
|
|
580
|
-
|
|
740
|
+
### 为什么需要本地部署?
|
|
581
741
|
|
|
582
|
-
|
|
742
|
+
- 🚀 **提升性能** - 避免 CDN 延迟
|
|
743
|
+
- 🔒 **隐私保护** - 完全离线运行
|
|
744
|
+
- 🌐 **网络独立** - 不依赖外部连接
|
|
583
745
|
|
|
584
|
-
|
|
746
|
+
### 可用脚本
|
|
585
747
|
|
|
586
|
-
|
|
748
|
+
项目根目录提供两个下载脚本:
|
|
749
|
+
|
|
750
|
+
#### 1️⃣ 复制 Human.js 模型
|
|
587
751
|
|
|
588
752
|
```bash
|
|
589
|
-
node copy-
|
|
753
|
+
node copy-models.js
|
|
590
754
|
```
|
|
591
755
|
|
|
592
|
-
|
|
593
|
-
-
|
|
594
|
-
-
|
|
595
|
-
-
|
|
596
|
-
-
|
|
756
|
+
**功能:**
|
|
757
|
+
- 从 `node_modules/@vladmandic/human/models` 复制模型
|
|
758
|
+
- 保存到 `public/models/` 目录
|
|
759
|
+
- 包含 `.json` 和 `.bin` 模型文件
|
|
760
|
+
- 自动显示文件大小和进度
|
|
597
761
|
|
|
598
|
-
#### 2
|
|
762
|
+
#### 2️⃣ 下载 TensorFlow WASM 文件
|
|
599
763
|
|
|
600
764
|
```bash
|
|
601
|
-
node download-
|
|
765
|
+
node download-wasm.js
|
|
602
766
|
```
|
|
603
767
|
|
|
604
|
-
|
|
605
|
-
-
|
|
606
|
-
-
|
|
607
|
-
-
|
|
768
|
+
**功能:**
|
|
769
|
+
- 自动下载 TensorFlow.js WASM 后端
|
|
770
|
+
- 保存到 `public/wasm/` 目录
|
|
771
|
+
- 下载 4 个关键文件:
|
|
608
772
|
- `tf-backend-wasm.min.js`
|
|
609
773
|
- `tfjs-backend-wasm.wasm`
|
|
610
774
|
- `tfjs-backend-wasm-simd.wasm`
|
|
611
775
|
- `tfjs-backend-wasm-threaded-simd.wasm`
|
|
612
|
-
-
|
|
613
|
-
1. unpkg.com
|
|
614
|
-
2. cdn.jsdelivr.net
|
|
615
|
-
3. esm.sh
|
|
616
|
-
4. cdn.esm.sh
|
|
776
|
+
- **智能多 CDN 源** 自动回退:
|
|
777
|
+
1. unpkg.com(推荐)
|
|
778
|
+
2. cdn.jsdelivr.net
|
|
779
|
+
3. esm.sh
|
|
780
|
+
4. cdn.esm.sh
|
|
617
781
|
|
|
618
|
-
###
|
|
782
|
+
### 配置项目使用本地文件
|
|
619
783
|
|
|
620
|
-
|
|
784
|
+
下载完成后,在引擎初始化时指定本地路径:
|
|
621
785
|
|
|
622
786
|
```typescript
|
|
623
787
|
const engine = new FaceDetectionEngine({
|
|
624
|
-
//
|
|
788
|
+
// 使用本地文件而不是 CDN
|
|
625
789
|
human_model_path: '/models',
|
|
626
790
|
tensorflow_wasm_path: '/wasm',
|
|
627
791
|
|
|
628
|
-
//
|
|
792
|
+
// 其他配置...
|
|
629
793
|
})
|
|
630
794
|
```
|
|
631
795
|
|
|
632
|
-
|
|
796
|
+
### 自动化设置(推荐)
|
|
797
|
+
|
|
798
|
+
在 `package.json` 中配置 `postinstall` 钩子实现自动下载:
|
|
799
|
+
|
|
800
|
+
```json
|
|
801
|
+
{
|
|
802
|
+
"scripts": {
|
|
803
|
+
"postinstall": "node scripts/copy-models.js && node scripts/download-wasm.js"
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
```
|
|
807
|
+
|
|
808
|
+
---
|
|
809
|
+
|
|
810
|
+
## 🌐 浏览器兼容性
|
|
633
811
|
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
812
|
+
| 浏览器 | 版本 | 支持 | 备注 |
|
|
813
|
+
|--------|------|------|------|
|
|
814
|
+
| Chrome | 60+ | ✅ | 完全支持 |
|
|
815
|
+
| Firefox | 55+ | ✅ | 完全支持 |
|
|
816
|
+
| Safari | 11+ | ✅ | 完全支持 |
|
|
817
|
+
| Edge | 79+ | ✅ | 完全支持 |
|
|
637
818
|
|
|
638
|
-
|
|
819
|
+
**系统要求:**
|
|
639
820
|
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
821
|
+
- 📱 支持 **WebRTC** 的现代浏览器
|
|
822
|
+
- 🔒 **HTTPS 环境**(开发可用 localhost)
|
|
823
|
+
- ⚙️ **WebGL** 或 **WASM** 后端支持
|
|
824
|
+
- 📹 **用户授权** - 需要摄像头权限
|
|
644
825
|
|
|
645
|
-
|
|
646
|
-
```typescript
|
|
647
|
-
engine.updateConfig({
|
|
648
|
-
video_width: 480,
|
|
649
|
-
video_height: 480
|
|
650
|
-
})
|
|
651
|
-
```
|
|
826
|
+
---
|
|
652
827
|
|
|
653
|
-
|
|
654
|
-
- Avoid backlighting
|
|
655
|
-
- Ensure face is well-lit
|
|
828
|
+
## 📄 许可证
|
|
656
829
|
|
|
657
|
-
|
|
658
|
-
```typescript
|
|
659
|
-
engine.on('detector-debug', (debug) => {
|
|
660
|
-
if (debug.stage === 'detection') {
|
|
661
|
-
console.time(debug.message)
|
|
662
|
-
}
|
|
663
|
-
})
|
|
664
|
-
```
|
|
830
|
+
[MIT License](./LICENSE) - 自由使用和修改
|
|
665
831
|
|
|
666
|
-
##
|
|
832
|
+
## 🤝 贡献
|
|
667
833
|
|
|
668
|
-
|
|
669
|
-
- Ensure HTTPS is used (or localhost for development)
|
|
670
|
-
- Check browser permissions
|
|
671
|
-
- User must grant camera access
|
|
834
|
+
欢迎提交 Issue 和 Pull Request!
|
|
672
835
|
|
|
673
|
-
|
|
674
|
-
- Check internet connection
|
|
675
|
-
- Verify model files are accessible
|
|
676
|
-
- Increase `video_load_timeout`
|
|
836
|
+
---
|
|
677
837
|
|
|
678
|
-
|
|
679
|
-
- Ensure good lighting
|
|
680
|
-
- Keep face centered in frame
|
|
681
|
-
- Face should be 50-90% of frame
|
|
682
|
-
- Face should be frontal (not tilted)
|
|
838
|
+
<div align="center">
|
|
683
839
|
|
|
684
|
-
|
|
685
|
-
- Increase `detection_frame_delay`
|
|
686
|
-
- Reduce `video_width` and `video_height`
|
|
687
|
-
- Disable `show_action_prompt` if not needed
|
|
840
|
+
**[⬆ 返回顶部](#人脸活体检测引擎)**
|
|
688
841
|
|
|
689
|
-
|
|
842
|
+
Made with ❤️ by [sssxyd](https://github.com/sssxyd)
|
|
690
843
|
|
|
691
|
-
|
|
844
|
+
</div>
|
|
692
845
|
|
|
693
|
-
## Support
|
|
694
846
|
|
|
695
|
-
For issues and questions, please visit: https://github.com/sssxyd/face-liveness-detector/issues
|