更新记录

1.0.0(2026-06-04)

UTS 人脸识别与录入组件 (y-tech-faceai)

本插件是基于 Android CameraX 与 FaceAI SDK 原生封装的 uni-app UTS 插件。包含三个核心模块:

  1. faceai-search-view:视图组件,用于人脸 1:N 检索、活体检测、TTS语音播报。
  2. faceai-register-view:视图组件,用于采集人脸图像、提取人脸特征码并存入本地数据库进行人脸录入。
  3. 人脸数据管理 API:JS 函数集,提供人脸数据的初始化、录入(图片/相机)、删除、清空、数量查询等操作。

模块一:人脸比对检索组件 (faceai-search-view)

用于在已录入的本地人脸库中匹配当前画面中的人脸。

#


平台兼容性

uni-app(4.81)

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

uni-app x(4.81)

Chrome Safari Android iOS 鸿蒙 微信小程序
- - 7.0 - - -

UTS 人脸识别与录入组件 (y-tech-faceai)

本插件是基于 Android CameraX 与 FaceAI SDK 原生封装的 uni-app UTS 插件。包含三个核心模块:

  1. faceai-search-view:视图组件,用于人脸 1:N 检索、活体检测、TTS语音播报。
  2. faceai-register-view:视图组件,用于采集人脸图像、提取人脸特征码并存入本地数据库进行人脸录入。
  3. 人脸数据管理 API:JS 函数集,提供人脸数据的初始化、录入(图片/相机)、删除、清空、数量查询等操作。

模块一:人脸比对检索组件 (faceai-search-view)

用于在已录入的本地人脸库中匹配当前画面中的人脸。

1. 组件属性 (Props)

属性名 类型 默认值 描述说明
searchThreshold Number 0.80 人脸相似度比对成功阈值(0~1)。低于此分值判定为未知人员。
needFaceLive Boolean false 是否开启活体检测(过滤静态照片/视频作弊)。
isCameraSizeHigh Boolean false 是否开启 720p 高清分辨率采集。默认 false(使用 4:3 预览以保帧率)。
searchIntervalTime Number 1800 匹配成功后的冷却延迟时间(毫秒),防止同人多次触发。
livenessThreshold Number 0.3 活体判定分数阈值。
searchTimeOut Number 4000 人脸检测超时时间。
previewShape String 'circle' 相机遮罩形状:'circle' (圆形), 'square' (四角方形), 'none' (不加遮罩遮蔽)。
maskColor String '#ffffff' 相机框外的遮罩背景色(支持透明度配置,格式如 '#FF1a1a1a''#66000000')。
frameBorderColor String '#4CD964' 指示框边缘线颜色(避免使用 borderColor 导致被 CSS 样式拦截)。
borderWidth Number 4 边缘线粗细(px)。

2. 组件事件 (Events)

事件名 回调数据格式 (e.detail) 触发时机
@faceMatched { faceID: String, score: Number, livenessValue: Number } 比对成功(匹配分高于阈值)时触发。
@faceMatchedAll { results: String } 返回本次比对的所有相似候选人(JSON字符串)。
@livenessFailed { faceID: String, score: Number, livenessValue: Number } 人脸相似但活体判定分值过低(照片/视频作弊)。
@processTips { code: Number, message: String } 原生提示通知(如人脸太近、太远、请正对相机等)。

3. 主动控制方法 (Methods via Ref)

  • startSearch(): void:开启相机预览并启动检索引擎。
  • stopSearch(): void:停止检索并关闭相机,清除人脸框。
  • setCameraLensFacing(facing: Number): void:切换摄像头,0 后置,1 前置。

模块二:人脸录入注册组件 (faceai-register-view)

用于给新用户录入人脸特征码和图像。

1. 组件属性 (Props)

属性名 类型 默认值 描述说明
performanceMode Number 1 采集算法模式:0 代表普通精细图,1 代表性能高速采集(推荐)。
isCameraSizeHigh Boolean false 是否开启 720p 分辨率采集。
previewShape String 'circle' 遮罩形状:'circle', 'square', 'none'
maskColor String '#ffffff' 遮罩背景色(支持带透明度的颜色编码如 '#AARRGGBB')。
frameBorderColor String '#4CD964' 指示框边缘颜色(改用 frameBorderColor 命名,避免被 CSS 默认的 borderColor 属性拦截)。
borderWidth Number 4 指示框边缘宽度。

2. 组件事件 (Events)

事件名 回调数据格式 (e.detail) 触发时机
@registerSuccess { faceID: String, faceFeature: String, imagePath: String } 人脸图像采集成功、特征码提取成功并存入本地库时触发。返回特征码及图像在本地缓存的绝对路径。
@registerFailed { message: String } 注册失败(如环境光线太暗、未检测到人脸、特征提取失败等)。
@processTips { code: Number, message: String } 人脸采集时的状态建议(如太近、太远、头抬高一点等)。

