Techeek's Studio.

无服务器开发人脸识别小程序

字数统计: 12.7k阅读时长: 55 min
2019/01/30

从2006年AWS发布的第一个云服务S3开始,存储,计算等IT基础设施的能力纷纷被以服务的方式提供给用户。过去十年,云服务深刻的改变了社会获取和使用计算能力的方式,云服务自身也以极快的速度演进,新的服务形态不断涌现,无服务器计算(serverless computing)就是其中之一。国内各大厂商也在近两年推出了自家的无服务器计算产品,比如腾讯云的无服务器云函数 SCF,阿里云的函数计算等产品。

前言

前段时间我还在想,如果小程序能使用无服务器计算产品那该多好,果不其然,最近微信与腾讯云联合开发的原生 serverless 云服务产品——小程序 · 云开发,其具备简化运维、高效鉴权等优势,让你零门槛快速上线小程序。为此,我决定尝试下这种新的开发方式,看看是不是真的如官方所说。

那么,用什么项目去尝试呢?看了下自己以前写的文章,发现这篇如何在小程序中实现人脸识别功能文章关注量还挺多,况且腾讯云人脸识别API于2019年1月25日全量更新为了最新的3.0版本,API调用方式及也有较大变化。我完全可以用最新版的API结合云开发去体验下这个过程。

当然,最大的优势在于省钱!!!小程序 · 云开发这款产品还在免费阶段,同时腾讯云人脸识别服务每月为各个接口提供 1 万次免费调用,很划算。

这篇文章将分享我开发过程中的一些思路,如何考虑产品应用性,如何优化逻辑等问题。同时也会分享整个的开发过程,从怎么注册账户到怎么调用API,以及代码是如何一点一点拼接的。大家也可以将这篇文章看为一篇教程,我会从0~1分享整个项目的开发过程。当然,如果你是一名Developer,请直接使用我撰写好的代码,已经分享到了GitHub,欢迎大家参阅!

那么,我们就开始吧!

准备

在撰写代码之前,我们需要先准备一下小程序的开发环境,所需要的环境有GitNodeJSnpm微信开发者工具。同时,因为要调用腾讯云人脸识别API,我们还需要注册腾讯云的账号,同时还需要注册腾讯云的API密钥

安装Git

git是一个分布式版本控制软件,开发小程序的时候做好代码版本控制非常重要,如果代码写错,可以使用Git快速恢复。安装Git较为简单,我们只需要打开Git官网,点击右侧的Download 2.20.1 for Windows,当然,版本号和系统都可能不同,大家按照自己的系统及最新的版本号下载即可。

1548824618151

下载完成后双击打开安装包,然后一路点击下一步直至安装完成。安装完成后我们按键盘上的Win+R,然后输入CMD打开命令提示符窗口,然后在命令提示符窗口中输入git,如果你看到类似下面的截图,证明你的Git安装成功,可以进行下一步了。

1548829195489

安装NodeJS和npm

NodeJS是一个可以跨平台在服务端运行JavaScript的运行环境,我们小程序云开发所使用的服务端环境就是NodeJS,为了优化并测试代码,建议在本地安装NodeJS运行环境。npm是Node包管理器,通过npm包管理器,我们可以非常方便的安装云开发所需要的环境。

首先,我们打开NodeJS官网,下载NodeJS安装包。

下载完成后双击打开,并一路下一步安装。

1548650991772

安装完成后,打开命令提示符,试试node命令和npm命令是否正常。

1548651326941

看到如图类似的内容,证明你的nodenpm都已经安装成功了。

搭建小程序开发环境

开发小程序的第一步,你需要拥有一个小程序帐号,通过这个帐号你就可以管理你的小程序。

申请账号

点击 https://mp.weixin.qq.com/wxopen/waregister?action=step1 根据指引填写信息和提交相应的资料,就可以拥有自己的小程序帐号。

1540868585543

如果你注册过小程序,可以点击右侧的立即登录。如果没有的话,请重新注册,值得注意的是,邮箱必须填写未在微信公众平台、未在微信开放平台、个人未绑定的邮箱,不然这里是无法注册的。密码请填写你能记住的密码即可。

现在登录https://mp.weixin.qq.com/,点击左侧的设置——开发设置,在这里,我们就能看到你小程序的AppID了。

1540869204895

当小程序的ID拿到之后,我们就可以下载安装开发工具了。

安装开发工具

现在,打开 开发者工具下载页面 ,根据自己的操作系统下载对应的安装包进行安装。

1540869347691

我这里使用的是Windows 64位操作系统,所以我点击Windwos 64位按钮进行下载。下载完成后,右键,然后以管理员身份运行安装文件。

1540869482972

之后,一路点击下一步安装即可。

1540869564103

接下来,就可以开始运行开发者工具了。使用前需要我们扫描二维码才能开始使用,请打开微信,然后点击发现——扫一扫,扫描开发者工具展示的二维码,之后在手机上点击登录即可。

1540870003085

人脸识别API申请

如果要使用人脸识别API,必须在腾讯云进行实名认证,实名认证后,您可以登录腾讯云 控制台进行使用。如果没有账号,请参考账号 注册教程。注册完成后,需要创建相关开发密钥,不然无法使用API。

您需要在 访问管理 创建密钥,点击图中的新建密钥,即可创建密钥。

1542095339804

创建完成后,点击SecretKey的显示按钮,显示当前SecretKey,然后将APPIDSecretIdSecretKey记录下了,后面教程中使用。

现在,开发小程序所需要的所有环境就已经搭建完成,我们可以开始创建一个新的项目了。

创建云开发项目

首先,我们新建一个云开发的项目,注意AppID是你自己在小程序AppID,同时不要勾选建立云开发模版。

接下来,我们新建两个目录,一个目录(client)存放小程序的客户端,一个目录(server)存放小程序云开发的服务端,如图。

接下来,打开配置文件project.config.json,我们需要新增两行文件。

1
2
"cloudfunctionRoot": "server/",
"miniprogramRoot": "client/",

cloudfunctionRoot参数填写你新建的云开发服务器的文件目录,miniprogramRoot填写你小程序客户端的目录,如图。

当你的server文件夹图标变成了☁的样式,证明我们云开发环境就搭建完成了。

项目开发思考

在开始写代码之前,我们先理一下思路。什么东西放在服务端,什么东西放在客户端?

从安全角度考虑,我们在腾讯云申请到的API密钥是不能暴漏的,否则别人可以通过抓包去获取我们的ID,从而滥用造成经济上的损失,接下来就是为了识别人脸而上传的图片文件,用户数据十分重要,图片千万不能暴漏。小程序官方也提供了一些如数据库、存储、云函数所相关的能力,我们可以通云开发提供的云函数能力将腾讯的API密钥存放在服务端运行,同样的,也可以使用期所提供的云存储和数据库存放用户的图片及数据。

从产品开发角度考虑,希望产品运行足够的快,减少客户端与服务器的通讯次数,降低运维压力,增加并发数,同时,也要考虑到后期维护,所以代码尽可能的精简。

