uni-app+云函数+百度AI(人脸检测,人脸库注册,人脸1:N搜索)

前期准备

Hbuilderx编辑器(代码编辑器)

unicloud(云服务空间,本项目采用的是阿里云

百度智能云 (人脸识别API,有免费测试资源可用)

Hbuilderx

1.创建项目,启动云开发,选择阿里云

2.新建云函数

face --- 百度AI人脸识别业务

face_token --- 生成与更新百度AI所需要的access_token

getTable --- 获取人脸签到的用户信息列表

注意:函数内部需要用到npm下载的依赖 

npm i request -save  //内部请求api用
npm i request-promise -save  //内部请求api用
npm i moment -save  //解决云函数线上时间戳差异问题

3.新建前端页面

face --- 人脸识别

face_sign_in --- 人脸库注册

index --- 各业务入口

table --- 登记表格

注意:内部需要用到npm下载的依赖 

npm i image-tools //图片转base64
npm i moment -save //传递时间获取列表时需要

4.manifest.json - App模块配置 - 直播推流

 

云函数

face/index.js (云函数)

'use strict';
//链接数据库
const db = uniCloud.database()
//引入request模块
const rq = require("request-promise")
//引入moment 解决时间差异问题
const moment = require('moment')
//用户列表
const user_info = db.collection('userInfo')
//登记表
const enroll = db.collection('enroll')
exports.main = async (event, context) => {
	return new Promise((resolve)=>{
		//启动参数  type  get 获取人脸信息供录入  signin 人脸注册  comparison 人脸 1:N 搜索
		let type=event.type
		//判断百度AI签名是否正餐
		uniCloud.callFunction({
			name:'face_token'
		}).then(res=>{
			let access_token=res.result.access_token
			if(res.result.code=='200'){
				//人脸检测
				if(event.type=='get'){
					resolve(get(event,access_token))
				}
				//人脸注册
				if(event.type=='signin'){
					//加入人脸库
					//数据库注册用户信息 获取id
						//判断是否存在此人
						user_info.where({name:event.name}).get().then(res=>{
							if(res.data.length==0){
								user_info.add({name:event.name}).then(res=>{
									var _user_info_={
										name:event.name
									}
									 var options = {
											'method': 'POST',
											'uri': 'https://aip.baidubce.com/rest/2.0/face/v3/faceset/user/add?access_token=' + access_token,
											'headers': {
													'Content-Type': 'application/json'
											},
											body: JSON.stringify({
													"group_id": "yj_face_arr",
													"image": event.face_image.substring(23),
													"image_type": "BASE64",
													"user_id": res.id,//必填
													"user_info":JSON.stringify(_user_info_)
											})
										};
										rq(options).then(res=>{
											resolve(
												{
													code:'200',
													message:'人脸注册成功'
												}
											) 
										})
								})	
								
							}else{
								resolve(
									{
										code:'400',
										message:'此用户已存在'
									}
								)
							}
						})
				}
				//人脸 1:N 搜索
				if(event.type=='comparison'){
						//先查看图片质量
						get(event,access_token).then(res=>{
							// 图片可以
							if(res.code=='200'){
								//开始匹配
								var options = {
								        'method': 'POST',
								        'url': 'https://aip.baidubce.com/rest/2.0/face/v3/search?access_token=' + access_token,
								        'headers': {
								                'Content-Type': 'application/json'
								        },
								        body: JSON.stringify({
								                "group_id_list": "yj_face_arr",
												"image": res.image.substring(23),
								                "image_type": "BASE64",
												"match_threshold":80
								        })
								}
								rq(options).then(res=>{
									if(JSON.parse(res).error_msg=='SUCCESS'){
										Inenroll(JSON.parse(res).result.user_list[0]).then(res=>{
											resolve(res)
										})
									}else{
										resolve(
											{	
												code:'500',
												message:'暂无用户信息',
											}
										)
									}
								})
							}else{
							// 图片不行
								resolve(res)
							}
							
						})
						
				}	
			}
			
		})
	})
};
//人脸检测
function get(event,access_token){
	return new Promise(resolve=>{
		var options = {
		        'method': 'POST',
		        'url': 'https://aip.baidubce.com/rest/2.0/face/v3/detect?access_token=' + access_token ,
		        'headers': {
		                'Content-Type': 'application/json'
		        },
		        body: JSON.stringify({
		                "image": event.face_image.substring(23),
		                "image_type": "BASE64",
		                "face_field": "quality",
		        })
		};
		rq(options).then(res=>{
			resolve(feedback(res,event)) 
		})
	})
}
//阈值
function feedback(res,event){
	//是否检测到人脸
	if(JSON.parse(res).error_msg=='SUCCESS'){
		//人脸是否完整
		if(JSON.parse(res).result.face_list[0].quality.completeness){
			//阈值判断+提示
			//左眼
			let left_eye=JSON.parse(res).result.face_list[0].quality.occlusion.left_eye
			//右眼
			let right_eye=JSON.parse(res).result.face_list[0].quality.occlusion.right_eye
			//鼻子
			let nose=JSON.parse(res).result.face_list[0].quality.occlusion.nose
			//嘴巴
			let mouth=JSON.parse(res).result.face_list[0].quality.occlusion.mouth
			//左脸
			let left_cheek=JSON.parse(res).result.face_list[0].quality.occlusion.left_cheek
			//右脸
			let right_cheek=JSON.parse(res).result.face_list[0].quality.occlusion.right_cheek
			//下巴
			let chin_contour=JSON.parse(res).result.face_list[0].quality.occlusion.chin_contour
			//图片质量可以
			if(left_eye<=0.6&&right_eye<=0.6&&nose<=0.7&&mouth<=0.7&&left_cheek<=0.8&&right_cheek<=0.8&&chin_contour<=0.6){
				return(
					{	
						code:'200',
						image:event.face_image,
					}
				)
			}else{
				if(left_eye>0.6){
					return(
						{
							code:'400',
							message:'左眼被遮挡'
						}
					)
				}else if(right_eye>0.6){
					return(
						{
							code:'400',
							message:'右眼被遮挡'
						}
					)
				}else if(nose>0.7){
					return(
						{
							code:'400',
							message:'鼻子被遮挡'
						}
					)
				}else if(mouth>0.7){
					return(
						{
							code:'400',
							message:'嘴巴被遮挡'
						}
					)
				}else if(left_cheek>0.8){
					return(
						{
							code:'400',
							message:'左脸被遮挡'
						}
					)
				}else if(right_cheek>0.8){
					return(
						{
							code:'400',
							message:'右脸被遮挡'
						}
					)
				}else if(chin_contour>0.6){
					return(
						{
							code:'400',
							message:'下巴被遮挡'
						}
					)
				}
			}
		}else{
			return(
				{
					code:'400',
					message:'未检测到人脸'
				}
			)
		}
	}else{
		//未检出人脸  反馈前端
		return( 
			{
				code:'400',
				message:'未检测到人脸',
			}
		)
	} 
	
}
//表格登记
function Inenroll(e){
	return new Promise((resolve)=>{
		//当前日期
		var today=new Date().getTimezoneOffset()==0? moment().add(8, 'hours').format('YYYY-MM-DD') : moment().format('YYYY-MM-DD')
		// 查询数据库是否存在
		enroll.where({date:today}).get().then(res=>{
			console.log(res)
			//没有今日数据
			if(res.data.length==0){
				var user_arr=[]
				console.log(e)
				user_arr.push({
					name:JSON.parse(e.user_info).name,
					user_id:e.user_id,
					Intime:new Date().getTimezoneOffset()==0? moment().add(8, 'hours').format('HH:mm:ss') : moment().format('HH:mm:ss')
				})
				enroll.add({
					date:today,
					user_arr,
				}).then(res=>{
					resolve({
						code:'200',
						message:"——"+JSON.parse(e.user_info).name+"——"
					})
				})
			}else{
				//判断今日是否已登记
				if(!res.data[0].user_arr.find(obj=>obj.user_id===e.user_id)){
					//未登记
					var user_arr=res.data[0].user_arr
					user_arr.push({
						name:JSON.parse(e.user_info).name,
						user_id:e.user_id,
						Intime:new Date().getTimezoneOffset()==0? moment().add(8, 'hours').format('HH:mm:ss') : moment().format('HH:mm:ss')
					})
					enroll.doc(res.data[0]._id).update({
						user_arr,
					}).then(res=>{
						resolve({
							code:'200',
							message:"——"+JSON.parse(e.user_info).name+"——"
						})
					})
					
				}else{
					//已登记
					resolve({
						code:'500',
						message:"——"+JSON.parse(e.user_info).name+"——今日已登记"
					})
				}
				
			}                             
		})
	})
}

face_token/index.js (云函数) 

'use strict';
//链接数据库
const db = uniCloud.database()
//引入request模块
const rq = require("request-promise")
//声明百度AI配置表
const Bai_Ai = db.collection('face_config')
exports.main = async (event, context) => {
	//查询数据库中 百度AI配置信息
	let Bai_Ai_data =( await Bai_Ai.get()).data[0]
	return new Promise((resolve)=>{
		//当前时间戳 s
		var timestamp =( Date.parse(new Date()))/1000;
		//检查苦衷有无签名
		if(Bai_Ai_data.face_Access_Token.length>0){
			//检查是否过期 有效期大于两天
			//此时的时间戳 + 60*60*24 < 失效期时间戳
			if(timestamp+(60*60*24)<Bai_Ai_data.AT_Outdate){
				resolve({
					code:200,
					access_token:Bai_Ai_data.face_Access_Token
				})
			}else{
				getAccessToken(Bai_Ai,Bai_Ai_data,timestamp).then(res=>{
					resolve({
						code:200,
						access_token:res
					})
				})
			}
		}else{
			getAccessToken(Bai_Ai,Bai_Ai_data,timestamp).then(res=>{
				resolve({
					code:200,
					access_token:res
				})
			})
		}
	})
};
/**
 * 使用 AK,SK 生成鉴权签名(Access Token)
 * @return string 鉴权签名信息(Access Token)
*/
function getAccessToken(Bai_Ai,Bai_Ai_data,timestamp) {
    return new Promise((resolve, reject) => {
		var options = {
			   'method': 'POST',
			   'url': 'https://aip.baidubce.com/oauth/2.0/token?client_id='+Bai_Ai_data.face_API_Key+'&client_secret='+Bai_Ai_data.face_Secret_Key+'&grant_type=client_credentials',
			   'headers': {
					   'Content-Type': 'application/json',
					   'Accept': 'application/json'
			   }
		};
		rq(options).then(res=>{
			Bai_Ai.doc(Bai_Ai_data._id).update({
				'face_Access_Token':JSON.parse(res).access_token,
				'AT_Indate':timestamp,
				'AT_Outdate':timestamp+JSON.parse(res).expires_in
			})
			resolve(JSON.parse(res).access_token) 
		})
    })
}

getTable/index.js (云函数)

'use strict';
//链接数据库
const db = uniCloud.database()
//登记表
const enroll = db.collection('enroll')
//用户列表
const user_info = db.collection('userInfo')
exports.main = async (event, context) => {
	return new Promise((resolve)=>{
		//获取当日登记表数据
		enroll.where({date:event.date}).get().then(res=>{
			resolve(res.data[0])
		})
	})
};

页面

face/index.nvue  注意:此处是 nvue 文件

<template>
	<view class="live-camera" :style="{ width: windowWidth, height: windowHeight }">
	        <view class="preview" :style="{ width: windowWidth, height: windowHeight}">
	            <live-pusher id="livePusher" ref="livePusher" class="livePusher" mode="FHD" beauty="1" whiteness="0"
	                min-bitrate="1000" audio-quality="16KHz" :auto-focus="true" :muted="true"
	                :enable-camera="true" :enable-mic="false" :zoom="false" orientation="horizontal" @statechange="statechange"
	                :style="{ width: cameraWidth, height: cameraHeight }"></live-pusher>
	
	            <!--提示语-->
	            <cover-view class="remind">
	                <text class="remind-text" style="">{{ message }}</text>
	            </cover-view>
	
	            <!--辅助线-->
	            <cover-view class="outline-box" >
	                <cover-image class="outline-img" :style="{width: windowWidth+20, height: windowHeight+150}" src="../../static/face2.png"></cover-image>
	            </cover-view>
	    </view>
	</view>
</template>

<script>
	import { pathToBase64, base64ToPath } from 'image-tools'
    export default {
        data() {
            return {
				//启动参数
				type:'',
                //提示
                message: '',
                //相机画面宽度
                cameraWidth: '',
                //相机画面宽度
                cameraHeight: '',
                //屏幕可用宽度
                windowWidth: '',
                //屏幕可用高度
                windowHeight: '',
                //流视频对象
                livePusher: null,
                //照片
                snapshotsrc: null,
                ifPhoto: false,
            };
        },
        onLoad(e) {
            //获取屏幕高度
            this.initCamera();
			this.type=e.type
        },
        onReady() {
          this.livePusher = uni.createLivePusherContext('livePusher', this);
        },
        onShow() {
            //开启预览并设置摄像头
            this.startPreview();
        },
        methods: {
            //获取屏幕高度
            initCamera() {
                let that = this
                uni.getSystemInfo({
                    success: function(res) {
                        that.windowWidth = res.windowWidth;
                        that.windowHeight = res.windowHeight;
                        that.cameraWidth = res.windowWidth;
                        that.cameraHeight = res.windowWidth * 1.5;
                    }
                });
            },
            //启动相机
            startPreview() {
				var that=this
				setTimeout(()=>{
					console.log(that.livePusher)
					that.livePusher.startPreview({
					    success(res) {
					        console.log('启动相机', res)
					    }
					});
				},100)
               
            },
            //停止相机
            stopPreview() {
                let that = this
                this.livePusher.stopPreview({
                    success(res) {
						that.statechange()
                        console.log('停止相机', res)
                    }
                });
            },
            //摄像头 状态
            statechange(e) {
                 //拍照
                this.snapshot()
            },
            //抓拍
            snapshot() {
                let that = this
				console.log('拍照')
				that.message=''
				setTimeout(()=>{
					that.livePusher.snapshot({
					    success(res) {
					        that.snapshotsrc = res.message.tempImagePath;
					        that.uploadingImg(res.message.tempImagePath)
					    }
					});
				},100)
            },
            // 图片上传
            uploadingImg(e) { 
                let url = e
                let that = this 
				//图片转base64
				pathToBase64(e).then(base64 => {
					//上传云端进行分析
					uniCloud.callFunction({
						name:'face',
						data:{
							type:that.type,
							face_image:base64
						}  
					}).then(res=>{
						if(res.result.code=='200'){
							console.log(res)
							//如果是检测 则须保存人脸图片进行渲染
							if(that.type=='get'){
								uni.setStorageSync('user_face',res.result.image)
								uni.showToast({
									icon:'success',
								})
								setTimeout(()=>{
									uni.navigateBack()
								},500)
							}
							//人脸 1:N 匹配 结果
							if(that.type=='comparison'){
								uni.showToast({
									icon:'success',
									title:res.result.message
								})
								setTimeout(()=>{
									uni.navigateBack()
								},500)
							}
							
						}
						if(res.result.code=='400'){
							that.message=res.result.message
							console.log('错误',res.result.message)
							//重新启动拍照
							setTimeout(()=>{
								that.snapshot()
							},500)
						}
						if(res.result.code=='500'){
							uni.showModal({
								content:res.result.message,
								success(e) {
									if (e.confirm) {
									    // console.log('用户点击确定');
									    uni.navigateBack()
									} else if (e.cancel) {
									    // console.log('用户点击取消');
									    uni.navigateBack()
									}
								}
							})
						}
					})
				})
            },
	
            //验证请求
            request(url) {
                let data = {
                    token: this.userInfo.token,
                    photo: url
                }
            },
            // 认证失败,重新认证
            anew(msg) {
                let that = this
                uni.showModal({
                    content: msg,
                    confirmText: '重新审核',
                    success(res) {
                        if (res.confirm) {
                            // console.log('用户点击确定');
                            that.getCount()
                        } else if (res.cancel) {
                            // console.log('用户点击取消');
                            uni.navigateBack({
                                delta: 1
                            })
                        }
                    }
                })
            },
        }
    };
</script>
<style lang="scss">
	  .live-camera {
	        .preview {
	            justify-content: center;
	            align-items: center;
	            .outline-box {
	                position: absolute;
	                top: 0;
	                left: 0;
	                bottom: 0;
	                z-index: 99;
	                align-items: center;
	                justify-content: center;
	            }
	
	            .remind {
	                position: absolute;
	                bottom: 10px;
	                width: 750rpx;
	                z-index: 100;
	                align-items: center;
	                justify-content: center;
	                .remind-text {
	                    color: #dddddd;
	                    font-weight: bold;
	                }
	            }
	        }
	    }
</style>

face_sign_in/index.vue

<template>
	<view class="content">
		<view class="face-box" @click="getface">
			<image class="face-bg" src="../../static/face_bg.png" mode="" v-if="!image"></image>
			<view class="fb-msg" v-if="!image">
				点击录入人脸信息
			</view>
			<image class="face" :src="image" mode="" v-if="image"></image>
		</view>
		<view class="top">
			<view class="t-l">
				姓名:
			</view>
			<input class="t-r" placeholder="请输入姓名" type="text" @input="getuser_name" />
		</view>
		<view class="foot" hover-class="foot-hover" @click="getdata">
			提交信息
		</view>
	</view>
</template>

<script>
	
	export default {
		data() {
			return {
				image:'',
				name:''
			}
		},
		onLoad() {
			
		},
		onShow() {
			var image=uni.getStorageSync('user_face')
			this.image=image
		},
		methods: {
			//获取人脸信息
			getface(){
				uni.navigateTo({
					url:"/pages/face/new_file?type=get"
				})
			},
			getuser_name(e){
				this.name=e.target.value
			},
			getdata(){
				var that=this
				if(that.image&&that.name){
					uniCloud.callFunction({
						name:'face',
						data:{
							type:'signin',
							face_image:that.image,
							name:that.name
						}
					}).then((res)=>{
						console.log('人脸信息注册接口',res)
						if(res.result.code=='200'){
							uni.showToast({
								icon:'none',
								title:res.result.message
							})
							setTimeout(()=>{
								uni.removeStorageSync('user_face')
								uni.navigateBack()
							},1000)
						}
						
					})
					
					
					
				}else{
					uni.showToast({
						icon:"none",
						title:'请完善信息'
					})
				}
			}
		}
	}
</script>

<style>
	.content {
		height: 100%;
		width:100%;
		display: flex;
		flex-direction: column;
		align-items: center;
	}
	.face-box{
		margin-top: 30rpx;
		border-radius: 10rpx;
		border:1px solid #64aff4;
		height: 500rpx;
		width: 400rpx;
		overflow: hidden;
		display: flex;
		flex-direction: column;
		align-items: center;
		position: relative;
	}
	.face-bg{
		height: 100%;
		width: 120%;
	}
	.face{
		height: 160%;
		width: 100%;
	}
	.fb-msg{
		position: absolute;
		bottom: 20rpx;
		color: #ffffff;
	}
	.top{
		width: 80%;
		margin-top: 100rpx;
		height: 100rpx;
		display: flex;
		align-items: center;
		justify-content: center;
	}
	.t-l{
		font-size: 32rpx;
		text-align: center;
	}
	.t-r{
		margin-left: 20rpx;
		height: 100%;
		width: 40%;
		border-radius: 10rpx;
		text-indent: 20rpx;
		border:1px solid #64aff4;
	}
	.foot{
		width: 400rpx;
		text-align: center;
		height: 120rpx;
		line-height: 120rpx;
		border-radius: 15rpx;
		margin-top: 50rpx;
		color: #ffffff;
		background-color: #64aff4;
	}
	.foot-hover{
		width: 400rpx;
		text-align: center;
		height: 120rpx;
		line-height: 120rpx;
		border-radius: 15rpx;
		margin-top: 50rpx;
		color: #ffffff;
		background-color: #487fb0;
	}
</style>

index/index.vue

<template>
	<view class="content">
		<image class="logo" src="/static/logo.png"></image>
		
		<view class="text-area"> 
			<text class="title" @click="zhuce">注册人脸信息</text>
		</view>
		<view class="text-area">
			<text class="title" @click="bidui">人脸1:N搜索信息</text>
		</view>
		<view class="text-area">
			<text class="title" @click="table">查看统计表</text>
		</view>
	</view>
</template>

<script>
	export default {
		data() {
			return {
				title: ''
			} 
		},
		onLoad() {
			var that=this
			// uniCloud.callFunction({
			// 	name:'ceshi'
			// })
		},
		methods: {
			zhuce(){
				uni.navigateTo({
					url:'/pages/face_sign_in/face_sign_in'
				})
			},
			bidui(){
				uni.navigateTo({
					url:'/pages/face/new_file?type=comparison'
				})
			},
			table(){
				uni.navigateTo({
					url:'/pages/table/table'
				})
			}
		}
	}
</script>

<style>
	.content {
		display: flex;
		flex-direction: column;
		align-items: center;
		justify-content: center;
	}

	.logo {
		height: 200rpx;
		width: 200rpx;
		margin-top: 200rpx;
		margin-left: auto;
		margin-right: auto;
		margin-bottom: 50rpx;
	}

	.text-area {
		height: 100rpx;
		border:1px solid #4d9ae7;
		display: flex;
		padding: 0 30rpx;
		border-radius: 20rpx;
		margin-top: 40rpx;
		align-items: center;
		justify-content: center;
	}

	.title {
		font-size: 36rpx;
		color: #8f8f94;
	}
</style>

table/index.vue

<template>
	<view class="content">
		<uni-table border stripe emptyText="暂无更多数据" >
			<!-- 表头行 -->
			<uni-tr>
				<uni-th align="center">日期</uni-th>
				<uni-th align="center">统计</uni-th>
			</uni-tr>
			<!-- 表格数据行 -->
			<uni-tr>
				<uni-td align="center">{{list?list.date:''}}</uni-td>
				<uni-td align="center">{{list?list.user_arr.length:''}}</uni-td>
			</uni-tr>
			<uni-tr>
				<uni-th align="center">时间</uni-th>
				<uni-th align="center">姓名</uni-th>
			</uni-tr>
			<uni-tr v-for="(item,index) in list.user_arr" :key="index">
				<uni-td align="center">{{item.Intime}}</uni-td>
				<uni-td align="center">{{item.name}}</uni-td>
			</uni-tr>
		
		</uni-table>
	</view>
</template>

<script>
	import moment from "moment"
	export default {
		data() {
			return {
				list:''
			}
		},
		onLoad() {
			var that=this
			var today=new Date().getTimezoneOffset()==0? moment().add(8, 'hours').format('YYYY-MM-DD') : moment().format('YYYY-MM-DD')
			that.getTable(today)
		},
		methods: {
			getTable(e){
				var that=this
				uniCloud.callFunction({
					name:'getTable',
					data:{
						date:e
					}
				}).then((res)=>{
					console.log(res)
					that.list=res.result
				})
			}
		}
	}
</script>

<style>
	.content {
		height: 100%;
		width:100%;
	}
</style>

unicloud

数据库

enroll --- 人脸登记表

face_config --- 百度AI配置表

userInfo --- 用户信息表

 enroll 结构

face_config 结构

userInfo 结构

百度智能云

1.控制台申请人脸识别免费配额并创建应用

2.可视化人脸库中创建 yj_face_arr 用户组

3.将应用信息在数据库 face_config 表中配置好

效果 (平板电脑横屏)

index/index.vue

face/index.nvue

face_sign_in/index.vue

table/index.vue

温馨提示:本章只对该业务做了基础运用,好看的页面效果需要各位道友自己整哦!!!哥哥姐姐们给个三连吧!!!!

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

欧的曼

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值