赞
踩
针对互联网上已有的人脸识别小程序项目,很多只是基于手动拍照,然后上传到SDK进行识别。这一过程完全脱离实际场景!无法直接使用!
本文项目是基于微信摄像头中的实时视频帧数据
,通过实时动态识别小程序端所获取的照片帧,从而自动触发人脸识别和登录!完全贴合实际,真正的拿来即用!
人脸识别是一个老生常谈的问题了,再继小程序的逐步普及化后,人脸识别的功能也变为了各大业务的家常菜。目前市面上已有的解决方案,一方面除了自研以外,对于本来就是小成本进行创业或者开发的个人以及初创型公司来说,接入一些专业的第三方的人脸识别接口不妨是一个优秀的解决方案。目前市面上常用的第三方支持技术有哪些,可以阅读本专栏的文章: 微信小程序 | 人脸识别的最终解决方案 – 里面做了详细的介绍和解读,相信能带你弄请这个方向的技术行情。
为此,这一次我们自己动手来实现一次全流程的人脸识别小程序
。 没有基础不用担心,本文就是带领大家从零到一开始搭建,细致到每个网页功能如何选择、代码如何编写,前后端的逻辑如何编写。只会前端或者后端的小伙伴也不用担心,本文提供全流程的操作指引,让零基础的前/后端小白都能轻松上手。 废话不多说相信看到上面酷炫的效果大家应该都按捺不住了,那就让我们开整把。
前端 | 后端 |
---|---|
- 语言:vue 2.0 | - 框架 : uni-app | - UI组件: uview | - 语言:python | - 框架: Flask |
要使用百度智能云的api接口,就需要在控制台创建你的项目应用,通过项目应用去获取API Key``Secret Key ``Access_token
,这三个参数是后续调用api接口的必填参数,缺一不可!
人脸比对
),其实为了方便我们可以一次性将所有的人脸识别相关的产品全部都进行一次购买,反正都是按量计费,不进行调用也就不用收钱了。-在开通了服务的基础上,由于要获取三大认证参数,再调用接口之前我们仍需要进行应用的创建。
本项目是继续uniapp
框架进行开发(采用uniapp框架开发的好处在于:在面对众多的小程序平台,我们只需要编写这一套代码就可以实现多平台的运行,极大程度的降低了开发成本!可以说是只要会一套小程序开发的流程,基本每个平台你都能拿下。
)
接口能力
业务应用
用于比对多张图片中的人脸相似度并返回两两比对的得分,可用于判断两张脸是否是同一人的可能性大小。
典型应用场景:如人证合一验证,用户认证等,可与您现有的人脸库进行比对验证。
result = client.match([
{
'image': base64.b64encode(open('1.jpg', 'rb').read()).decode(),
'image_type': 'BASE64',
},
{
'image': base64.b64encode(open('2.jpg', 'rb').read()).decode(),
'image_type': 'BASE64',
}
])
请求参数
参数 | 必选 | 类型 | 说明 |
---|---|---|---|
image | 是 | string | 图片信息(总数据大小应小于10M),图片上传方式根据image_type来判断。 两张图片通过json格式上传,格式参考表格下方示例 |
image_type | 是 | string | 图片类型 BASE64:图片的base64值,base64编码后的图片数据,编码后的图片大小不超过2M;URL:图片的 URL地址( 可能由于网络等原因导致下载图片时间过长);FACE_TOKEN: 人脸图片的唯一标识,调用人脸检测接口时,会为每个人脸图片赋予一个唯一的FACE_TOKEN,同一张图片多次检测得到的FACE_TOKEN是同一个。 |
face_type | 否 | string | 人脸的类型LIVE表示生活照:通常为手机、相机拍摄的人像图片、或从网络获取的人像图片等,IDCARD表示身份证芯片照:二代身份证内置芯片中的人像照片, WATERMARK表示带水印证件照:一般为带水印的小图,如公安网小图 CERT表示证件照片:如拍摄的身份证、工卡、护照、学生证等证件图片 默认LIVE |
quality_control | 否 | string | 图片质量控制 NONE: 不进行控制 LOW:较低的质量要求 NORMAL: 一般的质量要求 HIGH: 较高的质量要求 默认 NONE |
liveness_control | 否 | string | 活体检测控制 NONE: 不进行控制 LOW:较低的活体要求(高通过率 低攻击拒绝率) NORMAL: 一般的活体要求(平衡的攻击拒绝率, 通过率) HIGH: 较高的活体要求(高攻击拒绝率 低通过率) 默认NONE |
返回参数
参数名 | 必选 | 类型 | 说明 |
---|---|---|---|
score | 是 | float | 人脸相似度得分 |
face_list | 是 | array | 人脸信息列表 |
+face_token | 是 | string | 人脸的唯一标志 |
返回示例
{
"score": 44.3,
"face_list": [ //返回的顺序与传入的顺序保持一致
{
"face_token": "fid1"
},
{
"face_token": "fid2"
}
]
}
为了实现保存用户的人脸信息,用于验证人脸请求。一般都是有两种方案:
client.match()
方法对用户的登录信息进行比对。这样的做法可以实现高度自定义化,但是维护成本相对较高,主要包括数据的存储和可视化的展示
Group
分组的形式进行用户数据管理,这就使得对于用户的所归宿的不同组的权限进行区分。同样地,借助组的字段概念可以与业务系统打通,进而更为方便的管理用户。微信摄像头实时帧的获取与数据处理
要实现小程序端自动采集人脸数据并进行接口验证,就必须使用微信摄像头图像帧数据的采集与监听功能。
1、调用
onCameraFrame
接口,生成帧数据监听器,然后使用listener.start()
启动监听服务。
2、在listen接口
中设置一个setInterVal()
时间循环任务,从而实现一个循环获取帧数据并循环上报并处理接口返回数据的任务,保证人脸登录的实时性!
3、特别地,对于获取到的帧数据,仍需要进行处理:将其转为Base64格式数据从而进行数据传输。
startFaceCapture(){ console.log('=====show==facelogin=====') var that = this this.isAuthCamera = true var i = 1; const context = wx.createCameraContext() console.log('=====load===') const listener = context.onCameraFrame((frame) => { // console.log('==获取帧动画===',that.flag) if (frame && that.flag) { i++; that.frameQueue.push(frame) // console.log('==push任务',that.frameQueue.length) that.flag = false // console.log(i++, frame.data, frame.width, frame.height) } }) listener.start({ success: function(res) { console.log("开始监听"); let task = setInterval(function() { var timeStart = Date.now(); //在此处处理store[0](图像的数据); // store.shift(); var frame = that.frameQueue.shift() console.log("开始运行===",frame,that.flag); that.flag = true; if(frame != undefined){ let pngData = UPNG.encode([frame.data], frame.width, frame.height), base64 = Base64Util.arrayBufferToBase64(pngData) uni.request({ url: 'http://127.0.0.1:8099/faceAuth' , method: 'post', data: {openId:uni.getStorageSync("openId"),base64Img:base64} , dataType:'json', header: { 'content-type':'application/json'//自定义请求头信息 }, success:(res)=>{ console.log('===result===',res) clearInterval(task) this.$refs.uToast.show({ ...failTips, complete() { } }) }, fail:(err)=>{ console.log("====执行失败===",err) clearInterval(task) that.isAuthCamera = false uni.navigateTo({ url:'./login' }) } }) } // if(i==2){ // clearInterval(task) // that.isAuthCamera = false // } }, 2000); } }) },
<template> <view> <view class="content cover" :animation="animationBack"> <!-- <view class="bg"></view> <view class="bg2"></view> <view class="tips"> <text class="title">登录</text> <text class="subtitle">欢迎再次回来~</text> </view> <view class="form-box"> <view class="btn"style="margin-top:10px;" @click='rotateFn(2)'>密码登录</view> <view class="other"> <text>找回密码</text> <text style="color:#00c6fc;">录入人脸</text> </view> </view> --> <capture ref="faceLogin" @changeLoginMode="rotateFn(2)"></capture> </view> <view class="content cover" :animation="animationMain"> <view class="bg"></view> <view class="bg2"></view> <view class="tips"> <text class="title">登录</text> <text class="subtitle">欢迎再次回来~</text> </view> <view class="form-box"> <view class="input-box"> <image class="left" src="https://6e69-niew6-1302638010.tcb.qcloud.la/%E7%99%BB%E5%BD%95%E6%A0%B7%E5%BC%8F/%E7%99%BB%E5%BD%959/%E8%B4%A6%E5%8F%B7.png"> </image> <input placeholder="请输入账号" /> <image class="right" src="https://6e69-niew6-1302638010.tcb.qcloud.la/%E7%99%BB%E5%BD%95%E6%A0%B7%E5%BC%8F/%E7%99%BB%E5%BD%959/%E4%B8%8B%E6%8B%89.png"> </image> </view> <view class="input-box"> <image class="left" src="https://6e69-niew6-1302638010.tcb.qcloud.la/%E7%99%BB%E5%BD%95%E6%A0%B7%E5%BC%8F/%E7%99%BB%E5%BD%959/%E5%AF%86%E7%A0%81.png"> </image> <input placeholder="请输入密码" /> <image class="right" src="https://6e69-niew6-1302638010.tcb.qcloud.la/%E7%99%BB%E5%BD%95%E6%A0%B7%E5%BC%8F/%E7%99%BB%E5%BD%959/%E6%98%BE%E7%A4%BA%E5%AF%86%E7%A0%81.png"> </image> </view> <view class="btn">登录</view> <view class="btn" style="margin-top:10px;" @click='login()'>刷脸登录</view> <view class="other"> <text>找回密码</text> <text style="color:#00c6fc;">快速注册</text> </view> </view> </view> </view> </template> <script> import capture from './capture.vue' export default { components: { capture }, data() { return { animationMain: null, //正 animationBack: null, //反 } }, methods: { rotateFn(e) { this.animation_main = uni.createAnimation({ duration: 400, timingFunction: 'linear' }) this.animation_back = uni.createAnimation({ duration: 400, timingFunction: 'linear' }) // 点击正面 if (e == 1) { this.animation_main.rotateY(180).step() this.animation_back.rotateY(0).step() this.animationMain = this.animation_main.export() this.animationBack = this.animation_back.export() this.$refs.faceLogin.startFaceCapture() } else { this.animation_main.rotateY(0).step() this.animation_back.rotateY(-180).step() this.animationMain = this.animation_main.export() this.animationBack = this.animation_back.export() } }, login() { var that = this; if (uni.getStorageSync("openId") == undefined) { wx.login({ success(res) { console.log('===触发登录====', res) if (res.code) { uni.request({ url: 'http://127.0.0.1:8000/miniapp/faceEngine/login/' + res.code, method: 'get', dataType: 'json', success: (res) => { console.log("====执行成功===", res) that.rotateFn(1) uni.setStorageSync("authed", true) uni.setStorageSync("openId", res.data.openid) }, fail: (err) => { console.log("====执行失败===", err) } }) } else { console.log('登录失败!') } } }) } else { that.rotateFn(1) } }, } } </script> <style lang="scss"> page { margin: 0; padding: 0; min-height: 100vh; position: relative; background-color: rgb(118, 218, 255); overflow: hidden; } page::before, page::after { content: ''; position: absolute; top: 90vh; min-width: 300vw; min-height: 300vw; background-color: #76daff; animation: rote 10s linear infinite; } page::before { left: -95%; border-radius: 45%; opacity: .5; } page::after { left: -95%; border-radius: 46%; } @keyframes rote { from { transform: rotateZ(0); } to { transform: rotateZ(360deg); } } .cover { position: absolute; top: 0; left: 0; } .content { width: 100vw; height: 100vh; background-color: #ffffff; transition: all 1s; backface-visibility: hidden; .tips { padding-top: 200rpx; padding-left: 80rpx; display: flex; flex-direction: column; .title { line-height: 70rpx; font-weight: bold; font-size: 50rpx; } .subtitle { line-height: 70rpx; font-size: 35rpx; font-weight: bold; color: #b0b0b1; } } .bg { position: fixed; top: -250rpx; right: -250rpx; width: 600rpx; height: 600rpx; border-radius: 100%; background-color: #00baef; z-index: 2 } .bg2 { position: fixed; top: -150rpx; right: -300rpx; width: 600rpx; height: 600rpx; border-radius: 100%; background-color: #ade8f9; z-index: 1; } .form-box { padding-top: 180rpx; padding-left: 70rpx; width: 610rpx; .input-box { margin: 40rpx 0; display: flex; justify-content: flex-start; align-items: center; height: 100rpx; background-color: #f5f5f5; border-radius: 100rpx; width: 100%; input { flex: 1; height: 100%; font-size: 30rpx; } .left { padding: 0 30rpx; width: 35rpx; height: 35rpx; } .right { padding: 0 30rpx; width: 40rpx; height: 40rpx; } } .btn { display: flex; justify-content: center; align-items: center; width: 100%; height: 100rpx; border-radius: 100rpx; color: #FFFFFF; background: linear-gradient(to right, #00c6fc, #9adcf1); } .other { display: flex; justify-content: space-between; text { line-height: 80rpx; font-size: 28rpx; } } } } </style>
<template> <view class="page-content"> <view class="containerV"> <!-- 标题 --> <view class="headerV"> <view class="top-tips1"> <view>{{tipsText}}</view> </view> </view> <u-toast ref="uToast"></u-toast> <!-- 拍摄区域 --> <view class="contentV"> <view class="mark"></view> <image class="image" v-if="tempImg" mode="widthFix" :src="tempImg" /> <camera v-if='isAuthCamera' device-position="front" class="camera" flash="off" resolution='high' /> </view> <!-- 操作区域 --> <view class="footerV"> <view style="width: 100%;"> <view v-if="!tempImg" style="width: 100%;"> <view class="take-photo-bgV"> <!-- 图片上传 --> <view v-show="true" class="btn-change-upload" @click="handleChooseImage" /> <!--拍照--> <view class="btn-take-photo" @click="handleTakePhotoClick" /> <!-- 切换镜头 --> <view class="btn-change-camera" @click="handleChangeCameraClick" /> </view> </view> <view class="confirmV" v-else> <view class="btn-cancel" @click="handleCancelClick"> 取消 </view> <view class="btn-ok" @click="handleOkClick"> 确定 </view> </view> </view> </view> </view> </view> </template> <script> // import crudFace from '@/api/face.js' import Base64Util from '@/util/Base64Util.js' import UPNG from '@/util/UPNG.js' export default { components: {}, data() { return { frameQueue: [], flag: false, tipsText: '请将脸部移入框内', // 错误文案提示 tempImg: '', // 本地图片路径 cameraEngine: null, // 相机引擎 devicePosition: true, // 摄像头朝向 isAuthCamera: false, // 是否拥有相机权限 cameraListen: '', failTips: { type: 'error', message: "请勿遮挡人脸!", iconUrl: 'https://cdn.uviewui.com/uview/demo/toast/error.png' }, successTips: { type: 'success', message: "识别成功!", iconUrl: 'https://cdn.uviewui.com/uview/demo/toast/success.png' } }; }, created() { }, onLoad() { // this.init(); const context = wx.createCameraContext() console.log('=====load===') const listener = context.onCameraFrame((frame) => { console.log(frame.data instanceof ArrayBuffer, frame.width, frame.height) }) this.cameraListen = listener // listener.start() }, onShow() { console.log('=====show===', ) }, methods: { startFaceCapture(){ console.log('=====show==facelogin=====') var that = this this.isAuthCamera = true var i = 1; const context = wx.createCameraContext() console.log('=====load===') const listener = context.onCameraFrame((frame) => { // console.log('==获取帧动画===',that.flag) if (frame && that.flag) { i++; that.frameQueue.push(frame) // console.log('==push任务',that.frameQueue.length) that.flag = false // console.log(i++, frame.data, frame.width, frame.height) } }) listener.start({ success: function(res) { console.log("开始监听"); let task = setInterval(function() { var timeStart = Date.now(); //在此处处理store[0](图像的数据); // store.shift(); var frame = that.frameQueue.shift() console.log("开始运行===",frame,that.flag); that.flag = true; if(frame != undefined){ let pngData = UPNG.encode([frame.data], frame.width, frame.height), base64 = Base64Util.arrayBufferToBase64(pngData) // that.$refs.uToast.show({ // ...that.successTips, // complete() { // } // }) // clearInterval(task) // setTimeout(function(){ // uni.navigateTo({ // url:'./home' // }) // },1000) uni.request({ url: 'http://127.0.0.1:8099/faceAuth' , method: 'post', data: {openId:uni.getStorageSync("openId"),base64Img:base64} , dataType:'json', header: { 'content-type':'application/json'//自定义请求头信息 }, success:(res)=>{ console.log('===result===',res) clearInterval(task) this.$refs.uToast.show({ ...failTips, complete() { } }) }, fail:(err)=>{ console.log("====执行失败===",err) clearInterval(task) that.isAuthCamera = false uni.navigateTo({ url:'./login' }) } }) } // if(i==2){ // clearInterval(task) // that.isAuthCamera = false // } }, 2000); } }) }, handleChangeCameraClick() { // 切换设备镜头 this.devicePosition = !this.devicePosition; this.$emit('changeLoginMode') }, handleChooseImage() { // 图片上传 uni.chooseImage({ count: 1, sizeType: ['original', 'compressed'], sourceType: ['album'], success: (res) => { if (res.errMsg === 'chooseImage:ok') { this.handleOkClick(res.tempFilePaths[0]) } }, fail: (res) => { }, }); }, handleTakePhotoClick() { // 拍照点击 listener.stop() console.log('===to TaskPhoto====') if (this.tipsText != "" && this.tipsText != "请拍照") { return; } uni.getSetting({ success: (res) => { if (!res.authSetting['scope.camera']) { this.isAuthCamera = false uni.openSetting({ success: (res) => { if (res.authSetting['scope.camera']) { this.isAuthCamera = true; } } }) } } }) this.cameraEngine.takePhoto({ quality: "high", success: ({ tempImagePath }) => { this.handleOkClick(tempImagePath) } }) }, async handleOkClick(filePath) { // 点击确定上传 uni.showLoading({ title: '上传中...', mask: true }) let img = await this.$util.uploadopt.image(filePath, 2) uni.$emit('onface', img); uni.navigateBack(); }, handleCancelClick() { // 点击 - 取消上传 this.tempImg = '' }, } } </script> <style lang="scss"> page { height: 100%; width: 100%; } .page-content { width: 100%; height: 100%; .containerV { width: 100%; height: 100%; display: flex; flex-direction: column; /* 标题 */ .headerV { padding: 163rpx 0 56rpx; text-align: center; .top-tips1 { color: #333333; font-size: 42rpx; } } /* 拍摄区域 */ .contentV { position: relative; display: flex; flex-direction: column; align-items: center; justify-content: center; width: 564rpx; height: 564rpx; border-radius: 50%; margin: 0 auto; border: 10rpx solid #1568E0; background-color: #d8d8d8; .camera { width: 100%; height: 100%; border-radius: 50%; } .mark { position: absolute; left: 0; top: 0; z-index: 1; width: 100%; height: 100%; border-radius: 50%; } .image { position: absolute; width: 100%; height: 100%; z-index: 3; border-radius: 50%; } } /* 操作按钮 */ .footerV { width: 100%; flex-grow: 1; display: flex; flex-direction: row; align-items: center; justify-content: center; .privacyV { padding-top: 30rpx; display: flex; flex-direction: row; align-items: center; justify-content: center; .text { font-size: 30rpx; color: #1C1C1C; text-align: center; line-height: 42rpx; margin-left: 15rpx; text { font-size: 30rpx; color: #00AAFF; text-align: center; line-height: 42rpx; } } } .bottom-tips-2 { margin-top: 20rpx; color: #999999; text-align: center; font-size: 26rpx; } .take-photo-bgV { width: 100%; margin-top: 30rpx; display: flex; flex-direction: row; align-items: center; justify-content: center; // 由于左边没有按钮,所以左边要便宜更大,以便是拍照按钮居中 .btn-take-photo { margin: 0rpx 80rpx 0rpx 80rpx; width: 196rpx; height: 196rpx; background: url("https://www.hujinqiang.com/anjiantong/face/photo.png") no-repeat; background-size: 100% auto; } .btn-change-upload { left: 130rpx; width: 80rpx; height: 80rpx; background: url("https://www.hujinqiang.com/anjiantong/face/album.png") no-repeat; background-size: 100% auto; } .btn-change-camera { right: 130rpx; width: 80rpx; height: 80rpx; background: url("https://www.hujinqiang.com/anjiantong/face/changing.png") no-repeat; background-size: 100% auto; } } .confirmV { margin: 200rpx 100rpx 0rpx 100rpx; display: flex; flex-direction: row; align-items: center; justify-content: space-between; .btn-cancel { font-size: 32rpx; color: #1C1C1C; } .btn-ok { font-size: 32rpx; color: #00AAFF; } } } } } </style>
<template> <view> <!-- #ifndef H5 --> <view class="ns">我的账户</view> <!-- #endif --> <view class="flexView"> <view class="scrollView"> <view class="head-read"> <view class="flex"> <image class="read-img" :src="userData.headimgurl" mode="aspectFill" /> <view class="flex-box"> <view class="flex-box-text">姓名:{{ userData.name }}({{ userData.id }})</view> <view class="flex-box-text">手机号:{{ userData.phone }}</view> </view> <view class="arrow arrow-one" @click="showModal = true"><span>名词解释</span></view> </view> </view> <view class="white-box"> <view class="tx"> <view class="tx-grid"> <view class="tx-grid-text"> <view class="title">可提现余额</view> <view class="money"> <text>{{ userData.withdrawable }}</text> <text class="money-b">元</text> </view> </view> </view> <view class="tx-grid" @click="navTo('/pages/withdraw/index')"><span class="tx-grid-comm-sign">立即提现</span></view> </view> <view class="palace palace-one"> <view class="palace-grid"> <view class="palace-grid-text"> <view class="palace-grid-text-data"> <text>{{ userData.coming }}</text> <text class="palace-grid-text-data-b">元</text> </view> <view class="palace-grid-text-name">即将到账</view> </view> </view> <view class="palace-grid"> <view class="palace-grid-text"> <view class="palace-grid-text-data"> <text>{{ userData.came }}</text> <text class="palace-grid-text-data-b">元</text> </view> <view class="palace-grid-text-name">累计到账</view> </view> </view> <view class="palace-grid"> <view class="palace-grid-text"> <view class="palace-grid-text-data"> <text>{{ userData.withdrawed }}</text> <text class="palace-grid-text-data-b">元</text> </view> <view class="palace-grid-text-name">累计提现</view> </view> </view> </view> </view> <view class="top"> <tui-tabs :tabs="tabs" :height="88" :currentTab="currentTab" :sliderWidth="150" :sliderHeight="60" bottom="50%" color="#888" selectedColor="#fff" sliderBgColor="#ff8a4a" @change="changeTab" ></tui-tabs> </view> <view class="list-view"> <view class="list-item" v-for="(item, index) in list" :key="index" hover-class="hover" :hover-stay-time="150" bindtap="detail"> <view class="content-box"> <view class="des-box"> <view class="tit">{{ currentTab == 3 ? '流水号:' + item.extract_no : '订单号:' + item.order_no }}</view> <view v-if="currentTab == 3" class="source" :style="{ color: item.status == 1 ? '#4caf50' : item.status == 2 ? '#ff1e0f' : '#00b7ff' }"> 提现{{ item.status == 1 ? '成功' : item.status == 2 ? '失败' : '处理中' }} </view> <view class="time">{{ item.create_time }}</view> </view> </view> <view class="money" :class="{ less: is_withdraw }">{{ is_withdraw ? '-' : '+' }}{{ currentTab == 3 ? item.real_money : item.money }}</view> </view> </view> <view class="tip">仅显示近半年内的收支记录</view> </view> <view class="cu-modal" :class="showModal ? 'show' : ''"> <view class="cu-dialog"> <view class="cu-bar bg-white justify-end"> <view class="content">名词解释</view> <view class="action" @tap="showModal = false"><text class="cuIcon-close text-red"></text></view> </view> <view class="padding-xl" style="text-align: left;"> <view> <text class="text-red">可提现余额:</text> <text>当前您可以提现的金额</text> </view> <view class="margin-top-sm"> <text class="text-red">即将到账:</text> <text>交易中的金额,交易成功后可提现</text> </view> <view class="margin-top-sm"> <text class="text-red">累计到账:</text> <text>累计交易成功的金额</text> </view> <view class="margin-top-sm"> <text class="text-red">累计提现:</text> <text>累计提现成功的金额</text> </view> <view class="margin-top-sm"><text class="text-red">*注:所有金额币种均为人民币,单位为元,符号¥</text></view> </view> <view class="cu-bar bg-white justify-end"> <view class="action"><button class="cu-btn bg-green margin-left" @tap="showModal = false">我已知晓</button></view> </view> </view> </view> </view> </view> </template> <script> import tuiTabs from '@/components/tui-tabs/tui-tabs'; export default { components: { tuiTabs }, data() { return { is_withdraw: false, list: [], userData: {name:'陶人',id:12,phone:18648759961,withdrawable:98989,coming:288,came:770,withdrawed:8888289}, showModal: false, date: 'incomeMonth', currentTab: 0, tabs: [ { name: '本月收入' }, { name: '今日收入' }, { name: '昨日收入' }, { name: '提现记录' } ] }; }, onLoad() { this.$api.loading(true); this.loadData(); setTimeout(() => { this.$api.loading(false); }, 500); }, methods: { async loadData() { this.userData = await this.$api.json('userData'); this.list = await this.$api.json('incomeMonth'); }, async getFundList() { if (this.currentTab == 3) { this.list = await this.$api.json('extractList'); } else { this.list = await this.$api.json(this.date); } }, changeTab(e) { this.currentTab = e.index; this.list = []; if (this.currentTab == 3) { this.is_withdraw = true; } else if (this.currentTab == 0) { this.date = 'incomeMonth'; this.is_withdraw = false; } else if (this.currentTab == 1) { this.date = 'incomeToday'; this.is_withdraw = false; } else if (this.currentTab == 2) { this.date = 'incomeYesterday'; this.is_withdraw = false; } this.$api.loading(true); this.getFundList(); setTimeout(() => { this.$api.loading(false); }, 500); }, navTo(url) { uni.navigateTo({ url }); } }, onPullDownRefresh() { this.loadData(); setTimeout(function() { uni.stopPullDownRefresh(); }, 500); } }; </script> <style lang="scss" scoped> page { background-color: #fff; } .ns { width: 100%; height: 60px; text-align: center; line-height: 200rpx; color: white; font-size: 34rpx; font-weight: bold; // background: linear-gradient(to right, #ff8440, #ff1e0f); background: linear-gradient(to right, #7CF7FF , #4B73FF ); } .top { margin-top: 20rpx; } .flexView { width: 100%; height: 100%; margin: 0 auto; display: flex; flex-direction: column; .scrollView { width: 100%; height: 100%; flex: 1; overflow-y: auto; overflow-x: hidden; position: relative; padding-bottom: 116rpx; .head-read { // background: linear-gradient(to right, #ff8440, #ff1e0f); background: linear-gradient(to right, #7CF7FF , #4B73FF ); background-color: #4B73FF; padding-bottom: 100rpx; .flex { display: flex; align-items: center; padding: 50rpx; position: relative; .read-img { width: 120rpx; height: 120rpx; border-radius: 100%; overflow: hidden; margin-right: 20rpx; border: 4rpx solid rgba(255, 255, 255, 0.3); } .flex-box { flex: 1; min-width: 0; font-size: 26rpx; color: #333; &-text { margin: 10rpx 0; color: #f5f5f5; font-weight: normal; } } .arrow { position: relative; padding-right: 30rpx; span { font-size: 28rpx; color: white; } &:after { content: ' '; display: inline-block; height: 12rpx; width: 12rpx; border-width: 4rpx 4rpx 0 0; border-color: #848484; border-style: solid; transform: matrix(0.71, 0.71, -0.71, 0.71, 0, 0); position: relative; top: -4rpx; position: absolute; top: 50%; margin-top: -8rpx; right: 4rpx; border-radius: 2rpx; } } .arrow-one:after { border-color: white; margin-top: -6rpx; } } } .white-box { width: 94%; background: white; border-radius: 10rpx; margin: -120rpx auto 20rpx; box-shadow: 0 6rpx 20rpx #e7e7e7; .tx { padding-top: 26rpx; overflow: hidden; display: flex; justify-content: space-between; align-content: center; &-grid { box-sizing: border-box; &:first-child { margin-left: 40rpx; } &-comm-sign { display: block; border-radius: 40rpx 0 0 40rpx; font-size: 26rpx; padding: 16rpx 44rpx; background: linear-gradient(to right, #fef082, #ffc551); background-color: #fef082; color: #4B73FF; } &-text { display: block; color: #333; font-size: 26rpx; white-space: nowrap; text-overflow: ellipsis; overflow: hidden; .title { font-size: 26rpx; font-weight: normal; color: #ff6423; } .money { font-size: 60rpx; color: #ff6423; letter-spacing: 2rpx; margin-bottom: 10rpx; &-b { font-size: 28rpx; } } } } } .palace { padding-bottom: 20rpx; overflow: hidden; display: flex; justify-content: center; &-grid { flex: 1; position: relative; box-sizing: border-box; &:not(:last-child) { &:after { width: 1rpx; height: 80rpx; background: #fddece; content: ' '; display: inline-block; position: absolute; top: 0; right: 0; } } &-text { display: block; text-align: center; color: #333; font-size: 32rpx; white-space: nowrap; text-overflow: ellipsis; overflow: hidden; &-name { font-size: 26rpx; font-weight: normal; color: #ff8a4a; } &-data { font-size: 32rpx; color: #ff8a4a; letter-spacing: 2rpx; margin-bottom: 5rpx; &-b { font-size: 20rpx; } } } } } } } } .list-view { position: relative; width: 100%; overflow: hidden; } .list-item { width: 100%; padding: 30rpx 28rpx; box-sizing: border-box; background: #fff; display: flex; align-items: flex-start; justify-content: space-between; border-bottom: 1rpx solid #eaeef1; } .item-last::after { left: 0 !important; } .content-box { display: flex; align-items: flex-start; justify-content: space-between; } .des-box { min-height: 80rpx; padding-left: 28rpx; box-sizing: border-box; vertical-align: top; color: #333; font-size: 24rpx; display: flex; flex-direction: column; justify-content: space-between; } .tit { font-size: 32rpx; max-width: 420rpx; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } .source { margin: 12rpx 0; } .time { color: #888; } .money { font-size: 38rpx; font-weight: 500; color: #ff1e0f; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; padding-left: 20rpx; } .less { color: #4caf50 !important; } .tip { margin-top: 50rpx; display: flex; justify-content: center; align-content: center; font-size: 24rpx; color: #888; } </style>
from flask import Flask, jsonify, request import re,os from aip import AipFace basedir = os.path.abspath(os.path.dirname(__file__)) # 定义一个根目录 用于保存图片用 import base64 from io import BytesIO from PIL import Image imageType = "BASE64" """ 你的 APPID AK SK """ APP_ID = '***' API_KEY = '***' SECRET_KEY = '***' client = AipFace(APP_ID, API_KEY, SECRET_KEY) app = Flask(__name__) @app.route('/faceRegister', methods=['GET', 'POST']) def faceRegister(): # 获取图片文件 name = upload user_data = request.get_json() print('==user--data==',user_data['base64Img']) image = user_data['base64Img'].split(',')[1] userId = user_data['openId'] # 将base64字符串转换为图像对象 img_data = base64.b64decode(user_data['base64Img'].split(',')[1]) img = Image.open(BytesIO(img_data)) # 将图像对象保存为文件 img.save('face.png') """ 调用人脸检测 : 校验摄像头中是否检测到人脸 """ detect_res = client.detect(image, imageType) print(detect_res) if detect_res['error_code'] == 222203 : return jsonify({"code": 101, "data": "无法解析人脸", "msg": "验证失败"}) if detect_res['face_num'] != 0 : return jsonify({"code": 101, "data": "解析人脸失败!", "msg": "验证失败"}) groupId = "vip" """ 调用人脸注册 : 将人脸数据注册到人脸库中 """ client.addUser(image, imageType, groupId, userId); """ 调用人脸搜索 """ client.search(image, imageType, groupId); print(detect_res['face_list'][0]['quality']['occlusion']) return detect_res @app.route('/faceAuth', methods=['GET', 'POST']) def faceAuth(): user_data = request.get_json() groupId = "vip" image = user_data['base64Img'].split(',')[1] userId = user_data['openId'] options = {} options["user_id"] = userId """ 调用人脸搜索 """ auth_result = client.search(image, imageType, groupId,options) # { # "face_token": "fid", # "user_list": [ # { # "group_id": "test1", # "user_id": "u333333", # "user_info": "Test User", # "score": 99.3 # } # ] # } if len(auth_result['user_list'])>0: return jsonify({"code": 200, "msg": "登录成功!"}) else: return jsonify({"code": 102, "msg": "用户尚未注册!"}) if __name__ == '__main__': app.run(host="0.0.0.0", port=int("8099"), debug=True)
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。