具体思路是这样子的:

  1. 客户端选择完图片,然后在小程序端调用云存储上传API上传图片到云存储,之后由云存储返回一个文件的ID到客户端。
  2. 客户端获取文件上传后的ID,调用云函数,在云函数端去读取云存储的文件,读取其真实的URL地址。
  3. 将获取到的地址在云函数端发送至腾讯云人脸识别API,等待人脸识别接口返回相关内容。
  4. 人脸识别API返回内容后,云函数原封不动的将数据发回给客户端。
  5. 客户端做解析,并展示给前端。

整个过程云函数只与客户端通讯两次,同时将人脸识别API调用及用户图片存放在服务端,保证密钥及资料的安全,能够达到我们的要求。

  • 对于云存储的使用,我在如何进行小程序云存储开发有相关的讲解,请参阅。云存储可以在小程序的客户端调用,也可以在云函数的服务端调用。项目架构中,我们在客户端上传了相关文件,之后获取URL地址等操作均是在服务端完成的。

服务端开发

首先,我们先开发服务端,因为服务端作为架构的中心枢纽,负责接收和发送数据,非常重要。当开发完服务端,撰写客户端数据处理的时候,才能事半功倍。

新建云函数

接下来,我们开始新建云函数,在server文件夹上面点击右键,选择新建NodeJS云函数,然后输入你要建立云函数的名称,我这里命名为Face_Detection

1548834996929

新建完成后,系统会自动生成index.jspackage.json文件。其中index.js是官方给出的Demo文件,我们将生成的代码稍微处理下,只保留最基本的模版。

云函数 - index.js

1
2
3
4
5
6
7
// 云函数入口文件
const cloud = require('wx-server-sdk')
cloud.init()
// 云函数入口函数
exports.main = async (event, context) => {

}

腾讯云人脸识别API

我们打开腾讯云人脸识别的官网,先看看官方文档的API怎么调用。

1548835462118

我们看到API概览中有很多人脸相关的接口,因为我们只做最基本的人脸检测,所以,选择人脸检测与分析相关接口

1548835571045

点击人脸检测与分析DetectFace接口,查看API文档。我们看到接口提供了人脸(Face)的位置、相应的面部属性和人脸质量信息,位置包括 (x,y,w,h),面部属性包括性别(gender)、年龄(age)、表情(expression)、魅力(beauty)、眼镜(glass)、发型(hair)、口罩(mask)和姿态 (pitch,roll,yaw),人脸质量信息包括整体质量分(score)、模糊分(sharpness)、光照分(brightness)和五官遮挡分(completeness)等信息的识别,那么我们的小程序所返回的相关数据也逃不出这几个内容,所以,给用户展示的信息建议也从这里面的参数中选取。向下拉,查看请求相关参数。

1548835808227

请求参数如图,必填的是ActionVersion,分别对应接口名称及版本号,按照文档,我们这里选择DetectFace2018-03-01。其他数据是选填的,如MaxFaceNum人脸数,Url图片的地址等。我们项目架构中由云存储所分享的是图片的地址,所以URL参数是必要的。同时我们想获取图片的人脸属性信息,所以按照表内容的内容,NeedFaceAttributes参数也是必要的。

输入参数看完了,我们看看输出参数。

1548836162194

输出参数有4组,分别是图片的宽高,人脸的信息以及请求的ID。当然,最重要的还是我们的FaceInfos参数,我看点击蓝色的FaceInfos,看看具体有什么内容。

1548836271418

数据蛮多的,这里的FaceAttributesInfo参数我们会用到,因为在输入参数中,我们需要NeedFaceAttributes,所以当NeedFaceAttributes参数等于1的时候,FaceAttributesInfo参数才会返回相关的数据,我们点开FaceAttributesInfo参数看看。

1548836386235

这里就是返回参数的具体数据了,有性别,年龄,微笑程度等信息,具体大家请看描述。我们看到还有一个Hair参数,是头发的长度、刘海、发色等信息,点开FaceHairAttributesInfo看看。

1548836487864

这里就是该接口的相关信息及所有的参数了,心里大概有个底就可以了。

腾讯云人脸识别SDK

继续向下看文档,我们发现,API中给我们提供了相关的SDK。因为我们客户端的代码是NodeJS的,官方也提供了相关的SDK,那么就直接使用吧!

1548836657553

咦,等下,API Explorer是什么,点击去看看,原来,这是官方给我们提供的一个GUI页面,通过简单的设置,即可生成API调用的代码,能显著降低使用云 API 的难度。

1548836765992

我们就用这个工具来生成我们NodeJS端的代码吧!(注:当然,这里也可自己写相关代码去调用API,但是文档中对签名验证这块讲的模糊不清,所以我还是打算使用SDK)

1548836999323

点开API Explorer工具,我们看到一个页面,我们看到框内有我们前面所准备的SecretIdSecretKey,同时也有前文中我们提到的输入参数UrlNeedFaceAttributes等。等下,必填ActionVersion参数去哪里了?

原来,当我们进入人脸识别的页面后,系统已经自动帮我们填写好了ActionVersion参数,我们只需要直接使用就行了。

我们填写下SecretIdSecretKey这两个参数,然后Url参数中,我们填入一张人脸的图片,这里大家可以自己去搜索下,然后将图片的地址粘贴到这里就可以。最后,将NeedFaceAttributes 参数为1,根据文档,我们要返回人脸属性。Region参数大区可以不填,文档中已经说明。

1548837850218

如图,当填写完后,我们点击代码生成,然后选择NodeJS,系统会自动生成我们所需要的代码。(这里有个BUG,URL地址不识别冒号,希望官方修复)

我们点击右侧的在线调用,然后点击发送请求,看看是否返回了正常的数据。

1548838038031

如图,这样的数据就是正常的响应结果。如果显示说您未开通人脸识别服务,请前往这里开启。我们回到代码生成页面,复制NodeJS环境的所有代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
const tencentcloud = require("../../../../tencentcloud-sdk-nodejs");

const IaiClient = tencentcloud.iai.v20180301.Client;
const models = tencentcloud.iai.v20180301.Models;

const Credential = tencentcloud.common.Credential;
const ClientProfile = tencentcloud.common.ClientProfile;
const HttpProfile = tencentcloud.common.HttpProfile;

let cred = new Credential("AKIDypUyGMo2czFdu0La5NSK0UlpiPtEAuLa", "BAxXw99wa5OUOJ3bw52mPq57wa2HKAoG");
let httpProfile = new HttpProfile();
httpProfile.endpoint = "iai.tencentcloudapi.com";
let clientProfile = new ClientProfile();
clientProfile.httpProfile = httpProfile;
let client = new IaiClient(cred, "", clientProfile);

let req = new models.DetectFaceRequest();

let params = '{"Url":"https://img.ltn.com.tw/Upload/liveNews/BigPic/600_phprquLjl.jpg","NeedFaceAttributes":1}'
req.from_json_string(params);


client.DetectFace(req, function(errMsg, response) {

if (errMsg) {
console.log(errMsg);
return;
}

console.log(response.to_json_string());
});

