[轉] 無伺服器開發人臉識別小程式

firefule發表於2021-09-09

原文作者:張誠
原文連結:
發表日期:一月 30日 2019, 10:43:49 上午
更新日期:January 31st 2019, 2:44:27 pm
版權宣告:本文采用知識共享署名-非商業性使用 4.0 國際許可協議進行許可,轉載請註明出處。

無伺服器開發人臉識別小程式

從2006年AWS釋出的第一個雲服務S3開始,儲存,計算等IT基礎設施的能力紛紛被以服務的方式提供給使用者。過去十年,雲服務深刻的改變了社會獲取和使用計算能力的方式,雲服務自身也以極快的速度演進,新的服務形態不斷湧現,無伺服器計算(serverless computing)就是其中之一。國內各大廠商也在近兩年推出了自家的無伺服器計算產品,比如騰訊雲的,阿里雲的函式計算等產品。

前言

前段時間我還在想,如果小程式能使用無伺服器計算產品那該多好,過不奇然,最近微信與騰訊雲聯合開發的原生 serverless 雲服務產品——,其具備簡化運維、高效鑑權等優勢,讓你零門檻快速上線小程式。為此,我決定嘗試下這種新的開發方式,看看是不是真的如官方所說。

那麼,用什麼專案去嘗試呢?看了下自己以前寫的文章,發現這篇文章關注量還挺多,況且騰訊雲API於2019年1月25日全量更新為了最新的3.0版本,API呼叫方式及也有較大變化。我完全可以用最新版的API結合雲開發去體驗下這個過程。

當然,最大的優勢在於省錢!!!小程式 · 雲開發這款產品還在免費階段,同時騰訊雲服務每月為各個介面提供 1 萬次免費呼叫,很划算。

這篇文章將分享我開發過程中的一些思路,如何考慮產品應用性,如何最佳化邏輯等問題。同時也會分享整個的開發過程,從怎麼註冊賬戶到怎麼呼叫API,以及程式碼是如何一點一點拼接的。大家也可以將這篇文章看為一篇教程,我會從0~1分享整個專案的開發過程。當然,如果你是一名Developer,請直接使用我撰寫好的程式碼,已經分享到了,歡迎大家參閱!

那麼,我們就開始吧!

準備

在撰寫程式碼之前,我們需要先準備一下小程式的開發環境,所需要的環境有GitNodeJSnpm微信開發者工具。同時,因為要呼叫騰訊雲人臉識別API,我們還需要註冊的賬號,同時還需要註冊騰訊雲的。

安裝Git

git是一個分散式版本控制軟體,開發小程式的時候做好程式碼版本控制非常重要,如果程式碼寫錯,可以使用Git快速恢復。安裝Git較為簡單,我們只需要開啟,點選右側的Download 2.20.1 for Windows,當然,版本號和系統都可能不同,大家按照自己的系統及最新的版本號下載即可。

圖片描述

下載完成後雙擊開啟安裝包,然後一路點選下一步直至安裝完成。安裝完成後我們按鍵盤上的Win+R,然後輸入CMD開啟命令提示符視窗,然後在命令提示符視窗中輸入git,如果你看到類似下面的截圖,證明你的Git安裝成功,可以進行下一步了。

圖片描述

安裝NodeJS和npm

NodeJS是一個可以跨平臺在服務端執行JavaScript的執行環境,我們小程式雲開發所使用的服務端環境就是NodeJS,為了最佳化並測試程式碼,建議在本地安裝NodeJS執行環境。npm是Node包管理器,透過npm包管理器,我們可以非常方便的安裝雲開發所需要的環境。

首先,我們開啟,下載NodeJS安裝包。

圖片描述

下載完成後雙擊開啟,並一路下一步安裝。

圖片描述

安裝完成後,開啟命令提示符,試試node命令和npm命令是否正常。

圖片描述

看到如圖類似的內容,證明你的nodenpm都已經安裝成功了。

搭建小程式開發環境

開發小程式的第一步,你需要擁有一個小程式帳號,透過這個帳號你就可以管理你的小程式。

申請賬號

點選 https://mp.weixin.qq.com/wxopen/waregister?action=step1 根據指引填寫資訊和提交相應的資料,就可以擁有自己的小程式帳號。

圖片描述

