uniapp 实现人脸认证
前言
对于前端来说,需要后端提供一个人脸识别接口,前端传入图片,接口识别并返回结果,如此看来,其实前端只需实现图片传入即可,但是其实不然,在传入图片时,需要进行以下几点操作:
- 判断图片格式,市场上比较常见的是
.jpg、.jpeg、.png - 计算文件大小,一般要求不超过5MB
- 对图片进行base64加密
其实前2点具体要看接口要求,但是第3点,是实现人脸识别必备步骤,下文重点讲述一下移动端实现人脸识别的base64加密方法
问题
项目主要使用的技术栈是uniapp,uniapp的优点是上手快,基于vue开发,但缺点也很明显,多环境兼容导致兼容性较差,真机调试和运行较慢。比如h5端可以轻松实现base64加密,但是安卓环境完全不行,因为本地上传图片时,会返回一个blob流,但是uniapp的blob流是以http://localhost…(安卓环境无法识别localhost) 开始,导致无法进行base64加密
解决办法
经过多方实现后,借用html5+ api的多个结合方法(plus.zip.compressImage、plus.io.resolveLocalFileSystemURL、plus.io.FileReader)实现加密,主要代码如下:
//app压缩图片 用for循环 来处理图片压缩 的问题,原因是 plus.zip.compressImage 方法 是异步执行的,for循环很快, 同时手机可执行的压缩方法有限制:应该是3个吧。超出直接就不执行了。所以 原理就是 在图片压缩成功后 继续 回调 压缩函数。 以到达循环压缩图片的功能。
app_img(num, rem) {let that = this;let index = rem.tempFiles[num].path.lastIndexOf('.'); //获取图片地址最后一个点的位置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(); //时间戳//压缩图片plus.zip.compressImage({src: rem.tempFiles[num].path, //你要压缩的图片地址dst: img_yuanshi + d2 + '.' + img_type, //压缩之后的图片地址(注意压缩之后的路径最好和原生路径的位置一样,不然真机上报code-5)quality: 70 //[10-100]},function (e) {//压缩之后路径转base64位的//通过URL参数获取目录对象或文件对象plus.io.resolveLocalFileSystemURL(e.target, function (entry) {// 可通过entry对象操作test.html文件entry.file(function (file) {//获取文件数据对象var fileReader = new plus.io.FileReader(); // 文件系统中的读取文件对象,用于获取文件的内容//alert("getFile:" + JSON.stringify(file));fileReader.readAsDataURL(file); //以URL编码格式读取文件数据内容fileReader.onloadend = function (evt) {//读取文件成功完成的回调函数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]);// 【注意】在此人脸认证中,只会传一张图片,故不考虑多张图片情况//利用递归循环来实现多张图片压缩// 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});});
},
详细实现思路
其实对于uniapp实现人脸识别功能来讲,大概要经过这么几个步骤
onImage():打开手机相册上传图片,获取blob流(本地临时地址)#ifdef APP-PLUS/#ifndef APP-PLUS:判断系统环境,是h5还是安卓环境,然后在进行图片压缩和加密,具体实现代码如下:
//#ifdef APP-PLUS
//图片压缩
that.app_img(0, res);
//#endif// #ifndef APP-PLUS
that.blobTobase64(res.tempFilePaths[0]);
// #endif
app_img()/blobTobase64():对要识别的图片进行base64加密onSave()—>upImage():附件上传,并处理识别信息
具体代码
<!-- 人脸认证 -->
<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('前一个页面数据', this.lastData);uni.setNavigationBarTitle({title: this.lastData.CnName + '-人脸认证 '});},methods: {onSave() {if (!this.imageSrc) {this.$refs.uToast.show({title: '请先拍照',type: 'error'});}// 人脸上传,附件上传,劳务人员信息修改this.upImage();},// h5压缩图片的方式,url为图片流blobTobase64(url) {console.log('进来了2', url);let imgFile = url;let _this = this;uni.request({url: url,method: 'GET',responseType: 'arraybuffer',success: res => {let base64 = uni.arrayBufferToBase64(res.data); //把arraybuffer转成base64_this.base64Img = 'data:image/jpeg;base64,' + base64; //不加上这串字符,在页面无法显示}});},//app压缩图片 用for循环 来处理图片压缩 的问题,原因是 plus.zip.compressImage 方法 是异步执行的,for循环很快, 同时手机可执行的压缩方法有限制:应该是3个吧。超出直接就不执行了。所以 原理就是 在图片压缩成功后 继续 回调 压缩函数。 以到达循环压缩图片的功能。app_img(num, rem) {let that = this;let index = rem.tempFiles[num].path.lastIndexOf('.'); //获取图片地址最后一个点的位置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(); //时间戳//压缩图片plus.zip.compressImage({src: rem.tempFiles[num].path, //你要压缩的图片地址dst: img_yuanshi + d2 + '.' + img_type, //压缩之后的图片地址(注意压缩之后的路径最好和原生路径的位置一样,不然真机上报code-5)quality: 70 //[10-100]},function(e) {//压缩之后路径转base64位的//通过URL参数获取目录对象或文件对象plus.io.resolveLocalFileSystemURL(e.target, function(entry) {// 可通过entry对象操作test.html文件entry.file(function(file) {//获取文件数据对象var fileReader = new plus.io.FileReader(); // 文件系统中的读取文件对象,用于获取文件的内容//alert("getFile:" + JSON.stringify(file));fileReader.readAsDataURL(file); //以URL编码格式读取文件数据内容fileReader.onloadend = function(evt) {//读取文件成功完成的回调函数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]);// 【注意】在此人脸认证中,只会传一张图片,故不考虑多张图片情况//利用递归循环来实现多张图片压缩// 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});});},// 打开手机相机相册功能onImage() {const that = this;// 安卓系统无法默认打开前置摄像头,具体请看下面app-plus原因,uni.chooseImage({count: 1, //默认9sizeType: ['original', 'compressed'], //可以指定是原图还是压缩图,默认二者都有sourceType: ['camera'], // 打开摄像头-'camera',从相册选择-'album'success: function(res) {console.log('文件结果', 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环境了');// 指定要获取摄像头的索引值,1表示主摄像头,2表示辅摄像头。如果没有设置则使用系统默认主摄像头。// 平台支持【注意注意注意】// Android - 2.2+ (不支持) :// 暂不支持设置默认使用的摄像头,忽略此属性值。打开拍摄界面后可操作切换。// 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},// 上传附件至[人脸认证]服务器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, //劳务人员IduserName: this.lastData.CnName //劳务姓名};uni.showLoading();registerOrUpdateFaceInfo(params).then(res => {if (res.success) {this.$refs.uToast.show({title: '认证成功',type: 'success'});// 上传至附件服务器+修改劳务人员信息this.uploadFile();} else {this.$refs.uToast.show({title: '认证失败,' + res.message,type: 'error'});uni.hideLoading();}}).catch(err => {uni.hideLoading();uni.showModal({title: '提示',content: err});});},// 上传附件至附件服务器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,因为that.imageSrc的blob流为地址头为localhost(本地临时文件)uni.uploadFile({url: `${sysConfig().fileServer}/UploadFile/UploadByProject?${str}`,filePath: that.imageSrc,formData: {...obj},header: {// 必须传token,不然会报[系统标识不能为空]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();}});},// 修改劳务人员信息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>
相关文章:
uniapp 实现人脸认证
前言 对于前端来说,需要后端提供一个人脸识别接口,前端传入图片,接口识别并返回结果,如此看来,其实前端只需实现图片传入即可,但是其实不然,在传入图片时,需要进行以下几点操作&…...
自学大数据第三天~终于轮到hadoop了
前面那几天是在找大数据的门,其实也是在搞一些linux的基本命令,现在终于轮到hadoop了 Hadoop hadoop的安装方式 单机模式: 就如字面意思,在一台机器上运行,存储是采用本地文件系统,没有采用分布式文件系统~就如我们一开始入门的时候都是从本地开始的; 伪分布式模式 存储采用…...
Unity 入门精要00---Unity提供的基础变量和宏以及一些基础知识
头文件引入: XXPROGRAM ... #include "UnityCG.cginc"; ... ENDXX 常用的结构体(在UnityCg.cginc文件中):在顶点着色器输入和输出时十分好用 。 关于如何使用这些结构体,可在Unity安装文件目录/Editor…...
Kubernetes的网络架构及其安全风险
本博客地址:https://security.blog.csdn.net/article/details/129137821 一、常见的Kubernetes网络架构 如图所示: 说明: 1、集群由多个节点组成。 2、每个节点上运行若干个Pod。 3、每个节点上会创建一个CNI网桥(默认设备名称…...
Blob分析+特征+(差分)
Blob分析特征0 前言1 概念2 方法2.1 图像采集2.2 图像分割2.3 特征提取3 主要应用场景:0 前言 在缺陷检测领域,halcon通常有6种处理方法,包括Blob分析特征、Blob分析特征差分、频域空间域、光度立体法、特征训练、测量拟合,本篇博…...
Flink 提交模式
Flink的部署方式有很多,支持Local,Standalone,Yarn,Docker,Kubernetes模式等。而根据Flink job的提交模式,又可以分为三种模式: 模式1:Application Mode Flink提交的程序,被当做集群内部Application,不再需要Client端做繁重的准备工作。(例如执行main函数,生成JobG…...
网络总结知识点(网络工程师必备)三
♥️作者:小刘在C站 ♥️个人主页:小刘主页 ♥️每天分享云计算网络运维课堂笔记,努力不一定有收获,但一定会有收获加油!一起努力,共赴美好人生! ♥️夕阳下,是最美的绽放,树高千尺,落叶归根人生不易,人间真情 目录 前言 51.什么是ARP代理?...
测开:前端基础-css
一、CSS介绍和引用 1.1 css概述 层叠样式表,是一种样式表语言,用来描述HTML和XML文档的呈现。 CSS 用于简化HTML标签,把关于样式部分的内容提取出来,进行单独的控制,使结构与样式分离开发。 CSS 是以HTML为基础&…...
Java学习记录之JDBC
JDBC JDBC 是 Java Database Connectivity 的缩写,是允许Java 程序访问并操作关系型数据库数据的一套 应用程序接口。本身就是一种规范,它提供的接口有一套完整的,可移植的访问底层数据库的程序。 JDBC 的架构 JDBC API支持两层和三层处理…...
矩阵翻硬币
题目描述 小明先把硬币摆成了一个 n 行 m 列的矩阵。 随后,小明对每一个硬币分别进行一次 Q 操作。 对第 x 行第 y 列的硬币进行 Q 操作的定义:将所有第 ix 行,第 jy 列的硬币进行翻转。...
【C语言跬步】——指针数组和数组指针(指针进阶)
一.指针数组和数组指针的区别 1.指针数组是数组,是一种存放指针的数组; 例如: int* arr[10]; 2.数组指针是指针,是一种指向数组的指针,存放的是数组的地址; 例如: int arr[5]; int (p)[5]&a…...
第十四届蓝桥杯模拟赛第三期(Python)
写在前面 包含本次模拟赛的10道题题解能过样例,应该可以AC若有错误,欢迎评论区指出本次题目除了最后两题有些难度,其余题目较为简单,我只将代码和结果给出,如果不能理解欢迎私信我,我会解答滴。start 2022…...
css-盒模型
巧妙运用margin负值盒模型和怪异盒模型(border padding 包含在内)display: block 能让textarea input 水平尺寸自适应父容器? – 不能 * {box-sizing: border-box; // bs: bb }<textarea/> 是替换元素,尺寸由内部元素决定,不受display水平影响. 当然可以直接设置宽度10…...
Linux | 调试器GDB的详细教程【纯命令行调试】
文章目录一、前言二、调试版本与发布版本1、见见gdb2、程序员与测试人员3、为什么Release不能调试但DeBug可以调试❓三、使用gdb调试代码1、指令集汇总2、命令演示⌨ 行号显示⌨ 断点设置⌨ 查看断点信息⌨ 删除断点⌨ 开启 / 禁用断点⌨ 运行 / 调试⌨ 逐过程和逐语句⌨ 打印 …...
wifi芯片大市场和个人小生活
3.3 是日也,天朗气清,惠风和畅。仰观宇宙之大,俯察论文论坛,所以游目骋怀,足以极视听之娱,信可乐也。 夫人之相与,俯仰一世,或取诸怀抱,悟言一室之内;或因寄所…...
全国计算机技术与软件专业技术资格(水平)考试 上半年2023年3月13日开始,下半年2023年8月14日开始
根据2023年计算机技术与软件专业技术资格(水平)考试工作计划,可以得知,2023年软考报名时间——上半年2023年3月13日开始,下半年2023年8月14日开始。 点击查看:人力资源社会保障部办公厅关于2023年度专业技术人员职业资格考试工作计划及有关事项的通知 点击查看:2023年度…...
大数据框架之Hadoop:MapReduce(六)Hadoop企业优化
一、MapReduce 跑的慢的原因 MapReduce程序效率的瓶颈在于两点: 1、计算机性能 CPU、内存、磁盘、网络 2、IO操作优化 数据倾斜Map和Reduce数设置不合理Map运行时间太长,导致Reduce等待过久小文件过多大量的不可分块的超大文件Spill次数过多Merge次…...
Spring File Storage的详细文档
快速入门配置pom.xml引入依赖<dependencies><!-- spring-file-storage 必须要引入 --><dependency><groupId>cn.xuyanwu</groupId><artifactId>spring-file-storage</artifactId><version>0.7.0</version></dependen…...
Java软件开发好学吗?学完好找工作吗?
互联网高速发展的当下,Java语言无处不在:手机APP、Java游戏、电脑应用,都有它的身影。作为最热门的开发语言之一,Java在编程圈的地位不可撼动。可是,听名字就很专业的样子。Java语言到底好学吗?刚入坑编程圈…...
【独家C】华为OD机试提供C语言题解 - 优秀学员统计
最近更新的博客 华为od 2023 | 什么是华为od,od 薪资待遇,od机试题清单华为OD机试真题大全,用 Python 解华为机试题 | 机试宝典【华为OD机试】全流程解析+经验分享,题型分享,防作弊指南)华为od机试,独家整理 已参加机试人员的实战技巧文章目录 最近更新的博客使用说明优秀…...
阿里云ACP云计算备考笔记 (5)——弹性伸缩
目录 第一章 概述 第二章 弹性伸缩简介 1、弹性伸缩 2、垂直伸缩 3、优势 4、应用场景 ① 无规律的业务量波动 ② 有规律的业务量波动 ③ 无明显业务量波动 ④ 混合型业务 ⑤ 消息通知 ⑥ 生命周期挂钩 ⑦ 自定义方式 ⑧ 滚的升级 5、使用限制 第三章 主要定义 …...
【CSS position 属性】static、relative、fixed、absolute 、sticky详细介绍,多层嵌套定位示例
文章目录 ★ position 的五种类型及基本用法 ★ 一、position 属性概述 二、position 的五种类型详解(初学者版) 1. static(默认值) 2. relative(相对定位) 3. absolute(绝对定位) 4. fixed(固定定位) 5. sticky(粘性定位) 三、定位元素的层级关系(z-i…...
AI编程--插件对比分析:CodeRider、GitHub Copilot及其他
AI编程插件对比分析:CodeRider、GitHub Copilot及其他 随着人工智能技术的快速发展,AI编程插件已成为提升开发者生产力的重要工具。CodeRider和GitHub Copilot作为市场上的领先者,分别以其独特的特性和生态系统吸引了大量开发者。本文将从功…...
图表类系列各种样式PPT模版分享
图标图表系列PPT模版,柱状图PPT模版,线状图PPT模版,折线图PPT模版,饼状图PPT模版,雷达图PPT模版,树状图PPT模版 图表类系列各种样式PPT模版分享:图表系列PPT模板https://pan.quark.cn/s/20d40aa…...
【碎碎念】宝可梦 Mesh GO : 基于MESH网络的口袋妖怪 宝可梦GO游戏自组网系统
目录 游戏说明《宝可梦 Mesh GO》 —— 局域宝可梦探索Pokmon GO 类游戏核心理念应用场景Mesh 特性 宝可梦玩法融合设计游戏构想要素1. 地图探索(基于物理空间 广播范围)2. 野生宝可梦生成与广播3. 对战系统4. 道具与通信5. 延伸玩法 安全性设计 技术选…...
Spring Cloud Gateway 中自定义验证码接口返回 404 的排查与解决
Spring Cloud Gateway 中自定义验证码接口返回 404 的排查与解决 问题背景 在一个基于 Spring Cloud Gateway WebFlux 构建的微服务项目中,新增了一个本地验证码接口 /code,使用函数式路由(RouterFunction)和 Hutool 的 Circle…...
Reasoning over Uncertain Text by Generative Large Language Models
https://ojs.aaai.org/index.php/AAAI/article/view/34674/36829https://ojs.aaai.org/index.php/AAAI/article/view/34674/36829 1. 概述 文本中的不确定性在许多语境中传达,从日常对话到特定领域的文档(例如医学文档)(Heritage 2013;Landmark、Gulbrandsen 和 Svenevei…...
Linux离线(zip方式)安装docker
目录 基础信息操作系统信息docker信息 安装实例安装步骤示例 遇到的问题问题1:修改默认工作路径启动失败问题2 找不到对应组 基础信息 操作系统信息 OS版本:CentOS 7 64位 内核版本:3.10.0 相关命令: uname -rcat /etc/os-rele…...
LRU 缓存机制详解与实现(Java版) + 力扣解决
📌 LRU 缓存机制详解与实现(Java版) 一、📖 问题背景 在日常开发中,我们经常会使用 缓存(Cache) 来提升性能。但由于内存有限,缓存不可能无限增长,于是需要策略决定&am…...
TSN交换机正在重构工业网络,PROFINET和EtherCAT会被取代吗?
在工业自动化持续演进的今天,通信网络的角色正变得愈发关键。 2025年6月6日,为期三天的华南国际工业博览会在深圳国际会展中心(宝安)圆满落幕。作为国内工业通信领域的技术型企业,光路科技(Fiberroad&…...
