更新记录

1.0.0(2026-11-09)

  • 首个版本:Android 离线人脸识别 / 活体检测 uni-app x 标准模式组件,支持蒸汽(Vapor)模式
  • module API:init / detect / compare / register / search / remove / count / listUsers / rename / setThreshold / version / release / requestPermission
  • 标准模式组件 <zy-face-liveness-x>:native-view 绑定原生相机预览与人脸框,支持实时检测 / 识别事件
  • 组件方法:start / stop / switchCamera / setDetectFlags / registerFace / capture / setScaleType / release

平台兼容性

uni-app x(4.31)

Chrome Safari Android Android插件版本 iOS 鸿蒙 微信小程序
- - 7.0 1.0.0 - - -

其他

多语言 暗黑模式 宽屏模式 蒸汽模式
× × × √

zy-face-liveness-x 离线人脸识别 / 活体检测

Android 平台 uni-app x 标准模式组件(UTS 插件,components + native-view 封装),兼容 VDOM 与蒸汽(Vapor)模式,全程离线,不依赖任何云服务。

能力 说明
人脸检测 单图 / 相机实时,支持同时识别多人,返回位置、角度、置信度、跟踪 ID
人脸识别 1:N 本地人脸库检索、1:1 两图比对(判断是否同一人)
活体检测 RGB 活体(静默)+ 动作活体(眨眼 / 张嘴 / 摇头 / 抬头)
人脸属性 年龄段、性别、人种、口罩、睁闭眼、表情、人脸质量
人脸库 注册(单/批量)、检索、删除、分页名单、重命名、总数,持久化到本机
相机组件 <zy-face-liveness-x> 预览 + 人脸框 + 实时检测事件 + 相机录入 + 抓拍 + 前后摄切换
  • 支持平台:App-Android(uni-app x)
  • 最低系统:Android 7.0(API 24)
  • 模型包:Pikachu(约 17MB,默认,速度快)/ Megatron(约 61MB,精度更高),随 SDK 一起打进 apk,无需额外下载
  • 组件依赖 androidx.camera 与离线引擎 SDK,首次使用需重做自定义基座(详见文末)

示例页面

示例 文件 内容
离线人脸识别(API) pages/face-liveness-x/face-liveness-x.uvue 初始化/模型包切换、单图多人检测与全部属性、两图比对、人脸库注册/检索/名单/删除/改名、运行日志
人脸相机(组件) pages/face-liveness-x/face-liveness-x-camera.uvue <zy-face-liveness-x> 预览、人脸框、实时属性、开关热切换、相机录入、抓拍

组件用法要点:标准模式组件按 easycom 规范自动解析,页面里直接写标签 <zy-face-liveness-x>,不需要 import、也不需要 components 注册。 组件的属性/事件/方法在 components/zy-face-liveness-x/zy-face-liveness-x.uvue 中声明,能力实现调用 utssdk/app-android/index.uts 的 ZyFaceLivenessView。

注意:native-view 不支持 border / background / box-shadow,需要背景或圆角时请在组件外层用 view 包裹并设置样式。


使用场景

  • 门禁 / 考勤 / 访客登记:本地人脸库录入后离线 1:N 识别,断网可用。
  • 实名核验辅助:1:1 比对两张图是否同一人,配合活体分值判断是否为真人。
  • 相机实时预览:需要边预览边显示人脸框、实时属性(年龄/性别/口罩)的业务场景。
  • 合规与隐私敏感场景:人脸数据只留在本机,不出设备。

使用流程

  1. init(options) 初始化离线引擎(首次解压模型,耗时数秒,必须展示 loading)。
  2. 需要相机时调用 requestPermission(options) 申请 CAMERA 权限(插件内统一申请)。
  3. 调用业务 API:detect / compare / register / search / listUsers 等,在 success / fail 中取结果。
  4. 需要相机预览时,页面中使用 <zy-face-liveness-x>,监听 @load(原生视图就绪)后调用组件方法 start()。
  5. 监听组件 @onEvent,按 detail.type 分发(detect / recognize / register / capture / error 等)。
  6. 页面卸载时调用组件 release() 与插件 release({}),释放相机与引擎。

用法

import {
    init,
    detect,
    compare,
    register,
    search,
    remove,
    count,
    listUsers,
    rename,
    setThreshold,
    version,
    release,
    requestPermission
} from '@/uni_modules/zy-face-liveness-x'

1. 初始化引擎