如果你註冊過小程式,可以點選右側的立即登入。如果沒有的話,請重新註冊,值得注意的是,郵箱必須填寫未在微信公眾平臺、未在微信開放平臺、個人未繫結的郵箱,不然這裡是無法註冊的。密碼請填寫你能記住的密碼即可。

現在登入https://mp.weixin.qq.com/,點選左側的設定——開發設定,在這裡,我們就能看到你小程式的AppID了。

圖片描述

當小程式的ID拿到之後,我們就可以下載安裝開發工具了。

安裝開發工具

現在,開啟 ,根據自己的作業系統下載對應的安裝包進行安裝。

圖片描述

我這裡使用的是Windows 64位作業系統,所以我點選Windwos 64位按鈕進行下載。下載完成後,右鍵,然後以管理員身份執行安裝檔案。

圖片描述

之後,一路點選下一步安裝即可。

圖片描述

接下來,就可以開始執行開發者工具了。使用前需要我們掃描二維碼才能開始使用,請開啟微信,然後點選發現——掃一掃,掃描開發者工具展示的二維碼,之後在手機上點選登入即可。

圖片描述

  • 關於小程式的快速搭建,我在有相關介紹,感興趣的小夥伴歡迎閱讀。

人臉識別API申請

如果要使用人臉識別API,必須在騰訊雲進行實名認證,實名認證後,您可以登入騰訊雲 進行使用。如果沒有賬號,請參考賬號 。註冊完成後,需要建立相關開發金鑰,不然無法使用API。

您需要在 建立金鑰,點選圖中的新建金鑰,即可建立金鑰。

圖片描述

建立完成後,點選SecretKey的顯示按鈕,顯示當前SecretKey,然後將APPIDSecretIdSecretKey記錄下了,後面教程中使用。

現在,開發小程式所需要的所有環境就已經搭建完成,我們可以開始建立一個新的專案了。

建立雲開發專案

首先,我們新建一個雲開發的專案,注意AppID是你自己在小程式AppID,同時不要勾選建立雲開發模版。

圖片描述

接下來,我們新建兩個目錄,一個目錄(client)存放小程式的客戶端,一個目錄(server)存放小程式雲開發的服務端,如圖。

圖片描述

接下來,開啟配置檔案project.config.json,我們需要新增兩行檔案。

"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

圖片描述

新建完成後,系統會自動生成index.jspackage.json檔案。其中index.js是官方給出的Demo檔案,我們將生成的程式碼稍微處理下,只保留最基本的模版。

雲函式 - index.js

// 雲函式入口檔案
const cloud = require('wx-server-sdk')
cloud.init()
// 雲函式入口函式
exports.main = async (event, context) => {

}

騰訊雲人臉識別API

我們開啟騰訊雲人臉識別的,先看看官方文件的API怎麼呼叫。

圖片描述

我們看到官方給了7中人臉相關的介面,因為我們只做最基本的人臉檢測,所以,選擇人臉檢測與分析相關介面

圖片描述

點選與分析DetectFace介面,檢視API文件。我們看到介面提供了人臉(Face)的位置、相應的面部屬性和人臉質量資訊,位置包括 (x,y,w,h),面部屬性包括性別(gender)、年齡(age)、表情(expression)、魅力(beauty)、眼鏡(glass)、髮型(hair)、口罩(mask)和姿態 (pitch,roll,yaw),人臉質量資訊包括整體質量分(score)、模糊分(sharpness)、光照分(brightness)和五官遮擋分(completeness)等資訊的識別,那麼我們的小程式所返回的相關資料也逃不出這幾個內容,所以,給使用者展示的資訊建議也從這裡面的引數中選取。向下拉,檢視請求相關引數。

圖片描述

請求引數如圖,必填的是ActionVersion,分別對應介面名稱及版本號,按照文件,我們這裡選擇DetectFace2018-03-01。其他資料是選填的,如MaxFaceNum人臉數,Url圖片的地址等。我們專案架構中由雲端儲存所分享的是圖片的地址,所以URL引數是必要的。同時我們想獲取圖片的人臉屬性資訊,所以按照表內容的內容,NeedFaceAttributes引數也是必要的。

輸入引數看完了,我們看看輸出引數。

圖片描述

輸出引數有4組,分別是圖片的寬高,人臉的資訊以及請求的ID。當然,最重要的還是我們的FaceInfos引數,我看點選藍色的FaceInfos,看看具體有什麼內容。

圖片描述

