# 2 分钟完成颜值打分小程序

# 体验 demo

选择克隆或下载代码到小程序端 IDE, 更新项目 project.config.json 中的 appid 字段为您的小程序 ID,在当前环境中部署部署 (opens new window) AI 人脸特征分析与检测扩展能力,即可体验完整颜值打分小程序。亦可扫码体验线上版本。

# 克隆

  1. 确保您已安装 git 客户端。
  2. 在命令行中通过以下指令,克隆 demo 仓库:
    git clone https://github.com/TencentCloudBase/Cloudbase-Examples.git--branch feature/keylessCall
    
  3. 进入 clone 下来的 tcb-demo-ai 目录可见项目代码
    cd ./tcb-demo-ai
    

# 下载

若无 git 客户端,或其他原因,可以选择手动下载方式:点击下载 (opens new window)项目代码包,解压到您喜欢的目录。

# 开发

在小程序 IDE 中打开项目,注意更新 project.config.json 中的 appid 字段为您的小程序 ID。

# 目录说明

  • /client/pages/index - 首页导航目录
  • /client/pages/face-detect/detect - 人脸检测与识别入口
  • /client/components/iai - 人脸识别组件
    • /client/components/iai/iai.js - 组件业务逻辑,处理图片上传,调用云函数进行识别分析,展示分析结果等。
    • /client/components/iai/iai.wxml - 组件页面框架结构。
    • /client/components/iai/iai.wxss - 组件样式,依赖 weui。
  • /client/libs/ - 依赖库相关
    • /client/libs/tcb-services-mp-sdk - 小程序中使用的 tcb-services-mp-sdk,封装 tcb 相关公共能力及方法
    • /client/libs/runtime.js - 在某页面引用后,可在该页面使用 async/await 语法,主要用于兼容 iOS 手机
    • /client/libs/weui.wxss - weui 样式文件,在人脸识别组件中有使用,可选。

# 上传图片

  1. iai.wxml #23 (opens new window) 中上传按钮绑定 tap 事件处理函数 handleUploadTap,点击按钮将触发上传图片操作。

    <view class="button-container">
      <button type="primary" bindtap="handleUploadTap">
        上传人脸
      </button>
    </view>
    
  2. iai.js (opens new window) 中,组件 method 对象上实现 handleUploadTap 方法。通过调用 wx.chooseImage 选择需要上传的图片,获取到图片对象后,使用云开发存储能力,调用 wx.cloud.uploadFile (opens new window) 将图片上传到云端,在成功回调中获取云端文件对象,拿到 fileID 存储在组件 data 中。

    handleUploadTap() {
      // 通过微信 API,选择上传的图片
      wx.chooseImage({
        success: dRes => {
          wx.showLoading({
            title: "上传中"
          });
    
          const fileName = dRes.tempFilePaths[0];
          const dotPosition = fileName.lastIndexOf(".");
          const extension = fileName.slice(dotPosition);
          const cloudPath = `${Date.now()}-${Math.floor(
            Math.random(0, 1) * 10000
          )}${extension}`;
          // 云开发上传图片到云端
          wx.cloud.uploadFile({
            cloudPath,
            filePath: dRes.tempFilePaths[0],
            // 上传成功回调
            success: res => {
              console.log(res);
              // 将 fileID 存在组件 data 上
              this.setData(
                {
                  fileID: res.fileID,
                  hasUploaded: true
                },
                () => {
                  wx.hideLoading();
                }
              );
            },
            // 上传失败回调,可用于捕获错误,进行提示
            fail: () => {
              wx.hideLoading();
              wx.showToast({
                title: "上传失败",
                icon: "none"
              });
            }
          });
        }
      });
    }
    

# 图片分析

在上一步,我们将一张本地图片上传到云端,并且获取到了云端 fileID,接下来我们将调用云开发扩展能力解决方案 - AI 人脸特征分析与检测,进行图片分析。首先请确保在云开发中部署了 AI 人脸特征分析与检测能力,详见 AI 人脸特征分析与检测使用指引 (opens new window)

  1. iai.wxml #29 (opens new window) 中添加分析按钮并绑定 tap 事件处理函数 handleRecognizeTap,点击按钮将触发图片分析动作。

    <view class="button-container">
      <button type="primary" bindtap="handleRecognizeTap">
        上传人脸
      </button>
      <!-- add -->
      <button
        type="primary"
        disabled="{{!hasUploaded}}"
        bindtap="handleRecognizeTap"
      >
        识别人脸
      </button>
    </view>
    
  2. 首先在文件头引入依赖/client/libs/tcb-services-mp-sdk/client/libs/runtime.js

    import regeneratorRuntime from "../../../libs/runtime";
    import TcbService from "../../../libs/tcb-service-mp-sdk/index";
    const tcbService = new TcbService();
    

    之后在 iai.js (opens new window) 中,组件 method 对象上实现 handleRecognizeTap 方法。通过 tcbService.callService 方法,传入之前存储于组件 data 中的 fileID,调用 AI 人脸特征分析与检测能力,获取图片识别结果,简单处理后存储于组件 data 待用。

    async handleRecognizeTap() {
      wx.showLoading({
        title: "识别中",
        icon: "none"
      });
    
      try {
        let result = await tcbService.callService({
          service: "ai",
          action: "tcbService-ai-detectFace",
          data: {
            FileID: this.data.fileID
          }
        });
        /**
        或可简单的直接调用云函数
        let result = await wx.cloud.callFunction({
          name: 'tcbService-ai-detectFace',
          data: {
            FileID: this.data.fileID
          }
        });
        **/
        wx.hideLoading();
    
        if (!result.code && result.data) {
          // 图像识别成功,结果对象 result.data
          // 对结果作简单的解析,存储于组件 data
          this.setData(
            {
              // this.getFaceRects 将在下一步进行实现
              faceRects: this.getFaceRects(result.data)
            },
            () => {
              this.triggerEvent("finish", result.data);
            }
          );
        } else {
          // 识别失败,抛出错误进行错误处理
          throw result;
        }
      } catch (e) {
        wx.hideLoading();
        wx.showToast({
          title: "识别失败",
          icon: "none"
        });
        console.log(e);
      }
    }
    

