陜西省政府網(wǎng)站建設(shè)要求營銷型網(wǎng)站建設(shè)套餐
前言
對于前端來說,需要后端提供一個(gè)人臉識別接口,前端傳入圖片,接口識別并返回結(jié)果,如此看來,其實(shí)前端只需實(shí)現(xiàn)圖片傳入即可,但是其實(shí)不然,在傳入圖片時(shí),需要進(jìn)行以下幾點(diǎn)操作:
- 判斷圖片格式,市場上比較常見的是
.jpg
、.jpeg
、.png
- 計(jì)算文件大小,一般要求不超過5MB
- 對圖片進(jìn)行base64加密
其實(shí)前2點(diǎn)具體要看接口要求,但是第3點(diǎn),是實(shí)現(xiàn)人臉識別必備步驟,下文重點(diǎn)講述一下移動端實(shí)現(xiàn)人臉識別的base64加密方法
問題
項(xiàng)目主要使用的技術(shù)棧是uniapp,uniapp的優(yōu)點(diǎn)是上手快,基于vue開發(fā),但缺點(diǎn)也很明顯,多環(huán)境兼容導(dǎo)致兼容性較差,真機(jī)調(diào)試和運(yùn)行較慢。比如h5端可以輕松實(shí)現(xiàn)base64加密,但是安卓環(huán)境完全不行,因?yàn)楸镜厣蟼鲌D片時(shí),會返回一個(gè)blob流,但是uniapp的blob流是以http://localhost…(安卓環(huán)境無法識別localhost) 開始,導(dǎo)致無法進(jìn)行base64加密
解決辦法
經(jīng)過多方實(shí)現(xiàn)后,借用html5+ api的多個(gè)結(jié)合方法(plus.zip.compressImage
、plus.io.resolveLocalFileSystemURL
、plus.io.FileReader
)實(shí)現(xiàn)加密,主要代碼如下:
//app壓縮圖片 用for循環(huán) 來處理圖片壓縮 的問題,原因是 plus.zip.compressImage 方法 是異步執(zhí)行的,for循環(huán)很快, 同時(shí)手機(jī)可執(zhí)行的壓縮方法有限制:應(yīng)該是3個(gè)吧。超出直接就不執(zhí)行了。所以 原理就是 在圖片壓縮成功后 繼續(xù) 回調(diào) 壓縮函數(shù)。 以到達(dá)循環(huán)壓縮圖片的功能。
app_img(num, rem) {let that = this;let index = rem.tempFiles[num].path.lastIndexOf('.'); //獲取圖片地址最后一個(gè)點(diǎn)的位置let img_type = rem.tempFiles[num].path.substring(index + 1, rem.tempFiles[num].path.length); //截取圖片類型如png jpglet img_yuanshi = rem.tempFiles[num].path.substring(0, index); //截取圖片原始路徑let d2 = new Date().getTime(); //時(shí)間戳//壓縮圖片plus.zip.compressImage({src: rem.tempFiles[num].path, //你要壓縮的圖片地址dst: img_yuanshi + d2 + '.' + img_type, //壓縮之后的圖片地址(注意壓縮之后的路徑最好和原生路徑的位置一樣,不然真機(jī)上報(bào)code-5)quality: 70 //[10-100]},function (e) {//壓縮之后路徑轉(zhuǎn)base64位的//通過URL參數(shù)獲取目錄對象或文件對象plus.io.resolveLocalFileSystemURL(e.target, function (entry) {// 可通過entry對象操作test.html文件entry.file(function (file) {//獲取文件數(shù)據(jù)對象var fileReader = new plus.io.FileReader(); // 文件系統(tǒng)中的讀取文件對象,用于獲取文件的內(nèi)容//alert("getFile:" + JSON.stringify(file));fileReader.readAsDataURL(file); //以URL編碼格式讀取文件數(shù)據(jù)內(nèi)容fileReader.onloadend = function (evt) {//讀取文件成功完成的回調(diào)函數(shù)that.base64Img = evt.target.result.split(',')[1]; //拿到‘data:image/jpeg;base64,‘后面的console.log('that.base64Img', that.base64Img);// rem.tempFiles[num].Base64_Path = evt.target.result.split(',')[1];};});});// that.base64Img = that.base64Img.concat(rem.tempFiles[num]);// 【注意】在此人臉認(rèn)證中,只會傳一張圖片,故不考慮多張圖片情況//利用遞歸循環(huán)來實(shí)現(xiàn)多張圖片壓縮// if (num == rem.tempFiles.length - 1) {// return;// } else {// that.app_img(num + 1, rem);// }},function (error) {console.log('Compress error!');console.log(JSON.stringify(error));uni.showToast({title: '編碼失敗' + error});});
},
詳細(xì)實(shí)現(xiàn)思路
其實(shí)對于uniapp實(shí)現(xiàn)人臉識別功能來講,大概要經(jīng)過這么幾個(gè)步驟
onImage()
:打開手機(jī)相冊上傳圖片,獲取blob流(本地臨時(shí)地址)#ifdef APP-PLUS
/#ifndef APP-PLUS
:判斷系統(tǒng)環(huán)境,是h5還是安卓環(huán)境,然后在進(jìn)行圖片壓縮和加密,具體實(shí)現(xiàn)代碼如下:
//#ifdef APP-PLUS
//圖片壓縮
that.app_img(0, res);
//#endif// #ifndef APP-PLUS
that.blobTobase64(res.tempFilePaths[0]);
// #endif
app_img()
/blobTobase64()
:對要識別的圖片進(jìn)行base64加密onSave()
—>upImage()
:附件上傳,并處理識別信息
具體代碼
<!-- 人臉認(rèn)證 -->
<template><view><view class="u-margin-30 text-center"><u-avatar size="600" :src="imageSrc"></u-avatar></view><view class="u-margin-60"><u-button type="primary" class="u-margin-top-60" @click="onImage">{{ !imageSrc ? '拍照' : '重拍' }}</u-button><!-- <u-button type="primary" class="u-margin-top-30">重拍</u-button> --><u-button type="primary" class="u-margin-top-50" @click="onSave">保存</u-button></view><u-toast ref="uToast" /></view>
</template><script>
import { registerOrUpdateFaceInfo, UpdateLaborPersonnel } from '@/api/mww/labor.js';
import { UploadByProject } from '@/api/sys/upload.js';
import { sysConfig } from '@/config/config.js';
import storage from 'store';
import { ACCESS_TOKEN } from '@/store/mutation-types';
export default {name: 'face-authentication',data() {return {imageSrc: '',lastData: {},base64Img: '',base64: ''};},onLoad(option) {this.lastData = JSON.parse(decodeURIComponent(option.lastData));console.log('前一個(gè)頁面數(shù)據(jù)', this.lastData);uni.setNavigationBarTitle({title: this.lastData.CnName + '-人臉認(rèn)證 '});},methods: {onSave() {if (!this.imageSrc) {this.$refs.uToast.show({title: '請先拍照',type: 'error'});}// 人臉上傳,附件上傳,勞務(wù)人員信息修改this.upImage();},// h5壓縮圖片的方式,url為圖片流blobTobase64(url) {console.log('進(jìn)來了2', url);let imgFile = url;let _this = this;uni.request({url: url,method: 'GET',responseType: 'arraybuffer',success: res => {let base64 = uni.arrayBufferToBase64(res.data); //把a(bǔ)rraybuffer轉(zhuǎn)成base64_this.base64Img = 'data:image/jpeg;base64,' + base64; //不加上這串字符,在頁面無法顯示}});},//app壓縮圖片 用for循環(huán) 來處理圖片壓縮 的問題,原因是 plus.zip.compressImage 方法 是異步執(zhí)行的,for循環(huán)很快, 同時(shí)手機(jī)可執(zhí)行的壓縮方法有限制:應(yīng)該是3個(gè)吧。超出直接就不執(zhí)行了。所以 原理就是 在圖片壓縮成功后 繼續(xù) 回調(diào) 壓縮函數(shù)。 以到達(dá)循環(huán)壓縮圖片的功能。app_img(num, rem) {let that = this;let index = rem.tempFiles[num].path.lastIndexOf('.'); //獲取圖片地址最后一個(gè)點(diǎn)的位置let img_type = rem.tempFiles[num].path.substring(index + 1, rem.tempFiles[num].path.length); //截取圖片類型如png jpglet img_yuanshi = rem.tempFiles[num].path.substring(0, index); //截取圖片原始路徑let d2 = new Date().getTime(); //時(shí)間戳//壓縮圖片plus.zip.compressImage({src: rem.tempFiles[num].path, //你要壓縮的圖片地址dst: img_yuanshi + d2 + '.' + img_type, //壓縮之后的圖片地址(注意壓縮之后的路徑最好和原生路徑的位置一樣,不然真機(jī)上報(bào)code-5)quality: 70 //[10-100]},function(e) {//壓縮之后路徑轉(zhuǎn)base64位的//通過URL參數(shù)獲取目錄對象或文件對象plus.io.resolveLocalFileSystemURL(e.target, function(entry) {// 可通過entry對象操作test.html文件entry.file(function(file) {//獲取文件數(shù)據(jù)對象var fileReader = new plus.io.FileReader(); // 文件系統(tǒng)中的讀取文件對象,用于獲取文件的內(nèi)容//alert("getFile:" + JSON.stringify(file));fileReader.readAsDataURL(file); //以URL編碼格式讀取文件數(shù)據(jù)內(nèi)容fileReader.onloadend = function(evt) {//讀取文件成功完成的回調(diào)函數(shù)that.base64Img = evt.target.result.split(',')[1]; //拿到‘data:image/jpeg;base64,‘后面的console.log('that.base64Img', that.base64Img);// rem.tempFiles[num].Base64_Path = evt.target.result.split(',')[1];};});});// that.base64Img = that.base64Img.concat(rem.tempFiles[num]);// 【注意】在此人臉認(rèn)證中,只會傳一張圖片,故不考慮多張圖片情況//利用遞歸循環(huán)來實(shí)現(xiàn)多張圖片壓縮// if (num == rem.tempFiles.length - 1) {// return;// } else {// that.app_img(num + 1, rem);// }},function(error) {console.log('Compress error!');console.log(JSON.stringify(error));uni.showToast({title: '編碼失敗' + error});});},// 打開手機(jī)相機(jī)相冊功能onImage() {const that = this;// 安卓系統(tǒng)無法默認(rèn)打開前置攝像頭,具體請看下面app-plus原因,uni.chooseImage({count: 1, //默認(rèn)9sizeType: ['original', 'compressed'], //可以指定是原圖還是壓縮圖,默認(rèn)二者都有sourceType: ['camera'], // 打開攝像頭-'camera',從相冊選擇-'album'success: function(res) {console.log('文件結(jié)果', res);if (res.tempFilePaths.length > 0) {// Blob流地址that.imageSrc = res.tempFilePaths[0];//#ifdef APP-PLUS//圖片壓縮that.app_img(0, res);//#endif// #ifndef APP-PLUSthat.blobTobase64(res.tempFilePaths[0]);// #endif} else {that.$refs.uToast.show({title: '無文件信息',type: 'error'});}},fail: function(res) {console.log('失敗了', res.errMsg);that.$refs.uToast.show({title: res.errMsg,type: 'error'});}});// #ifdef APP-PLUS// console.log('app環(huán)境了');// 指定要獲取攝像頭的索引值,1表示主攝像頭,2表示輔攝像頭。如果沒有設(shè)置則使用系統(tǒng)默認(rèn)主攝像頭。// 平臺支持【注意注意注意】// Android - 2.2+ (不支持) :// 暫不支持設(shè)置默認(rèn)使用的攝像頭,忽略此屬性值。打開拍攝界面后可操作切換。// iOS - 4.3+ (支持)// var cmr = plus.camera.getCamera(1);// var res = cmr.supportedImageResolutions[0];// var fmt = cmr.supportedImageFormats[0];// console.log('Resolution: ' + res + ', Format: ' + fmt);// cmr.captureImage(// function(path) {// alert('Capture image success: ' + path);// },// function(error) {// alert('Capture image failed: ' + error.message);// },// { resolution: res, format: fmt }// );// #endif},// 上傳附件至[人臉認(rèn)證]服務(wù)器upImage() {if (!this.base64Img) {this.$refs.uToast.show({title: '無圖片信息',type: 'error'});return;}const params = {identityId: this.lastData.IdCard, //身份證號碼imgInfo: this.base64Img, //頭像采用base64編碼userId: this.lastData.Id, //勞務(wù)人員IduserName: this.lastData.CnName //勞務(wù)姓名};uni.showLoading();registerOrUpdateFaceInfo(params).then(res => {if (res.success) {this.$refs.uToast.show({title: '認(rèn)證成功',type: 'success'});// 上傳至附件服務(wù)器+修改勞務(wù)人員信息this.uploadFile();} else {this.$refs.uToast.show({title: '認(rèn)證失敗,' + res.message,type: 'error'});uni.hideLoading();}}).catch(err => {uni.hideLoading();uni.showModal({title: '提示',content: err});});},// 上傳附件至附件服務(wù)器uploadFile() {const obj = {project: this.lastData.OrgCode || this.$store.getters.projectCode.value,module: 'mww.personnelCertification',segment: this.lastData.OrgCode,businessID: this.lastData.Id,storageType: 1};let str = `project=${obj.project}&module=${obj.module}&segment=${obj.segment}&businessID=${obj.businessID}&storageType=${obj.storageType}`;console.log('str', str);// const url = '';// console.log('url', url);// const formData = new FormData();// formData.append('file', this.imageSrc, '.png');// UploadByProject(str, formData).then(res => {// if (res.success) {// this.$refs.uToast.show({// title: '上傳成功',// type: 'success'// });// } else {// this.$refs.uToast.show({// title: res.message,// type: 'error'// });// }// });const token = uni.getStorageSync(ACCESS_TOKEN);const that = this;// 需要使用uniapp提供的api,因?yàn)閠hat.imageSrc的blob流為地址頭為localhost(本地臨時(shí)文件)uni.uploadFile({url: `${sysConfig().fileServer}/UploadFile/UploadByProject?${str}`,filePath: that.imageSrc,formData: {...obj},header: {// 必須傳token,不然會報(bào)[系統(tǒng)標(biāo)識不能為空]authorization: `Bearer ${token}`},name: 'file',success: res => {that.$refs.uToast.show({title: '上傳成功',type: 'success'});that.lastData.CertificationUrl = res.data[0].virtualPath;that.lastData.Certification = 1;that.updateLaborPersonnel();},fail: err => {console.log('上傳失敗了', err);that.$refs.uToast.show({title: '上傳失敗,' + err,type: 'error'});uni.hideLoading();}});},// 修改勞務(wù)人員信息updateLaborPersonnel() {UpdateLaborPersonnel(this.lastData).then(res => {if (res.success) {this.$refs.uToast.show({title: '修改成功',type: 'success'});// uni.showToast({// title: '成功了'// });setTimeout(() => {uni.navigateBack({delta: 1});}, 800);} else {this.$refs.uToast.show({title: '修改失敗,' + res.message,type: 'error'});}}).finally(() => {uni.hideLoading();});}}
};
</script><style scoped lang="less"></style>