資料蠻多的,這裡的FaceAttributesInfo引數我們會用到,因為在輸入引數中,我們需要NeedFaceAttributes,所以當NeedFaceAttributes引數等於1的時候,FaceAttributesInfo引數才會返回相關的資料,我們點開FaceAttributesInfo引數看看。

圖片描述

這裡就是返回引數的具體資料了,有性別,年齡,微笑程度等資訊,具體大家請看描述。我們看到還有一個Hair引數,是頭髮的長度、劉海、髮色等資訊,點開FaceHairAttributesInfo看看。

圖片描述

這裡就是該介面的相關資訊及所有的引數了,心裡大概有個底就可以了。

騰訊雲人臉識別SDK

繼續向下看文件,我們發現,API中給我們提供了相關的SDK。因為我們客戶端的程式碼是NodeJS的,官方也提供了相關的SDK,那麼就直接使用吧!

圖片描述

咦,等下,API Explorer是什麼,點選去看看,原來,這是官方給我們提供的一個GUI頁面,透過簡單的設定,即可生成API呼叫的程式碼,能顯著降低使用雲 API 的難度。

圖片描述

我們就用這個工具來生成我們NodeJS端的程式碼吧!(注:當然,這裡也可自己寫相關程式碼去呼叫API,但是文件中對簽名驗證這塊講的模糊不清,所以我還是打算使用SDK)

圖片描述

點開工具,我們看到一個頁面,我們看到框內有我們前面所準備的SecretIdSecretKey,同時也有前文中我們提到的輸入引數UrlNeedFaceAttributes等。等下,必填ActionVersion引數去哪裡了?

原來,當我們進入人臉識別的頁面後,系統已經自動幫我們填寫好了ActionVersion引數,我們只需要直接使用就行了。

我們填寫下SecretIdSecretKey這兩個引數,然後Url引數中,我們填入一張人臉的圖片,這裡大家可以自己去搜尋下,然後將圖片的地址貼上到這裡就可以。最後,將NeedFaceAttributes引數為1,根據文件,我們要返回人臉屬性。Region引數大區可以不填,文件中已經說明。

圖片描述

如圖,當填寫完後,我們點選程式碼生成,然後選擇NodeJS,系統會自動生成我們所需要的程式碼。(這裡有個BUG,URL地址不識別冒號,希望官方修復)

我們點選右側的線上呼叫,然後點選傳送請求,看看是否返回了正常的資料。

圖片描述

如圖,這樣的資料就是正常的響應結果。如果顯示說您未開通人臉識別服務,請前往開啟。我們回到程式碼生成頁面,複製NodeJS環境的所有程式碼。

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":"","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

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":"","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包管理器了。在我們的雲函式目錄上面右鍵,選擇在終端中開啟

圖片描述

然後輸入下面的命令,透過npm包管理器,安裝雲函式和騰訊雲SDK的依賴檔案。

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

這兩行命令即可安裝我們需要的依賴檔案,如圖所示。

圖片描述

不要關閉這個視窗,我們對我們的人臉識別介面進行測試,看看能否正常返回資料,執行下面的命令。

node index.js

圖片描述

如果看到類似的內容,就證明你的程式碼配置沒有錯誤,可以進行下一步了。

雲端儲存API呼叫

根據上面的架構,我們在服務端獲取到檔案的ID後,使用檔案ID去雲端儲存檔案的URL地址,目前我們在雲端儲存端還沒有檔案。那麼,第一步,將檔案上傳到雲端儲存。還好,雲開發控制檯可以直接上傳檔案,開啟控制檯,點選儲存管理,如圖。

圖片描述

我們在這裡隨便上傳一個檔案,建議上傳一張人臉的照片,已方便我們後續測試。上傳之後點選右側的複製按鈕,複製檔案的ID。

圖片描述

接下來,我們開啟,呼叫getTempFileURL介面,然後將ID傳送給雲端儲存。等待雲端儲存返回URL地址,這裡我們直接複製給出的程式碼。

圖片描述

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。

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

現在,開啟我們雲函式的index.js檔案,將上面的程式碼整合到其中,並將返回的內容result.fileList列印到控制檯。

雲函式 - index.js

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":"","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
}

接下來,我們部署下雲函式,讓其在雲開發端執行,我們看看能不能正常讀取到我們所需要的檔案。在雲函式上右鍵,選擇上傳並部署:所有檔案,這一步,我們將我們剛剛寫的程式碼及所需要的依賴環境部署在服務端。