我们分析下代码,第一行代码是引入名为tencentcloud-sdk-nodejs的包文件,这个是腾讯云SDK所依赖的文件。后面几行代码就是声明人脸识别的相关版本,定义HTTP请求等。之后的cred是声明我们的在腾讯云的相关密钥,注意你使用的时候改成自己的,httpProfile.endpoint是我们的接口地址,接下来是重点,params参数是我们人人脸识别特有的接口,后续要增加或删除相关参数都在这里操作。最后的内容是回调函数,并通过console.log控制台输出,那么如何在小程序端调用呢?

我们稍微修改下代码。

云函数 - index.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
const cloud = require('wx-server-sdk')// 云函数入口文件
const tencentcloud = require("tencentcloud-sdk-nodejs"); //腾讯云API 3.0 SDK

cloud.init() // 云开发初始化

var synDetectFace = function (url) { //调用人脸识别API函数
const IaiClient = tencentcloud.iai.v20180301.Client; //API版本
const models = tencentcloud.iai.v20180301.Models; //API版本

const Credential = tencentcloud.common.Credential;
const ClientProfile = tencentcloud.common.ClientProfile;
const HttpProfile = tencentcloud.common.HttpProfile;

let cred = new Credential("AKIDypUyGMo2czFdu0La5NSK0UlpiPtEAuLa", "BAxXw99wa5OUOJ3bw52mPq57wa2HKAoG"); //腾讯云的SecretId和SecretKey
let httpProfile = new HttpProfile();
httpProfile.endpoint = "iai.tencentcloudapi.com"; //腾讯云人脸识别API接口
let clientProfile = new ClientProfile();
clientProfile.httpProfile = httpProfile;
let client = new IaiClient(cred, "", clientProfile);

let req = new models.DetectFaceRequest();

let params = '{"Url":"https://img.ltn.com.tw/Upload/liveNews/BigPic/600_phprquLjl.jpg","NeedFaceAttributes":1}'
req.from_json_string(params);

client.DetectFace(req, function (errMsg, response) {

if (errMsg) {
console.log(errMsg);
return;
}

console.log(response.to_json_string());
});
}

synDetectFace();
// 云函数入口函数
exports.main = async (event, context) => {

}

这里,我们将官方所给的代码封装成名为synDetectFace的函数,然后在最后通过synDetectFace()去调用这个函数。exports.main暂时留空,我们先做第一步测试。注意删掉tencentcloud参数中多余的../../../../因为后面我们将用npm包管理器安装这个依赖,无需多余的../../../../

接下来,我们需要安装相关的依赖文件,因为不管是运行云开发还是运行腾讯云SDK都需要相关的依赖文件,这里,我们就需要用到NodeJS运行环境和npm包管理器了。在我们的云函数目录上面右键,选择在终端中打开

1548840085185

然后输入下面的命令,通过npm包管理器,安装云函数和腾讯云SDK的依赖文件。

1
2
npm install tencentcloud-sdk-nodejs --save
npm install wx-server-sdk --save

这两行命令即可安装我们需要的依赖文件,如图所示。

1548840382984

不要关闭这个窗口,我们对我们的人脸识别接口进行测试,看看能否正常返回数据,运行下面的命令。

1
node index.js

1548840540310

如果看到类似的内容,就证明你的代码配置没有错误,可以进行下一步了。

云存储API调用

根据上面的架构,我们在服务端获取到文件的ID后,使用文件ID去云存储文件的URL地址,目前我们在云存储端还没有文件。那么,第一步,将文件上传到云存储。还好,云开发控制台可以直接上传文件,打开控制台,点击存储管理,如图。

1548900634843

我们在这里随便上传一个文件,建议上传一张人脸的照片,已方便我们后续测试。上传之后点击右侧的复制按钮,复制文件的ID。

1548900916106

接下来,我们打开小程序云存储上传文档,调用getTempFileURL接口,然后将ID传送给云存储。等待云存储返回URL地址,这里我们直接复制官方文档给出的代码。

1548901200271

1
2
3
4
5
6
7
8
9
const cloud = require('wx-server-sdk')

exports.main = async (event, context) => {
const fileList = ['cloud://xxx', 'cloud://yyy']
const result = await cloud.getTempFileURL({
fileList,
})
return result.fileList
}

将代码中的cloud://xxx更换为刚才复制的文件ID。

1
const fileList = ['cloud://test-f97abe.7465-test-f97abe/demo.jpg']

现在,打开我们云函数的index.js文件,将上面的代码整合到其中,并将返回的内容result.fileList打印到控制台。

云函数 - index.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
const cloud = require('wx-server-sdk')// 云函数入口文件
const tencentcloud = require("tencentcloud-sdk-nodejs"); //腾讯云API 3.0 SDK

cloud.init() // 云开发初始化

var synDetectFace = function (url) { //调用人脸识别API函数
const IaiClient = tencentcloud.iai.v20180301.Client; //API版本
const models = tencentcloud.iai.v20180301.Models; //API版本

const Credential = tencentcloud.common.Credential;
const ClientProfile = tencentcloud.common.ClientProfile;
const HttpProfile = tencentcloud.common.HttpProfile;

let cred = new Credential("AKIDypUyGMo2czFdu0La5NSK0UlpiPtEAuLa", "BAxXw99wa5OUOJ3bw52mPq57wa2HKAoG"); //腾讯云的SecretId和SecretKey
let httpProfile = new HttpProfile();
httpProfile.endpoint = "iai.tencentcloudapi.com"; //腾讯云人脸识别API接口
let clientProfile = new ClientProfile();
clientProfile.httpProfile = httpProfile;
let client = new IaiClient(cred, "", clientProfile);

let req = new models.DetectFaceRequest();

let params = '{"Url":"https://img.ltn.com.tw/Upload/liveNews/BigPic/600_phprquLjl.jpg","NeedFaceAttributes":1}'
req.from_json_string(params);

client.DetectFace(req, function (errMsg, response) {

if (errMsg) {
console.log(errMsg);
return;
}

console.log(response.to_json_string());
});
}

synDetectFace();
// 云函数入口函数
exports.main = async (event, context) => {
const fileList = ['cloud://test-f97abe.7465-test-f97abe/demo.jpg']
const result = await cloud.getTempFileURL({
fileList,
})
return result.fileList
}

接下来,我们部署下云函数,让其在云开发端运行,我们看看能不能正常读取到我们所需要的文件。在云函数上右键,选择上传并部署:所有文件,这一步,我们将我们刚刚写的代码及所需要的依赖环境部署在服务端。

1548902342995

当弹出的对话框显示上传并部署完成后,我们就可以打开云开发的控制台进行测试了。

1548902436125

点击云函数按钮,选择我们刚刚上传的云函数,然后单击右侧的测试。

1548902532432

当点击运行测试按钮后,查看当前返回结果,如果显示成功,返回结果内有你提交的文件ID及文件的URL地址,证明我们云存储在服务端的调用执行成功。

1548902662710

云函数代码整合

既然腾讯云人脸API和云函数端的云存储API已经调用并测试成功,那么就需要我们整合下两个代码,因为现在咱们服务端的代码是分离的,况且代码中传入的图片也不是我们想要的图片。

