Hi头像

Hi头像

  • 网站首页
  • 技术博客
  • 教程小册

›4-实战开发

标题

  • 《从0到1开发一个智能头像识别小程序》

1-前言

  • 1.1 产品需求设计、页面流程图及项目规划
  • 1.2 技术选型的依据

2-项目配置

  • 2.1 小程序开启云开发模式
  • 2.2 人脸五官分析的环境配置

3-知识储备

  • 3.1 图片裁剪、模糊、保存、分享的技巧
  • 3.2 Canvas绘制技巧,包含小程序与 Web 端
  • 3.2 云开发图像处理的使用方法

4-实战开发

  • 4.1.1 上篇:借助云存储和图像处理扩展人脸智能裁剪
  • 4.1.2 下篇:图像识别流程——图片裁剪、图片压缩、base64转换
  • 4.2 云开发必备——图像安全审核
  • 4.3 通过五官分析实现为人脸佩戴贴纸
  • 4.4 人脸贴纸移动技巧
  • 4.5 头像绘制和图片保存弹窗
  • 4.6【未写】海报分享页面
  • 4.7 CMS扩展实战-节日主题贴纸放在云开发
  • 4.8 【第二期】个人中心的制作
  • 4.9【第二期】小程序与Web端的账户体系摸索

云开发-公众号外发

  • 图片识别加速,从10秒变为1秒,是怎么做到的呢?
  • 简单几步,让微信小程序变身 H5 网页
  • 【草稿】借助云存储,让大图识别转换真正加速
  • 云开发CMS实战-Hi头像节日主题贴纸大升级
  • 小程序云开发环境部署静态网站托管的实战
  • Web 云开发实战之盲水印添加工具(核心版)
  • Web 云开发实战之盲水印添加工具(步骤版)

更多探索

  • 【草稿】深色模式在Web端的适配技巧,附带小程序侧的思考
  • 【草稿】小程序框架:Taro 2.0与Taro 3.0的核心分析

4.3 通过五官分析实现为人脸佩戴贴纸

知识点

  • 600px与600rpx的快速转换
  • 腾讯云五官分析服务
  • 确认出圣诞帽或口罩的位置
  • 补充:face-api 达到纯前端的人脸识别

启发我做这个小程序的是这两个文章,《「圣诞特辑」纯前端实现人脸识别自动佩戴圣诞帽》和《我要戴口罩 – 为微信、微博等社交网络头像戴口罩》。

600px与600rpx的快速转换

Hi头像小程序制作的头像宽高为600px。在小程序中头像贴纸元素都用的rpx为单位,而刚好的就是 600rpx 对应 600px,所有元素都等比缩放即可。也就是说,View元素宽高为600rpx,而头像是600rpx,那么头像上的贴纸的单位也都为rpx。

rpx(responsive pixel): 可以根据屏幕宽度进行自适应。规定屏幕宽为750rpx。如在 iPhone6 上,屏幕宽度为375px,共有750个物理像素,则750rpx = 375px = 750物理像素,1rpx = 0.5px = 1物理像素。

腾讯云的五官分析服务

在 项目搭建-腾讯云环境配置及特色人工功能介绍一文中,已经了解了五官分析接口如何调用。

// taro/src/utils/image-analyze-face.js
c/**
 * 五官分析
 * @param {string} base64Main
 */
export const imageAnalyzeFace = async (base64Main) => {
  try {
    const res = await wx.serviceMarket.invokeService({
      service: 'wx2d1fd8562c42cebb',
      api: 'analyzeFace',
      data: {
        Action: 'AnalyzeFace',
        Image: base64Main
      },
    })

    let data = getResCode(res)
    return data
  } catch (error) {
    console.log('error :', error)
    throw error
  }
}

接口示意

{
  "Response": {
    "ImageWidth": 600,
    "ImageHeight": 600,
    "FaceShapeSet": [
      {
        "FaceProfile": [],
        "LeftEye": [],
        "RightEye": [],
        "LeftEyeBrow": [],
        "RightEyeBrow": [],
        "Mouth": [],
        "Nose": [],
        "LeftPupil": [],
        "RightPupil": []
      }
    ],
    "RequestId": ""
  }
}

对请求图片进行五官定位(也称人脸关键点定位),计算构成人脸轮廓的 90 个点,包括眉毛(左右各 8 点)、眼睛(左右各 8 点)、鼻子(13 点)、嘴巴(22 点)、脸型轮廓(21 点)、眼珠[或瞳孔](2点)。

FaceShapeSet 五官定位(人脸关键点)具体信息。

名称描述
FaceProfile描述脸型轮廓的 21 点。
LeftEye描述左侧眼睛轮廓的 8 点。
RightEye描述右侧眼睛轮廓的 8 点。
LeftEyeBrow描述左侧眉毛轮廓的 8 点。
RightEyeBrow描述右侧眉毛轮廓的 8 点。
Mouth描述嘴巴轮廓的 22 点。
Nose描述鼻子轮廓的 13 点。
LeftPupil左瞳孔轮廓的 1 个点。
RightPupil右瞳孔轮廓的 1 个点。

圣诞帽或皇冠定位

核心:如何算出额头宽度和额头中间位置?

在《「圣诞特辑」纯前端实现人脸识别自动佩戴圣诞帽》(下文提交为圣诞帽文章)一文中详细地讲解了如何定位。