init({
    packName: 'Pikachu',   // 'Pikachu'(快)/ 'Megatron'(准)
    thresholds: 0.48,      // 1:N 阈值,越大越严格
    maxDetectFaceNum: 5,
    success: (res) => {
        console.log('引擎就绪', res.version, '重复初始化=', res.alreadyInited)
    },
    fail: (err) => {
        console.error('初始化失败', err.errCode, err.errMsg)
    }
})

2. 识别一张图片的人脸信息

detect({
    image: '/storage/emulated/0/DCIM/a.jpg',  // 路径 / file:// / content:// / http(s) / base64
    flags: {
        rgbLiveness: true,
        quality: true,
        mask: true,
        eyeState: true,
        actions: true,
        attribute: true,
        emotion: true,
        recognize: false
    },
    success: (res) => {
        console.log('人脸数', res.faceNum)
        const face = res.faces[0]
        console.log('位置', face.rect.x, face.rect.y, face.rect.width, face.rect.height)
        console.log('角度', face.angle.pitch, face.angle.roll, face.angle.yaw)
        console.log('活体', face.liveness, '质量', face.quality, '口罩', face.mask)
        console.log('年龄', face.ageBracket, '性别', face.gender, '人种', face.race)
        console.log('动作', face.actions.blink, face.actions.jawOpen, face.actions.shake, face.actions.headRaise)
        if (face.faceId > 0) {
            console.log('人脸库命中', face.faceId, face.name)
        }
    },
    fail: (err) => {
        console.error('检测失败', err.errCode, err.errMsg)
    }
})

3. 判断两张图片是否同一人

compare({
    image1: '/sdcard/a.jpg',
    image2: '/sdcard/b.jpg',
    thresholds: 0.48,
    success: (res) => {
        console.log('相似度', res.similarity, res.percentage + '%', '是否同一人', res.same)
    },
    fail: (err) => {
        // 9280005 表示某张图没检测到人脸
    }
})

4. 录入人脸库并检索

// 录入(可批量)
register({
    images: ['/sdcard/a.jpg', '/sdcard/b.jpg'],
    names: ['张三', '李四'],
    success: (res) => {
        res.items.forEach((item) => {
            console.log(item.success, item.id, item.name, item.message)
        })
    }
})

// 从人脸库找人
search({
    image: '/sdcard/c.jpg',
    topK: 3,
    success: (res) => {
        if (res.matched) {
            console.log('命中', res.items[0].id, res.items[0].name, res.items[0].similarity)
        }
    }
})

// 名单管理
count({ success: (res) => console.log('特征总数', res.count, '已命名', res.namedCount) })
listUsers({ page: 1, pageSize: 20, success: (res) => console.log(res.total, res.users) })
rename({ id: 3, name: '王五', success: (res) => console.log(res.message) })
remove({ ids: [3, 4], success: (res) => console.log('删除', res.removed) })

5. 申请相机权限

requestPermission({
    success: (res) => {
        // 授权与拒绝都走 success,用 granted 判断
        console.log('granted=', res.granted, res.msg, '不再询问=', res.doNotAskAgain)
    }
})

6. 页面卸载时释放

onUnmounted(() => {
    release({})
})

组件(相机预览)

<template>
    <view class="camera-wrap">
        <zy-face-liveness-x
            ref="faceRef"
            class="camera"
            :front="true"
            :auto-start="false"
            @load="onload"
            @onEvent="onFaceEvent"></zy-face-liveness-x>
    </view>
</template>

<script setup lang="uts">
    import { init } from '@/uni_modules/zy-face-liveness-x'
    import {
        FaceStartOptions,
        FaceComponentFlags,
        FaceComponentCallbackOptions,
        FaceSwitchCameraOptions,
        FaceRegisterFaceOptions,
        FaceCaptureOptions
    } from '@/uni_modules/zy-face-liveness-x/utssdk/interface.uts'

    // 事件负载类型(纯 TS 写法,事件桥只给 JSON 字符串,不需要 UTSJSONObject 的 getXxx 方法)
    type FaceEventFace = { faceId: number, name: string, liveness: number, quality: number }
    type FaceDetectPayload = { faceNum: number, width: number, height: number, faces: FaceEventFace[] }

    // 组件暴露的方法(vapor 模式下用带类型的 ref 取方法)
    type FaceComponentMethods = {
        start: (options?: FaceStartOptions) => void
        stop: (options?: FaceComponentCallbackOptions) => void
        switchCamera: (options?: FaceSwitchCameraOptions) => void
        setDetectFlags: (flags: FaceComponentFlags, options?: FaceComponentCallbackOptions) => void
        registerFace: (options?: FaceRegisterFaceOptions) => void
        capture: (options?: FaceCaptureOptions) => void
        setScaleType: (scaleType: string, options?: FaceComponentCallbackOptions) => void
        release: () => void
    }

    const faceRef = ref<FaceComponentMethods | null>(null)

    function onload() {
        // 相机检测依赖引擎单例,务必先 init
        init({
            packName: 'Pikachu',
            success: () => faceRef.value?.start({ front: true })
        })
    }

    function onFaceEvent(e: UniNativeViewEvent) {
        // 外层字段用下标读(事件桥只保留属性),负载再按类型 parse
        // 不要用「dataJson 必填」的类型去 JSON.parse 整份 detail,缺字段会抛
        // Failed to contruct type, missing required property
        const type = e.detail['type'] as string
        const json = e.detail['dataJson']
        const dataJson = json == null ? '' : json as string
        if (type == 'detect' && dataJson.length > 0) {
            const payload = JSON.parse<FaceDetectPayload>(dataJson)
            if (payload != null) {
                console.log('人脸数', payload!.faceNum, '第一张脸活体', payload!.faces[0].liveness)
            }
        }
        if (type == 'recognize') {
            console.log('识别命中', dataJson)
        }
    }

    function startRegister() {
        faceRef.value?.registerFace({ name: '张三' })
    }