云函数 - index.js(最终版)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
const cloud = require('wx-server-sdk') //小程序云开发SDK
const tencentcloud = require("tencentcloud-sdk-nodejs"); //腾讯云API 3.0 SDK
cloud.init() //云开发初始化
var synDetectFace = function(url) { //人脸识别API
const IaiClient = tencentcloud.iai.v20180301.Client; //API版本
const models = tencentcloud.iai.v20180301.Models; //API版本

const Credential = tencentcloud.common.Credential;
const ClientProfile = tencentcloud.common.ClientProfile;
const HttpProfile = tencentcloud.common.HttpProfile;
let cred = new Credential("AKIDypUyGMo2czFdu0La5NSK0UlpiPtEAuLa", "BAxXw99wa5OUOJ3bw52mPq57wa2HKAoG"); //腾讯云的SecretId和SecretKey
let httpProfile = new HttpProfile();
httpProfile.endpoint = "iai.tencentcloudapi.com"; //腾讯云人脸识别API接口
let clientProfile = new ClientProfile();
clientProfile.httpProfile = httpProfile;
let client = new IaiClient(cred, "", clientProfile); //调用就近地域

let req = new models.DetectFaceRequest();
let params = '{"Url":"' + url + '","NeedFaceAttributes":1}' //拼接参数
req.from_json_string(params);
return new Promise(function(resolve, reject) { //构造异步函数
client.DetectFace(req, function(errMsg, response) {
if (errMsg) {
reject(errMsg)
} else {
resolve(response);
}
})
})
}


exports.main = async(event, context) => {
const data = event
const fileList = [data.fileID] //读取来自客户端的fileID
const result = await cloud.getTempFileURL({
fileList, //向云存储发起读取文件临时地址请求
})
const url = result.fileList[0].tempFileURL
datas = await synDetectFace(url) //调用异步函数,向腾讯云API发起请求
return datas
}

我们将两个代码进行了整合,并增加了相关的备注。

首先,将腾讯云人脸识别API整体封装成为一个名为synDetectFace异步函数,该函数携带名为url的变量,当调用函数的时候,我们传入url参数,函数会通过Promise方式将人脸识别返回的内容重新返回给调用端。

接下来,为了方便云函数的调用,我们将客户端传过来的内容(文件ID)存为变量data,并向云存储发起URL请求,将请求的返回值传到异步函数synDetectFace(url),此时,该函数会向腾讯云发起AI识别请求,返回的请求值最终会返回给客户端。

修改完代码,别忘了部署在服务端。到这一步,我们服务端的开发工作就全部搞定了。

客户端开发

服务端开发完成后,我们已经完成了三分之二的代码,接下来就是撰写客户端的代码。关掉server文件夹。打开client文件夹,然后新建一个名为app.json的文件,如图。

1548904366711

将下面的代码复制到app.json文件中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
"pages": [
"pages/index/index"
],
"window": {
"navigationBarBackgroundColor": "#ffffff",
"navigationBarTextStyle": "black",
"navigationBarTitleText": "人脸识别Demo",
"backgroundColor": "#eeeeee",
"backgroundTextStyle": "light",
"enablePullDownRefresh": false
},
"cloud": true
}

保存完成后,系统将自动生成相关目录及文件。

1548904440595

选择图片API

根据流程,我们的第一步就是选择图片了,小程序官方也提供了图片选择API,废话不多说,我们直接看代码。首先,打开index.js文件,注意,这里选择的是客户端的文件,不是服务端的。

客户端 - index.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
// pages/index/index.js
Page({

/**
* 页面的初始数据
*/
data: {

},

/**
* 生命周期函数--监听页面加载
*/
onLoad: function (options) {

},

/**
* 生命周期函数--监听页面初次渲染完成
*/
onReady: function () {

},

/**
* 生命周期函数--监听页面显示
*/
onShow: function () {

},

/**
* 生命周期函数--监听页面隐藏
*/
onHide: function () {

},

/**
* 生命周期函数--监听页面卸载
*/
onUnload: function () {

},

/**
* 页面相关事件处理函数--监听用户下拉动作
*/
onPullDownRefresh: function () {

},

/**
* 页面上拉触底事件的处理函数
*/
onReachBottom: function () {

},

/**
* 用户点击右上角分享
*/
onShareAppMessage: function () {

}
})

同样,官方也提供了一堆已经存在的函数,为了方便我们撰写代码,我们删除用不到的内容。

客户端 - index.js

1
2
3
4
5
6
Page({
data: {
},
onLoad: function (options) {
}
})

data存放我们一些静态数据,以便前端调用,onLoad是小程序的初始化执行函数,当小程序页面加载成功后会自动调用该函数,我们后续会用到。

接下来我们新建一个函数,这个函数用于当用户在前端点击某个按钮的时候,会执行该函数,我们将其命名为UploadImage

客户端 - index.js

1
2
3
4
5
6
7
8
9
10
11
Page({
data: {

},
UploadImage(){

},
onLoad: function (options) {

}
})

参考小程序官方的文档中的示例代码,修改代码如下。

客户端 - index.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Page({
data: {

},
UploadImage(){
wx.chooseImage({
count: 1,
sizeType: ['original', 'compressed'],
sourceType: ['album', 'camera'],
success(res) {
const tempFilePaths = res.tempFilePaths
console.log(tempFilePaths)
}
})
},
onLoad: function (options) {

}
})

1548906349255

参考官方的文档,参数中,我们定一个图片的张数,图片的尺寸、来源。之后该API回调返回tempFilePaths临时图片地址。

后端函数写完了,我们需要点击一个按钮执行上传,撰写下前端,打开index.wxml文件,修改代码如下。

客户端 - index.wxml

1
<button type="primary" bindtap="UploadImage">上传照片</button>

1548906608444

当用户点击图中的上传照片按钮的时候,会自动调用我们在后端撰写的UploadImage函数,如图。

1548906689999

当我们选择照片后,会在控制台输出当前文件的临时地址,有了临时地址,接下来一步就是将临时地址传给云存储的API,让其将文件上传。

云存储上传文件API

同选择图片API一样,微信官方文档中也提供了相关的实例代码。我们可以直接使用,先看代码。

客户端 - index.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
Page({
data: {

},
UploadImage() {
wx.chooseImage({
count: 1,
sizeType: ['original', 'compressed'],
sourceType: ['album', 'camera'],
success(res) {
const tempFilePaths = res.tempFilePaths[0]
console.log(tempFilePaths)
wx.cloud.uploadFile({
cloudPath: 'example.jpg',
filePath: tempFilePaths, // 文件路径
success: res => {
console.log(res.fileID)
},
fail: err => {
}
})
}
})
},
onLoad: function (options) {
wx.cloud.init({
env: 'test-f97abe'
})
}
})

当上传图片API返回tempFilePaths参数后,我们需要将其传入云存储上传文件的目录。,根据文档,filePath参数就是文件资源的目录。我们直接将返回tempFilePaths参数放在该参数下,当选择完图片,会自动调用该API上传名为example.jpg的图片文件,同时我们上传的文件只有一张,也就是临时目录的第一张,所以返回的参数应改为res.tempFilePaths[0]

