微信小程序 |基于百度AI从零实现人脸识别小程序

慈云数据 2024-03-27 技术支持 125 0

请添加图片描述

请添加图片描述

在这里插入图片描述

在这里插入图片描述


写在前面

针对互联网上已有的人脸识别小程序项目,很多只是基于手动拍照,然后上传到SDK进行识别。这一过程完全脱离实际场景!无法直接使用!

本文项目是基于微信摄像头中的实时视频帧数据,通过实时动态识别小程序端所获取的照片帧,从而自动触发人脸识别和登录!完全贴合实际,真正的拿来即用!

一、需求背景

人脸识别是一个老生常谈的问题了,再继小程序的逐步普及化后,人脸识别的功能也变为了各大业务的家常菜。目前市面上已有的解决方案,一方面除了自研以外,对于本来就是小成本进行创业或者开发的个人以及初创型公司来说,接入一些专业的第三方的人脸识别接口不妨是一个优秀的解决方案。目前市面上常用的第三方支持技术有哪些,可以阅读本专栏的文章: 微信小程序 | 人脸识别的最终解决方案 – 里面做了详细的介绍和解读,相信能带你弄请这个方向的技术行情。

为此,这一次我们自己动手来实现一次全流程的人脸识别小程序。 没有基础不用担心,本文就是带领大家从零到一开始搭建,细致到每个网页功能如何选择、代码如何编写,前后端的逻辑如何编写。只会前端或者后端的小伙伴也不用担心,本文提供全流程的操作指引,让零基础的前/后端小白都能轻松上手。 废话不多说相信看到上面酷炫的效果大家应该都按捺不住了,那就让我们开整把。

在这里插入图片描述


二、系统架构

2.1 系统技术栈

前端后端
- 语言:vue 2.0 | - 框架 : uni-app | - UI组件: uview- 语言:python | - 框架: Flask

2.2 系统架构图

在这里插入图片描述

2.3 系统请求时序图

在这里插入图片描述

2.4 详细接口请求时序图

在这里插入图片描述


三、项目实现

要使用百度智能云的api接口,就需要在控制台创建你的项目应用,通过项目应用去获取API Key``Secret Key ``Access_token,这三个参数是后续调用api接口的必填参数,缺一不可!

