face-liveness-detector
v1.3.0
Published
A Capacitor plugin for iOS and Android that captures photos using the device camera at specified intervals or frame counts, and uploads them to a server API for face liveness detection. Supports multiple images and multiple action types.
Readme
face-liveness-detector
一个用于 iOS 和 Android 的 Capacitor 插件,通过 WebSocket 通信提供实时人脸活体检测功能。插件会定时捕获图像并通过 WebSocket 与后端进行实时通信,获得即时的动作验证和进度更新。
✨ 核心特性
- 🔗 实时 WebSocket 通信:与后端进行即时双向通信
- 📸 智能图像捕获:每 500ms 自动捕获和压缩图像
- 🎯 动态动作管理:后端控制所有动作和验证逻辑
- 📊 实时进度更新:从 0% 到 100% 的实时进度跟踪
- 📱 跨平台支持:iOS 和 Android 原生实现
- 🔄 自动动作流转:基于后端响应无缝切换动作
- ⚡ 无倒计时延迟:立即开始检测,无等待时间
- 🛡️ 全面错误处理:强大的错误处理和重试机制
- 🔍 自动相机缩放:设置相机到最小缩放以获得更广视野
- 📐 智能帧检测:向后端发送精确的帧坐标用于准确检测
- 🎨 可自定义 UI:支持完整的 UI 主题和颜色定制
安装
npm install face-liveness-detector
npx cap sync工作原理
插件使用基于 WebSocket 的实时通信方法:
- WebSocket 连接:建立与后端的持久连接
- 会话初始化:发送
start_session命令开始检测 - 相机设置:自动将相机设置为最小缩放以获得最佳视野
- 连续图像捕获:每 500ms 捕获、旋转和压缩图像
- 帧坐标传输:随每张图像发送检测帧坐标
- 实时验证:后端处理图像并立即响应
- 动态动作流:后端控制执行哪些动作以及何时执行
- 实时进度更新:从 0% 到 100% 的实时进度更新
- action_done 完成:只有收到 action_done 事件才认为检测完成
使用方法
import { liveness } from "face-liveness-detector";
async function startWebSocketDetection() {
// 检查相机权限
const checkResult = await liveness.checkCameraPermission();
if (!checkResult.granted) {
await liveness.requestCameraPermission();
return;
}
// 开始 WebSocket 活体检测
const options = {
token: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", // 认证令牌
host: "your-server.com:9966", // 服务器地址和端口
wsUrl: "ws://your-server.com:9966/face-recognize?token=...", // 完整 WebSocket URL
captureInterval: 500, // 可选:图像捕获间隔(毫秒)
autoRotateCamera: true, // 自动旋转相机预览
enableFaceDetectionFrame: true, // 启用人脸检测框
nativeUI: {
backgroundColor: "#000000",
textColor: "#FFFFFF",
primaryColor: "#FF9800",
cameraFrameColor: "#00FF00",
showProgressBar: true,
showCancelButton: true,
useNativeAnimations: true, // 启用原生动画
customTexts: {
lookLeft: "请向左转头",
lookRight: "请向右转头",
lookUp: "请向上抬头",
lookDown: "请向下低头",
headShake: "请左右摇头",
nod: "请上下点头",
cancel: "取消"
}
}
};
try {
const result = await liveness.startSmartLivenessDetection(options);
console.log("检测结果:", result);
if (result.success && result.pass) {
console.log("✅ 活体检测通过!");
console.log(`进度: ${(result.progress * 100).toFixed(1)}%`);
console.log(`耗时: ${(result.totalDuration / 1000).toFixed(2)}秒`);
// 处理完成的动作
if (result.completedActions) {
console.log("完成的动作:", result.completedActions);
}
// 处理成功的图像
if (result.successfulActionImages) {
console.log("成功图像数量:", result.successfulActionImages.length);
result.successfulActionImages.forEach(actionImage => {
console.log(`${actionImage.action}: ${actionImage.count}张图片`);
});
}
} else if (result.cancelled) {
console.log("🚫 检测被用户取消");
} else {
console.log("❌ 活体检测失败:", result.errorMsg);
}
} catch (error) {
console.error("检测错误:", error);
}
}🔧 配置参数
核心参数
| 参数 | 类型 | 必需 | 默认值 | 描述 |
|------|------|------|--------|------|
| token | string | ✅ | - | 认证令牌 |
| host | string | ✅ | - | 服务器地址和端口 |
| wsUrl | string | ✅ | - | 完整的 WebSocket URL(包含令牌) |
| captureInterval | number | ❌ | 500 | 图像捕获间隔(毫秒) |
相机和 UI 行为
| 参数 | 类型 | 默认值 | 描述 |
|------|------|--------|------|
| autoRotateCamera | boolean | true | 自动旋转相机预览以匹配设备方向 |
| enableFaceDetectionFrame | boolean | true | 显示圆形人脸检测叠加层 |
UI 自定义 (nativeUI)
interface NativeUIConfig {
backgroundColor?: string; // 背景颜色 (hex) - 默认: "#000000"
textColor?: string; // 文本颜色 (hex) - 默认: "#FFFFFF"
primaryColor?: string; // 主要强调色 (hex) - 默认: "#FF9800"
cameraFrameColor?: string; // 相机框颜色 (hex) - 默认: "#00FF00"
showProgressBar?: boolean; // 显示进度指示器 - 默认: true
showCancelButton?: boolean; // 显示取消按钮 - 默认: true
useNativeAnimations?: boolean; // 启用原生动画 - 默认: false
customTexts?: { // 自定义文本翻译
lookLeft?: string; // "向左看" 文本
lookRight?: string; // "向右看" 文本
lookUp?: string; // "向上看" 文本
lookDown?: string; // "向下看" 文本
headShake?: string; // "摇头" 文本
nod?: string; // "点头" 文本
cancel?: string; // 取消按钮文本
};
}📤 响应格式
成功响应
成功完成所有活体检测动作后,返回的结果包含详细的动作信息和图片文件列表:
{
"success": true,
"pass": true,
"progress": 1,
"content": "[{\"actionType\":\"NOD\",\"pass\":true,\"passedFiles\":[{\"id\":\"1ef3ed7b-1e8c-43a7-be1d-38873b0638ea\",\"name\":\"face_verify_e9ce8f51-fae5-4643-93bd-c41f746b7ca3.jpg\"},{\"id\":\"a616dddf-7361-4089-aa76-acc63e48e815\",\"name\":\"face_verify_42c1b23f-7f05-4b72-b32b-916636f3323a.jpg\"},{\"id\":\"be598050-952d-4852-807d-90c24c1849a5\",\"name\":\"face_verify_e6b594b5-55e6-4782-8a8c-d5be96af6ab2.jpg\"}],\"actionDescription\":\"请点点头\"},{\"actionType\":\"MOUTH\",\"pass\":true,\"passedFiles\":[{\"id\":\"3fee65c6-2bd4-4f89-b1d2-c929ecdd41d6\",\"name\":\"face_verify_7bd8f40a-5504-406d-a7e5-9a273010df70.jpg\"},{\"id\":\"6f4fdd26-7188-4ffc-ac6d-0340aae39f2e\",\"name\":\"face_verify_05aa22de-c04f-41ce-ade3-1238d30dd355.jpg\"},{\"id\":\"1503f71e-3f5e-4594-b11a-bb286d205240\",\"name\":\"face_verify_a8b12597-971b-4a2e-bffb-e9e3b5dd34cb.jpg\"}],\"actionDescription\":\"请开合嘴巴\"}]",
"totalDuration": 19174
}content 字段解析后的结构:
[
{
"actionType": "NOD", // 动作类型:点头
"pass": true, // 是否通过
"passedFiles": [ // 通过验证的图片文件
{
"id": "1ef3ed7b-1e8c-43a7-be1d-38873b0638ea",
"name": "face_verify_e9ce8f51-fae5-4643-93bd-c41f746b7ca3.jpg"
},
{
"id": "a616dddf-7361-4089-aa76-acc63e48e815",
"name": "face_verify_42c1b23f-7f05-4b72-b32b-916636f3323a.jpg"
},
{
"id": "be598050-952d-4852-807d-90c24c1849a5",
"name": "face_verify_e6b594b5-55e6-4782-8a8c-d5be96af6ab2.jpg"
}
],
"actionDescription": "请点点头"
},
{
"actionType": "MOUTH", // 动作类型:张嘴
"pass": true,
"passedFiles": [
{
"id": "3fee65c6-2bd4-4f89-b1d2-c929ecdd41d6",
"name": "face_verify_7bd8f40a-5504-406d-a7e5-9a273010df70.jpg"
},
{
"id": "6f4fdd26-7188-4ffc-ac6d-0340aae39f2e",
"name": "face_verify_05aa22de-c04f-41ce-ade3-1238d30dd355.jpg"
},
{
"id": "1503f71e-3f5e-4594-b11a-bb286d205240",
"name": "face_verify_a8b12597-971b-4a2e-bffb-e9e3b5dd34cb.jpg"
}
],
"actionDescription": "请开合嘴巴"
}
]失败响应
{
success: false,
pass: false,
errorMsg: "WebSocket连接失败: Connection timeout",
progress: 0.0,
content: "",
totalDuration: 5000
}取消响应
{
success: false,
pass: false,
cancelled: true,
errorMsg: "Detection cancelled by user",
progress: 0.3,
totalDuration: 8000
}关键响应字段
| 字段 | 类型 | 描述 |
|------|------|------|
| success | boolean | 整体操作是否成功 |
| pass | boolean | 所有动作是否通过验证 |
| progress | number | 最终进度 (0.0 到 1.0) |
| content | string | 后端返回的详细内容(JSON字符串格式,包含所有动作的详细信息和通过的图片文件列表) |
| totalDuration | number | 总检测时间(毫秒) |
| cancelled | boolean | 用户是否取消了检测 |
| errorMsg | string? | 失败时的错误消息 |
Content 字段说明:
content 字段是一个 JSON 字符串,解析后包含每个动作的详细信息:
actionType: 动作类型(如 NOD、MOUTH、LOOK_LEFT 等)pass: 该动作是否通过验证passedFiles: 通过验证的图片文件数组id: 文件唯一标识name: 文件名称
actionDescription: 动作的描述文本
🔄 WebSocket 通信协议
消息流程
建立连接
Client -> Server: WebSocket 连接到 ws://host/face-recognize?token=...会话开始
Client -> Server: {"cmd": "start_session"}动作初始化
Server -> Client: { "cmd": "action_init", "content": "[{\"action\":\"NOD\",\"actionDescription\":\"请点点头\"},{\"action\":\"MOUTH\",\"actionDescription\":\"请开合嘴巴\"}]", "progress": 0.0 }图像提交 (每 500ms 带帧坐标)
Client -> Server: { "cmd": "action_input", "action": "NOD", "images": ["base64_image_data"], "frameInfos": [{ "imageWidth": 320, "imageHeight": 240, "centerX": 160, "centerY": 96, "radius": 64 }] }动作进度更新
Server -> Client: { "cmd": "action_output", "content": "[{\"actionType\":\"NOD\",\"pass\":false},{\"actionType\":\"MOUTH\",\"pass\":false}]", "progress": 0.25 }通知消息 (可选)
Server -> Client: { "cmd": "action_notify", "content": "请将脸部放入框内" }错误处理
Server -> Client: { "cmd": "action_error", "content": "Face quality check failed", "progress": 0.0 }完成 (只有收到 action_done 才认为检测完成)
Server -> Client: { "cmd": "action_done", "content": "[{\"actionType\":\"NOD\",\"pass\":true,\"passedFiles\":[{\"id\":\"...\",\"name\":\"...jpg\"}]},{\"actionType\":\"MOUTH\",\"pass\":true,\"passedFiles\":[{\"id\":\"...\",\"name\":\"...jpg\"}]}]", "progress": 1.0 }
重要提示: 插件只在收到 action_done 事件时才认为检测完成并返回结果,不会根据 progress >= 1.0 来判断完成。这确保了完整的检测流程由后端控制。
🎨 UI 主题示例
深色主题
const darkTheme = {
backgroundColor: "#1a1a1a",
textColor: "#ffffff",
primaryColor: "#007AFF",
cameraFrameColor: "#00ff00",
showProgressBar: true,
showCancelButton: true,
useNativeAnimations: true
};浅色主题
const lightTheme = {
backgroundColor: "#ffffff",
textColor: "#333333",
primaryColor: "#ff6b35",
cameraFrameColor: "#007AFF",
showProgressBar: true,
showCancelButton: true,
useNativeAnimations: false
};极简 UI
const minimalUI = {
backgroundColor: "#000000",
textColor: "#ffffff",
cameraFrameColor: "#ffffff",
showProgressBar: false,
showCancelButton: false,
useNativeAnimations: true
};📡 后端集成指南
WebSocket 服务器要求
你的 WebSocket 服务器应该处理以下消息类型:
1. 会话开始处理器
// 接收: {"cmd": "start_session"}
// 发送动作配置和描述
{
"cmd": "action_init",
"content": JSON.stringify([
{"action": "LOOK_LEFT", "actionDescription": "请向左看"},
{"action": "LOOK_RIGHT", "actionDescription": "请向右看"}
]),
"progress": 0.0
}2. 图像处理器
// 接收: {
// "cmd": "action_input",
// "action": "LOOK_LEFT",
// "images": ["base64..."],
// "frameInfos": [{"imageWidth": 320, "imageHeight": 240, ...}]
// }
// 处理活体检测并响应验证结果
{
"cmd": "action_output",
"content": JSON.stringify([
{"actionType": "LOOK_LEFT", "pass": true}
]),
"progress": 0.5
}3. 可选通知
{
"cmd": "action_notify",
"content": "请将脸部完全放入框内"
}4. 错误响应
{
"cmd": "action_error",
"content": "图像质量不足,请在光线充足的环境中使用",
"progress": 0.0
}5. 完成响应
{
"cmd": "action_done",
"content": JSON.stringify([
{"actionType": "LOOK_LEFT", "pass": true, "passedFiles": ["image1.jpg"]},
{"actionType": "LOOK_RIGHT", "pass": true, "passedFiles": ["image2.jpg"]}
]),
"progress": 1.0
}🚀 主要优势
实时性能
- 即时反馈:通过 WebSocket 获得即时验证结果
- 优化捕获:500ms 间隔和压缩图像确保流畅检测
- 动态流程:后端控制动作序列和时机
- 实时通知:通过 action_notify 进行实时用户指导
增强的图像质量
- 自动缩放:相机自动设置为最小缩放以获得更广视野
- 智能压缩:图像调整为 320px 宽度,40% JPEG 质量
- 帧坐标:随每张图像发送精确的检测区域坐标
- 肖像优化:自动 -90° 旋转以获得最佳方向
简化架构
- 后端控制:所有逻辑由服务器管理
- 无状态客户端:插件纯粹专注于 UI 和图像捕获
- 灵活动作:易于在服务器端添加新动作
- 丰富消息:支持通知、错误和进度更新
增强用户体验
- 无延迟:动作间自动进展
- 实时进度:带通知的实时进度指示器
- 响应式 UI:动作完成时的即时反馈
- 可自定义:完整的 UI 主题和颜色自定义
API 参考
checkCameraPermission()
checkCameraPermission() => Promise<{ granted: boolean; message: string; }>检查相机权限状态
返回值: Promise<{ granted: boolean; message: string; }>
requestCameraPermission()
requestCameraPermission() => Promise<{ granted: boolean; message: string; }>向用户请求相机权限
返回值: Promise<{ granted: boolean; message: string; }>
startSmartLivenessDetection(...)
startSmartLivenessDetection(options: SmartLivenessOptions) => Promise<SmartLivenessResult>使用 WebSocket 开始智能活体检测 - WebSocket版本 使用 WebSocket 实现实时活体检测,后端管理动作和会话
| 参数 | 类型 |
| ------------- | ----------------------------------------------------------------- |
| options | SmartLivenessOptions |
返回值: Promise<SmartLivenessResult>
接口
SmartLivenessOptions
智能活体检测配置 - WebSocket版本
| 属性 | 类型 | 描述 |
| ------------------------------- | ------------------------------------------------------- | ----------------------------------------------- |
| token | string | 认证令牌 |
| host | string | 服务器地址和端口 |
| wsUrl | string | WebSocket URL (包含认证token) |
| captureInterval | number | 图片捕获间隔(毫秒),默认500ms |
| nativeUI | NativeUIConfig | 原生UI配置(仅在原生平台生效) |
| autoRotateCamera | boolean | 是否自动旋转相机预览以匹配设备方向 |
| enableFaceDetectionFrame | boolean | 是否启用人脸检测框显示 |
SmartLivenessResult
智能活体检测结果 - WebSocket版本
| 属性 | 类型 | 描述 |
| ------------------- | -------------------- | -------------------------------- |
| success | boolean | 整体操作是否成功 |
| pass | boolean | 所有动作是否都通过检测 |
| errorMsg | string | 错误信息 |
| progress | number | 检测进度 (0.0-1.0) |
| content | string | 后端返回的内容(动作状态等) |
| totalDuration | number | 总耗时(毫秒) |
| cancelled | boolean | 是否被用户取消 |
| completedActions | string[] | 成功完成的动作列表 |
| successfulActionImages | object[] | 每个动作成功图像的统计信息 |
NativeUIConfig
原生UI配置选项
| 属性 | 类型 | 描述 |
| ----------------------- | --------------------------------------------------------- | ----------------- |
| backgroundColor | string | 背景颜色 (hex格式) |
| textColor | string | 文字颜色 (hex格式) |
| primaryColor | string | 主题颜色 (hex格式) |
| cameraFrameColor | string | 相机预览框颜色 (hex格式) |
| showProgressBar | boolean | 是否显示进度条 |
| showCancelButton | boolean | 是否显示取消按钮 |
| useNativeAnimations | boolean | 是否启用原生动画效果 |
| customTexts | { lookLeft?: string; lookRight?: string; lookUp?: string; lookDown?: string; headShake?: string; nod?: string; cancel?: string; } | 自定义提示文本 |
类型别名
LivenessAction
'LOOK_LEFT' | 'LOOK_RIGHT' | 'LOOK_UP' | 'LOOK_DOWN' | 'HEAD_SHAKE' | 'NOD'
支持的活体检测动作类型(由后端动态管理)
🐛 故障排除
常见问题
WebSocket 连接失败
- 检查服务器是否运行并可访问
- 验证 WebSocket URL 格式和令牌
- 确保网络连接正常
相机权限被拒绝
- 在开始检测前调用
checkCameraPermission() - 使用
requestCameraPermission()请求权限
- 在开始检测前调用
检测质量差
- 确保光线条件良好
- 检查人脸是否正确位于框内
- 验证后端帧坐标处理
性能问题
- 调整
captureInterval(较慢设备增加间隔) - 检查后端处理速度
- 监控 WebSocket 消息频率
- 调整
调试日志
启用调试日志以排除问题:
- Android:按标签
LivenessDetectionActivity过滤 - 监控 WebSocket 消息流
- 检查图像处理管道日志
📄 许可证
此项目采用 MIT 许可证 - 详细信息请参见 LICENSE 文件。
🤝 贡献
- Fork 仓库
- 创建你的功能分支
- 提交你的更改
- 推送到分支
- 创建 Pull Request
📞 支持
获取支持和问题咨询:
- 在 GitHub 仓库中创建问题
- 查看现有文档和示例
- 审查 WebSocket 协议实现