根据文档要求,在调用云开发之前,我们还必须初始化,才能正常使用。所以代码中我们调用了wx.cloud.init方法,并填写了env参数,这里的参数请改为你自己的,打开云开发控制台可查看到你的环境ID

1548907014519

上传成功后,返回回调内容,并在控制台打印出其文件ID。

1548907534040

控制台第一行是我们选择图片后的临时地址,第二行上传到云存储后的文件ID。

调用云函数API

当云存储调用完成后,我们拿到了文件的ID,下一步就是真正的调用云函数了,我们将文件ID传给云函数,并等待云函数返回人脸识别的结果。看一下官方的文档

1548907675791

我们必须填写的有云函数的名字,选填的有dataconfig,由于我们不更改局部配置文件,所以config用不到。但是我们需要将我们的文件ID传送给服务端,所以要填写的还有data参数。

参考官方代码,我们调用下上面撰写的Face_Detection云函数。

客户端 - index.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
Page({
data: {

},
UploadImage() {
wx.chooseImage({
count: 1,
sizeType: ['original', 'compressed'],
sourceType: ['album', 'camera'],
success(res) {
const tempFilePaths = res.tempFilePaths[0]
console.log(tempFilePaths)
wx.cloud.uploadFile({
cloudPath: 'example.jpg',
filePath: tempFilePaths, // 文件路径
success: res => {
console.log(res.fileID)
wx.cloud.callFunction({
name: 'Face_Detection',
data: {
fileID:res.fileID
},
success: res => {
console.log(res.result)
},
})
},
fail: err => {
}
})
}
})
},
onLoad: function (options) {
wx.cloud.init({
env: 'test-f97abe'
})
}
})

这里的代码逻辑是,当我们图片上传完成后,通过返回的文件ID去调用名为Face_Detection的云函数,该函数在服务端进行一系列操作后,将数据返回给客户端,最终通过控制台打印出来。

1548910423662

如果看到类似的结果,证明客户端基本的请求操作已经写完,这里返回的内容就是我们人脸识别后腾讯云API所返回的数据,我们可以进行下一步操作了。

当然,这里代码中有一个BUG,就是所有上传的图片名称都是example.jpg,一个人使用当然不会造成什么问题,但如果多个人并发使用这个小程序,就会产生一个很大的BUG,同一时间内请求的图片地址相同,那么返回的结果也相同,更有可能造成云存储系统BUG。

为此,我将图片的名称变更为一个随机数,这个同一时间内随机数的内容不同,那么图片请求的时候就不会有多大问题,修改代码如下。

客户端 - index.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
Page({
data: {

},
UploadImage() {
var random = Date.parse(new Date()) + Math.ceil(Math.random() * 1000)
wx.chooseImage({
count: 1,
sizeType: ['original', 'compressed'],
sourceType: ['album', 'camera'],
success(res) {
const tempFilePaths = res.tempFilePaths[0]
console.log(tempFilePaths)
wx.cloud.uploadFile({
cloudPath: random + '.png',
filePath: tempFilePaths, // 文件路径
success: res => {
console.log(res.fileID)
wx.cloud.callFunction({
name: 'Face_Detection',
data: {
fileID:res.fileID
},
success: res => {
console.log(res.result)
},
})
},
fail: err => {
}
})
}
})
},
onLoad: function (options) {
wx.cloud.init({
env: 'test-f97abe'
})
}
})

我们新增了一个名为random的随机数,这个随机数是当前的UTC时间与4位随机数相加而得到的一个结果,然后将这个随机数存为我们的图片文件的名称。这样写的好处是,在同1秒内,最多可以支持1000张图片的并发。

返回值优化

接下来,我们将返回的数据展示在前端,先随便找一个参数返回在前端。先简单测试下,我们将部分数据输出到控制台。首先,我们看看腾讯云人脸识别返回的参数列表。

1548911461249

简单点,我们返回人脸Beauty参数到控制台吧,参考人脸识别返回的json数据,修改上面代码中的console.log(res.result)返回参数。

1
console.log(res.result.FaceInfos[0].FaceAttributesInfo.Beauty)

再次上传一遍数据,看看返回结果变成什么了。

1548911886626

这里返回的就是当前的魅力值,接下来,我们将这里的魅力数据传到前端。我们可以使用官方的setData方法。修改整体代码如下。

客户端 - index.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
Page({
data: {
Beauty:""
},
UploadImage() {
var random = Date.parse(new Date()) + Math.ceil(Math.random() * 1000)
var myThis = this
wx.chooseImage({
count: 1,
sizeType: ['original', 'compressed'],
sourceType: ['album', 'camera'],
success(res) {
const tempFilePaths = res.tempFilePaths[0]
console.log(tempFilePaths)
wx.cloud.uploadFile({
cloudPath: random + '.png',
filePath: tempFilePaths, // 文件路径
success: res => {
console.log(res.fileID)
wx.cloud.callFunction({
name: 'Face_Detection',
data: {
fileID:res.fileID
},
success: res => {
console.log(res.result.FaceInfos[0].FaceAttributesInfo.Beauty)
myThis.setData({
Beauty: res.result.FaceInfos[0].FaceAttributesInfo.Beauty
})
},
})
},
fail: err => {
}
})
}
})
},
onLoad: function (options) {
wx.cloud.init({
env: 'test-f97abe'
})
}
})

客户端 - index.wxml

1
2
<button type="primary" bindtap="UploadImage">上传照片</button>
<text>{{Beauty}}</text>

我们在data中,新增一个Beauty变量,然后在下面的UploadImage函数中,使用myThis.setData函数改变变量Beauty数据。最后,我们在index.wxml文件中去显示这个返回的内容。

1548912314184

当然,返回的一个参数没有多大用,我们将后台的所有数据都返回到前端,并参考腾讯云官方的文档,优化下首页显示。

客户端 - index.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
Page({
data: {
age: "请上传照片",
glasses: "请上传照片",
beauty: "请上传照片",
mask: "请上传照片",
hat: "请上传照片",
gender: "请上传照片",
hair_length: "请上传照片",
hair_bang: "请上传照片",
hair_color: "请上传照片",
},
UploadImage() {
var random = Date.parse(new Date()) + Math.ceil(Math.random() * 1000)
var myThis = this
wx.chooseImage({
count: 1,
sizeType: ['original', 'compressed'],
sourceType: ['album', 'camera'],
success(res) {
const tempFilePaths = res.tempFilePaths[0]
console.log(tempFilePaths)
wx.cloud.uploadFile({
cloudPath: random + '.png',
filePath: tempFilePaths, // 文件路径
success: res => {
console.log(res.fileID)
wx.cloud.callFunction({
name: 'Face_Detection',
data: {
fileID:res.fileID
},
success: res => {
myThis.setData({
age: res.result.FaceInfos[0].FaceAttributesInfo.Age,
glasses: res.result.FaceInfos[0].FaceAttributesInfo.Glass,
beauty: res.result.FaceInfos[0].FaceAttributesInfo.Beauty,
mask: res.result.FaceInfos[0].FaceAttributesInfo.Mask,
hat: res.result.FaceInfos[0].FaceAttributesInfo.Hat,
gender:res.result.FaceInfos[0].FaceAttributesInfo.Gender,
hair_length: res.result.FaceInfos[0].FaceAttributesInfo.Hair.Length,
hair_bang: res.result.FaceInfos[0].FaceAttributesInfo.Hair.Bang,
hair_color: res.result.FaceInfos[0].FaceAttributesInfo.Hair.Bang
})
},
})
},
fail: err => {
}
})
}
})
},
onLoad: function (options) {
wx.cloud.init({
env: 'test-f97abe'
})
}
})

