我在微信小程序里手搓人脸识别引导,结果被“右转头”和“手遮脸”教育了

最近在小程序里做了一套人脸检测拍照引导,目标很明确:用户进入相机后,必须满足"正脸、距离合适、居中、无遮挡"之后,才能拍照进入后续测肤流程。

一开始以为只是调个相机组件,后来发现真正麻烦的是:微信小程序给你的不是完整原生相机能力,而是一套沙盒里的视觉能力。很多判断都要自己用规则补齐。

技术方案

这套方案主要用了:

  • wx.createVKSession():获取人脸 anchor。
  • WebGL canvas:渲染相机帧。
  • 自定义引导层:绘制取景框、状态边框、提示文案。
  • 人脸规则判断:距离、居中、姿态、遮挡、置信度。

核心流程大概是:

js 复制代码
const session = wx.createVKSession({
  track: { face: { mode: 1 } },
  version: 'v1',
  gl,
  cameraPosition: 1
})

session.on('updateAnchors', (anchors) => {
  const result = evaluateFaceGuidance(anchors[0])
  applyGuidance(result)
})

updateAnchors 会返回人脸框、关键点、角度、置信度等信息。真正的判断逻辑都放在 evaluateFaceGuidance() 里。

状态设计

我把人脸状态拆成几个阶段:

js 复制代码
NO_FACE     // 未检测到人脸
TOO_FAR     // 太远
TOO_NEAR    // 太近
OFF_CENTER  // 没对准取景框
BAD_POSE    // 姿态或遮挡不合格
READY       // 可以拍照

整体判断顺序是:

  1. 有没有人脸。
  2. 人脸大小是否合适。
  3. 人脸中心是否在取景框里。
  4. 是否正脸。
  5. 是否有遮挡。
  6. 是否允许拍照。

距离判断用的是"人脸宽度 / 取景框宽度":

js 复制代码
export const FACE_SCALE_RANGE = {
  min: 1.12,
  max: 1.3
}

这个值不是固定标准,需要真机反复调。不同机型、人脸距离、预览比例都会影响效果。

坑一:坐标是镜像的

前置摄像头下,用户看到的是镜像画面,但 VKSession 返回的人脸坐标不一定和视觉预览一致。

所以我做了镜像处理:

js 复制代码
if (options.mirrorX !== false) {
  x = 1 - x - w
}

否则会出现很诡异的问题:用户明明往左移,算法却觉得他往右偏。

坑二:左转头能识别,右转头很差

最开始我只用 yaw 判断左右转头:

js 复制代码
if (Math.abs(angles.yaw) > opt.maxYaw) {
  issues.push('请正对镜头,不要左右转头')
}

但真机测试发现,左转头提示比较准,右转头经常漏判。

原因大概率是 VKSession 返回的 yaw 在左右方向上不完全对称。后来我加了关键点分布兜底:如果关键点明显偏向脸框某一侧,就认为不是正脸。

js 复制代码
const sideBalance = minSide / maxSide
const meanOffset = Math.abs((meanX - centerX) / rect.w)

if (sideBalance < 0.58 || meanOffset > 0.12) {
  return { ok: false, issue: '请正对镜头,不要左右转头' }
}

这个兜底比单看 yaw 稳很多。

坑三:遮挡判断一开始"不生效"

遮挡是最容易误判的地方。

一开始我的逻辑是:关键点数量太少就跳过,关键点落入脸框比例太低也跳过。结果手挡住一部分脸时,关键点刚好变少或异常,反而绕过了遮挡判断。

后来改成:

  • 有一定关键点就参与判断。
  • 关键点覆盖范围太窄,认为脸部不完整。
  • 上下左右关键点分布不均,认为有遮挡。
  • 置信度低时,也提示脸部不完整。

文案最后统一成:

脸部不完整,请移开遮挡物

这个文案比"脸部不全"更自然,也能覆盖手挡脸、额头没露、眼睛被遮、嘴巴被挡等情况。

坑四:置信度不是遮挡概率

VKSession 返回的 confidence 可以理解成"当前人脸识别结果有多可靠"。

但它不是"遮挡概率"。

低置信度可能是遮挡,也可能是光线差、侧脸、模糊、距离不合适。所以不能只靠它判断遮挡,只能作为辅助条件。

js 复制代码
if (confidence != null && confidence < 0.55) {
  issues.push('脸部不完整,请移开遮挡物')
}

最终还是要结合关键点、脸框形态、姿态一起判断。

坑五:不要每帧都刷新 UI

updateAnchors 触发频率很高,如果每帧都 setData,页面会很卡。

我做了节流:

js 复制代码
const now = Date.now()
if (now - this.lastUiUpdate < 80) return
this.lastUiUpdate = now

80ms 左右对引导提示已经够用了,用户感知不到明显延迟,但性能会稳定很多。

坑六:WebGL 上面普通 view 盖不上去

因为相机帧是 WebGL canvas,普通 view 层级有时会出问题。提示文案这类 UI 最好用 cover-view

xml 复制代码
<cover-view class="hint-overlay">
  <cover-view class="main-tip">{{mainTip}}</cover-view>
</cover-view>

否则容易出现真机和开发者工具表现不一致。

最后总结

小程序里做人脸识别引导,难点不在"能不能识别人脸",而在于把识别结果变成稳定、可解释、体验好的拍照规则。

我的经验是:

  • 不要只依赖单个字段,比如 yawconfidence
  • 正脸判断要结合角度、脸框形态、关键点分布。
  • 遮挡判断要避免过早跳过。
  • 真机调参比开发者工具重要得多。
  • 提示文案要产品化,不要暴露算法术语。

如果只是简单拍照,可以用普通 <camera>。但如果要做"正脸、无遮挡、距离合适"的强引导,wx.createVKSession + WebGL + 自定义规则 是一条可行路线,只是需要大量真机测试和阈值调整。

相关推荐
橙子家8 小时前
浏览器缓存之【基础键值存储】:Local storage 和 Session storage
前端
星星在线10 小时前
MusicFree:一个「All in One」的个人音乐服务器,让听歌回归简单
前端·后端
IT_陈寒11 小时前
Redis的SETNX并发问题让我加了三天班
前端·人工智能·后端
demo007x12 小时前
Docling 文档转换以及技术架构分析
前端·后端·程序员
京东云开发者12 小时前
京东市民服务又“上新”!这次是黑龙江“龙易办”
前端
袋鱼不重13 小时前
我的神奇同事,AI 用多了居然写了个 Open In Codex
前端·后端·ai编程
Fireworks13 小时前
深入vue3源码解读 -- 1、响应式的基础概念
前端
程序员黑豆13 小时前
JDK 下载安装与配置详细教程
java·前端·ai编程
hunterandroid13 小时前
文件存储:内部存储与外部存储
前端
NorBugs14 小时前
飞机大战 Low 版 (Made in AI)
前端