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

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

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

技术方案

这套方案主要用了:

  • 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 + 自定义规则 是一条可行路线,只是需要大量真机测试和阈值调整。

相关推荐
David_Xia10 小时前
干爆 11s 提交卡顿!引入 Rust 级 oxlint 彻底拯救团队 Git Commit 噩梦的重构实践
前端
前端环境观察室10 小时前
别急着让 Agent 跑任务,先把浏览器环境上下文建模
前端
蝎子莱莱爱打怪11 小时前
零基础用AI写App?兄弟😂 醒醒吧,那只是个玩具罢了!
前端·人工智能·后端
用户13060956072311 小时前
elpis里程碑一的阶段性总结
前端
砍材农夫11 小时前
物联网 基于netty控制报文结构(发布与接收)
java·开发语言·前端·javascript·物联网
光影少年11 小时前
react的Context 跨层传值、优缺点、适用场景
前端·react.js·掘金·金石计划
kevinten1011 小时前
说实话,我做了个"不务正业"的 AI:专门推荐冷门冒险地
前端
上单带刀不带妹11 小时前
Vue3 中 getCurrentInstance() 与 proxy 详解
前端·javascript·vue.js
Csvn11 小时前
前端 AI 应用:让浏览器运行机器学习模型
前端