客户端 - index.wxml

1
2
3
4
5
6
7
8
9
10
<button type="primary" bindtap="UploadImage">上传照片</button>
<text class="text_size">性别:{{gender}}</text>
<text class="text_size">年龄:{{age}}</text>
<text class="text_size">颜值:{{beauty}}</text>
<text class="text_size">是否带眼镜:{{glasses}}</text>
<text class="text_size">是否有帽子:{{hat}}</text>
<text class="text_size">是否有口罩:{{mask}}</text>
<text class="text_size">头发长度:{{hair_length}}</text>
<text class="text_size">有无刘海:{{hair_bang}}</text>
<text class="text_size">头发颜色:{{hair_color}}</text>

1548912790724

如图,我们已经将所需要的数据展示在前端了。但是展示的不够完美,很多数据给用户展示用户也无法看懂。比如性别:0头发长度:3有无刘海:1等。参考腾讯云API文档,我们将这里的数据使用switchif语句做下判断,不同的数据返回不同的内容,让用户看明白。

客户端 - index.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
Page({
data: {
age: "请上传照片",
glasses: "请上传照片",
beauty: "请上传照片",
mask: "请上传照片",
hat: "请上传照片",
gender: "请上传照片",
hair_length: "请上传照片",
hair_bang: "请上传照片",
hair_color: "请上传照片",
},
UploadImage() {
var random = Date.parse(new Date()) + Math.ceil(Math.random() * 1000)
var myThis = this
wx.chooseImage({
count: 1,
sizeType: ['original', 'compressed'],
sourceType: ['album', 'camera'],
success(res) {
const tempFilePaths = res.tempFilePaths[0]
console.log(tempFilePaths)
wx.cloud.uploadFile({
cloudPath: random + '.png',
filePath: tempFilePaths, // 文件路径
success: res => {
console.log(res.fileID)
wx.cloud.callFunction({
name: 'Face_Detection',
data: {
fileID:res.fileID
},
success: res => {
myThis.setData({
age: res.result.FaceInfos[0].FaceAttributesInfo.Age,
glasses: res.result.FaceInfos[0].FaceAttributesInfo.Glass,
beauty: res.result.FaceInfos[0].FaceAttributesInfo.Beauty,
mask: res.result.FaceInfos[0].FaceAttributesInfo.Mask,
hat: res.result.FaceInfos[0].FaceAttributesInfo.Hat,
})
if (res.result.FaceInfos[0].FaceAttributesInfo.Gender < 50) {
myThis.setData({
gender: "女"
});
} else {
myThis.setData({
gender: "男"
});
}
switch (res.result.FaceInfos[0].FaceAttributesInfo.Hair.Length) {
case 0:
myThis.setData({
hair_length: "光头"
});
break;
case 1:
myThis.setData({
hair_length: "短发"
});
break;
case 2:
myThis.setData({
hair_length: "中发"
});
break;
case 3:
myThis.setData({
hair_length: "长发"
});
break;
case 4:
myThis.setData({
hair_length: "绑发"
});
break;
}
switch (res.result.FaceInfos[0].FaceAttributesInfo.Hair.Bang) {
case 0:
myThis.setData({
hair_bang: "有刘海"
});
break;
case 1:
myThis.setData({
hair_bang: "无刘海"
});
break;
}
switch (res.result.FaceInfos[0].FaceAttributesInfo.Hair.Color) {
case 0:
myThis.setData({
hair_color: "黑色"
});
break;
case 1:
myThis.setData({
hair_color: "金色"
});
break;
case 0:
myThis.setData({
hair_color: "棕色"
});
break;
case 1:
myThis.setData({
hair_color: "灰白色"
});
break;
}
},
})
},
fail: err => {
}
})
}
})
},
onLoad: function (options) {
wx.cloud.init({
env: 'test-f97abe'
})
}
})

1548913281434

现在,我们已经将返回的内容正常显示在前端了。

交互优化

接下来,我们可以优化前端了。

文本优化

首先,最难看的莫过于这里交错的文字了,这里就需要修改index.wxss文件。

客户端 - index.wxss

1
2
3
4
5
6
7
8
9
10
.text_viwe_size{
padding-top: 20px;
padding-bottom: 30px;
padding-left: 30px;
padding-right: 30px;
}
.text_size{
display:flex;
color: #444444;
}

我们定义一下文本显示框的大小,并定义文本显示方式和色彩。然后,修改index.wxml文件,让其引用index.wxss文件。

客户端 - index.wxml

1
2
3
4
5
6
7
8
9
10
11
12
<button type="primary" bindtap="UploadImage">上传照片</button>
<view class='text_viwe_size'>
<text class="text_size">性别:{{gender}}</text>
<text class="text_size">年龄:{{age}}</text>
<text class="text_size">颜值:{{beauty}}</text>
<text class="text_size">是否带眼镜:{{glasses}}</text>
<text class="text_size">是否有帽子:{{hat}}</text>
<text class="text_size">是否有口罩:{{mask}}</text>
<text class="text_size">头发长度:{{hair_length}}</text>
<text class="text_size">有无刘海:{{hair_bang}}</text>
<text class="text_size">头发颜色:{{hair_color}}</text>
</view>

1548913835858

上传进度

参考小程序官方的文档,调用progress组件,我们新增一个进度条组件,打开index.wxml文件,先在前端显示相关内容。

客户端 - index.wxml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<button type="primary" bindtap="UploadImage">上传照片</button>
<view class='progress_view_size'>
<progress percent="{{progress}}" show-info />
<text class="text_size">上传进度</text>
</view>
<view class='text_viwe_size'>
<text class="text_size">性别:{{gender}}</text>
<text class="text_size">年龄:{{age}}</text>
<text class="text_size">颜值:{{beauty}}</text>
<text class="text_size">是否带眼镜:{{glasses}}</text>
<text class="text_size">是否有帽子:{{hat}}</text>
<text class="text_size">是否有口罩:{{mask}}</text>
<text class="text_size">头发长度:{{hair_length}}</text>
<text class="text_size">有无刘海:{{hair_bang}}</text>
<text class="text_size">头发颜色:{{hair_color}}</text>
</view>

当然,前端展示了还无法显示当前进度,我们需要在后端调用返回相关进度,然后再向前端展示。