</script>

<style>
    .camera-wrap {
        width: 100%;
        height: 260px;
        background-color: #000000;
    }

    .camera {
        width: 100%;
        height: 100%;
    }
</style>

组件属性

属性 类型 默认 说明
front Boolean true 是否前置摄像头(人脸场景建议前置)
auto-start Boolean true 原生视图就绪并拿到相机权限后自动开始预览
detect-flags String "" 初始检测开关(JSON 字符串),如 '{"rgbLiveness":true,"recognize":true,"attribute":true}'

组件方法

方法 参数 说明
start { front?, cameraId?, physicalCameraId? } 开始预览,未授权时先申请相机权限;相机就绪时触发 success,拒绝授权 fail 回 9280010(cameraId / physicalCameraId 当前版本预留)
stop { success?, fail? } 停止预览
switchCamera { success?, fail? } 前后摄切换
setDetectFlags { rgbLiveness?, quality?, mask?, eyeState?, actions?, attribute?, emotion?, recognize? } 运行时更新检测开关(整体传入,未传字段回落到原生默认)
registerFace { name?, savePath?, needBase64? } 请求录入:下一帧若恰好 1 张人脸则写入人脸库,结果经 onEvent(type=register)
capture { savePath?, needBase64? } 抓拍当前帧,结果经 onEvent(type=capture)
setScaleType 'FILL_CENTER' \| 'FIT_CENTER' 画面缩放模式(默认铺满)
release - 释放相机资源(组件卸载时会自动调用)

组件事件 @onEvent(e)

e.detail = { type, code, msg, dataJson },dataJson 是负载的 JSON 字符串——跨 native-view 事件桥后嵌套对象会退化成普通 JS 对象(没有 UTSJSONObject 的 getNumber/getArray 等方法),所以负载统一给字符串,页面用 JSON.parse<自定类型>(dataJson) 还原成 TS 对象后直接属性访问。另外 JSON.parse<类型> 是强校验的:类型里声明的必填字段在 JSON 里必须真的存在(可能缺的字段要写成 ? 可选),否则运行期抛 Failed to contruct type, missing required property: xxx;也不要拿“dataJson 必填”的类型去 parse 整份 e.detail(旧基座没有该字段),外层字段一律下标读取。

type dataJson(解析后的字段)
started / switchCamera { front }
stopped {}
detect { faceNum, width, height, faces: FaceInfo[] }(约每 60ms 一帧,未检测到人脸也会回调)
recognize { faceNum, width, height, faces: FaceInfo[] }(仅命中人脸库的人脸)
register { success, id, name, code, message, path?, base64? }
capture { path, base64, width, height }
flags / scaleType { ... }(开关/缩放设置回显)
error {}(看 detail.code / detail.msg)

插件方法

方法 说明 回调
init(options) 初始化离线引擎(幂等) FaceInitResult
detect(options) 单图人脸信息 FaceDetectResult
compare(options) 两图比对 FaceCompareResult
register(options) 批量注册人脸 FaceRegisterResult
search(options) 1:N 检索 FaceSearchResult
remove(options) 按 ID 删除 FaceRemoveResult
count(options) 人脸库特征总数 FaceCountResult
listUsers(options) 分页名单 FaceListResult
rename(options) 修改姓名 FaceSimpleResult
setThreshold(options) 修改 1:N 阈值 FaceSimpleResult
version(options) 引擎版本(同步回调) FaceVersionResult
release(options) 释放引擎 FaceSimpleResult
requestPermission(options) 申请相机权限 FacePermissionResult