圖片描述

當彈出的對話方塊顯示上傳並部署完成後,我們就可以開啟雲開發的控制檯進行測試了。

圖片描述

點選雲函式按鈕,選擇我們剛剛上傳的雲函式,然後單擊右側的測試。

圖片描述

當點選執行測試按鈕後,檢視當前返回結果,如果顯示成功,返回結果內有你提交的檔案ID及檔案的URL地址,證明我們雲端儲存在服務端的呼叫執行成功。

圖片描述

  • 對於雲端儲存的使用,我在有相關的講解,請參閱。

雲函式程式碼整合

既然騰訊雲人臉API和雲函式端的雲端儲存API已經呼叫並測試成功,那麼就需要我們整合下兩個程式碼,因為現在我們們服務端的程式碼是分離的,況且程式碼中傳入的圖片也不是我們想要的圖片。

雲函式 - index.js(最終版)

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的檔案,如圖。

圖片描述

將下面的程式碼複製到app.json檔案中。

{
  "pages": [
    "pages/index/index"
  ],
  "window": {
    "navigationBarBackgroundColor": "#ffffff",
    "navigationBarTextStyle": "black",
    "navigationBarTitleText": "人臉識別Demo",
    "backgroundColor": "#eeeeee",
    "backgroundTextStyle": "light",
    "enablePullDownRefresh": false
  },
  "cloud": true
}

儲存完成後,系統將自動生成相關目錄及檔案。

圖片描述

選擇圖片API

根據流程,我們的第一步就是選擇圖片了,小程式官方也提供了,廢話不多說,我們直接看程式碼。首先,開啟index.js檔案,注意,這裡選擇的是客戶端的檔案,不是服務端的。

客戶端 - index.js

// 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

Page({
  data: {
  },
  onLoad: function (options) {
  }
})

data存放我們一些靜態資料,以便前端呼叫,onLoad是小程式的初始化執行函式,當小程式頁面載入成功後會自動呼叫該函式,我們後續會用到。

接下來我們新建一個函式,這個函式用於當使用者在前端點選某個按鈕的時候,會執行該函式,我們將其命名為UploadImage

客戶端 - index.js

Page({
  data: {

  },
  UploadImage(){

  },
  onLoad: function (options) {

  }
})

參考的文件中的示例程式碼,修改程式碼如下。

客戶端 - index.js

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) {

  }
})

圖片描述

參考官方的文件,引數中,我們定一個圖片的張數,圖片的尺寸、來源。之後該API回撥返回tempFilePaths臨時圖片地址。

  • 關於圖片API的具體使用,可以參考這篇文章,文章中有較為詳細的介紹。

後端函式寫完了,我們需要點選一個按鈕執行上傳,撰寫下前端,開啟index.wxml檔案,修改程式碼如下。

客戶端 - index.wxml

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

圖片描述

當使用者點選圖中的上傳照片按鈕的時候,會自動呼叫我們在後端撰寫的UploadImage函式,如圖。

圖片描述

當我們選擇照片後,會在控制檯輸出當前檔案的臨時地址,有了臨時地址,接下來一步就是將臨時地址傳給雲端儲存的API,讓其將檔案上傳。

雲端儲存上傳檔案API

同選擇圖片API一樣,微信官方文件中也提供了相關的例項程式碼。我們可以直接使用,先看程式碼。

客戶端 - index.js

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

圖片描述

上傳成功後,返回回撥內容,並在控制檯列印出其檔案ID。

圖片描述

控制檯第一行是我們選擇圖片後的臨時地址,第二行上傳到雲端儲存後的檔案ID。

呼叫雲函式API

當雲端儲存呼叫完成後,我們拿到了檔案的ID,下一步就是真正的呼叫雲函式了,我們將檔案ID傳給雲函式,並等待雲函式返回人臉識別的結果。看一下官方的。

圖片描述

我們必須填寫的有云函式的名字,選填的有dataconfig,由於我們不更改區域性配置檔案,所以config用不到。但是我們需要將我們的檔案ID傳送給服務端,所以要填寫的還有data引數。

參考官方程式碼,我們呼叫下上面撰寫的Face_Detection雲函式。

客戶端 - index.js

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的雲函式,該函式在服務端進行一系列操作後,將資料返回給客戶端,最終透過控制檯列印出來。

圖片描述