客户端 - index.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
Page({
data: {
age: "请上传照片",
glasses: "请上传照片",
beauty: "请上传照片",
mask: "请上传照片",
hat: "请上传照片",
gender: "请上传照片",
hair_length: "请上传照片",
hair_bang: "请上传照片",
hair_color: "请上传照片",
},
UploadImage() {
var random = Date.parse(new Date()) + Math.ceil(Math.random() * 1000)
var myThis = this
wx.chooseImage({
count: 1,
sizeType: ['original', 'compressed'],
sourceType: ['album', 'camera'],
success(res) {
const tempFilePaths = res.tempFilePaths[0]
console.log(tempFilePaths)
var uploadTask = wx.cloud.uploadFile({
cloudPath: random + '.png',
filePath: tempFilePaths, // 文件路径
success: res => {
console.log(res.fileID)
wx.cloud.callFunction({
name: 'Face_Detection',
data: {
fileID:res.fileID
},
success: res => {
myThis.setData({
age: res.result.FaceInfos[0].FaceAttributesInfo.Age,
glasses: res.result.FaceInfos[0].FaceAttributesInfo.Glass,
beauty: res.result.FaceInfos[0].FaceAttributesInfo.Beauty,
mask: res.result.FaceInfos[0].FaceAttributesInfo.Mask,
hat: res.result.FaceInfos[0].FaceAttributesInfo.Hat,
})
if (res.result.FaceInfos[0].FaceAttributesInfo.Gender < 50) {
myThis.setData({
gender: "女"
});
} else {
myThis.setData({
gender: "男"
});
}
switch (res.result.FaceInfos[0].FaceAttributesInfo.Hair.Length) {
case 0:
myThis.setData({
hair_length: "光头"
});
break;
case 1:
myThis.setData({
hair_length: "短发"
});
break;
case 2:
myThis.setData({
hair_length: "中发"
});
break;
case 3:
myThis.setData({
hair_length: "长发"
});
break;
case 4:
myThis.setData({
hair_length: "绑发"
});
break;
}
switch (res.result.FaceInfos[0].FaceAttributesInfo.Hair.Bang) {
case 0:
myThis.setData({
hair_bang: "有刘海"
});
break;
case 1:
myThis.setData({
hair_bang: "无刘海"
});
break;
}
switch (res.result.FaceInfos[0].FaceAttributesInfo.Hair.Color) {
case 0:
myThis.setData({
hair_color: "黑色"
});
break;
case 1:
myThis.setData({
hair_color: "金色"
});
break;
case 0:
myThis.setData({
hair_color: "棕色"
});
break;
case 1:
myThis.setData({
hair_color: "灰白色"
});
break;
}
},
})
},
fail: err => {
}
})
uploadTask.onProgressUpdate((res) => {
myThis.setData({
progress: res.progress //上传进度
})
})
}
})
},
onLoad: function (options) {
wx.cloud.init({
env: 'test-f97abe'
})
}
})

这里的代码中,我们将wx.cloud.uploadFile存为名为uploadTask的变量,然后在最后调用onProgressUpdate(res)方法,并通过setData方法,将数据展示在前端。接下来,优化下wxss的代码,将前端修改漂亮点。

客户端 - index.wxss

1
2
3
4
5
6
7
8
9
10
11
12
13
14
.text_viwe_size{
padding-top: 20px;
padding-bottom: 30px;
padding-left: 30px;
padding-right: 30px;
}
.text_size{
display:flex;
color: #444444;
}
.progress_view_size{
padding-left: 30px;
padding-right: 30px;
}

1548914713074

显示识别图片

现在展示端还不够完美,人脸识别,当然是要将用户的照片展示在前端,我们在index.wxml文件中插入图片。

客户端 - index.wxml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<view class="image_viwe_size">
<image src="{{image_src}}" style="width: 200px; height: 200px;"></image>
</view>
<view class='button_viwe_size'>
<button class="button_size" type="primary" bindtap="UploadImage">上传照片</button>
</view>
<view class='progress_view_size'>
<progress percent="{{progress}}" show-info />
<text class="text_size">上传进度</text>
</view>
<view class='text_viwe_size'>
<text class="text_size">性别:{{gender}}</text>
<text class="text_size">年龄:{{age}}</text>
<text class="text_size">颜值:{{beauty}}</text>
<text class="text_size">是否带眼镜:{{glasses}}</text>
<text class="text_size">是否有帽子:{{hat}}</text>
<text class="text_size">是否有口罩:{{mask}}</text>
<text class="text_size">头发长度:{{hair_length}}</text>
<text class="text_size">有无刘海:{{hair_bang}}</text>
<text class="text_size">头发颜色:{{hair_color}}</text>
</view>

然后,我们需要在后端去写image_src图片地址,做展示,打开index.js文件,在data中指定图片地址。同时,当用户点击图片上传按钮的时候,我们将图片地址替换为上传的临时文件。

客户端 - index.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
Page({
data: {
age: "请上传照片",
glasses: "请上传照片",
beauty: "请上传照片",
mask: "请上传照片",
hat: "请上传照片",
gender: "请上传照片",
hair_length: "请上传照片",
hair_bang: "请上传照片",
hair_color: "请上传照片",
image_src:"https://cdn-img.easyicon.net/image/2019/panda-index.svg"
},
UploadImage() {
var random = Date.parse(new Date()) + Math.ceil(Math.random() * 1000)
var myThis = this
wx.chooseImage({
count: 1,
sizeType: ['original', 'compressed'],
sourceType: ['album', 'camera'],
success(res) {
const tempFilePaths = res.tempFilePaths[0]
console.log(tempFilePaths)
myThis.setData({
image_src: res.tempFilePaths[0]
});
var uploadTask = wx.cloud.uploadFile({
cloudPath: random + '.png',
filePath: tempFilePaths, // 文件路径
success: res => {
console.log(res.fileID)
wx.cloud.callFunction({
name: 'Face_Detection',
data: {
fileID:res.fileID
},
success: res => {
myThis.setData({
age: res.result.FaceInfos[0].FaceAttributesInfo.Age,
glasses: res.result.FaceInfos[0].FaceAttributesInfo.Glass,
beauty: res.result.FaceInfos[0].FaceAttributesInfo.Beauty,
mask: res.result.FaceInfos[0].FaceAttributesInfo.Mask,
hat: res.result.FaceInfos[0].FaceAttributesInfo.Hat,
})
if (res.result.FaceInfos[0].FaceAttributesInfo.Gender < 50) {
myThis.setData({
gender: "女"
});
} else {
myThis.setData({
gender: "男"
});
}
switch (res.result.FaceInfos[0].FaceAttributesInfo.Hair.Length) {
case 0:
myThis.setData({
hair_length: "光头"
});
break;
case 1:
myThis.setData({
hair_length: "短发"
});
break;
case 2:
myThis.setData({
hair_length: "中发"
});
break;
case 3:
myThis.setData({
hair_length: "长发"
});
break;
case 4:
myThis.setData({
hair_length: "绑发"
});
break;
}
switch (res.result.FaceInfos[0].FaceAttributesInfo.Hair.Bang) {
case 0:
myThis.setData({
hair_bang: "有刘海"
});
break;
case 1:
myThis.setData({
hair_bang: "无刘海"
});
break;
}
switch (res.result.FaceInfos[0].FaceAttributesInfo.Hair.Color) {
case 0:
myThis.setData({
hair_color: "黑色"
});
break;
case 1:
myThis.setData({
hair_color: "金色"
});
break;
case 0:
myThis.setData({
hair_color: "棕色"
});
break;
case 1:
myThis.setData({
hair_color: "灰白色"
});
break;
}
},
})
},
fail: err => {
}
})
uploadTask.onProgressUpdate((res) => {
myThis.setData({
progress: res.progress //上传进度
})
})
}
})
},
onLoad: function (options) {
wx.cloud.init({
env: 'test-f97abe'
})
}
})