圣诞帽文章中绘制圣诞帽的目标是canvas。

而我的效果有两种,

  1. 宽高均为600rpx的view元素上,以便后续的图形贴纸位置移动
  2. 在图形贴纸位置确认后,再绘制到 canvas 上(后文中会介绍)

图形贴纸的 View 定位

  • faceWidth 脸部宽度:与圣诞帽文章相同 getFaceWith
  • angle 脸部旋转角度:与圣诞帽文章相同 getFaceRadian
  • headPos头顶中心点:与圣诞帽文章相同 getHeadPos
// 代码有精简
export function getHatShapeList(mouthList, shapeItem) {
  const { _id: shapeId } = shapeItem || {}
  return mouthList.map(item => {
    let { faceWidth, angle, headPos = {} } = item

    const shapeCenterX = headPos.X
    const shapeCenterY = headPos.Y
    const rotate = angle / Math.PI * 180

  
    // 角度计算有点难
    let widthScaleDpr = Math.sin(Math.PI / 4 - angle) * Math.sqrt(2) * faceWidth
    let heightScaleDpr = Math.cos(Math.PI / 4 - angle) * Math.sqrt(2) * faceWidth

    const resizeCenterX = shapeCenterX + widthScaleDpr - 2
    const resizeCenterY = shapeCenterY + heightScaleDpr - 2

    const shapeWidth = faceWidth / 0.6

    return {
      // 图形id
      shapeId,
      // 图形宽度
      shapeWidth,
      // 图形中间位置 X 轴
      shapeCenterX,
      // 图形中间位置 Y 轴
      shapeCenterY,
      // 旋转操作时的 X 轴的相对位置
      resizeCenterX,
      // 旋转操作时的 Y 轴的相对位置
      resizeCenterY,
      // 旋转角度
      rotate,
      // 水平翻转,正向为1,反向为-1
      reserve: 1
    }
  })
}
// 单个图形在view上的位置,transX、transY与 translateHat 有类似的逻辑
// 在 JSX 中的代码
let transX = shapeCenterX - shapeWidth / 2 - 2 + 'rpx'
let transY = shapeCenterY - shapeWidth / 2 - 2 + 'rpx'

let shapeStyle = {
  width: shapeWidth + 'rpx',
  height: shapeWidth + 'rpx',
  transform: `translate(${transX}, ${transY}) rotate(${rotate + 'deg'})`,
  zIndex: shapeIndex === currentShapeIndex ? 2 : 1
}

差异点为圣诞帽文章上有以下两点

  • 圣诞帽文章中用了 translateHat 来修改位置,而我这里也用shapeCenterX - shapeWidth / 2 - 2 + 'rpx'这样的方式来重新确认圣诞帽的左上角。
  • 圣诞帽文章中特别计算了圣诞帽可以戴的范围,但我这里的贴纸都是宽高均为 300px 的图形,我在确认好确认好脸部宽度、旋转角度及头顶中心位置后,圣诞帽的具体偏移位置是我在设计软件中慢慢拖动看效果的。原因是有多个圣诞帽,挨个计算可戴区域会特别麻烦。

// 0.7 为可戴区域占总区域的比重(为了让帽子更大一点,选择 0.6),0.65 是图片的宽高比
const picSize = { width: faceWidth / 0.6, height: (faceWidth * 0.65) / 0.6 };

当然,在戴口罩源码中用的确认好脸部宽度后,用 scale 进行的方法或缩小操作。但CSS 的 transform 会影响内部所有元素的代销,所以我还是直接计算出 shapeWidth,虽然这样反复操纵 width 对渲染效果来说不太好,但这样更容易理解。

口罩定位

  • faceWidth 脸部宽度:与圣诞帽文章相同 getFaceWith
  • angle 脸部旋转角度:与圣诞帽文章相同 getFaceRadian
  • mouthMidPoint 嘴巴中心点:与圣诞帽文章相同 getMidPoint
const { leftPoint, rightPoint } = getMouthLeftRigthPoint(mouthPoint)
const mouthMidPoint = getMidPoint(leftPoint, rightPoint)

补充:face-api达到纯前端的人脸识别

在“自动佩戴圣诞帽”中,使用的方案是纯前端的 face-api,想放到小程序中就会有如下几个小问题:

  • face-api 的识别模型有 5M 大小还多,即使纯前端加载,也显得比较大。而小程序的 canvas 与 web 网页中的还是有差异的,没法直接用 face-api。
  • face-api 放在 nodejs 上加载,还需要配合tensorflow和canvas模拟。实际实现后发现,图片识别过程还是比较慢的(图片上传后、获取图片内容、识别五官位置、返回五官数据),容易让接口请求发生超时的情况。
← 4.2 云开发必备——图像安全审核4.4 人脸贴纸移动技巧 →
  • 600px与600rpx的快速转换
  • 腾讯云的五官分析服务
  • 圣诞帽或皇冠定位
    • 图形贴纸的 View 定位
  • 口罩定位
  • 补充:face-api达到纯前端的人脸识别
Hi头像
文档
开篇介绍
相关资源
云开发 CloudBaseTaro
关于
联系我们GitHub主页Star
Copyright © 2020. All Rights Reserved.
沪ICP备2022023998号