所有方法统一 success / fail 回调,失败为 UniError(errSubject = 'zy-face-liveness-x')。

init 参数

字段 默认 说明
packName 'Pikachu' 模型包:Pikachu(17MB,快)/ Megatron(61MB,准)
thresholds 0.48 1:N 检索阈值
maxDetectFaceNum 5 单帧最多人脸数
searchMode 0 0 EAGER(快)/ 1 EXHAUSTIVE(准)
videoDetectMode 2 视频:0 每帧检测 / 1 轻量跟踪 / 2 检测+跟踪
imageDetectMode 0 图片:0 每帧检测
detectPixelLevel -1 检测像素级别,-1 自动

FaceInfo 字段

字段 说明
index 本帧序号(0 开始)
trackId 跟踪 ID(视频稳定,图片为 -1)
confidence 检测置信度
rect { x, y, width, height } 像素坐标
angle { pitch, roll, yaw } 欧拉角(度)
faceId 命中的人脸库 ID(>0 命中;-1 未命中/未开启识别)
name 命中人脸库时的姓名,未命中为空串
liveness RGB 活体 0~1,>0.5 判为活体
quality 人脸质量 0~1,>0.5 判为清晰
mask 口罩置信度 0~1,>0.5 判为戴口罩
leftEyeOpen / rightEyeOpen 1 睁 / 0 闭
emotion 0 无表情 1 开心 2 伤心 3 吃惊 4 恐惧 5 厌恶 6 愤怒
ageBracket 0:0~2 1:3~9 2:10~19 3:20~29 4:30~39 5:40~49 6:50~59 7:60~67 8:>70
gender 0 女 / 1 男
race 0 黑人 1 亚洲人 2 拉丁裔 3 中东 4 白人
actions { blink, jawOpen, shake, headRaise, normal },0 未发生 / 1 发生

错误码

错误码 说明
9280001 参数错误
9280002 引擎未初始化,请先调用 init
9280003 引擎初始化失败(模型包缺失 / 设备不支持)
9280004 图片读取失败(路径不存在 / 下载失败 / base64 非法)
9280005 未检测到人脸
9280006 检测到多张人脸(注册要求单张)
9280007 特征提取失败
9280008 人脸库操作失败
9280009 相机不可用(无摄像头 / 打开失败 / 被占用)
9280010 未获得相机权限
9280011 无法获取应用上下文(请在页面 onLoad 之后调用)
9280012 引擎已初始化(重复 init 按成功返回,此码保留)
9280013 文件保存失败
9280014 操作失败(含异常信息)

权限说明

权限 用途
android.permission.CAMERA 相机预览、相机录入、抓拍
android.permission.INTERNET 仅在 image 传入 http(s) 地址时下载图片(离线识别本身不联网)
android.permission.ACCESS_NETWORK_STATE 远程图片下载时的网络状态判断

注意事项

  1. 必须自定义基座:组件依赖 androidx.camera 与离线引擎 SDK(云端 maven 依赖),标准基座不含这些库,需「发行 → 制作自定义基座」后才能运行;改完原生代码需重新制作基座。
  2. 云打包需能访问 jitpack.io(拉取离线引擎 aar),首次打包会下载约数十 MB 依赖。
  3. 只开需要的开关:quality / mask / eyeState / actions / attribute / emotion 每开一项都会增加单帧耗时;相机实时识别默认只开 rgbLiveness,需要属性时再开,recognize: true 会额外做特征提取 + 检索,最耗性能。
  4. 动作活体不要只看单帧,建议统计 2~3 秒内连续帧(如 blink 命中 ≥1 且 liveness > 0.5 的帧数占比)。
  5. RGB 活体建议连续采集多帧,活体帧数多于非活体帧数时判为真人。
  6. 注册质量:录入图不要戴口罩 / 墨镜,人脸正对、光线均匀;先看 detect 结果里 quality > 0.5 再注册。
  7. init 时机:首次 init 会解压模型,务必在页面可交互后调用并给 loading;相机组件每帧检测依赖引擎单例,未 init 时 onEvent 会持续回 9280002。
  8. 存储位置:特征库 Android/data/<包名>/files/zy_face_liveness/face_feature.db,名单表 .../face_names.db;卸载或清除数据会丢失人脸库。

隐私、权限声明

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

"android.permission.CAMERA"(相机预览与录入)、"android.permission.INTERNET"(仅远程图片下载)

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

全部推理在本机完成,不上传任何人脸数据;仅在传入 http(s) 图片地址时才会请求该地址

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

无

暂无用户评论。