更新记录

1.0.0(2026-09-29) 下载此版本

初版人脸识别 在线访问 https://auth.yundudu.top


平台兼容性

uni-app(5.07)

Vue2 Vue3 Chrome Safari app-vue app-nvue Android iOS 鸿蒙
√ √ √ √ √ - 4.4 12 -
微信小程序 支付宝小程序 抖音小程序 百度小程序 快手小程序 京东小程序 鸿蒙元服务 QQ小程序 飞书小程序 小红书小程序 快应用-华为 快应用-联盟
- - - - - - - - - - - -

人脸识别活体检测(H5 版 + App WebView 版)

一套可直接跑的人脸活体检测前端实现:引导 → 实时活体检测 → 采集完直接在页面显示人脸图。

引擎基于 @sssxyd/face-liveness-detector(内部用 human.js 做人脸检测与关键点、OpenCV.js 做图像质量与屏幕翻拍分析),全部在浏览器/WebView 内离线运行,不依赖任何后端算法接口。

⚠️ 本插件不打包任何大体积资源(node_modules、模型 28MB、wasm、离线 H5 包约 50MB)。 全部由下面的命令在你本地生成,装完只需几分钟。


目录


一、特性与能力

能力 说明
静默活体 连续多帧人脸质量 + 微动检测,通过才算有效
动作活体 眨眼 / 张嘴 / 点头 / 抬头,可配置要做的动作个数(0 = 只做静默活体)
翻拍照片攻击检测 识别"用手机对着照片拍"的作弊,含屏幕摩尔纹、色彩分布、响应时间等分析
图像质量门槛 人脸占比、正脸度(偏航/俯仰/滚转)、模糊度(拉普拉斯方差)、出界判定
直接展示结果 检测通过后把人脸图直接显示在页面上,默认不上传
可选上传 通过回调/事件把图交给你的接口,字段与传输方式完全自定
摄像头降级 摄像头不可用(部分机型、http 非安全上下文)时,可改用相册/系统相机选图
全参数可配置 30+ 引擎参数 + 文案/颜色/路径全部集中在一个配置文件里

只做前端采集与判定,不上传、不比对、不保存 —— 人脸图怎么用由你自己决定。


二、平台支持

平台 实时检测 相册/拍照降级 说明
H5(浏览器) ✅ ✅ 需要 https 或 localhost(getUserMedia 要求安全上下文)
App-Android ✅ ✅ 用内置离线页在 <web-view> 里跑,宿主页负责申请相机权限
App-iOS ✅ ✅ 同上;iOS 相机权限由系统弹框
小程序 ❌ ✅ 不支持实时检测,只能走相册/拍照

App 端的实时检测跑在 WebView 里(不是原生算法)。原因:本引擎是 JS + WASM 实现, 搬到原生要重写整条算法链路。宿主页负责的只有"原生相机权限"这部分。


三、安装

3.1 环境要求

项 要求
HBuilderX 3.6.9 及以上(App 端需要,H5 不限)
Node.js 16 及以上(仅用于装依赖和跑资源脚本,不参与打包)
浏览器 Chrome / Safari 近两年版本(需支持 WebGL 与 WebAssembly)

3.2 安装依赖

在项目根目录执行:

npm install

package.json 里的三个依赖必须装(本插件不打包它们):

包 版本 安装后体积 用途
@sssxyd/face-liveness-detector ^0.4.3 约 1 MB 活体检测引擎
@vladmandic/human ^3.3.6 约 42 MB(其中模型 28 MB) 人脸检测(blazeface)与关键点(facemesh)
@techstark/opencv-js ^4.12.0-release.1 约 12 MB 图像质量分析、屏幕翻拍检测
# 或者手动装(版本号不要改,尤其 opencv-js)
npm i @sssxyd/face-liveness-detector@^0.4.3 @vladmandic/human@^3.3.6 @techstark/opencv-js@^4.12.0-release.1

@techstark/opencv-js 版本不能换。 v5 换成了"惰性工厂函数"导出,引擎无法自动初始化, 会一直卡在 OpenCV.js initialization timeout。必须用 4.12.0-release.1。

3.3 给 OpenCV 打补丁(关键,否则 H5 跑不起来)