# 渲染结果

上一步我们获取到了分析结果对象 DetectFactData 类型的 result.data (opens new window),并使用 this.getFaceRects 进行简单解析,接下来我们实现 getFaceRects 方法。并根据解析的结果进行简单的渲染。

  1. iai.js (opens new window) 中,组件 method 对象上实现 getFaceRects 方法。主要为了计算人脸在图片中的相对位置,以框出人脸。该方法接受的参数即 DetectFactData 类型 (opens new window)的对象。处理后的对象存储于 data.faceRects 中。

    getFaceRects(res) {
      const { ImageWidth, ImageHeight, FaceInfos } = res;
      return FaceInfos.map(item => ({
        ...item,
        rectX: (item.X / ImageWidth) * 100 + "%", // 人脸方框 左上角,X位置
        rectY: (item.Y / ImageHeight) * 100 + "%", // 人脸方框 左上角,Y位置
        rectWidth: (item.Width / ImageWidth) * 100 + "%", // 人脸方框 相对图片宽度
        rectHeight: (item.Height / ImageHeight) * 100 + "%" // 人脸方框 相对图片高度
      }));
    }
    
  2. iai.wxml #38 (opens new window),根据解析结果 faceRects,渲染页面。wx:for="" 遍历人脸数组,对每张人脸遍历 FaceAttributesInfoFaceQualityInfo 对象中的所有属性值,列出 key-value,通过事先定义的 data.resMap 将数值解释成可以理解的值。

    {
      "resMap": {
        "Gender": {
          "label": "性别",
          "valMap": { 0: "女", 1: "男" }
        },
        "Age": { "label": "年龄" },
        "Expression": {
          "label": "微笑",
          "valMap": { 0: "否", 1: "是" }
        },
        "Glass": { "label": "是否有眼镜" },
        "Beauty": { "label": "魅力" },
        "Hat": { "label": "是否有帽子" },
        "Mask": { "label": "是否有口罩" },
        "Score": { "label": "质量分" },
        "Sharpness": { "label": "清晰分" },
        "Brightness": { "label": "光照分" }
      }
    }
    
    <view class="result" wx:if="{{faceRects && faceRects.length}}">
      <view class="weui-cells__title">分析结果:</view>
      <!-- 遍历人脸结果 -->
      <view
        class="weui-cells"
        wx:for="{{faceRects}}"
        wx:key="*this"
        wx:for-item="face"
      >
        <!-- 遍历每个人脸对象中的 FaceAttributesInfo 和 FaceQualityInfo -->
        <block
          wx:for="{{ ['FaceAttributesInfo', 'FaceQualityInfo'] }}"
          wx:key="*this"
          wx:for-item="attr"
        >
          <view
            class="weui-cell"
            wx:for="{{face[attr]}}"
            wx:key="*this"
            wx:for-index="key"
            wx:if="{{resMap[key]}}"
          >
            <!-- 根据 resMap,解析属性中文名 -->
            <view class="weui-cell__bd">
              <p>{{ resMap[key].label }}</p>
            </view>
            <!-- 对于 true/false 类型的属性值,翻译为 是/否 -->
            <view
              class="weui-cell__ft"
              wx:if="{{ item === true || item === false }}"
              >{{ item ? '是' : '否' }}</view
            >
            <!-- 对于区间类型的属性值,特殊含义的通过 resMap 翻译(例如,性别),其他值直接展示 -->
            <view class="weui-cell__ft" wx:else
              >{{ resMap[key].valMap && resMap[key].valMap[item > 50 ? '1' : '0']
              || item }}</view
            >
          </view>
        </block>
      </view>
    </view>
    
  3. getFaceRects 算出了人脸方框相对原图的相对位置与大小,在 iai.wxml #8 (opens new window) 中,通过对 <view> 设置绝对定位,固定大小,在图片中框出人脸。

    <view class="image-container">
      <image class="image" src="{{fileID || imgUrl}}" mode="widthFix"></image>
      <!-- 遍历人脸结果数组 -->
      <block wx:if="faceRects && faceRects.length">
        <!-- view.face-result-item 通过 iai.wxss 设置样式为 position: absolute; border: 1px solid #1aad16;-->
        <!-- 再通过内联style 设置位置 top,left 与大小 width,height。通过在图片上叠加方框元素的方式圈出人脸-->
        <view
          wx:for="{{faceRects}}"
          wx:key="*this"
          class="face-result-item"
          style="left:{{item.rectX}};top:{{item.rectY}};height:{{item.rectHeight}};width:{{item.rectWidth}};"
        >
          <view class="face-result-text">
            <text>颜值 {{ item.FaceAttributesInfo.Beauty }} 分</text>
            <text>年龄 {{ item.FaceAttributesInfo.Age }} 岁</text>
          </view>
        </view>
      </block>
    </view>
    

至此,我们完成了一整套的上传、识别、展示流程。我们将识别结果存储在了 data 上,为了可以连续上传识别,我们需要做一点改动,在上传前清除掉上一次识别的结果。操作也很简单,在 handleUploadTap 方法中,选择图片前加入以下代码,清除 data 上的结果即可。

// 重新上传,清空结果
this.setData({
  imgUrl,
  faceRects: []
});

一个完整的使用 AI 人脸特征分析与检测 拓展能力的小程序就开发完成了。