如果看到類似的結果,證明客戶端基本的請求操作已經寫完,這裡返回的內容就是我們人臉識別後騰訊雲API所返回的資料,我們可以進行下一步操作了。

當然,這裡程式碼中有一個BUG,就是所有上傳的圖片名稱都是example.jpg,一個人使用當然不會造成什麼問題,但如果多個人併發使用這個小程式,就會產生一個很大的BUG,同一時間內請求的圖片地址相同,那麼返回的結果也相同,更有可能造成雲端儲存系統BUG。

為此,我將圖片的名稱變更為一個隨機數,這個同一時間內隨機數的內容不同,那麼圖片請求的時候就不會有多大問題,修改程式碼如下。

客戶端 - index.js

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張圖片的併發。

返回值最佳化

接下來,我們將返回的資料展示在前端,先隨便找一個引數返回在前端。先簡單測試下,我們將部分資料輸出到控制檯。首先,我們看看返回的引數列表。

圖片描述

簡單點,我們返回人臉Beauty引數到控制檯吧,參考人臉識別返回的json資料,修改上面程式碼中的console.log(res.result)返回引數。

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

再次上傳一遍資料,看看返回結果變成什麼了。

圖片描述

這裡返回的就是當前的魅力值,接下來,我們將這裡的魅力資料傳到前端。我們可以使用官方的setData方法。修改整體程式碼如下。

客戶端 - index.js

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

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

我們在data中,新增一個Beauty變數,然後在下面的UploadImage函式中,使用myThis.setData函式改變變數Beauty資料。最後,我們在index.wxml檔案中去顯示這個返回的內容。

圖片描述

當然,返回的一個引數沒有多大用,我們將後臺的所有資料都返回到前端,並參考騰訊雲官方的文件,最佳化下首頁顯示。

客戶端 - index.js

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

 <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>

圖片描述

如圖,我們已經將所需要的資料展示在前端了。但是展示的不夠完美,很多資料給使用者展示使用者也無法看懂。比如性別:0頭髮長度:3有無劉海:1等。參考騰訊雲API文件,我們將這裡的資料使用switchif語句做下判斷,不同的資料返回不同的內容,讓使用者看明白。

客戶端 - index.js

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'
    })
  }
})

圖片描述

現在,我們已經將返回的內容正常顯示在前端了。

互動最佳化

接下來,我們可以最佳化前端了。

文字最佳化

首先,最難看的莫過於這裡交錯的文字了,這裡就需要修改index.wxss檔案。

客戶端 - index.wxss

.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

 <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>

圖片描述

上傳進度

參考小程式官方的,呼叫progress元件,我們新增一個進度條元件,開啟index.wxml檔案,先在前端顯示相關內容。

客戶端 - index.wxml

<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

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

.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;
}

圖片描述

顯示識別圖片

現在展示端還不夠完美,人臉識別,當然是要將使用者的照片展示在前端,我們在index.wxml檔案中插入圖片。

客戶端 - index.wxml

<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

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

.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;
}

圖片描述

現在,整個UI好看多了。

識別狀態展示

現在,我們已經有了上傳圖片進度條,但是使用者上傳圖片後沒有相關提示資訊給使用者,使用者也不知道圖片上傳後是返回結果是不是正常的。那麼,怎麼去最佳化這塊呢?幸好微信官方給我們提供了,我們直接呼叫就行,直接上程式碼。

客戶端 - index.js

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介面,顯示訊息成功提示框。

圖片描述

現在,我們就完成了一款人臉識別小程式產品的開發,並能夠正常展示給使用者。

總結

專案終於寫完了,你學會了整體的小程式·雲開發並透過騰訊雲人臉識別流程了嗎?希望這篇文章能給你帶來一些新的經驗和想法!

當然,這裡的專案還有一些問題,比如圖片上傳到雲端儲存後會一直存在,沒有清空快取的機制。比如一秒內使用者最大併發是1000,因為圖片我們設定的隨機數最大是1000,後面建議將隨機數改為讀取圖片的md5值然後顯示出來。這些BUG我也會慢慢去最佳化,喜歡請關注(Star)我的小程式人臉識別專案()。

感謝您閱讀我的文章,如果有什麼新的意見或者建議,請在評論區留言。BUG反饋請在Github提交。

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/1834/viewspace-2821524/,如需轉載,請註明出處,否則將追究法律責任。

相關文章