十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

基于uni-app与百度云的小程序人脸录入与识别实战指南

基于uni-app与百度云的小程序人脸录入与识别实战指南 做一个人脸录入人脸识别的功能难吗如果是从头训练一个模型确实难但放到今天选一条“uni-app 做小程序端 百度云做算法端”的组合路线一个普通前端开发也能在两三天内把功能打通。我之前做过一个园区访客登记小程序核心就是来人先拍照录脸以后刷脸签到、刷脸开门。整个过程踩了不少坑尤其是微信小程序的摄像头权限、图片压缩、百度云token这些细节今天我把完整实现思路和能直接抄作业的代码一起整理出来给正准备做同类功能的同学一个参考。1. 项目背景与整体方案选型1.1 为什么选 uni-app 而不是原生微信小程序这个小程序最终需要同时覆盖微信端和后续可能的 App 端所以我直接用 uni-app 开发。uni-app 的语法接近 Vue一套代码可以编译到微信小程序、H5、App后续如果要扩展支付宝小程序或抖音小程序改动成本也低。人脸录入这种页面虽然涉及相机操作但 uni-app 已经把 camera 组件、uni.compressImage、uni.request 这些常用能力封装好了不用为每个平台单独写一套原生逻辑。如果只做微信小程序原生开发当然也行但考虑到团队后续还要做管理后台和员工端 App统一技术栈能省不少事。uni-app 在跨端的一致性上做得不错特别是摄像头、文件上传这类高频能力官方文档覆盖得比较全遇到问题也能在社区找到类似案例。1.2 为什么人脸算法交给百度云人脸识别听起来很唬人但企业自己训练模型不现实数据量、算力、模型迭代都是成本。市面上成熟的人脸识别服务不少我选百度云主要看中三点接入简单创建应用后拿 API Key 和 Secret Key换取 Access Token 就能调用几行代码搞定。功能完整人脸检测、人脸注册、人脸搜索、人脸比对、活体检测全都有不需要自己拼多个服务。免费额度够用个人开发和中小项目测试阶段基本够用QPS 不高但前期完全没问题。这里要明确分工uni-app 负责前端采集人脸照片和展示结果百度云负责从图片里找脸、提特征、建人脸库、比对打分。前端不需要关心特征向量怎么算只要把合规的图片传上去读返回的分数和 face_token 就行。1.3 整体业务流程设计人脸功能在业务里一般分两个阶段录入阶段用户打开录入页面配合摄像头拍一张正脸照片前端先做基础质量判断再把照片传给百度云人脸注册接口服务端把人脸特征存入指定的人脸库group同时把返回的 face_token 和用户业务ID绑定。识别阶段用户再次进入时拍一张照片传给百度云人脸搜索接口从人脸库里找出 top1 的人返回匹配分数再结合业务规则决定是放行、签到还是报警。百度云的人脸库不是简单的用户表它分为 group_id 和 user_id 两级结构。groupId 可以理解成一个业务分区比如“园区员工”“访客”“会员”每次搜索时指定在哪个 group 里找人避免全量匹配导致性能下降和误识别率上升。这个设计非常关键初期很多人会把所有用户塞进一个组等数据量大了再拆就很痛苦。2. 百度云 AI 开放平台接入准备2.1 创建应用与获取密钥去百度云 AI 开放平台控制台找到“人脸识别”服务创建应用后你会拿到三个关键信息API Key、Secret Key、应用 ID。API Key 和 Secret Key 就是调用接口的身份证后续获取 Access Token 必须用到。创建应用时注意勾选服务权限默认会把人脸检测、人脸搜索、人脸库管理等权限都带上。如果后续 403 或提示 not exist先回这里检查是否忘记开通对应服务。密钥是敏感信息前端代码里不能直接写死。规范做法是请求自己的后端由后端保存密钥并转发请求或者至少在后端完成 token 的获取和缓存。微信小程序是客户端代码任何人反编译都能看到你的密钥直接写在 uni.request 里等于把账号送给别人刷。我在项目里是把密钥放在一个 Node 中间层小程序每次只向后端要 token。2.2 Access Token 的获取与缓存百度云人脸接口统一使用 Bearer Token 认证官方称为 Access Token。获取方式很简单用 API Key 和 Secret Key 调用 OAuth 接口curl -i -X POST https://aip.baidubce.com/oauth/2.0/token?grant_typeclient_credentialsclient_id你的APIKeyclient_secret你的SecretKey返回结果里最重要的字段是 access_token默认有效期大概 30 天。这里有个巨大的坑如果前端每次请求都去换一次 token不仅浪费请求次数还会因为并发换 token 导致部分请求失败。我在项目里做了一个 token 管理模块用一个本地缓存加过期时间保证同一时刻只有一个请求在刷新 token// utils/token.js let tokenPromise null function getAccessToken() { const cached uni.getStorageSync(bd_access_token) const expire uni.getStorageSync(bd_token_expire) if (cached expire expire - Date.now() 60 * 60 * 1000) { return Promise.resolve(cached) } if (tokenPromise) { return tokenPromise } tokenPromise new Promise((resolve, reject) { uni.request({ url: https://aip.baidubce.com/oauth/2.0/token, method: POST, data: { grant_type: client_credentials, client_id: 你的APIKey, client_secret: 你的SecretKey }, success: (res) { if (res.data.access_token) { uni.setStorageSync(bd_access_token, res.data.access_token) uni.setStorageSync(bd_token_expire, Date.now() res.data.expires_in * 1000) resolve(res.data.access_token) } else { reject(res.data) } }, fail: reject, complete: () { tokenPromise null } }) }) return tokenPromise } module.exports { getAccessToken }注意 token 没过期时我还留了 1 小时的缓冲期避免刚好卡在过期临界点导致调用失败。这个缓冲设置很重要微信小程序里网络波动比较多卡点请求很容易出问题。2.3 接口域名与本地调试微信小程序对网络请求域名有强校验上线版本必须把请求域名加入到 request 合法域名且要求 HTTPS。百度云接口域名是 https://aip.baibce.com在微信公众平台“开发管理-开发设置-服务器域名”里加上这个地址即可。本地开发阶段在 HBuilderX 运行到微信开发者工具时可以在微信开发者工具右上角“详情-本地设置”里勾选“不校验合法域名”这样本地调试不会被域名拦。但真机预览时还是要按真机模式处理如果域名没配好真机上所有请求都会直接报 url not in domain list排查半天。3. 人脸录入功能实现3.1 前端拍照页面搭建人脸录入页面我用了 camera 组件因为 chooseImage 只能从相册选相册里的照片极可能是翻拍、P过的质量不可控录入效果很差。camera 组件可以让用户直接对着摄像头取景体验更像“采集人脸”。页面布局很简单上半部分摄像头实时预览下半部分放操作按钮和提示文案。关键代码template view classface-register camera v-ifshowCamera classcamera device-positionfront flashoff erroronCameraError / view classtip请将面部置于取景框内保持正脸、光线充足/view button clicktakePhoto拍照录入/button /view /template这里 device-position 我用的 front人脸录入场景前置摄像头更自然。如果项目需要支持后置摄像头参数改成 back 就行。拍照动作通过 createCameraContext 触发const ctx uni.createCameraContext() ctx.takePhoto({ quality: high, success: (res) { const tempImagePath res.tempImagePath this.handleImage(tempImagePath) } })拍照成功后拿到临时路径先做一次本地预览让用户确认再继续后续处理。不要刚拍完就上传用户表情没摆好、闭眼了、口罩没摘传上去也是浪费请求次数。3.2 图片压缩与 base64 转换百度云人脸接口要求传图片 base64且 base64 编码后大小建议不超过 1M否则容易超限。手机拍出来的照片动不动 3M、5M直接转 base64 肯定超所以一定先压缩。uni-app 提供了 uni.compressImage可以控制压缩质量uni.compressImage({ src: tempImagePath, quality: 80, success: (res) { this.compressPath res.tempFilePath this.convertToBase64(res.tempFilePath) } })转 base64 用 uni.getFileSystemManager().readFile编码格式选 base64const fs uni.getFileSystemManager() fs.readFile({ filePath: filePath, encoding: base64, success: (res) { const base64 res.data // 这里可以直接调用百度接口了 } })注意 readFile 在部分安卓机上有临时目录读取失败的问题如果遇到可以先 uni.saveFile 持久化一下再读。另外 base64 字符串里不能有换行符如果自己拼 data URI 的时候出现了 \n要 replace 掉否则百度接口会报 image format error。3.3 人脸检测与图片质量校验图片传给人脸库之前强烈建议先调一次百度云人脸检测接口确认图片里确实有人脸、人脸质量达标再入库。这一步能挡住大量低质量录入比如逆光、闭眼、侧脸、遮挡、多人脸。人脸检测接口是 detect我一般这样传参uni.request({ url: https://aip.baidubce.com/rest/2.0/face/v3/detect?access_token${token}, method: POST, data: { image: base64, image_type: BASE64, face_field: quality,angle, max_face_num: 1, face_type: LIVE }, success: (res) { const result res.data.result if (!result || !result.face_num) { uni.showToast({ title: 未检测到人脸, icon: none }) return } const faceList result.face_list const quality faceList[0].quality if (quality.blur 0.5 quality.illumination 40 quality.completeness 1) { // 质量合格继续注册 this.doRegister(base64) } else { uni.showToast({ title: 图片质量不合格请重拍, icon: none }) } } })face_field 里的 quality 会返回 blur模糊程度、illumination光照、completeness完整度、occlusion遮挡等指标angle 会返回人脸的三维旋转角度。这些阈值没有绝对标准我在测试时的经验是 blur 越低越好illumination 大概 40 以上completeness 接近 2 才是正常。实际业务里为了提升用户体验我做了前端二次校验检测到问题后直接提示“请正面面对屏幕”“光线太暗”“有遮挡”而不是只弹一个“质量不合格”。虽然要多写几行判断但用户被拒绝时的感受完全不一样。3.4 调用人脸注册接口质量校验通过后调用人脸注册接口把图片加入人脸库并关联用户function doRegister(base64, userId, groupId, userInfo) { return getAccessToken().then((token) { return new Promise((resolve, reject) { uni.request({ url: https://aip.baidubce.com/rest/2.0/face/v3/faceset/user/add?access_token${token}, method: POST, data: { image: base64, image_type: BASE64, group_id: groupId, user_id: userId, user_info: userInfo, quality_control: NORMAL, liveness_control: NORMAL }, success: (res) { if (res.data.error_code 0) { const faceToken res.data.result.face_token resolve(faceToken) } else { reject(res.data) } }, fail: reject }) }) }) }quality_control 和 liveness_control 是百度云内置的兜底开关。quality_control 设为 NORMAL 或 HIGH百度会在注册时自动拒绝模糊、低质量图片liveness_control 设为 NORMAL 或 HIGH会做一定的活体判断减少用照片翻拍注册的风险。注册和搜索接口都可以传这两个参数我在业务里统一用 NORMAL避免过严导致正常用户注册失败。一个用户可以被重复注册同一 user_id 多次 add 不会报错而是追加人脸。需要防止重复录入的话可以在业务层先查这个 user_id 是否已有 face_token或者用百度云的 face/delete 接口先清理旧人脸再重新注册。3.5 人脸库分组策略group_id 的设计会影响整个系统的扩展性。我在访客项目中按角色分了三组员工、访客、黑名单人员。搜索时根据当前业务场景指定 group_id_list比如门禁只搜员工组访客登记只搜访客组黑名单单独一组用于拦截。同一个 user_id 在不同 group 里是独立的比如一个人既可以是员工又可以是访客那他在两个组里会有两份人脸记录。用 user_id 管理业务身份用 face_token 管理人脸实例两者解耦后面调整权限时不用动人脸数据。4. 人脸识别功能实现4.1 人脸搜索接口调用识别阶段的核心接口是 search就是拿一张新照片去指定的人脸库里找最像的人function faceSearch(base64, groupIdList) { return getAccessToken().then((token) { return new Promise((resolve, reject) { uni.request({ url: https://aip.baidubce.com/rest/2.0/face/v3/search?access_token${token}, method: POST, data: { image: base64, image_type: BASE64, group_id_list: groupIdList.join(,), quality_control: NORMAL, liveness_control: NORMAL, max_user_num: 1 }, success: (res) { if (res.data.error_code 0) { resolve(res.data.result) } else { reject(res.data) } }, fail: reject }) }) }) }返回结果里最关键的是 user_list格式类似{ face_token: 人脸标识, user_id: 用户业务ID, group_id: 所属分组, score: 87.36 }score 是相似度分数范围 0~100。我在项目里定的经验阈值是 80超过 80 才认为匹配成功低于 80 直接提示“未识别”。这个阈值不是固定的要结合场景调门禁这种安全要求高的场景阈值可以提到 85 甚至 90会员识别的场景80 左右用户感知更好不至于经常识别失败。4.2 人脸比对接口与活体检测除了 search还有一个常用的 match 接口用于两张人脸图的比对比如身份证照片和现场自拍是不是同一个人。match 接口一次最多传 4 组图片对适合做人工审核辅助。我实际用得比较多的是在访客预约场景访客在小程序里先上传一张自拍完成预注册到现场时再拍一张用 match 比对两张脸这样可以防止别人拿预注册照片代打卡。关于活体检测很多人在这一步会忽略。百度云的 liveness_control 参数做的是图像层面的活体判断能拦住一部分用手机照片翻拍的情况但拦不住视频实时翻拍。如果业务对安全性要求高建议接入百度云的身份验证产品或者在端上做动作活体检测比如眨眼、摇头再配合后端接口验证。H5视频活体检测需要单独申请能力流程稍微复杂一点但安全等级高很多。4.3 识别结果与业务系统的联动识别成功拿到的 user_id 只是业务主键真正要做的是把它映射回自己的业务系统。我在后端维护了一张用户表user_id 存业务主键再关联姓名、手机号、角色、通行权限这些字段。前端识别出 user_id 后调自己的后端查询用户详情和权限决定是开门、签到、弹欢迎语还是告警。这里有个需要注意的问题search 接口返回的 user_list 可能包含多个候选当 top1 分数过低时不要直接拒绝可以先返回一个“未匹配请重试”的提示给用户重新拍摄的机会。我在测试时发现光线变化对识别分数影响非常大同一个人的分数可能在 78 到 92 之间波动。连续三次失败再走人工核验流程是兼顾体验和安全最好的方式。另外每个用户的识别日志一定要记录包括请求时间、图片、返回分数、阈值判断结果。这对后续调优阈值和做安全审计都很有用。5. 常见问题与避坑指南5.1 微信小程序合法域名与真机调试问题这是新人最容易卡住的问题。本地开发者工具里一切正常一上真机所有请求全部失败报错 url not in domain list。原因就是本地调试勾了“不校验合法域名”真机没有这个待遇。解决方式就是提前在微信公众平台配置 request 合法域名把 https://aip.baidubce.com 加进去。注意域名的协议必须是 HTTPS且不能带路径。配置提交后一般几分钟生效不用重新发布小程序。还有一个容易踩的小坑如果你把请求转发到自己的后端后端再调百度云那小程序域名只配置你自己的后端域名即可百度云相关内容只出现在服务端安全性也更高。我推荐有条件的新项目直接用这个模式而不是小程序直连百度云。5.2 base64 图片过大与压缩参数选择百度云人脸接口虽然有免费额度但单张 base64 超过限制会直接报错而且图片过大会拖慢上传速度用户等待时间变长体验很差。我在项目里把压缩质量控制在 70 到 80 之间压缩后的图片大概 100-300KB转 base64 后也在 1M 内。质量太低会影响人脸检测的准确率太高又容易超限。不同机型拍摄的原图尺寸差异很大压缩时最好按宽高限制而不是只压质量。uni.compressImage 的 compressedWidth 和 compressedHeight 参数可以限制尺寸做到 720 宽基本够人脸识别用了。还要注意 Android 和 iOS 的相机输出格式差异部分安卓机拍出的原图可能有 EXIF 旋转信息压缩后方向是正的但 width 和 height 会变后端存图或者前端展示时都要统一处理。5.3 Access Token 过期与刷新策略百度云的 Access Token 有效期 30 天但并不是说 30 天内就一直有效如果频繁调用或账号异常也可能被提前失效。我在代码里做了 token 失效自动重试一次的逻辑function requestWithRetry(options) { return getAccessToken().then((token) { options.url options.url ?access_token token return uniRequest(options) }).catch((err) { if (err.error_code 110 || err.error_code 111) { // token失效清缓存再试一次 uni.removeStorageSync(bd_access_token) uni.removeStorageSync(bd_token_expire) return getAccessToken().then((token) { options.url options.url ?access_token token return uniRequest(options) }) } throw err }) }error_code 110 是 token 无效111 是 token 过期。这两种情况都直接清掉本地缓存重新获取后再请求一次。注意重试次数只能一次防止循环重试把请求打爆。5.4 相机权限与用户引导微信小程序里 camera 组件首次渲染会触发授权弹窗用户拒绝后组件会黑屏或者报错。在进入人脸录入页面时提前检查授权状态很重要。我用的是 uni.getSetting 加 uni.authorize 组合uni.getSetting({ success: (res) { if (!res.authSetting[scope.camera]) { uni.authorize({ scope: scope.camera, success: () this.showCamera true, fail: () { uni.showModal({ title: 提示, content: 需要摄像头权限才能完成人脸录入, confirmText: 去设置, success: (res) { if (res.confirm) { uni.openSetting() } } }) } }) } else { this.showCamera true } } })用户拒绝授权后跳设置页的方法非常好用我把它封装成了一个公共方法凡是涉及相机、相册、位置这些敏感权限的页面都能复用。这里顺便提醒一下不要在用户刚进入页面就弹一堆授权最好是在点“拍照录入”按钮时再触发用户更愿意配合。5.5 几张常用表格建议直接收藏对照百度云人脸接口虽然官方文档很全但字段太多我这里整理了一张常用的参数速查表方便你做参数选择时快速判断接口核心参数用途detectimage, face_field, max_face_num检测人脸位置与质量faceset/user/addgroup_id, user_id, image注册人脸到指定人脸库faceset/user/deletegroup_id, user_id, face_token删除用户或人脸searchimage, group_id_list在指定人脸库中搜索相似人matchimage, image (多组)1:1 人脸比对faceset/group/getlist无查询当前账号下全部分组关于阈值我也给一个经验参考区间实际以你自己的业务验证为准场景匹配阈值区间建议门禁/支付88-92宁可误拒绝不可误放员工考勤82-88均衡安全与体验会员/访客78-84体验优先匹配失败走人工我先把这个阈值设置成一个配置文件而不是写死在业务代码里。上线后观察一周识别率和误识率再动态调整效果会稳很多。6. 上线前必须做的几项检查人脸功能最容易出问题的不是开发阶段而是上线后的边界情况。我在项目上线前会把下面这些项挨个过一遍第一项测试机型覆盖。微信小程序跑在各种各样的安卓机和 iPhone 上摄像头能力和性能差异很大。至少要测 iPhone 各代、主流 Android 厂商的高中低端机型特别关注拍照后压缩、base64 转换和 camera 组件黑屏问题。第二项网络环境兼容。弱网环境下图片上传可能超时。我为人脸请求单独设置了 10 秒超时超时后给用户明显的错误提示而不是默默失败。如果公司有自己的服务端建议做异步重试任务失败的人脸请求自动重试一次成功率能提高不少。第三项灰度发布。人脸功能不要一把梭全量上线先开放给内部员工或者少量测试用户观察接口报错率、平均响应耗时、用户投诉稳定后再放量。我之前经历过一次没做灰度上线当天预览版域名配置失效所有用户刷脸全部失败紧急回滚很狼狈。第四项数据合规与用户隐私。人脸信息属于敏感个人信息小程序端要有人脸信息采集告知弹窗明确说明收集目的、使用范围、存储方式。后端要控制人脸数据的访问权限不要随便允许前端拉取他人人脸照片或特征。在百度云后台定期清理没用的 face_token及时注销用户人脸数据。7. 实际操作中的一点体会这个项目做下来我最深的一个感受是调用第三方 AI 接口核心功夫全在接口之外。百度云的人脸接口本身很简单难的是把图片质量、token 管理、活体策略、用户引导、阈值调优这些周边环节串起来。只要每一步的质量都过关人脸识别率自然就高反过来任何一环偷懒比如图片不压缩、质量不校验、token 不缓存最后都会变成线上一个又一个的报错工单。给大家一个最实在的建议先别急着写业务代码花半天时间把百度云控制台里所有的测试页面都点一遍把 detect、search、add、match 这几个接口的请求参数和返回结构摸熟了再动手。接口通了前端只是组装参数和渲染结果的事。如果后续你打算把功能做成产品化可以考虑把人脸录入和识别封装成一个 uni-app 的公共模块通过配置项切换百度云账号、人脸库分组和匹配阈值。这样新项目接入时只需要传业务 ID 和回调函数就能跑通团队里其他人也不用重新踩一遍我踩过的坑。
返回列表