3. 主动控制方法 (Methods via Ref)

  • startRegister(faceID: String): void:传入唯一的 faceID,开启相机预览并开始录入人脸。
  • stopRegister(): void:主动终止录入并释放相机。

模块三:人脸数据管理 API

通过 JS 函数直接调用,无需使用组件。导入方式:

import {
  initFaceSDK,
  setCameraID,
  openAddFaceCamera,
  addFaceByImage,
  clearAllFaces,
  deleteFace,
  getFaceCount
} from '@/uni_modules/y-tech-faceai'

API 列表

函数名 参数 返回值 描述
initFaceSDK() void 初始化 SDK(MMKV、TTS、缓存目录等),建议在 App.vueonLaunch 中调用。
setCameraID(cameraID) cameraID: Number(0:前置, 1:后置) void 设置默认摄像头朝向。
openAddFaceCamera(faceID, callback) faceID: String, callback: Function void 打开 SDK 内置的全屏相机录入界面。回调 { code: 1, message: '...' }(1 代表录入成功,其它为取消或错误)。
addFaceByImage(filePath, faceID, callback) filePath: String, faceID: String, callback: Function void 通过图片路径注册人脸(支持 file://content://、绝对路径、assets 路径)。
clearAllFaces() Object 清空所有已注册的人脸数据及关联人脸图片。返回 { code: 0, message: '...' }
deleteFace(faceID) faceID: String Object 删除指定 ID 的人脸数据及关联人脸图片。返回 { code: 0, message: '...' }
getFaceCount() Object 查询已注册人脸数量。返回 { code: 0, count: Number }
getAllFaces() Object 查询所有已注册的人脸数据列表。返回 { code: 0, faces: String },其中 faces 为 JSON 数组字符串(结构包含 id 和 image 属性)。

四、 页面代码示例

1. 人脸检索 (1:N) 示例

<template>
    <view class="face-container">
        <view class="camera-wrapper">
            <faceai-search-view
                ref="faceView"
                class="camera-preview"
                :searchThreshold="0.82"
                :needFaceLive="true"
                previewShape="circle"
                maskColor="#1a1a1a"
                frameBorderColor="#00FF00"
                :borderWidth="6"
                @faceMatched="onFaceMatched"
                @livenessFailed="onLivenessFailed"
                @processTips="onProcessTips"
            />
        </view>
        <view class="control-panel">
            <text class="tips-text">状态:{{ tipsMsg }}</text>
            <button class="btn" type="primary" @click="startRecognize">开始人脸比对</button>
            <button class="btn" type="warn" @click="stopRecognize">停止</button>
        </view>
    </view>
</template>

<script>
export default {
    data() {
        return { tipsMsg: "待比对" }
    },
    methods: {
        startRecognize() {
            this.$refs.faceView.startSearch();
            this.tipsMsg = "识别模块启动中...";
        },
        stopRecognize() {
            this.$refs.faceView.stopSearch();
            this.tipsMsg = "识别已停止";
        },
        onFaceMatched(e) {
            const { faceID, score, livenessValue } = e.detail;
            this.tipsMsg = `匹配成功!ID: ${faceID}, 评分: ${(score * 100).toFixed(1)}%`;
            uni.showToast({ title: `欢迎,${faceID}`, icon: 'success' });
        },
        onLivenessFailed(e) {
            this.tipsMsg = `发现照片作弊,拒绝通行!`;
        },
        onProcessTips(e) {
            this.tipsMsg = e.detail.message;
        }
    }
}
</script>

2. 人脸注册录入示例

<template>
    <view class="face-container">
        <view class="input-wrapper">
            <input class="face-input" v-model="targetUserId" placeholder="请输入要录入的工号/姓名" />
        </view>
        <view class="camera-wrapper">
            <faceai-register-view
                ref="registerView"
                class="camera-preview"
                previewShape="circle"
                maskColor="#1a1a1a"
                frameBorderColor="#00aaff"
                :borderWidth="6"
                @registerSuccess="onRegisterSuccess"
                @registerFailed="onRegisterFailed"
                @processTips="onProcessTips"
            />
        </view>
        <view class="control-panel">
            <text class="tips-text">录入建议:{{ tipsMsg }}</text>
            <button class="btn" type="primary" @click="startEnroll">开始人脸录入</button>
            <button class="btn" type="warn" @click="stopEnroll">停止录入</button>
        </view>
    </view>
</template>

<script>
export default {
    data() {
        return {
            targetUserId: "",
            tipsMsg: "准备录入"
        }
    },
    methods: {
        startEnroll() {
            if (!this.targetUserId.trim()) {
                uni.showToast({ title: '请输入用户ID', icon: 'none' });
                return;
            }
            this.$refs.registerView.startRegister(this.targetUserId);
            this.tipsMsg = "请将面部对准圆框...";
        },
        stopEnroll() {
            this.$refs.registerView.stopRegister();
            this.tipsMsg = "录入已停止";
        },
        onRegisterSuccess(e) {
            const { faceID, faceFeature, imagePath } = e.detail;
            this.tipsMsg = `录入成功!ID: ${faceID}`;
            uni.showModal({
                title: '录入成功',
                content: `用户: ${faceID}\n特征长度: ${faceFeature.length}\n图片保存在: ${imagePath}`,
                showCancel: false
            });
        },
        onRegisterFailed(e) {
            this.tipsMsg = `录入失败: ${e.detail.message}`;
            uni.showToast({ title: '采集失败,请重试', icon: 'none' });
        },
        onProcessTips(e) {
            this.tipsMsg = e.detail.message;
        }
    }
}
</script>

<style>
.face-container {
    display: flex;
    flex-direction: column;
    align-items: center;
    background-color: #1a1a1a;
    height: 100vh;
    padding-top: 30px;
}
.input-wrapper {
    width: 80%;
    margin-bottom: 25px;
}
.face-input {
    background: #fff;
    padding: 12px 15px;
    border-radius: 8px;
    font-size: 16px;
}
.camera-wrapper {
    width: 320px;
    height: 320px;
    overflow: hidden;
    border-radius: 160px;
    background: #000;
}
.camera-preview {
    width: 100%;
    height: 100%;
}
.control-panel {
    margin-top: 30px;
    width: 80%;
    display: flex;
    flex-direction: column;
}
.tips-text {
    color: #fff;
    font-size: 15px;
    margin-bottom: 15px;
    text-align: center;
}
.btn {
    width: 100%;
    margin-bottom: 12px;
}
</style>

3. 人脸数据管理 API 示例

<template>
    <view class="container">
        <text class="title">人脸数据管理</text>

        <view class="info-row">
            <text>已注册人脸数:{{ faceCount }}</text>
            <button size="mini" @click="refreshCount">刷新</button>
        </view>

        <view class="input-row">
            <input class="input" v-model="inputFaceID" placeholder="输入人脸 ID(工号/姓名)" />
        </view>

        <button type="primary" @click="openCamera">SDK相机录入</button>
        <button @click="doAddByImage">通过图片注册</button>
        <button @click="doDelete">删除指定人脸</button>
        <button type="warn" @click="doClear">清空全部人脸</button>
    </view>
</template>

<script>
import {
    initFaceSDK,
    setCameraID,
    openAddFaceCamera,
    addFaceByImage,
    clearAllFaces,
    deleteFace,
    getFaceCount,
    getAllFaces
} from '@/uni_modules/y-tech-faceai'

export default {
    data() {
        return {
            inputFaceID: '',
            faceCount: 0
        }
    },
    onLoad() {
        // 初始化 SDK
        initFaceSDK();
        // 设置前置摄像头(0:前置,1:后置)
        setCameraID(0);
        // 查询数量
        this.refreshCount();
        this.loadAllFaces();
    },
    methods: {
        refreshCount() {
            const res = getFaceCount();
            if (res['code'] === 0) {
                this.faceCount = res['count'];
            }
        },

        openCamera() {
            openAddFaceCamera(this.inputFaceID, (res) => {
                console.log('openAddFaceCamera 回调:', JSON.stringify(res));
            });
        },

        doAddByImage() {
            uni.chooseImage({
                count: 1,
                success: (chooseRes) => {
                    const filePath = chooseRes.tempFilePaths[0];
                    addFaceByImage(filePath, this.inputFaceID, (res) => {
                        const code = res['code'];
                        if (code === 0) {
                            uni.showToast({ title: '注册成功', icon: 'success' });
                            this.refreshCount();
                        } else {
                            uni.showToast({ title: res['message'], icon: 'none' });
                        }
                    });
                }
            });
        },

        doDelete() {
            if (!this.inputFaceID) {
                uni.showToast({ title: '请输入要删除的 ID', icon: 'none' });
                return;
            }
            const res = deleteFace(this.inputFaceID);
            uni.showToast({ title: res['message'], icon: res['code'] === 0 ? 'success' : 'none' });
            this.refreshCount();
        },

        doClear() {
            uni.showModal({
                title: '警告',
                content: '确定要清空所有已录入的人脸数据吗?此操作不可恢复!',
                success: (modalRes) => {
                    if (modalRes.confirm) {
                        const res = clearAllFaces();
                        uni.showToast({ title: res['message'], icon: 'success' });
                        this.refreshCount();
                    }
                }
            });
        }
    }
}
</script>

<style>
.container { padding: 20px; }
.title { font-size: 20px; font-weight: bold; margin-bottom: 20px; }
.info-row { display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px; }
.input-row { margin-bottom: 15px; }
.input { background: #f5f5f5; padding: 10px 12px; border-radius: 6px; }
button { margin-bottom: 10px; width: 100%; }
</style>

隐私、权限声明

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

android.permission.CAMERA android.permission.WRITE_EXTERNAL_STORAGE android.permission.READ_EXTERNAL_STORAGE

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

插件不采集任何数据

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

暂无用户评论。