@techstark/opencv-js@4 的 UMD 产物在 webpack(uni-app H5 的构建器)下有两处会抛错:

  1. 末尾 }(this, function () { —— ESM 顶层 this 是 undefined,赋值 root.cv 直接报错
  2. if (typeof Module === 'undefined') Module = {}; —— 严格模式下裸赋值抛 ReferenceError

仓库里的 scripts/patch-opencv.js 会自动修掉这两处:

npm run patch:opencv

如果你用了本仓库的 package.json,npm install 之后 postinstall 已经自动跑过了,无需重复执行。

如果你是把这个功能拷进自己已有的项目,需要在装完依赖后手动跑一次(把脚本也拷过去):

node <path>/scripts/patch-opencv.js

补丁只改 node_modules,属于"装完依赖后的构建准备工作"。 换机器 / 重新 npm install / CI 环境都要重跑一次(放进 postinstall 最省事)。

3.4 准备模型与 wasm 资源

引擎需要两类静态资源,脚本会从 node_modules 与 CDN 取:

# 一次做完两件事:复制 human 模型 + 下载 tfjs wasm 后端
npm run setup-resources

# 或者分开
npm run copy-models       # node_modules/@vladmandic/human/models → static/models   (28 MB)
npm run download-wasm     # CDN → static/wasm                                     (1.3 MB)

产物落位:

static/models/    23 个文件(*.json + *.bin),约 28 MB
static/wasm/      4 个文件(tf-backend-wasm.min.js + 3 个 .wasm),约 1.3 MB

download-wasm 需要联网(依次尝试 unpkg → jsDelivr → esm.sh,版本自动对齐 @vladmandic/human 依赖的 @tensorflow/tfjs-backend-wasm@^4.22.0)。 如果你的机器无法联网,可以只要 static/models/:把配置里的 engine.tensorflow_backend 保持为 webgl(默认值)即可,wasm 只在 auto 降级或显式指定 wasm 时才用。

3.5 注册页面

把 pages.json 里的三个页面加到你的项目:

{
  "pages": [
    { "path": "pages/liveness/liveness", "style": { "navigationBarTitleText": "人脸识别", "navigationStyle": "custom" } },
    { "path": "pages/webview/webview",   "style": { "navigationBarTitleText": "人脸识别", "navigationStyle": "custom" } },
    { "path": "pages/agreement/agreement","style": { "navigationBarTitleText": "人脸识别服务协议", "navigationStyle": "custom" } }
  ],
  "easycom": {
    "^u-(.*)": "uview-ui/components/u-$1/u-$1.vue"
  }
}

3.6 确认基础依赖

页面壳用到了 uview-ui 的 u-icon / u-button:

npm i uview-ui@^2.0.38

在 main.js 里 Vue.use(uView),并在 App.vue / uni.scss 里引用主题(见本仓库的 main.js、App.vue)。 如果你的项目不用 uview,把 pages/liveness/liveness.vue 里的两处替换掉即可:

位置 替换建议
协议勾选的 <u-icon name="checkmark"> 换成你自己的勾选图标(本仓库默认用一张对勾图 / 文字 ✓ 也行)
components/apply-page 里的 <u-button> 换成 <button> 或你自己的按钮组件

四、H5 版用法

H5 版是自包含的:pages/liveness/liveness.vue 自己初始化引擎、自己渲染三个阶段。

4.1 直接访问

http://localhost:8080/#/pages/liveness/liveness

4.2 从业务页跳过去

// 带上身份信息(选填)。字段名可在配置里改:identity.queryKeys
uni.navigateTo({
  url: '/pages/liveness/liveness?apply_id=123&realname=' + encodeURIComponent('张三')
})

4.3 三个阶段

阶段 界面 可用按钮
intro 人脸图标 + 提示 + 协议勾选 开始验证;未勾协议会提示
detecting 圆形取景区 + 四角取景框 + 动作提示 / 状态提示 停止检测;底部灰字「拍照上传」降级入口
done 采集到的人脸图 + 标题 + 提示 重新采集

4.4 部署

H5 产物可以部署到任意目录。插件按 document.baseURI 推导资源路径 (resource.autoResolveBase,默认开),所以部署到 https://host/code/ 这种子目录也不会 404。

如果关掉 autoResolveBase,资源路径会写成 /static/models/,子目录部署时模型会全部 404、 引擎起不来(控制台表现为 blazeface.json / facemesh.json 等一连串 404)。


五、App(WebView)版用法

5.1 为什么 App 要单独一个宿主页

App 端的实时检测跑在 <web-view> 里,而 <web-view> 内的 getUserMedia 是否弹权限框, 取决于宿主 App 有没有拿到系统相机权限:

  • ❌ 直接在 App 里打开 pages/liveness/liveness:它是 H5 版逻辑,App 里没有 getUserMedia 环境,会走「平台不支持」提示
  • ✅ 正确姿势:跳 pages/webview/webview,由它加载活体页 + 在原生侧申请相机权限

宿主页做得刻意很薄,只干四件事:

  1. 加载活体页(优先 App 内置离线包,不依赖外网)
  2. 透传身份信息与状态栏高度(WebView 里 uni.getSystemInfoSync().statusBarHeight 恒为 0,必须原生透传,否则自定义导航栏会被状态栏遮住)
  3. 进入时主动申请相机权限(Android),被拒时弹窗引导去系统设置
  4. 承接内嵌页的「返回上一页」请求;采集完成只做日志/广播,不跳转、不上传

5.2 生成 App 内置离线活体页(推荐)

不生成也能跑(把配置里的 resource.onlinePage 指向你部署好的 H5 地址即可), 但线上包没同步 / 域名故障 / 弱网时会白屏。内置进 App 最稳:

# 1) 先出 H5 产物:HBuilderX → 发行 → 网站-H5
# 2) 再把产物内置成 App 资源
npm run build:offline

# 顺带剔除引擎永远不会加载的模型,省约 25 MB
npm run build:offline -- --prune-models

产物落位 hybrid/html/face-liveness/(约 50 MB,瘦身后约 25 MB)。

uni-app 的硬性约定:本地 HTML 必须放在项目根目录的 hybrid/html/ 下(或 static/ 下), 放别处 <web-view> 加载不到 → 白屏。脚本已按此约定输出。

对应的配置(默认值就是这样,一般不用改):

resource: {
  offlinePage: '/hybrid/html/face-liveness/index.html',
  onlinePage: '',                                  // 留空 = 只用内置页
  pageRoute: '#/pages/liveness/liveness'           // 离线包是 hash 路由,必须拼上
}

5.3 从 App 跳起人脸识别

uni.navigateTo({
  url: '/pages/webview/webview?apply_id=123&realname=' + encodeURIComponent('张三')
})

也可以不传 URL 参数,只靠本地缓存(宿主页会按 identity.storageKeys 去读):

uni.setStorageSync('loanApplyId', '123')
uni.setStorageSync('loanApplyUser', { realname: '张三', idcard: '110101...' })
uni.navigateTo({ url: '/pages/webview/webview' })

5.4 相机权限

Android:宿主页 onReady 里调 plus.android.requestPermissions(['android.permission.CAMERA']), 被拒时弹 Modal 引导去「设置 → 应用 → 权限」打开(配置开关 permission.openSettingsOnDenied)。 另外 manifest.json 里必须有:

"app-plus": {
  "distribute": {
    "android": {
      "permissions": [ "<uses-permission android:name=\"android.permission.CAMERA\"/>" ]
    }
  }
}

iOS:弹性很小 —— 相机权限由 WKWebView + 系统 SDK 弹框,JS 侧无法主动申请。 但必须声明用途描述,缺失时不是"弹框被拒",而是直接拿不到相机设备:

"app-plus": {
  "distribute": {
    "ios": {
      "privacyDescription": {
        "NSCameraUsageDescription": "用于人脸识别活体检测,采集人脸图像完成实人认证"
      }
    }
  }
}

5.5 打包注意

事项 说明
重新生成离线页 hybrid/html/face-liveness/ 是 H5 构建产物,不会跟随源码变化。改了页面源码必须重跑 5.2 两步
离线页不入库 该目录已加入 .gitignore,换机器 / CI 环境需重新生成
真机调试 内置资源必须随包安装。改了离线页要重新打自定义基座或云打包,热刷新看不到变化
首次加载 引擎要加载模型(约 28 MB,本地读取)+ 初始化 OpenCV,低端机首次进页面会有 1–3 秒白屏/黑屏,建议在引导态放个 loading 提示

六、拿到采集结果(上传对接)

检测通过后,页面只做三件事:显示人脸图 → 调 capture.onCaptured(若配置了)→ 广播 uni.$emit。 工程里没有任何后端接口路径,人脸图怎么用完全由你决定。

回调 / 事件收到的数据:

字段 类型 说明
image string 人脸图 base64 dataURL,可直接 <image :src>
base64 string 与 image 相同,语义化别名
identity object { applyId, realname, idcard },来自 URL 参数或本地缓存
stats object { silentPassedCount, actionPassedCount, bestQualityScore, totalTime }

方式一:配置回调(推荐)

// main.js
import { configure } from './common/liveness-config.js'

configure({
  capture: {
    onCaptured: async ({ image, base64, identity, stats }) => {
      // ① 走 multipart 文件流(需要先把 base64 转成本地临时文件,或用 fetch→blob→FormData)
      // ② 走 JSON 直接传 base64(简单,但体积大 1/3)
      await uni.request({
        url: 'https://your.api/face',
        method: 'POST',
        data: { face_image: base64, apply_id: identity.applyId }
      })
    }
  }
})

回调可以是 async;抛出异常只会打日志 + 一个 toast,不影响人脸图的展示。

base64 转 Blob(H5 上传文件流时用得上):

function base64ToBlob(dataURL, mime = 'image/jpeg') {
  const base64 = dataURL.replace(/^data:([^;]+);base64,/, '')
  const binary = atob(base64)
  const bytes = new Uint8Array(binary.length)
  for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i)
  return new Blob([bytes], { type: mime })
}

方式二:全局事件(不想改配置时)

uni.$on('faceLivenessCaptured', async ({ image, identity }) => {
  // 你的接口
})

事件名可在配置里改:capture.eventName;不需要就关掉:configure({ capture: { emitEvent: false } })。

方式三:什么都不做

纯前端展示,零网络依赖。检测通过就显示图,点「重新采集」可以再来一次。


七、配置说明

全部参数在 common/liveness-config.js,共 10 组。默认值 = 开箱可用,页面里不出现魔法数字。

// main.js 里覆盖一次即可(深合并,只覆盖你写的键)
import { configure } from './common/liveness-config.js'

configure({
  engine: { collect_min_face_frontal: 0.85 },   // 用户反馈"一直提示请正面"时放宽
  ui: { showLiveMetrics: false },              // 生产关掉质量/正对/动作实时指标
  texts: { navTitle: '人脸识别' },
  capture: { resultTitle: '人脸采集完成' }
})
分组 作用 常用键
engine 透传给检测引擎(键名与上游 SDK 完全一致) tensorflow_backend detect_video_ideal_width/height collect_min_face_frontal collect_min_image_quality action_liveness_action_count action_liveness_verify_timeout photo_attack_passed_frame_count
resource 模型 / wasm 目录、离线页地址 modelDir wasmDir autoResolveBase offlinePage onlinePage pageRoute
camera 摄像头行为 facingMode warmUpOnGesture fallbackToPhotoCapture
ui 颜色、实时指标、降级入口显隐 borderColors.* showLiveMetrics showFallbackEntry
texts 全部用户可见文案(含状态码/错误码映射) heroTitle btnStart codeMessages errorMessages
actions 动作标识 → 中文文案 labels
capture 结果展示与上传扩展点 showResultImage resultTitle mirrorResultImage onCaptured eventName
identity 身份信息透传的键名 queryKeys storageKeys userFields
navigation 协议页路径、宿主消息类型 agreementPath messageTypes
permission 宿主页权限行为 autoRequest openSettingsOnDenied

完整的逐项说明(含默认值、取值范围、调优建议、改造前后对照)见 docs/人脸识别配置清单.md。

调参速查

现象 改什么
一直提示「请正面对准摄像头」 engine.collect_min_face_frontal 0.9 → 0.85;collect_face_frontal_features.* 放宽到 5/6/4
一直提示「光线不足」 engine.collect_min_image_quality 0.5 → 0.4;collect_image_quality_features.min_laplacian_variance 40 → 30
检测太慢 / 低端机卡 engine.detect_video_ideal_width/height 降到 640×480;collect_min_collect_count 降到 2
张嘴动作总过不去 engine.action_liveness_min_mouth_open_percent 0.2 → 0.15
老年用户体验差 engine.action_liveness_action_list: ['blink'](只留眨眼);或 action_liveness_action_count: 0(纯静默活体)
要更高安全性 engine.action_liveness_action_count: 2;photo_attack_passed_frame_count 10 → 15
结果图左右颠倒 capture.mirrorResultImage: true
老机型 WebGL 异常 engine.tensorflow_backend 从 webgl 改 wasm(需先跑 npm run download-wasm)

配置自测

改完配置跑一下,避免改错导致行为悄悄变掉:

npm run test:config
# 42 项断言:默认值回归、深合并、嵌套合并、数组替换、数值夹紧、联动修正、路径重推导、无副作用

八、常见问题

Q1. 控制台一堆 blazeface.json / facemesh.json 404,引擎起不来

模型路径错了。九成是 H5 部署在子目录但资源路径被写成了根路径。

  • 确认 common/liveness-config.js 的 resource.autoResolveBase 是 true(默认)
  • 确认 static/models/ 与 static/wasm/ 确实存在(跑过 npm run setup-resources)
  • 临时排查:把 engine.human_model_path 显式写成完整地址,看是否恢复

Q2. 卡在 OpenCV.js initialization timeout

@techstark/opencv-js 没打补丁,或者版本装成了 v5。

npm ls @techstark/opencv-js        # 必须是 4.12.0-release.1
npm run patch:opencv               # 补丁在 node_modules 被重装后会丢,需要重跑

Q3. 点「开始验证」没有任何反应,也没弹权限框

两种可能:

  1. iOS WKWebView / 部分安卓 WebView 只在"用户手势"上下文里弹权限框。 插件已在点击回调里同步调申请,但如果你自己改过流程、在它前面加了 await,就会失去手势上下文, 表现为不弹框 + getUserMedia 静默失败。→ 保证 startDetection() 里 await warmUpCamera() 之前没有其他 await。
  2. http 非安全上下文:navigator.mediaDevices 在 http 下不存在(localhost 除外)。 页面会提示「当前环境无法打开摄像头」,走底部「拍照上传」即可。→ 生产环境务必用 https。

Q4. App 里进页面黑屏 / 白屏

  • 检查 hybrid/html/face-liveness/index.html 是否存在(跑过 npm run build:offline)
  • 检查 resource.offlinePage / pageRoute 是否与产物一致
  • 离线页是旧的:改了页面源码但没重新生成,旧产物里可能还指向已删除的路由
  • 真机调试时内置资源必须随包安装 → 重新打自定义基座

Q5. App 里能打开页面,但点开始后不弹相机权限框

Android 上必须由宿主 App 先拿到系统权限,<web-view> 内的 getUserMedia 才会复用。 确认你是通过 pages/webview/webview 进入的(不是直接打开 pages/liveness/liveness), 并检查 manifest.json 已声明 CAMERA 权限。

Q6. 首次进页面要等 1–3 秒才出画面

引擎要加载模型(本地读取 28 MB)+ 初始化 OpenCV + 申请摄像头。属正常现象。 建议在引导态给个「正在加载模型...」提示(页面已经做了:此时点开始会 toast 正在加载模型,请稍候)。

想更快:npm run build:offline -- --prune-models 把离线页里的模型从 28 MB 降到 3 MB (引擎实际只用 blazeface + facemesh)。

Q7. 结果图和预览左右相反

configure({ capture: { mirrorResultImage: true } })。

Q8. 采集到的图能直接上传给后端吗?尺寸多大?

是 1280×720(默认 detect_video_ideal_width/height)下的 JPEG,base64 后通常 100–300 KB。 嫌大可以调小分辨率,或在 onCaptured 里自己压缩后再传。

Q9. 我想用自己的页面版式,不想用这套 UI

pages/liveness/liveness.vue 里的模板和样式可以整体替换,保留 script 里的这几块就行:

  • initEngine() / engine.on(...) 四个事件回调
  • startDetection() / stopDetection() / handleFinish() / showResult()
  • ensureVideoElement()(必须用 document.createElement('video'),模板里的 <video> 会被编译成 uni-video)

配置、权限、引擎生命周期这些都不用动。

Q10. 能只做静默活体(不要用户做动作)吗?

configure({ engine: { action_liveness_action_count: 0 } })

九、需要拷贝哪些文件

只拷必要文件时,按这个清单来:

pages/liveness/liveness.vue          必须 —— H5 版(引擎 + 三态 UI)
pages/webview/webview.vue            必须 —— App 宿主页(只做 App 时需要)
pages/agreement/agreement.vue        建议 —— 协议页(没有它,协议链接会跳失败)
common/liveness-config.js            必须 —— 全部配置
components/apply-page/               必须 —— 页面外壳
components/nav-bar/                  必须 —— apply-page 依赖
components/page-bg/                  必须 —— apply-page 依赖
common/common.scss                   必须 —— App.vue 里 @import
uni.scss                             建议 —— 样式变量(含 uview 主题)
scripts/patch-opencv.js              必须 —— 依赖装完要跑
scripts/copy-models.js               建议 —— 生成 static/models
scripts/download-wasm.js             建议 —— 生成 static/wasm
scripts/build-offline-h5.js          只做 App 时需要
scripts/test-liveness-config.mjs     可选 —— 配置自测
docs/人脸识别配置清单.md              可选 —— 详细配置说明

不需要拷(由命令生成):node_modules/、static/models/、static/wasm/、hybrid/html/face-liveness/。

拷完之后:

npm i @sssxyd/face-liveness-detector@^0.4.3 @vladmandic/human@^3.3.6 @techstark/opencv-js@^4.12.0-release.1 uview-ui@^2.0.38
node <path>/scripts/patch-opencv.js
node <path>/scripts/copy-models.js
node <path>/scripts/download-wasm.js

最后别忘了在 main.js 里 Vue.use(uView) 并注册三个全局组件 (page-bg / nav-bar / apply-page,见本仓库 main.js)。


十、打包上传时排除什么

本插件刻意不打包大体积资源,源码包里请排除以下目录(否则体积会从几百 KB 涨到 300 MB+):

排除 体积 使用者如何生成
node_modules/ 60 MB+ npm install
static/models/ 28 MB npm run copy-models
static/wasm/ 1.3 MB npm run download-wasm
hybrid/html/face-liveness/ 约 50 MB(瘦身后 25 MB) HBuilderX 出 H5 产物 + npm run build:offline
unpackage/ 视情况 HBuilderX 自动生成
.git/、.hbuilderx/、.DS_Store — —

对应的 .gitignore(仓库里已配好,可直接复制):

node_modules/
unpackage/
.hbuilderx/
.DS_Store

# App 内置离线活体页(构建产物)
hybrid/html/face-liveness/

判断标准很简单:能用一条命令重新生成的,都不入包。 只保留源码 + package.json + scripts/(生成脚本是必须给的,否则使用者不知道资源怎么来)。


十一、合规提示

人脸信息属于敏感个人信息,上线前请自行确认:

  • 已取得用户的单独同意(页面的协议勾选是必备项,不要为了体验把它去掉)
  • 协议条款需明确告知:采集内容、使用目的、存储期限、删除方式、撤回授权的方式
  • 人脸图默认不上传。一旦你通过 onCaptured 上传,传输需加密(https), 存储需符合《个人信息保护法》及所属行业的留存要求
  • 本插件只提供技术能力,不提供合规背书。请按你的业务场景与法务意见调整协议文本与流程

十二、更新日志

1.0.0

  • 首版:H5 版 + App WebView 版
  • 静默活体 / 动作活体(眨眼、张嘴、点头、抬头)/ 翻拍照片攻击检测 / 图像质量门槛
  • 检测通过后直接展示人脸图,默认不上传;上传走 capture.onCaptured 或 uni.$on 事件
  • 全部参数集中在 common/liveness-config.js(10 组),附 42 项配置自测与逐项配置清单
  • 不含手机/平板分流(只有一套版式)

许可

本插件源码可自由使用与修改。内置的第三方引擎遵循其各自协议:

  • @sssxyd/face-liveness-detector MIT
  • @vladmandic/human MIT
  • @techstark/opencv-js Apache-2.0

隐私、权限声明

1. 本插件需要申请的系统权限列表:

需要相册权限

2. 本插件采集的数据、发送的服务器地址、以及数据用途说明:

无

3. 本插件是否包含广告,如包含需详细说明广告表达方式、展示频率:

无

许可协议

MIT协议

暂无用户评论。