@sssxyd/face-liveness-detector 0.3.5 → 0.4.0-alpha.2
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.md +61 -45
- package/README.zh-Hans.md +695 -0
- package/dist/index.esm.js +4096 -1368
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +4098 -1367
- package/dist/index.js.map +1 -1
- package/dist/types/__tests__/motion-liveness-detector.test.d.ts +6 -0
- package/dist/types/__tests__/motion-liveness-detector.test.d.ts.map +1 -0
- package/dist/types/browser_utils.d.ts +18 -0
- package/dist/types/browser_utils.d.ts.map +1 -0
- package/dist/types/config.d.ts +3 -6
- package/dist/types/config.d.ts.map +1 -1
- package/dist/types/enums.d.ts +7 -1
- package/dist/types/enums.d.ts.map +1 -1
- package/dist/types/face-detection-engine.d.ts +132 -0
- package/dist/types/face-detection-engine.d.ts.map +1 -0
- package/dist/types/face-detection-state.d.ts +35 -0
- package/dist/types/face-detection-state.d.ts.map +1 -0
- package/dist/types/face-frontal-calculator.d.ts +130 -0
- package/dist/types/face-frontal-calculator.d.ts.map +1 -0
- package/dist/types/image-quality-calculator.d.ts +66 -0
- package/dist/types/image-quality-calculator.d.ts.map +1 -0
- package/dist/types/index.d.ts +5 -191
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/library-loader.d.ts.map +1 -1
- package/dist/types/motion-liveness-detector.d.ts +198 -0
- package/dist/types/motion-liveness-detector.d.ts.map +1 -0
- package/dist/types/screen-capture-detector.d.ts +187 -0
- package/dist/types/screen-capture-detector.d.ts.map +1 -0
- package/dist/types/screen-color-profile-detect.d.ts +77 -0
- package/dist/types/screen-color-profile-detect.d.ts.map +1 -0
- package/dist/types/screen-moire-pattern-detect.d.ts +102 -0
- package/dist/types/screen-moire-pattern-detect.d.ts.map +1 -0
- package/dist/types/screen-rgb-emission-detect.d.ts +42 -0
- package/dist/types/screen-rgb-emission-detect.d.ts.map +1 -0
- package/dist/types/types.d.ts +67 -62
- package/dist/types/types.d.ts.map +1 -1
- package/dist/types/uniapp-resource-manager.d.ts +57 -0
- package/dist/types/uniapp-resource-manager.d.ts.map +1 -0
- package/dist/types/uniapp-sdk.d.ts +89 -0
- package/dist/types/uniapp-sdk.d.ts.map +1 -0
- package/package.json +14 -4
- package/dist/types/__tests__/config.test.d.ts +0 -5
- package/dist/types/__tests__/config.test.d.ts.map +0 -1
- package/dist/types/__tests__/enums.test.d.ts +0 -5
- package/dist/types/__tests__/enums.test.d.ts.map +0 -1
- package/dist/types/__tests__/event-emitter.test.d.ts +0 -5
- package/dist/types/__tests__/event-emitter.test.d.ts.map +0 -1
- package/dist/types/__tests__/face-detection-engine.test.d.ts +0 -7
- package/dist/types/__tests__/face-detection-engine.test.d.ts.map +0 -1
- package/dist/types/exports.d.ts +0 -11
- package/dist/types/exports.d.ts.map +0 -1
|
@@ -0,0 +1,695 @@
|
|
|
1
|
+
> **语言 / Languages:** [中文](#) · [English](./README.md)
|
|
2
|
+
|
|
3
|
+
# 人脸检测引擎
|
|
4
|
+
|
|
5
|
+
一个基于 **[Human.js](https://github.com/vladmandic/human)** 和 **[OpenCV.js](https://github.com/TechStark/opencv-js)** 的纯前端实时人脸活体检测引擎。这个基于 TypeScript 的 npm 包提供实时人脸检测、双重活体验证(静默 + 动作检测)、自动最佳帧选择和防欺骗功能 - 所有处理都 100% 在浏览器中运行,零后端依赖。
|
|
6
|
+
|
|
7
|
+
## 功能特性
|
|
8
|
+
|
|
9
|
+
- 💯 **纯前端实现** - 零后端依赖,所有处理在浏览器中本地运行
|
|
10
|
+
- 🔬 **混合 TensorFlow + OpenCV 方案** - 结合 TensorFlow.js 进行 AI 检测和 OpenCV.js 进行图像处理
|
|
11
|
+
- 🧠 **双检测模式** - 支持静默活体检测和动作检测(眨眼、张嘴、点头),自动选择最佳帧
|
|
12
|
+
- ⚡ **纯 JavaScript 和事件驱动** - 100% TypeScript,响应式事件架构,与任何前端框架(Vue、React、Angular、Svelte 或原生 JS)无缝集成
|
|
13
|
+
- 🎯 **全面的人脸分析** - 实时防欺骗、质量评估、正面度检测和模糊检测
|
|
14
|
+
- 🛡️ **高级防欺骗** - 实时活体分数和欺骗检测
|
|
15
|
+
|
|
16
|
+
## 🚀 在线演示
|
|
17
|
+
|
|
18
|
+
**[👉 在线演示: https://face.lowtechsoft.com/](https://face.lowtechsoft.com/)**
|
|
19
|
+
|
|
20
|
+
用手机扫描二维码立即测试检测引擎:
|
|
21
|
+
|
|
22
|
+
[](https://face.lowtechsoft.com/)
|
|
23
|
+
|
|
24
|
+
## 安装
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
npm install @sssxyd/face-liveness-detector @vladmandic/human @techstark/opencv-js
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
或使用 yarn:
|
|
31
|
+
```bash
|
|
32
|
+
yarn add @sssxyd/face-liveness-detector @vladmandic/human @techstark/opencv-js
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
或使用 pnpm:
|
|
36
|
+
```bash
|
|
37
|
+
pnpm add @sssxyd/face-liveness-detector @vladmandic/human @techstark/opencv-js
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
> **注意**:`@vladmandic/human` 和 `@techstark/opencv-js` 是对等依赖,必须单独安装以避免捆绑大型库。如果您在项目的其他地方已经使用这些库,这样做可以减小最终的打包大小。
|
|
41
|
+
|
|
42
|
+
## 快速开始 - 使用本地资源
|
|
43
|
+
|
|
44
|
+
> ⚠️ **重要提示**:`@techstark/opencv-js` 包含一个 ESM 不兼容的 UMD 格式 OpenCV.js 库,**会导致加载失败**。您必须应用补丁脚本。
|
|
45
|
+
> - **问题链接**:https://github.com/TechStark/opencv-js/issues/44
|
|
46
|
+
> - **补丁脚本**:[patch-opencv.js](https://github.com/sssxyd/face-liveness-detector/tree/main/demos/vue-demo/scripts/patch-opencv.js)
|
|
47
|
+
> - **设置方法**:添加到 `package.json` 的脚本中作为 `postinstall` 钩子,在依赖安装后自动应用
|
|
48
|
+
|
|
49
|
+
> ⚠️ **重要提示**:`@vladmandic/human` 需要下载大型模型文件和 TensorFlow WASM 后端文件。**没有这些资源组件将无法加载**。下载它们到您的项目目录并配置路径。
|
|
50
|
+
> - **模型下载脚本**:[copy-models.js](https://github.com/sssxyd/face-liveness-detector/tree/main/demos/vue-demo/scripts/copy-models.js)
|
|
51
|
+
> - **WASM 下载脚本**:[download-wasm.js](https://github.com/sssxyd/face-liveness-detector/tree/main/demos/vue-demo/scripts/download-wasm.js)
|
|
52
|
+
> - **设置方法**:运行两个脚本作为 `postinstall` 钩子,然后在引擎配置中配置路径
|
|
53
|
+
|
|
54
|
+
```typescript
|
|
55
|
+
import FaceDetectionEngine, { LivenessAction } from '@sssxyd/face-liveness-detector'
|
|
56
|
+
|
|
57
|
+
// 使用自定义配置初始化引擎
|
|
58
|
+
const engine = new FaceDetectionEngine({
|
|
59
|
+
// 配置资源路径
|
|
60
|
+
human_model_path: '/models',
|
|
61
|
+
tensorflow_wasm_path: '/wasm',
|
|
62
|
+
|
|
63
|
+
// 检测设置
|
|
64
|
+
video_width: 640,
|
|
65
|
+
video_height: 640,
|
|
66
|
+
|
|
67
|
+
// 质量设置
|
|
68
|
+
min_image_quality: 0.5,
|
|
69
|
+
min_face_frontal: 0.9,
|
|
70
|
+
|
|
71
|
+
// 活体设置 - 选择您偏好的动作
|
|
72
|
+
liveness_action_count: 1, // 0 表示仅静默检测,1-3 表示动作检测
|
|
73
|
+
liveness_action_list: [LivenessAction.BLINK, LivenessAction.MOUTH_OPEN, LivenessAction.NOD]
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
// 监听事件
|
|
77
|
+
engine.on('detector-loaded', (data) => {
|
|
78
|
+
console.log('✅ 引擎已就绪')
|
|
79
|
+
console.log(`OpenCV: ${data.opencv_version}`)
|
|
80
|
+
console.log(`Human.js: ${data.human_version}`)
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
engine.on('detector-info', (data) => {
|
|
84
|
+
// 实时检测信息
|
|
85
|
+
console.log({
|
|
86
|
+
quality: (data.quality * 100).toFixed(1) + '%',
|
|
87
|
+
frontal: (data.frontal * 100).toFixed(1) + '%',
|
|
88
|
+
liveness: (data.live * 100).toFixed(1) + '%',
|
|
89
|
+
realness: (data.real * 100).toFixed(1) + '%'
|
|
90
|
+
})
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
engine.on('detector-action', (data) => {
|
|
94
|
+
// 动作活体提示
|
|
95
|
+
if (data.status === 'started') {
|
|
96
|
+
console.log(`请执行动作: ${data.action}`)
|
|
97
|
+
} else if (data.status === 'completed') {
|
|
98
|
+
console.log(`✅ 动作识别成功: ${data.action}`)
|
|
99
|
+
}
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
engine.on('detector-finish', (data) => {
|
|
103
|
+
if (data.success) {
|
|
104
|
+
console.log('✅ 活体验证通过!')
|
|
105
|
+
console.log({
|
|
106
|
+
silentDetections: data.silentPassedCount,
|
|
107
|
+
actionsCompleted: data.actionPassedCount,
|
|
108
|
+
imageQuality: (data.bestQualityScore * 100).toFixed(1) + '%',
|
|
109
|
+
totalTime: (data.totalTime / 1000).toFixed(2) + 's',
|
|
110
|
+
bestFrame: data.bestFrameImage, // Base64 编码
|
|
111
|
+
bestFace: data.bestFaceImage // Base64 编码
|
|
112
|
+
})
|
|
113
|
+
} else {
|
|
114
|
+
console.log('❌ 活体验证失败')
|
|
115
|
+
}
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
engine.on('detector-error', (error) => {
|
|
119
|
+
console.error(`错误 [${error.code}]: ${error.message}`)
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
engine.on('detector-debug', (debug) => {
|
|
123
|
+
console.log(`[${debug.stage}] ${debug.message}`)
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
// 初始化并开始检测
|
|
127
|
+
async function runDetection() {
|
|
128
|
+
try {
|
|
129
|
+
// 初始化库(模型、TensorFlow WASM 等)
|
|
130
|
+
await engine.initialize()
|
|
131
|
+
|
|
132
|
+
// 获取视频元素
|
|
133
|
+
const videoElement = document.getElementById('video') as HTMLVideoElement
|
|
134
|
+
|
|
135
|
+
// 在视频流上开始检测
|
|
136
|
+
await engine.startDetection(videoElement)
|
|
137
|
+
|
|
138
|
+
// 检测一直运行到完成或出错
|
|
139
|
+
// 如需停止可手动调用:
|
|
140
|
+
// engine.stopDetection(true) // true 表示显示最佳图像
|
|
141
|
+
} catch (error) {
|
|
142
|
+
console.error('检测启动失败:', error)
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// 就绪时调用
|
|
147
|
+
runDetection()
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
## 配置
|
|
151
|
+
|
|
152
|
+
### FaceDetectionEngineConfig
|
|
153
|
+
|
|
154
|
+
#### 资源路径
|
|
155
|
+
|
|
156
|
+
| 属性 | 类型 | 描述 | 默认值 |
|
|
157
|
+
|-----|------|------|--------|
|
|
158
|
+
| `human_model_path` | `string` | Human.js 模型文件路径 | `undefined` |
|
|
159
|
+
| `tensorflow_wasm_path` | `string` | TensorFlow WASM 文件路径 | `undefined` |
|
|
160
|
+
| `tensorflow_backend` | `'auto' \| 'webgl' \| 'wasm'` | TensorFlow 后端选择 | `'auto'` |
|
|
161
|
+
|
|
162
|
+
#### 视频检测设置
|
|
163
|
+
|
|
164
|
+
| 属性 | 类型 | 描述 | 默认值 |
|
|
165
|
+
|-----|------|------|--------|
|
|
166
|
+
| `video_width` | `number` | 视频流宽度(像素) | `640` |
|
|
167
|
+
| `video_height` | `number` | 视频流高度(像素) | `640` |
|
|
168
|
+
| `video_mirror` | `boolean` | 水平镜像翻转视频 | `true` |
|
|
169
|
+
| `video_load_timeout` | `number` | 视频流加载超时时间(毫秒) | `5000` |
|
|
170
|
+
| `detection_frame_delay` | `number` | 检测帧间延迟(毫秒) | `100` |
|
|
171
|
+
| `error_retry_delay` | `number` | 错误重试延迟(毫秒) | `200` |
|
|
172
|
+
|
|
173
|
+
#### 检测质量设置
|
|
174
|
+
|
|
175
|
+
| 属性 | 类型 | 描述 | 默认值 |
|
|
176
|
+
|-----|------|------|--------|
|
|
177
|
+
| `silent_detect_count` | `number` | 静默检测收集数量 | `3` |
|
|
178
|
+
| `min_face_ratio` | `number` | 最小人脸尺寸比例 (0-1) | `0.5` |
|
|
179
|
+
| `max_face_ratio` | `number` | 最大人脸尺寸比例 (0-1) | `0.9` |
|
|
180
|
+
| `min_face_frontal` | `number` | 最小人脸正面度 (0-1) | `0.9` |
|
|
181
|
+
| `min_image_quality` | `number` | 最小图像质量分数 (0-1) | `0.5` |
|
|
182
|
+
| `min_live_score` | `number` | 最小活体分数 (0-1) | `0.5` |
|
|
183
|
+
| `min_real_score` | `number` | 最小防欺骗分数 (0-1) | `0.85` |
|
|
184
|
+
| `suspected_frauds_count` | `number` | 检测到欺骗前的检测数量 | `3` |
|
|
185
|
+
|
|
186
|
+
#### 人脸正面度特征 (`face_frontal_features`)
|
|
187
|
+
|
|
188
|
+
| 属性 | 类型 | 描述 | 默认值 |
|
|
189
|
+
|-----|------|------|--------|
|
|
190
|
+
| `yaw_threshold` | `number` | 偏航角阈值(度数) | `3` |
|
|
191
|
+
| `pitch_threshold` | `number` | 俯仰角阈值(度数) | `4` |
|
|
192
|
+
| `roll_threshold` | `number` | 翻滚角阈值(度数) | `2` |
|
|
193
|
+
|
|
194
|
+
#### 图像质量特征 (`image_quality_features`)
|
|
195
|
+
|
|
196
|
+
| 属性 | 类型 | 描述 | 默认值 |
|
|
197
|
+
|-----|------|------|--------|
|
|
198
|
+
| `require_full_face_in_bounds` | `boolean` | 要求人脸完全在边界内 | `false` |
|
|
199
|
+
| `use_opencv_enhancement` | `boolean` | 使用 OpenCV 增强进行质量检测 | `true` |
|
|
200
|
+
| `min_laplacian_variance` | `number` | 最小拉普拉斯方差(模糊检测) | `50` |
|
|
201
|
+
| `min_gradient_sharpness` | `number` | 最小梯度清晰度(模糊检测) | `0.15` |
|
|
202
|
+
| `min_blur_score` | `number` | 最小模糊分数 | `0.6` |
|
|
203
|
+
|
|
204
|
+
#### 活体检测设置
|
|
205
|
+
|
|
206
|
+
| 属性 | 类型 | 描述 | 默认值 |
|
|
207
|
+
|-----|------|------|--------|
|
|
208
|
+
| `liveness_action_list` | `LivenessAction[]` | 活体检测动作列表 | `[BLINK, MOUTH_OPEN, NOD]` |
|
|
209
|
+
| `liveness_action_count` | `number` | 需要执行的活体动作数量 | `1` |
|
|
210
|
+
| `liveness_action_randomize` | `boolean` | 是否随机化活体动作顺序 | `true` |
|
|
211
|
+
| `liveness_verify_timeout` | `number` | 活体验证超时时间(毫秒) | `60000` |
|
|
212
|
+
| `min_mouth_open_percent` | `number` | 最小张嘴百分比 (0-1) | `0.2` |
|
|
213
|
+
|
|
214
|
+
## API 参考
|
|
215
|
+
|
|
216
|
+
### 方法
|
|
217
|
+
|
|
218
|
+
#### `initialize(): Promise<void>`
|
|
219
|
+
加载并初始化检测库。使用检测功能前必须调用。
|
|
220
|
+
|
|
221
|
+
```typescript
|
|
222
|
+
await engine.initialize()
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
#### `startDetection(videoElement): Promise<void>`
|
|
226
|
+
在视频元素上开始人脸检测。
|
|
227
|
+
|
|
228
|
+
```typescript
|
|
229
|
+
const videoElement = document.getElementById('video') as HTMLVideoElement
|
|
230
|
+
await engine.startDetection(videoElement)
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
#### `stopDetection(success?: boolean): void`
|
|
234
|
+
停止检测过程。
|
|
235
|
+
|
|
236
|
+
```typescript
|
|
237
|
+
engine.stopDetection(true) // true 表示显示最佳图像
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
#### `updateConfig(config): void`
|
|
241
|
+
在运行时更新配置。
|
|
242
|
+
|
|
243
|
+
```typescript
|
|
244
|
+
engine.updateConfig({
|
|
245
|
+
min_face_ratio: 0.6,
|
|
246
|
+
liveness_action_count: 2
|
|
247
|
+
})
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
#### `getConfig(): FaceDetectionEngineConfig`
|
|
251
|
+
获取当前配置。
|
|
252
|
+
|
|
253
|
+
```typescript
|
|
254
|
+
const config = engine.getConfig()
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
#### `getStatus(): Object`
|
|
258
|
+
获取引擎状态。
|
|
259
|
+
|
|
260
|
+
```typescript
|
|
261
|
+
const { isReady, isDetecting, isInitializing } = engine.getStatus()
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
### 事件
|
|
265
|
+
|
|
266
|
+
引擎使用 TypeScript 事件发射器模式。所有事件都是类型安全的:
|
|
267
|
+
|
|
268
|
+
#### `detector-loaded`
|
|
269
|
+
引擎完成初始化时触发。
|
|
270
|
+
|
|
271
|
+
**数据:**
|
|
272
|
+
```typescript
|
|
273
|
+
interface DetectorLoadedEventData {
|
|
274
|
+
success: boolean // 初始化是否成功
|
|
275
|
+
error?: string // 错误信息(如有)
|
|
276
|
+
opencv_version?: string // OpenCV.js 版本
|
|
277
|
+
human_version?: string // Human.js 版本
|
|
278
|
+
}
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
**示例:**
|
|
282
|
+
```typescript
|
|
283
|
+
engine.on('detector-loaded', (data) => {
|
|
284
|
+
if (data.success) {
|
|
285
|
+
console.log('✅ 引擎就绪')
|
|
286
|
+
console.log(`OpenCV: ${data.opencv_version}`)
|
|
287
|
+
console.log(`Human.js: ${data.human_version}`)
|
|
288
|
+
} else {
|
|
289
|
+
console.error('引擎初始化失败:', data.error)
|
|
290
|
+
}
|
|
291
|
+
})
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
#### `detector-info`
|
|
295
|
+
每帧的实时检测信息。
|
|
296
|
+
|
|
297
|
+
**数据:**
|
|
298
|
+
```typescript
|
|
299
|
+
interface DetectorInfoEventData {
|
|
300
|
+
passed: boolean // 静默活体检查是否通过
|
|
301
|
+
code: DetectionCode // 检测状态码
|
|
302
|
+
size: number // 人脸尺寸比例 (0-1)
|
|
303
|
+
frontal: number // 人脸正面度分数 (0-1)
|
|
304
|
+
quality: number // 图像质量分数 (0-1)
|
|
305
|
+
real: number // 防欺骗分数 (0-1)
|
|
306
|
+
live: number // 活体分数 (0-1)
|
|
307
|
+
}
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
**检测代码:**
|
|
311
|
+
```typescript
|
|
312
|
+
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' // 所有检查通过
|
|
322
|
+
}
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
**示例:**
|
|
326
|
+
```typescript
|
|
327
|
+
engine.on('detector-info', (data) => {
|
|
328
|
+
console.log({
|
|
329
|
+
passed: data.passed,
|
|
330
|
+
status: data.code,
|
|
331
|
+
quality: (data.quality * 100).toFixed(1) + '%',
|
|
332
|
+
frontal: (data.frontal * 100).toFixed(1) + '%',
|
|
333
|
+
liveness: (data.live * 100).toFixed(1) + '%',
|
|
334
|
+
realness: (data.real * 100).toFixed(1) + '%'
|
|
335
|
+
})
|
|
336
|
+
})
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
#### `detector-action`
|
|
340
|
+
动作活体提示和状态更新。
|
|
341
|
+
|
|
342
|
+
**数据:**
|
|
343
|
+
```typescript
|
|
344
|
+
interface DetectorActionEventData {
|
|
345
|
+
action: LivenessAction // 要执行的动作
|
|
346
|
+
status: LivenessActionStatus // 动作状态
|
|
347
|
+
}
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
**动作类型:**
|
|
351
|
+
```typescript
|
|
352
|
+
enum LivenessAction {
|
|
353
|
+
BLINK = 'blink',
|
|
354
|
+
MOUTH_OPEN = 'mouth_open',
|
|
355
|
+
NOD = 'nod'
|
|
356
|
+
}
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
**动作状态:**
|
|
360
|
+
```typescript
|
|
361
|
+
enum LivenessActionStatus {
|
|
362
|
+
STARTED = 'started', // 动作提示已开始
|
|
363
|
+
COMPLETED = 'completed', // 动作识别成功
|
|
364
|
+
TIMEOUT = 'timeout' // 动作识别超时
|
|
365
|
+
}
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
**示例:**
|
|
369
|
+
```typescript
|
|
370
|
+
engine.on('detector-action', (data) => {
|
|
371
|
+
switch (data.status) {
|
|
372
|
+
case 'started':
|
|
373
|
+
console.log(`👤 请执行: ${data.action}`)
|
|
374
|
+
// 更新 UI 显示动作提示
|
|
375
|
+
break
|
|
376
|
+
case 'completed':
|
|
377
|
+
console.log(`✅ 动作识别: ${data.action}`)
|
|
378
|
+
// 更新进度指示器
|
|
379
|
+
break
|
|
380
|
+
case 'timeout':
|
|
381
|
+
console.log(`⏱️ 动作超时: ${data.action}`)
|
|
382
|
+
break
|
|
383
|
+
}
|
|
384
|
+
})
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
#### `detector-finish`
|
|
388
|
+
活体检测完成时触发(成功或失败)。
|
|
389
|
+
|
|
390
|
+
**数据:**
|
|
391
|
+
```typescript
|
|
392
|
+
interface DetectorFinishEventData {
|
|
393
|
+
success: boolean // 活体验证是否通过
|
|
394
|
+
silentPassedCount: number // 静默检测通过数量
|
|
395
|
+
actionPassedCount: number // 动作完成数量
|
|
396
|
+
totalTime: number // 总检测时间(毫秒)
|
|
397
|
+
bestQualityScore: number // 最佳图像质量分数 (0-1)
|
|
398
|
+
bestFrameImage: string | null // Base64 编码的最佳帧图像
|
|
399
|
+
bestFaceImage: string | null // Base64 编码的最佳人脸图像
|
|
400
|
+
}
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
**示例:**
|
|
404
|
+
```typescript
|
|
405
|
+
engine.on('detector-finish', (data) => {
|
|
406
|
+
if (data.success) {
|
|
407
|
+
console.log('✅ 活体验证通过!')
|
|
408
|
+
console.log({
|
|
409
|
+
silentDetections: data.silentPassedCount,
|
|
410
|
+
actionsCompleted: data.actionPassedCount,
|
|
411
|
+
quality: (data.bestQualityScore * 100).toFixed(1) + '%',
|
|
412
|
+
time: (data.totalTime / 1000).toFixed(2) + 's'
|
|
413
|
+
})
|
|
414
|
+
|
|
415
|
+
// 发送结果到服务器
|
|
416
|
+
if (data.bestFrameImage) {
|
|
417
|
+
uploadVerificationResult({
|
|
418
|
+
image: data.bestFrameImage,
|
|
419
|
+
quality: data.bestQualityScore,
|
|
420
|
+
timestamp: new Date()
|
|
421
|
+
})
|
|
422
|
+
}
|
|
423
|
+
} else {
|
|
424
|
+
console.log('❌ 活体验证失败')
|
|
425
|
+
// 提示用户重试
|
|
426
|
+
}
|
|
427
|
+
})
|
|
428
|
+
```
|
|
429
|
+
|
|
430
|
+
#### `detector-error`
|
|
431
|
+
检测过程中发生错误时触发。
|
|
432
|
+
|
|
433
|
+
**数据:**
|
|
434
|
+
```typescript
|
|
435
|
+
interface DetectorErrorEventData {
|
|
436
|
+
code: ErrorCode // 错误代码
|
|
437
|
+
message: string // 错误信息
|
|
438
|
+
}
|
|
439
|
+
```
|
|
440
|
+
|
|
441
|
+
**错误代码:**
|
|
442
|
+
```typescript
|
|
443
|
+
enum ErrorCode {
|
|
444
|
+
DETECTOR_NOT_INITIALIZED = 'DETECTOR_NOT_INITIALIZED',
|
|
445
|
+
CAMERA_ACCESS_DENIED = 'CAMERA_ACCESS_DENIED',
|
|
446
|
+
STREAM_ACQUISITION_FAILED = 'STREAM_ACQUISITION_FAILED',
|
|
447
|
+
SUSPECTED_FRAUDS_DETECTED = 'SUSPECTED_FRAUDS_DETECTED'
|
|
448
|
+
}
|
|
449
|
+
```
|
|
450
|
+
|
|
451
|
+
**示例:**
|
|
452
|
+
```typescript
|
|
453
|
+
engine.on('detector-error', (error) => {
|
|
454
|
+
console.error(`❌ 错误 [${error.code}]: ${error.message}`)
|
|
455
|
+
|
|
456
|
+
switch (error.code) {
|
|
457
|
+
case 'CAMERA_ACCESS_DENIED':
|
|
458
|
+
showErrorMessage('请授予摄像头权限')
|
|
459
|
+
break
|
|
460
|
+
case 'STREAM_ACQUISITION_FAILED':
|
|
461
|
+
showErrorMessage('摄像头访问失败')
|
|
462
|
+
break
|
|
463
|
+
case 'SUSPECTED_FRAUDS_DETECTED':
|
|
464
|
+
showErrorMessage('检测到欺骗 - 请重试')
|
|
465
|
+
break
|
|
466
|
+
default:
|
|
467
|
+
showErrorMessage('检测失败: ' + error.message)
|
|
468
|
+
}
|
|
469
|
+
})
|
|
470
|
+
```
|
|
471
|
+
|
|
472
|
+
#### `detector-debug`
|
|
473
|
+
开发和故障排除的调试信息。
|
|
474
|
+
|
|
475
|
+
**数据:**
|
|
476
|
+
```typescript
|
|
477
|
+
interface DetectorDebugEventData {
|
|
478
|
+
level: 'info' | 'warn' | 'error' // 调试级别
|
|
479
|
+
stage: string // 当前处理阶段
|
|
480
|
+
message: string // 调试信息
|
|
481
|
+
details?: Record<string, any> // 额外详情
|
|
482
|
+
timestamp: number // Unix 时间戳
|
|
483
|
+
}
|
|
484
|
+
```
|
|
485
|
+
|
|
486
|
+
**示例:**
|
|
487
|
+
```typescript
|
|
488
|
+
engine.on('detector-debug', (debug) => {
|
|
489
|
+
const time = new Date(debug.timestamp).toLocaleTimeString()
|
|
490
|
+
console.log(`[${time}] [${debug.stage}] ${debug.message}`)
|
|
491
|
+
|
|
492
|
+
if (debug.details) {
|
|
493
|
+
console.log('详情:', debug.details)
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
// 记录错误以便故障排除
|
|
497
|
+
if (debug.level === 'error') {
|
|
498
|
+
logErrorToServer({
|
|
499
|
+
stage: debug.stage,
|
|
500
|
+
message: debug.message,
|
|
501
|
+
details: debug.details
|
|
502
|
+
})
|
|
503
|
+
}
|
|
504
|
+
})
|
|
505
|
+
```
|
|
506
|
+
|
|
507
|
+
## 枚举
|
|
508
|
+
|
|
509
|
+
### LivenessAction
|
|
510
|
+
```typescript
|
|
511
|
+
enum LivenessAction {
|
|
512
|
+
BLINK = 'blink',
|
|
513
|
+
MOUTH_OPEN = 'mouth_open',
|
|
514
|
+
NOD = 'nod'
|
|
515
|
+
}
|
|
516
|
+
```
|
|
517
|
+
|
|
518
|
+
### LivenessActionStatus
|
|
519
|
+
```typescript
|
|
520
|
+
enum LivenessActionStatus {
|
|
521
|
+
STARTED = 'started', // 动作提示已开始
|
|
522
|
+
COMPLETED = 'completed', // 动作成功识别
|
|
523
|
+
TIMEOUT = 'timeout' // 动作识别超时
|
|
524
|
+
}
|
|
525
|
+
```
|
|
526
|
+
|
|
527
|
+
### DetectionCode
|
|
528
|
+
```typescript
|
|
529
|
+
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' // 所有检测检查通过
|
|
539
|
+
}
|
|
540
|
+
```
|
|
541
|
+
|
|
542
|
+
### ErrorCode
|
|
543
|
+
```typescript
|
|
544
|
+
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' // 检测到欺骗/欺诈
|
|
549
|
+
}
|
|
550
|
+
```
|
|
551
|
+
|
|
552
|
+
## 高级用法
|
|
553
|
+
|
|
554
|
+
有关全面的示例和高级用法模式,请参考官方演示项目:
|
|
555
|
+
|
|
556
|
+
**👉 [Vue 演示项目](https://github.com/sssxyd/face-liveness-detector/tree/main/demos/vue-demo/)**
|
|
557
|
+
|
|
558
|
+
演示包括:
|
|
559
|
+
- 完整的 Vue 3 与 TypeScript 集成
|
|
560
|
+
- 实时检测可视化
|
|
561
|
+
- 配置面板用于尝试不同设置
|
|
562
|
+
- 所有引擎事件的事件处理示例
|
|
563
|
+
- 显示详细检测信息的调试面板
|
|
564
|
+
- 移动端和桌面端响应式 UI 设计
|
|
565
|
+
- 错误处理和用户反馈模式
|
|
566
|
+
- 结果导出和图像捕获示例
|
|
567
|
+
|
|
568
|
+
本地运行演示:
|
|
569
|
+
|
|
570
|
+
```bash
|
|
571
|
+
cd demos/vue-demo
|
|
572
|
+
npm install
|
|
573
|
+
npm run dev
|
|
574
|
+
```
|
|
575
|
+
|
|
576
|
+
然后在浏览器中打开显示的本地 URL 查看检测引擎运行情况。
|
|
577
|
+
|
|
578
|
+
## 下载并托管模型文件
|
|
579
|
+
|
|
580
|
+
为了避免 CDN 依赖并提高性能,您可以在本地下载模型文件:
|
|
581
|
+
|
|
582
|
+
### 可用的下载脚本
|
|
583
|
+
|
|
584
|
+
根目录提供了两个脚本:
|
|
585
|
+
|
|
586
|
+
#### 1. 复制 Human.js 模型
|
|
587
|
+
|
|
588
|
+
```bash
|
|
589
|
+
node copy-human-models.js
|
|
590
|
+
```
|
|
591
|
+
|
|
592
|
+
**功能:**
|
|
593
|
+
- 从 `node_modules/@vladmandic/human/models` 复制人脸检测模型
|
|
594
|
+
- 保存到 `public/models/` 目录
|
|
595
|
+
- 下载 `.json` 和 `.bin` 模型文件
|
|
596
|
+
- 显示文件大小和进度
|
|
597
|
+
|
|
598
|
+
#### 2. 下载 TensorFlow.js WASM 文件
|
|
599
|
+
|
|
600
|
+
```bash
|
|
601
|
+
node download-tensorflow-wasm.js
|
|
602
|
+
```
|
|
603
|
+
|
|
604
|
+
**功能:**
|
|
605
|
+
- 下载 TensorFlow.js WASM 后端文件
|
|
606
|
+
- 保存到 `public/wasm/` 目录
|
|
607
|
+
- 下载 4 个关键文件:
|
|
608
|
+
- `tf-backend-wasm.min.js`
|
|
609
|
+
- `tfjs-backend-wasm.wasm`
|
|
610
|
+
- `tfjs-backend-wasm-simd.wasm`
|
|
611
|
+
- `tfjs-backend-wasm-threaded-simd.wasm`
|
|
612
|
+
- **支持多个 CDN 源**,并自动回退:
|
|
613
|
+
1. unpkg.com(主要)
|
|
614
|
+
2. cdn.jsdelivr.net(备用)
|
|
615
|
+
3. esm.sh(备选)
|
|
616
|
+
4. cdn.esm.sh(最后选择)
|
|
617
|
+
|
|
618
|
+
### 配置使用本地文件
|
|
619
|
+
|
|
620
|
+
下载完成后,配置引擎使用这些本地文件:
|
|
621
|
+
|
|
622
|
+
```typescript
|
|
623
|
+
const engine = new FaceDetectionEngine({
|
|
624
|
+
// 使用本地文件而不是 CDN
|
|
625
|
+
human_model_path: '/models',
|
|
626
|
+
tensorflow_wasm_path: '/wasm',
|
|
627
|
+
|
|
628
|
+
// 其他配置...
|
|
629
|
+
})
|
|
630
|
+
```
|
|
631
|
+
|
|
632
|
+
## 浏览器需求
|
|
633
|
+
|
|
634
|
+
- 支持 WebRTC 的现代浏览器(Chrome、Firefox、Edge、Safari 11+)
|
|
635
|
+
- getUserMedia 需要 HTTPS(开发环境可用 localhost)
|
|
636
|
+
- WebGL 或 WASM 后端支持
|
|
637
|
+
|
|
638
|
+
## 性能优化建议
|
|
639
|
+
|
|
640
|
+
1. **调整检测帧延迟** - 延迟越大 = CPU 使用越低,但检测越慢
|
|
641
|
+
```typescript
|
|
642
|
+
engine.updateConfig({ detection_frame_delay: 200 })
|
|
643
|
+
```
|
|
644
|
+
|
|
645
|
+
2. **减小画布尺寸** - 更小的画布处理更快
|
|
646
|
+
```typescript
|
|
647
|
+
engine.updateConfig({
|
|
648
|
+
video_width: 480,
|
|
649
|
+
video_height: 480
|
|
650
|
+
})
|
|
651
|
+
```
|
|
652
|
+
|
|
653
|
+
3. **优化光线条件** - 更好的光线 = 更好的检测
|
|
654
|
+
- 避免背光
|
|
655
|
+
- 确保人脸光线充足
|
|
656
|
+
|
|
657
|
+
4. **监控调试输出** - 使用调试事件识别瓶颈
|
|
658
|
+
```typescript
|
|
659
|
+
engine.on('detector-debug', (debug) => {
|
|
660
|
+
if (debug.stage === 'detection') {
|
|
661
|
+
console.time(debug.message)
|
|
662
|
+
}
|
|
663
|
+
})
|
|
664
|
+
```
|
|
665
|
+
|
|
666
|
+
## 故障排除
|
|
667
|
+
|
|
668
|
+
### "摄像头访问被拒"
|
|
669
|
+
- 确保使用 HTTPS(开发环境可用 localhost)
|
|
670
|
+
- 检查浏览器权限
|
|
671
|
+
- 用户必须授予摄像头访问权限
|
|
672
|
+
|
|
673
|
+
### "视频加载超时"
|
|
674
|
+
- 检查网络连接
|
|
675
|
+
- 验证模型文件是否可访问
|
|
676
|
+
- 增加 `video_load_timeout`
|
|
677
|
+
|
|
678
|
+
### 检测精度不佳
|
|
679
|
+
- 确保光线充足
|
|
680
|
+
- 保持人脸在画面中居中
|
|
681
|
+
- 人脸应占画面的 50-90%
|
|
682
|
+
- 人脸应正面(不倾斜)
|
|
683
|
+
|
|
684
|
+
### CPU 使用率过高
|
|
685
|
+
- 增加 `detection_frame_delay`
|
|
686
|
+
- 减小 `video_width` 和 `video_height`
|
|
687
|
+
- 禁用 `show_action_prompt`(如不需要)
|
|
688
|
+
|
|
689
|
+
## 许可证
|
|
690
|
+
|
|
691
|
+
MIT
|
|
692
|
+
|
|
693
|
+
## 支持
|
|
694
|
+
|
|
695
|
+
如有问题,请访问:https://github.com/sssxyd/face-liveness-detector/issues
|