3.1 开通百度智能云API

  • 登录到百度智能云人脸识别控制台
  • 选择相应的API接口并进行购买操作即可(其中我们选择的是人脸比对),其实为了方便我们可以一次性将所有的人脸识别相关的产品全部都进行一次购买,反正都是按量计费,不进行调用也就不用收钱了。在这里插入图片描述

    3.2 并获取API Key

    -在开通了服务的基础上,由于要获取三大认证参数,再调用接口之前我们仍需要进行应用的创建。

    在这里插入图片描述

    在这里插入图片描述

    • 由此即可获取到三大参数

      在这里插入图片描述

      3.3 搭建项目调试环境

      本项目是继续uniapp框架进行开发(采用uniapp框架开发的好处在于:在面对众多的小程序平台,我们只需要编写这一套代码就可以实现多平台的运行,极大程度的降低了开发成本!可以说是只要会一套小程序开发的流程,基本每个平台你都能拿下。)

      • 详细的开发环境搭建过程,可以参考:从零开始搭建uni-app框架的小程序开发环境

        3.4 参考百度云SDK文档

        • 人脸识别接口Pthon SDK文档:人脸接口文档地址

          3.5 人脸对比接口详解

          接口能力

          • 两张人脸图片相似度对比:比对两张图片中人脸的相似度,并返回相似度分值;
          • 多种图片类型:支持生活照、证件照、身份证芯片照、带网纹照四种类型的人脸对比;
          • 活体检测:基于图片中的破绽分析,判断其中的人脸是否为二次翻拍(举例:如用户A用手机拍摄了一张包含人脸的图片一,用户B翻拍了图片一得到了图片二,并用图片二伪造成用户A去进行识别操作,这种情况普遍发生在金融开户、实名认证等环节。);
          • 质量检测:返回模糊、光照等质量检测信息,用于辅助判断图片是否符合识别要求;

            业务应用

            用于比对多张图片中的人脸相似度并返回两两比对的得分,可用于判断两张脸是否是同一人的可能性大小。

            典型应用场景:如人证合一验证,用户认证等,可与您现有的人脸库进行比对验证。

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

            请求参数

            参数必选类型说明
            imagestring图片信息(总数据大小应小于10M),图片上传方式根据image_type来判断。 两张图片通过json格式上传,格式参考表格下方示例
            image_typestring图片类型 BASE64:图片的base64值,base64编码后的图片数据,编码后的图片大小不超过2M;URL:图片的 URL地址( 可能由于网络等原因导致下载图片时间过长);FACE_TOKEN: 人脸图片的唯一标识,调用人脸检测接口时,会为每个人脸图片赋予一个唯一的FACE_TOKEN,同一张图片多次检测得到的FACE_TOKEN是同一个。
            face_typestring人脸的类型LIVE表示生活照:通常为手机、相机拍摄的人像图片、或从网络获取的人像图片等,IDCARD表示身份证芯片照:二代身份证内置芯片中的人像照片, WATERMARK表示带水印证件照:一般为带水印的小图,如公安网小图 CERT表示证件照片:如拍摄的身份证、工卡、护照、学生证等证件图片 默认LIVE
            quality_controlstring图片质量控制 NONE: 不进行控制 LOW:较低的质量要求 NORMAL: 一般的质量要求 HIGH: 较高的质量要求 默认 NONE
            liveness_controlstring活体检测控制 NONE: 不进行控制 LOW:较低的活体要求(高通过率 低攻击拒绝率) NORMAL: 一般的活体要求(平衡的攻击拒绝率, 通过率) HIGH: 较高的活体要求(高攻击拒绝率 低通过率) 默认NONE

            返回参数

            参数名必选类型说明
            scorefloat人脸相似度得分
            face_listarray人脸信息列表
            +face_tokenstring人脸的唯一标志

            返回示例

            {
                "score": 44.3,
                "face_list": [  //返回的顺序与传入的顺序保持一致
                    {
                        "face_token": "fid1"
                    },
                    {
                        "face_token": "fid2"
                    }
                ]
            }
            

            3.6 人脸注册到人脸库功能实现

            为了实现保存用户的人脸信息,用于验证人脸请求。一般都是有两种方案:

            • 其一,是我们在自己的后台服务维护一套用户人脸信息库,然后在每次前端请求登入时进行读库操作,同时再调用client.match()方法对用户的登录信息进行比对。这样的做法可以实现高度自定义化,但是维护成本相对较高,主要包括数据的存储和可视化的展示
            • 其二,百度SDK平台给我们提供了一套完整的用于维护用户人脸信息库API,而且还以Group分组的形式进行用户数据管理,这就使得对于用户的所归宿的不同组的权限进行区分。同样地,借助组的字段概念可以与业务系统打通,进而更为方便的管理用户

              3.6.1 前端代码实现

              微信摄像头实时帧的获取与数据处理

              要实现小程序端自动采集人脸数据并进行接口验证,就必须使用微信摄像头图像帧数据的采集与监听功能。

              • 官方接口说明如下:

                获取 Camera 实时帧数据文档地址

                在这里插入图片描述

                • 程序中实现思路如下:

                  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);
                    					}
                    				})
                    			},
                    
                    3.6.1.1 首页登录界面源代码
                    
                    	
                    		
                    			
                    			
                    		
                    		
                    			
                    			
                    			
                    				登录
                    				欢迎再次回来~
                    			
                    			
                    				
                    					
                    					
                    					
                    					
                    					
                    				
                    				
                    					
                    					
                    					
                    					
                    					
                    				
                    				登录
                    				刷脸登录
                    				
                    					找回密码
                    					快速注册
                    				
                    			
                    		
                    	
                    
                    
                    	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)
                    				}
                    			},
                    		}
                    	}
                    
                    
                    	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;
                    				}
                    			}
                    		}
                    	}
                    
                    
                    3.6.1.2人脸采集界面代码
                    
                    	
                    		
                    			
                    			
                    				
                    					{{tipsText}}
                    				
                    			
                    			
                    			
                    			
                    				
                    				
                    				
                    			
                    			
                    			
                    				
                    					
                    						
                    							
                    							
                    							
                    							
                    							
                    							
                    						
                    					
                    					
                    						
                    							取消
                    						
                    						
                    							确定
                    						
                    					
                    				
                    			
                    		
                    	
                    
                    
                    	// 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 = ''
                    			},
                    		}
                    	}
                    
                    
                    	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;
                    					}
                    				}
                    			}
                    		}
                    	}
                    
                    
                    3.6.1.3 个人中心界面代码
                    
                    	
                    		
                    		我的账户
                    		
                    		
                    			
                    				
                    					
                    						
                    						
                    							姓名:{{ userData.name }}({{ userData.id }})
                    							手机号:{{ userData.phone }}
                    						
                    						名词解释
                    					
                    				
                    				
                    					
                    						
                    							
                    								可提现余额
                    								
                    									{{ userData.withdrawable }}
                    									元
                    								
                    							
                    						
                    						立即提现
                    					
                    					
                    						
                    							
                    								
                    									{{ userData.coming }}
                    									元
                    								
                    								即将到账
                    							
                    						
                    						
                    							
                    								
                    									{{ userData.came }}
                    									元
                    								
                    								累计到账
                    							
                    						
                    						
                    							
                    								
                    									{{ userData.withdrawed }}
                    									元
                    								
                    								累计提现
                    							
                    						
                    					
                    				
                    				
                    					
                    				
                    				
                    					
                    						
                    							
                    								{{ currentTab == 3 ? '流水号:' + item.extract_no : '订单号:' + item.order_no }}
                    								
                    									提现{{ item.status == 1 ? '成功' : item.status == 2 ? '失败' : '处理中' }}
                    								
                    								{{ item.create_time }}
                    							
                    						
                    						{{ is_withdraw ? '-' : '+' }}{{ currentTab == 3 ? item.real_money : item.money }}
                    					
                    				
                    				仅显示近半年内的收支记录
                    			
                    			
                    				
                    					
                    						名词解释
                    						
                    					
                    					
                    						
                    							可提现余额:
                    							当前您可以提现的金额
                    						
                    						
                    							即将到账:
                    							交易中的金额,交易成功后可提现
                    						
                    						
                    							累计到账:
                    							累计交易成功的金额
                    						
                    						
                    							累计提现:
                    							累计提现成功的金额
                    						
                    						*注:所有金额币种均为人民币,单位为元,符号¥
                    					
                    					
                    						我已知晓
                    					
                    				
                    			
                    		
                    	
                    
                    
                    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);
                    	}
                    };
                    
                    
                    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;
                    }
                    
                    

                    3.6.2 后端代码实现

                    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)
                    

                    四、推荐阅读

                    🥇入门和进阶小程序开发,不可错误的精彩内容🥇 :

                    • 《小程序开发必备功能的吐血整理【个人中心界面样式大全】》
                      • 《微信小程序 | 动手实现双十一红包雨》
                        • 《微信小程序 | 人脸识别的最终解决方案》
                          • 《来接私活吧?小程序接私活必备功能-婚恋交友【附完整代码】》
                            • 《吐血整理的几十款小程序登陆界面【附完整代码】》
微信扫一扫加客服

微信扫一扫加客服

点击启动AI问答
Draggable Icon