最后,优化下前端的index.wxss文件。

客户端 - index.wxss

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
.image_viwe_size{
display:flex;
justify-content: center;
background: #FFFFFF;
}
.image_size{
width: 300px;
}
.button_viwe_size{
display:flex;
padding-top: 10px;
padding-bottom: 10px;
}
.button_size{
height:50px;
width: 200px;
}
.text_viwe_size{
padding-top: 20px;
padding-bottom: 30px;
padding-left: 30px;
padding-right: 30px;
}
.text_size{
display:flex;
color: #444444;
}
.progress_view_size{
padding-left: 30px;
padding-right: 30px;
}

1548915288440

现在,整个UI好看多了。

识别状态展示

现在,我们已经有了上传图片进度条,但是用户上传图片后没有相关提示信息给用户,用户也不知道图片上传后是返回结果是不是正常的。那么,怎么去优化这块呢?幸好微信官方给我们提供了交互接口,我们直接调用就行,直接上代码。

客户端 - index.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
Page({
data: {
age: "请上传照片",
glasses: "请上传照片",
beauty: "请上传照片",
mask: "请上传照片",
hat: "请上传照片",
gender: "请上传照片",
hair_length: "请上传照片",
hair_bang: "请上传照片",
hair_color: "请上传照片",
image_src:"https://cdn-img.easyicon.net/image/2019/panda-index.svg"
},
UploadImage() {
var random = Date.parse(new Date()) + Math.ceil(Math.random() * 1000)
var myThis = this
wx.chooseImage({
count: 1,
sizeType: ['original', 'compressed'],
sourceType: ['album', 'camera'],
success(res) {
wx.showLoading({
title: '加载中...',
});
const tempFilePaths = res.tempFilePaths[0]
console.log(tempFilePaths)
myThis.setData({
image_src: res.tempFilePaths[0]
});
var uploadTask = wx.cloud.uploadFile({
cloudPath: random + '.png',
filePath: tempFilePaths, // 文件路径
success: res => {
console.log(res.fileID)
wx.cloud.callFunction({
name: 'Face_Detection',
data: {
fileID:res.fileID
},
success: res => {
wx.hideLoading()
wx.showToast({
title: '成功',
icon: 'success',
duration: 500
})
myThis.setData({
age: res.result.FaceInfos[0].FaceAttributesInfo.Age,
glasses: res.result.FaceInfos[0].FaceAttributesInfo.Glass,
beauty: res.result.FaceInfos[0].FaceAttributesInfo.Beauty,
mask: res.result.FaceInfos[0].FaceAttributesInfo.Mask,
hat: res.result.FaceInfos[0].FaceAttributesInfo.Hat,
})
if (res.result.FaceInfos[0].FaceAttributesInfo.Gender < 50) {
myThis.setData({
gender: "女"
});
} else {
myThis.setData({
gender: "男"
});
}
switch (res.result.FaceInfos[0].FaceAttributesInfo.Hair.Length) {
case 0:
myThis.setData({
hair_length: "光头"
});
break;
case 1:
myThis.setData({
hair_length: "短发"
});
break;
case 2:
myThis.setData({
hair_length: "中发"
});
break;
case 3:
myThis.setData({
hair_length: "长发"
});
break;
case 4:
myThis.setData({
hair_length: "绑发"
});
break;
}
switch (res.result.FaceInfos[0].FaceAttributesInfo.Hair.Bang) {
case 0:
myThis.setData({
hair_bang: "有刘海"
});
break;
case 1:
myThis.setData({
hair_bang: "无刘海"
});
break;
}
switch (res.result.FaceInfos[0].FaceAttributesInfo.Hair.Color) {
case 0:
myThis.setData({
hair_color: "黑色"
});
break;
case 1:
myThis.setData({
hair_color: "金色"
});
break;
case 0:
myThis.setData({
hair_color: "棕色"
});
break;
case 1:
myThis.setData({
hair_color: "灰白色"
});
break;
}
},
})
},
fail: err => {
}
})
uploadTask.onProgressUpdate((res) => {
myThis.setData({
progress: res.progress //上传进度
})
})
}
})
},
onLoad: function (options) {
wx.cloud.init({
env: 'test-f97abe'
})
}
})

图片文件选择成功后,我们调用wx.showLoading接口,展示加载中的提示框。当云函数回调成功后,我们立刻调用wx.hideLoading隐藏加载中的提示框。同时,我们调用wx.showToast接口,显示消息成功提示框。

1548916005707

现在,我们就完成了一款人脸识别小程序产品的开发,并能够正常展示给用户。

总结

项目终于写完了,你学会了整体的小程序·云开发并通过腾讯云人脸识别流程了吗?希望这篇文章能给你带来一些新的经验和想法!

当然,这里的项目还有一些问题,比如图片上传到云存储后会一直存在,没有清空缓存的机制。比如一秒内用户最大并发是1000,因为图片我们设置的随机数最大是1000,后面建议将随机数改为读取图片的md5值然后显示出来。这些BUG我也会慢慢去优化,喜欢请关注(Star)我的小程序人脸识别项目(https://github.com/Techeek/WX_TencentAI_Face)。

感谢您阅读我的文章,如果有什么新的意见或者建议,请在评论区留言。BUG反馈请在Github提交Issues

CATALOG
  1. 1. 前言
  2. 2. 准备
    1. 2.1. 安装Git
    2. 2.2. 安装NodeJS和npm
    3. 2.3. 搭建小程序开发环境
      1. 2.3.1. 申请账号
      2. 2.3.2. 安装开发工具
      3. 2.3.3. 人脸识别API申请
  3. 3. 创建云开发项目
  4. 4. 项目开发思考
  5. 5. 服务端开发
    1. 5.1. 新建云函数
    2. 5.2. 腾讯云人脸识别API
    3. 5.3. 腾讯云人脸识别SDK
    4. 5.4. 云存储API调用
    5. 5.5. 云函数代码整合
  6. 6. 客户端开发
    1. 6.1. 选择图片API
    2. 6.2. 云存储上传文件API
    3. 6.3. 调用云函数API
    4. 6.4. 返回值优化
    5. 6.5. 交互优化
      1. 6.5.1. 文本优化
      2. 6.5.2. 上传进度
      3. 6.5.3. 显示识别图片
      4. 6.5.4. 识别状态展示
  7. 7. 总结