添加人脸识别
This commit is contained in:
214
bundle/face/face-info.vue
Normal file
214
bundle/face/face-info.vue
Normal file
@ -0,0 +1,214 @@
|
|||||||
|
<template>
|
||||||
|
<view class="content">
|
||||||
|
<view class="title">请确保本人操作</view>
|
||||||
|
<view class="desc">为了防范身份信息被冒用,保障资金安全,请保持正脸在取景框中根据屏幕指示完成识别</view>
|
||||||
|
<view class="photo-box">
|
||||||
|
<image src="https://xh.stnav.com/uploads/sport/face1.png" style="width: 280rpx; height: 350rpx;"></image>
|
||||||
|
</view>
|
||||||
|
<view>
|
||||||
|
<view class="desc2">1.为了确保您账户的安全和真实性,我们需要对您进行身份验证;</view>
|
||||||
|
<view class="desc3">2.请您本人亲自完成,请将脸部置于提示框内,并按提示做动作。</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="photo-list" >
|
||||||
|
<view class="photo-item">
|
||||||
|
<view class="">
|
||||||
|
<image src="https://xh.stnav.com/uploads/sport/face2.png" style="width: 90rpx; height: 90rpx;"></image>
|
||||||
|
</view>
|
||||||
|
<view class="">正对手机</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="photo-item">
|
||||||
|
<view class="">
|
||||||
|
<image src="https://xh.stnav.com/uploads/sport/face3.png" style="width: 90rpx; height: 90rpx;"></image>
|
||||||
|
</view>
|
||||||
|
<view class="">正对手机</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="">
|
||||||
|
<view class="photo-item">
|
||||||
|
<image src="https://xh.stnav.com/uploads/sport/face4.png" style="width: 90rpx; height: 90rpx;"></image>
|
||||||
|
</view>
|
||||||
|
<view class="">正对手机</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="photo-btn" @click="takePhoto">开始录入</view>
|
||||||
|
<!-- <image v-if="imgSrc" :src="imgSrc" style="width: 200rpx; height: 200rpx; margin-top: 20rpx;"></image> -->
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
order_id: 0,
|
||||||
|
imgSrc: '',
|
||||||
|
loadding: true,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
onLoad(args) {
|
||||||
|
this.order_id = args.order_id || 0;
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
takePhoto() {
|
||||||
|
let self = this
|
||||||
|
console.log('take photo')
|
||||||
|
uni.navigateTo({
|
||||||
|
url: `/bundle/face/face-photo?order_id=${this.order_id}`,
|
||||||
|
events:{
|
||||||
|
data: (e) => {
|
||||||
|
// self.imgSrc = path
|
||||||
|
self.uploadFile(e.path, e.order_id)
|
||||||
|
// uni.redirectTo({
|
||||||
|
// url: '/pages/order/cg-my-order'
|
||||||
|
// });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/*上传图片*/
|
||||||
|
uploadFile: function(filePath, order_id) {
|
||||||
|
console.log("🚀 ~ filePat1, order_id:", filePath)
|
||||||
|
console.log("🚀 ~ filePat2, order_id:", order_id)
|
||||||
|
let self = this;
|
||||||
|
let params = {
|
||||||
|
token: uni.getStorageSync('token'),
|
||||||
|
app_id: self.getAppId()
|
||||||
|
};
|
||||||
|
uni.showLoading({
|
||||||
|
title: '图片上传中'
|
||||||
|
});
|
||||||
|
uni.uploadFile({
|
||||||
|
url: self.websiteUrl + '/index.php?s=/api/file.upload/image',
|
||||||
|
filePath: filePath,
|
||||||
|
name: 'iFile',
|
||||||
|
formData: params,
|
||||||
|
success: function(res) {
|
||||||
|
uni.hideLoading();
|
||||||
|
|
||||||
|
let result = typeof res.data === 'object' ? res.data : JSON.parse(res.data);
|
||||||
|
if (result.code === 1) {
|
||||||
|
setTimeout(() => {
|
||||||
|
uni.showLoading({
|
||||||
|
title: '人脸录入中...'
|
||||||
|
});
|
||||||
|
}, 100);
|
||||||
|
|
||||||
|
console.log("🚀 ~ filePath, order_id2:", filePath, order_id)
|
||||||
|
self._post(
|
||||||
|
'haikang.createPerson/hikCreate',
|
||||||
|
{
|
||||||
|
app_id: self.getAppId(),
|
||||||
|
faceUrl: result.data.file_path,
|
||||||
|
order_id: order_id
|
||||||
|
},
|
||||||
|
function(res) {
|
||||||
|
console.log("🚀 ~ res:", res)
|
||||||
|
uni.hideLoading();
|
||||||
|
setTimeout(() => {
|
||||||
|
uni.showToast({
|
||||||
|
title: '操作成功',
|
||||||
|
duration: 1500,
|
||||||
|
icon: 'success'
|
||||||
|
});
|
||||||
|
}, 100);
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
uni.redirectTo({
|
||||||
|
url: '/pages/order/cg-my-order?type=2'
|
||||||
|
});
|
||||||
|
}, 800)
|
||||||
|
}
|
||||||
|
);
|
||||||
|
// self.imgSrc = result.data.file_path;
|
||||||
|
} else {
|
||||||
|
// self.showError(result.msg);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
complete: function() {
|
||||||
|
uni.hideLoading();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
page {
|
||||||
|
background-color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
margin: 16rpx 60rpx 0;
|
||||||
|
padding-bottom: 10rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
font-size: 40rpx;
|
||||||
|
color: #303133;
|
||||||
|
line-height: 56rpx;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.desc {
|
||||||
|
margin-top: 20rpx;
|
||||||
|
text-align: left;
|
||||||
|
font-size: 26rpx;
|
||||||
|
color: #909399;
|
||||||
|
line-height: 36rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.photo-box {
|
||||||
|
margin-top: 52rpx;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.desc2, .desc3 {
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #606266;
|
||||||
|
line-height: 40rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.desc3 {
|
||||||
|
margin-top: 20rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.photo-list {
|
||||||
|
margin: 70rpx 18rpx 0;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.photo-item {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
& view:last-child {
|
||||||
|
margin-top: 20rpx;
|
||||||
|
color: #606266;
|
||||||
|
line-height: 36rpx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.photo-btn {
|
||||||
|
margin-top: 222rpx;
|
||||||
|
width: 630rpx;
|
||||||
|
height: 90rpx;
|
||||||
|
line-height: 90rpx;
|
||||||
|
text-align: center;
|
||||||
|
border-radius: 8rpx;
|
||||||
|
background: #365A9A;
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 30rpx;
|
||||||
|
color: #FFFFFF;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
46
bundle/face/face-photo.vue
Normal file
46
bundle/face/face-photo.vue
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
<template>
|
||||||
|
<view>
|
||||||
|
<face-bio-assay :isDev="false" :action="['StraightenHead']" ref="faceDetect" @detectFailed="photoChange" @photoChange="photoChange">
|
||||||
|
</face-bio-assay>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import faceBioAssay from '@/uni_modules/face-bio-assay/components/face-bio-assay/face-bio-assay.vue'
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
faceBioAssay,
|
||||||
|
},
|
||||||
|
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
order_id: 0,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
onLoad(args) {
|
||||||
|
this.order_id = args.order_id || 0;
|
||||||
|
this.$refs.faceDetect.initData()
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
detectFailed() {
|
||||||
|
uni.showToast({
|
||||||
|
title: "人脸核验失败~",
|
||||||
|
icon: 'none'
|
||||||
|
})
|
||||||
|
uni.navigateBack()
|
||||||
|
},
|
||||||
|
|
||||||
|
photoChange(path) {
|
||||||
|
let self = this
|
||||||
|
uni.navigateBack()
|
||||||
|
this.getOpenerEventChannel().emit('data',{path: path, order_id: self.order_id});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
</style>
|
||||||
@ -688,7 +688,7 @@ export default {
|
|||||||
uni.$off("payment")
|
uni.$off("payment")
|
||||||
if (params.result) {
|
if (params.result) {
|
||||||
uni.redirectTo({
|
uni.redirectTo({
|
||||||
url: `/bundle/reserve/notice?order_id=${params.order_id}`
|
url: `/bundle/reserve/notice?order_id=${params.order_id}&ground_type=${self.venue.type_id}`
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
uni.redirectTo({
|
uni.redirectTo({
|
||||||
@ -761,11 +761,11 @@ export default {
|
|||||||
uni.$off("payment")
|
uni.$off("payment")
|
||||||
if (params.result) {
|
if (params.result) {
|
||||||
uni.redirectTo({
|
uni.redirectTo({
|
||||||
url: `/bundle/reserve/notice?order_id=${params.order_id}`
|
url: `/bundle/reserve/notice?order_id=${params.order_id}&ground_type=${self.venue.type_id}`
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
uni.redirectTo({
|
uni.redirectTo({
|
||||||
url: '/pages/order/cg-my-order'
|
url: '/pages/order/cg-my-order?type=2'
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}, 500)
|
}, 500)
|
||||||
|
|||||||
@ -12,18 +12,39 @@
|
|||||||
<view class="btn1" @click="seeOrder">查看订单</view>
|
<view class="btn1" @click="seeOrder">查看订单</view>
|
||||||
<view class="btn2" @click="done">完成</view>
|
<view class="btn2" @click="done">完成</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
|
<!-- 人脸录入提示 -->
|
||||||
|
<Popup :show="facePopup" radius="16rpx">
|
||||||
|
<view class="notice-popup" style="width: 100%; position: relative;">
|
||||||
|
<image style="width: 36rpx;height: 36rpx;position: absolute; top: 0; right: 46rpx;" src="https://xh.stnav.com/uploads/sport/icon_close.png" mode="" @click="facePopup = false"></image>
|
||||||
|
|
||||||
|
<view class="title">提示</view>
|
||||||
|
<view class="desc" style="text-align: center;">
|
||||||
|
为了给您提供更安全、便捷的入场体验,请完成人脸识别认证。只需几秒钟,轻松刷脸入场!
|
||||||
|
</view>
|
||||||
|
<view class="btn" @click="toTakeFacePhoto">去录入</view>
|
||||||
|
</view>
|
||||||
|
</Popup>
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import Popup from '@/components/uni-popup.vue';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
components: {
|
||||||
|
Popup,
|
||||||
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
order_id: 0
|
order_id: 0,
|
||||||
|
ground_type: 1, // 1 网球 2篮球
|
||||||
|
facePopup: false,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
onLoad(args) {
|
onLoad(args) {
|
||||||
|
this.ground_type = args.ground_type || 1;
|
||||||
this.order_id = args.order_id || 0;
|
this.order_id = args.order_id || 0;
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -39,10 +60,23 @@
|
|||||||
},
|
},
|
||||||
|
|
||||||
done() {
|
done() {
|
||||||
|
if (this.ground_type == 1) {
|
||||||
uni.reLaunch({
|
uni.reLaunch({
|
||||||
url: '/pages/index/index'
|
url: '/pages/index/index'
|
||||||
});
|
});
|
||||||
|
} else if (this.ground_type == 2) {
|
||||||
|
// 篮球场需要人脸录入
|
||||||
|
this.facePopup = true;
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// 去录入人脸照片
|
||||||
|
toTakeFacePhoto() {
|
||||||
|
this.facePopup = false;
|
||||||
|
uni.navigateTo({
|
||||||
|
url: `/bundle/face/face-info?order_id=${this.order_id}`
|
||||||
|
});
|
||||||
|
},
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
@ -111,4 +145,35 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.notice-popup {
|
||||||
|
padding-bottom: 20rpx;
|
||||||
|
|
||||||
|
.title {
|
||||||
|
font-size: 36rpx;
|
||||||
|
color: #303133;
|
||||||
|
line-height: 50rpx;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.desc {
|
||||||
|
margin-top: 48rpx;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 32rpx;
|
||||||
|
color: #303133;
|
||||||
|
line-height: 52rpx;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn {
|
||||||
|
font-size: 32rpx;
|
||||||
|
background: #365A9A;
|
||||||
|
border-radius: 8rpx;
|
||||||
|
margin: 92rpx 44rpx 0;
|
||||||
|
height: 80rpx;
|
||||||
|
line-height: 80rpx;
|
||||||
|
text-align: center;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@ -157,6 +157,9 @@
|
|||||||
"permission" : {
|
"permission" : {
|
||||||
"scope.userLocation" : {
|
"scope.userLocation" : {
|
||||||
"desc" : "获取您与体育场馆的距离"
|
"desc" : "获取您与体育场馆的距离"
|
||||||
|
},
|
||||||
|
"scope.camera": {
|
||||||
|
"desc": "需要获取摄像头权限进行人脸录入"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
12
pages.json
12
pages.json
@ -1013,6 +1013,18 @@
|
|||||||
"style": {
|
"style": {
|
||||||
"navigationStyle": "custom"
|
"navigationStyle": "custom"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "face/face-info",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "face/face-photo",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": ""
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}],
|
}],
|
||||||
|
|||||||
@ -134,6 +134,11 @@
|
|||||||
<block v-if="item.order_status >= 2">
|
<block v-if="item.order_status >= 2">
|
||||||
<button class="theme-btn del-btn" @click="onDelOrder(item.id)">删除订单</button>
|
<button class="theme-btn del-btn" @click="onDelOrder(item.id)">删除订单</button>
|
||||||
</block>
|
</block>
|
||||||
|
|
||||||
|
<!-- 在篮球场下且订单是已预约有个人脸录入 -->
|
||||||
|
<block v-if="item.order_status == 1 && ballType == 2 && item.face_status == 0">
|
||||||
|
<button class="theme-btn pay-btn" @click="onTakePhoto(item.id)">人脸录入</button>
|
||||||
|
</block>
|
||||||
</block>
|
</block>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
@ -251,6 +256,10 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
onLoad(e) {
|
onLoad(e) {
|
||||||
|
if (e.type) {
|
||||||
|
this.ballType = e.type;
|
||||||
|
}
|
||||||
|
|
||||||
if (typeof e.dataType != 'undefined') {
|
if (typeof e.dataType != 'undefined') {
|
||||||
this.dataType = e.dataType;
|
this.dataType = e.dataType;
|
||||||
}
|
}
|
||||||
@ -572,6 +581,12 @@
|
|||||||
uni.navigateTo({
|
uni.navigateTo({
|
||||||
url: `/bundle/reserve/details?id=${ground_id}&typeId=${this.ballType}`
|
url: `/bundle/reserve/details?id=${ground_id}&typeId=${this.ballType}`
|
||||||
});
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
onTakePhoto(order_id) {
|
||||||
|
uni.navigateTo({
|
||||||
|
url: `/bundle/face/face-info?order_id=${order_id}`
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
36
uni_modules/face-bio-assay/changelog.md
Normal file
36
uni_modules/face-bio-assay/changelog.md
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
## 1.1.1(2023-04-13)
|
||||||
|
文档更新:动作容器的使用案例
|
||||||
|
## 1.1.0(2023-04-13)
|
||||||
|
整理代码,补充文档信息
|
||||||
|
## 1.0.14(2023-04-13)
|
||||||
|
更新文档
|
||||||
|
## 1.0.13(2023-04-13)
|
||||||
|
更新文档信息
|
||||||
|
## 1.0.12(2023-04-13)
|
||||||
|
修复授权弹窗bug
|
||||||
|
## 1.0.11(2023-04-11)
|
||||||
|
文档调整
|
||||||
|
## 1.0.10(2023-04-11)
|
||||||
|
更新文档
|
||||||
|
## 1.0.9(2023-04-11)
|
||||||
|
1.优化点头摇头动作
|
||||||
|
2.解决页面回退在ios出现空白页
|
||||||
|
3.调整ui布局
|
||||||
|
## 1.0.8(2023-04-03)
|
||||||
|
修改文档,并提出在ios中二次调用bug的解决方案
|
||||||
|
## 1.0.7(2023-03-28)
|
||||||
|
未通过核验停留两秒,对未授权拍照bug修复
|
||||||
|
## 1.0.6(2023-03-27)
|
||||||
|
提示用户开启摄像头权限
|
||||||
|
## 1.0.5(2023-03-22)
|
||||||
|
bug修复,文档修订
|
||||||
|
## 1.0.4(2023-03-21)
|
||||||
|
更新文档
|
||||||
|
## 1.0.3(2023-03-21)
|
||||||
|
新增 动作容器(ActionContainer) 类 开发者可通过自定义动作 添加到该容器中管理。也可使用已有动作进行排列组合创建动作组
|
||||||
|
## 1.0.2(2023-03-16)
|
||||||
|
调整通过条件,一次行为就可以通过验证
|
||||||
|
## 1.0.1(2023-03-15)
|
||||||
|
参数调整,提高通过率
|
||||||
|
## 1.0.0(2023-03-14)
|
||||||
|
基于微信小程序的人脸识别做的简易版活体检测
|
||||||
@ -0,0 +1,59 @@
|
|||||||
|
class ActionContainer {
|
||||||
|
//动作组 actions
|
||||||
|
//起始动作下标 index
|
||||||
|
//初始提示 tip
|
||||||
|
//绑定 完成所有动作的回调 endFun
|
||||||
|
//绑定 动作进行中 ingFun
|
||||||
|
//绑定 动作完成时 successFun
|
||||||
|
//绑定 动作失败时 failFun
|
||||||
|
constructor(actions, index, tip, endFun, ingFun, successFun, failFun) {
|
||||||
|
this.actions = actions || []
|
||||||
|
this.index = index || 0
|
||||||
|
this.tip = tip || '检测不到人脸'
|
||||||
|
this.endFun = endFun || this.__tempFun
|
||||||
|
this.ingFun = ingFun || this.__tempFun
|
||||||
|
this.successFun = successFun || this.__tempFun
|
||||||
|
this.failFun = failFun || this.__tempFun
|
||||||
|
}
|
||||||
|
__tempFun(){
|
||||||
|
|
||||||
|
}
|
||||||
|
next(faceData) {
|
||||||
|
if (this.index >= this.actions.length) {
|
||||||
|
this.endFun()
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
if (this.actions[this.index].state === 'ing') {
|
||||||
|
this.tip = this.actions[this.index].tip
|
||||||
|
this.actions[this.index].check(faceData)
|
||||||
|
this.ingFun()
|
||||||
|
return this
|
||||||
|
} else if (this.actions[this.index].state === 'success') {
|
||||||
|
this.index++;
|
||||||
|
this.successFun()
|
||||||
|
return this
|
||||||
|
} else if (this.actions[this.index].state === 'fail') {
|
||||||
|
this.failFun()
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
end(fun) { //绑定 完成所有动作的回调
|
||||||
|
this.endFun = fun || this.__tempFun
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
ing(fun) { //绑定 动作进行中
|
||||||
|
this.ingFun = fun || this.__tempFun
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
success(fun) { //绑定 动作完成时
|
||||||
|
this.successFun = fun || this.__tempFun
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
fail(fun) { //绑定 动作失败时
|
||||||
|
this.failFun = fun || this.__tempFun
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ActionContainer
|
||||||
@ -0,0 +1,57 @@
|
|||||||
|
const STATE = {
|
||||||
|
ING:'ing',
|
||||||
|
SUCCESS:'success',
|
||||||
|
FAIL:'fail'
|
||||||
|
}
|
||||||
|
export default class Action {
|
||||||
|
constructor(second=10,fun,limit,initTip) {
|
||||||
|
this.second = second
|
||||||
|
this.endTime = Infinity
|
||||||
|
this.frames = []
|
||||||
|
this.tip = initTip
|
||||||
|
this.initTip = initTip
|
||||||
|
this.state = STATE.ING
|
||||||
|
this.fun = fun
|
||||||
|
this.limit = limit
|
||||||
|
}
|
||||||
|
end(){
|
||||||
|
if(this.fun){
|
||||||
|
this.fun(this.state)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
check(faceData){
|
||||||
|
if(this.endTime === Infinity){
|
||||||
|
this.endTime = new Date().getTime() + (this.second*1000)
|
||||||
|
}
|
||||||
|
if(this.state !== STATE.ING ){
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if(new Date().getTime()>this.endTime){
|
||||||
|
this.state = STATE.FAIL
|
||||||
|
this.end()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if(this.frames.length>=this.limit){
|
||||||
|
this.state = STATE.SUCCESS
|
||||||
|
this.end()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.takeFrameAfter(faceData)?.takeFrame(faceData)
|
||||||
|
}
|
||||||
|
takeFrame(faceData){
|
||||||
|
|
||||||
|
}
|
||||||
|
takeFrameAfter(faceData){
|
||||||
|
let face = faceData.faceInfo[0]
|
||||||
|
this.tip = this.initTip
|
||||||
|
if(faceData.x == -1 || faceData.y == -1) {
|
||||||
|
this.tip = '检测不到人脸'
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
if(faceData.faceInfo.length > 1) {
|
||||||
|
this.tip = '请保证只有一人做核验'
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,24 @@
|
|||||||
|
import Action from "./Action.js"
|
||||||
|
class NodHead extends Action {
|
||||||
|
constructor(second=10,fun) {
|
||||||
|
super(second,fun,1,'请点头')
|
||||||
|
this.maxPitch = 0
|
||||||
|
this.minPitch = 0
|
||||||
|
}
|
||||||
|
takeFrame(faceData){
|
||||||
|
let face = faceData.faceInfo[0]
|
||||||
|
if(face.angleArray.pitch>this.maxPitch){
|
||||||
|
this.maxPitch = face.angleArray.pitch
|
||||||
|
}
|
||||||
|
if(face.angleArray.pitch<this.minPitch){
|
||||||
|
this.minPitch = face.angleArray.pitch
|
||||||
|
}
|
||||||
|
if(Math.abs(this.minPitch-this.maxPitch)>0.45 && (this.minPitch || this.maxPitch) ){
|
||||||
|
this.frames.push('点头')
|
||||||
|
this.maxPitch = 0
|
||||||
|
this.minPitch = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default NodHead
|
||||||
@ -0,0 +1,24 @@
|
|||||||
|
import Action from "./Action.js"
|
||||||
|
class ShakeHead extends Action {
|
||||||
|
constructor(second = 10, fun) {
|
||||||
|
super(second,fun,1,'请摇头')
|
||||||
|
this.minYaw = 0
|
||||||
|
this.maxYaw = 0
|
||||||
|
}
|
||||||
|
takeFrame(faceData) {
|
||||||
|
let face = faceData.faceInfo[0]
|
||||||
|
if(face.angleArray.yaw>this.maxYaw){
|
||||||
|
this.maxYaw = face.angleArray.yaw
|
||||||
|
}
|
||||||
|
if(face.angleArray.yaw<this.minYaw){
|
||||||
|
this.minYaw = face.angleArray.yaw
|
||||||
|
}
|
||||||
|
if(Math.abs(this.minYaw-this.maxYaw)>0.45 && (this.minYaw || this.maxYaw) ){
|
||||||
|
this.frames.push('摇头')
|
||||||
|
this.minYaw = 0
|
||||||
|
this.maxYaw = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ShakeHead
|
||||||
@ -0,0 +1,27 @@
|
|||||||
|
import Action from "./Action.js"
|
||||||
|
class StraightenHead extends Action {
|
||||||
|
constructor(second = 10, fun) {
|
||||||
|
super(second, fun, 10, '请平视摄像头')
|
||||||
|
}
|
||||||
|
takeFrame(faceData) {
|
||||||
|
let face = faceData.faceInfo[0]
|
||||||
|
if (Math.abs(face.angleArray.pitch) >= 0.3 || Math.abs(face.angleArray.roll) >= 0.2 || Math.abs(face
|
||||||
|
.angleArray.yaw) >= 0.2) {
|
||||||
|
this.frames = []
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (Math.abs(face.confArray.global) <= 0.8 || Math.abs(face.confArray.leftEye) <= 0.8 || Math.abs(
|
||||||
|
face.confArray.mouth) <=
|
||||||
|
0.8 || Math.abs(face.confArray.nose) <= 0.8 || Math.abs(face.confArray.rightEye) <= 0.8) {
|
||||||
|
this.tip = '请勿遮挡五官'
|
||||||
|
this.frames = []
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.tip = '正在核验,请保持'
|
||||||
|
this.frames.push('正')
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export default StraightenHead
|
||||||
@ -0,0 +1,9 @@
|
|||||||
|
import NodHead from "./NodHead.js"
|
||||||
|
import ShakeHead from "./ShakeHead.js"
|
||||||
|
import StraightenHead from "./StraightenHead.js"
|
||||||
|
|
||||||
|
export {
|
||||||
|
NodHead,
|
||||||
|
ShakeHead,
|
||||||
|
StraightenHead,
|
||||||
|
}
|
||||||
@ -0,0 +1,436 @@
|
|||||||
|
<template>
|
||||||
|
<view class="modal bottom-modal" :class="show ? 'show' : ''">
|
||||||
|
<camera flash="off" device-position="front" resolution="high" @stop="stop" @error="error"
|
||||||
|
style="width: 100vw;height: 100vh; position: fixed;top: 0;left: 0;">
|
||||||
|
<cover-view class="cover">
|
||||||
|
<cover-view class="cover-top cover-item">
|
||||||
|
<cover-view class="bar bg-white" style="justify-content: flex-start;">
|
||||||
|
<cover-view class="action" @tap="close">关闭
|
||||||
|
</cover-view>
|
||||||
|
</cover-view>
|
||||||
|
<template>
|
||||||
|
<slot>
|
||||||
|
<cover-view v-if="isDev && face">
|
||||||
|
<cover-view :style="Math.abs(face.pitch)>0.5?'color:red':''">
|
||||||
|
{{ face.pitch ? face.pitch.toFixed(2): 'null'}}
|
||||||
|
</cover-view>
|
||||||
|
<cover-view :style="Math.abs(face.roll)>0.5?'color:red':''">
|
||||||
|
{{ face.roll ? face.roll.toFixed(2):'null'}}
|
||||||
|
</cover-view>
|
||||||
|
<cover-view :style="Math.abs(face.yaw)>0.5?'color:red':''">
|
||||||
|
{{ face.yaw ? face.yaw.toFixed(2):'null'}}
|
||||||
|
</cover-view>
|
||||||
|
</cover-view>
|
||||||
|
</slot>
|
||||||
|
</template>
|
||||||
|
<cover-view class="detectInfo">{{ isSuccess ? '人脸检测成功' : tipsText}}</cover-view>
|
||||||
|
</cover-view>
|
||||||
|
<cover-view class="camera">
|
||||||
|
<cover-image class="camera" src="../../static/images/cover.png"></cover-image>
|
||||||
|
</cover-view>
|
||||||
|
<cover-view class="cover-bottom cover-item"></cover-view>
|
||||||
|
</cover-view>
|
||||||
|
</camera>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// 人脸检测
|
||||||
|
/**
|
||||||
|
* @event {Function} photoChange 拍照完成事件
|
||||||
|
* @event {Function} detectFailed 人脸检测失败
|
||||||
|
* @event {Function} detectOver 人脸检测结束
|
||||||
|
* @method {Function} initData 初始化人脸检测
|
||||||
|
*/
|
||||||
|
import {
|
||||||
|
NodHead,
|
||||||
|
StraightenHead,
|
||||||
|
ShakeHead
|
||||||
|
} from './actions/index.js'
|
||||||
|
import ActionContainer from './ActionContainer.js'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'face-detect',
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
show: false,
|
||||||
|
tipsText: '检测不到人脸',
|
||||||
|
isSuccess: false, //是否检测完成
|
||||||
|
face: {},
|
||||||
|
actionsList: null,
|
||||||
|
context: "",
|
||||||
|
tipsTextCss: "tipsTextCss",
|
||||||
|
listener: null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
buildActionContainer: Function,
|
||||||
|
actions: () => {
|
||||||
|
return []
|
||||||
|
},
|
||||||
|
isDev: false,
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
// uni.initFaceDetect()
|
||||||
|
// this.onCameraFrame()
|
||||||
|
},
|
||||||
|
destroyed() {
|
||||||
|
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
setMaxFace(faceData) {
|
||||||
|
const faces = faceData.faceInfo // 获取到所有人脸信息
|
||||||
|
let maxFaceIndex = 0
|
||||||
|
let maxFaceSize = 0
|
||||||
|
// 遍历所有人脸信息,找到最大的人脸
|
||||||
|
for (let i = 0; i < faces.length; i++) {
|
||||||
|
const face = faces[i]
|
||||||
|
const faceSize = face.width * face.height
|
||||||
|
if (faceSize > maxFaceSize) {
|
||||||
|
maxFaceSize = faceSize
|
||||||
|
maxFaceIndex = i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 返回最大的那张脸的坐标信息
|
||||||
|
const maxFace = faces[maxFaceIndex]
|
||||||
|
faceData.faceInfo = [maxFace]
|
||||||
|
},
|
||||||
|
onCameraFrame() {
|
||||||
|
uni.initFaceDetect()
|
||||||
|
let time = new Date().getTime()
|
||||||
|
this.context = uni.createCameraContext()
|
||||||
|
this.listener = this.context.onCameraFrame((frame) => {
|
||||||
|
uni.faceDetect({
|
||||||
|
frameBuffer: frame.data,
|
||||||
|
width: frame.width,
|
||||||
|
height: frame.height,
|
||||||
|
enablePoint: true,
|
||||||
|
enableConf: true,
|
||||||
|
enableAngle: true,
|
||||||
|
enableMultiFace: true,
|
||||||
|
success: (faceData) => {
|
||||||
|
time = new Date().getTime()
|
||||||
|
this.setMaxFace(faceData)
|
||||||
|
this.showData(faceData)
|
||||||
|
this.actionsList.next(faceData)
|
||||||
|
this.tipsText = this.actionsList.tip
|
||||||
|
},
|
||||||
|
fail: (err) => {
|
||||||
|
if ((time + 10 * 1000) < new Date().getTime()) {
|
||||||
|
this.tipsText = '检测不到人脸'
|
||||||
|
this.cameraError()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (err.x == -1 || err.y == -1) {
|
||||||
|
this.tipsText = '检测不到人脸'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
this.listener.start()
|
||||||
|
},
|
||||||
|
error() {
|
||||||
|
this.tipsText = '相机异常'
|
||||||
|
this.cameraError()
|
||||||
|
},
|
||||||
|
stop() {
|
||||||
|
this.tipsText = '相机异常'
|
||||||
|
this.cameraError()
|
||||||
|
},
|
||||||
|
// 核验失败
|
||||||
|
cameraError(e) {
|
||||||
|
this.t = setTimeout(() => {
|
||||||
|
clearTimeout(this.t)
|
||||||
|
this.hideModal()
|
||||||
|
this.$emit('detectFailed')
|
||||||
|
}, 2000);
|
||||||
|
},
|
||||||
|
close() {
|
||||||
|
clearTimeout(this.t)
|
||||||
|
this.hideModal()
|
||||||
|
this.$emit('detectFailed')
|
||||||
|
},
|
||||||
|
// 关闭
|
||||||
|
hideModal() {
|
||||||
|
uni.stopFaceDetect()
|
||||||
|
this.show = false
|
||||||
|
this.tipsText = '检测不到人脸'
|
||||||
|
this.face = {}
|
||||||
|
this.isSuccess = false
|
||||||
|
},
|
||||||
|
// 拍照
|
||||||
|
takePhoto() {
|
||||||
|
this.context.takePhoto({
|
||||||
|
quality: 'low',
|
||||||
|
success: (res) => {
|
||||||
|
this.$emit('photoChange', res.tempImagePath)
|
||||||
|
},
|
||||||
|
fail: (e) => {
|
||||||
|
console.log(e)
|
||||||
|
},
|
||||||
|
complete: (e) => {
|
||||||
|
console.log(e)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
// 检测完成
|
||||||
|
detectOver() {
|
||||||
|
this.isSuccess = true
|
||||||
|
let t = setTimeout(() => {
|
||||||
|
this.hideModal()
|
||||||
|
clearTimeout(t)
|
||||||
|
this.$emit('detectOver')
|
||||||
|
}, 3000);
|
||||||
|
},
|
||||||
|
|
||||||
|
initData() {
|
||||||
|
uni.getSetting({
|
||||||
|
success: (res) => {
|
||||||
|
if (res.authSetting['scope.camera']===true) {
|
||||||
|
this.onCameraFrame()
|
||||||
|
this.faceDetect()
|
||||||
|
} else if(res.authSetting['scope.camera']=== false) {
|
||||||
|
this.getCameraAuth()
|
||||||
|
}else{
|
||||||
|
this.onCameraFrame()
|
||||||
|
this.faceDetect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
getCameraAuth() {
|
||||||
|
uni.showModal({
|
||||||
|
title: '温馨提示',
|
||||||
|
content: '需要获取您摄像头权限才能更好的为您服务!是否授权摄像头权限?',
|
||||||
|
confirmText: '授权',
|
||||||
|
confirmColor: '#f94218',
|
||||||
|
success: (res) => {
|
||||||
|
if (res.confirm) {
|
||||||
|
// 选择弹框内授权
|
||||||
|
uni.openSetting({
|
||||||
|
success: (res) => {
|
||||||
|
if (res.authSetting[
|
||||||
|
'scope.camera'
|
||||||
|
]) {
|
||||||
|
this.onCameraFrame()
|
||||||
|
this.faceDetect()
|
||||||
|
} else {
|
||||||
|
this.tipsText = "您未授权摄像头权限"
|
||||||
|
this.cameraError()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else if (res.cancel) {
|
||||||
|
this.tipsText = "您未授权摄像头权限"
|
||||||
|
this.cameraError()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
showData(faceData) {
|
||||||
|
this.$emit("showData", faceData)
|
||||||
|
if (this.isDev) {
|
||||||
|
let face = faceData.faceInfo[0].angleArray
|
||||||
|
this.face = face
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
buildActions() {
|
||||||
|
if (this.buildActionContainer) {
|
||||||
|
return this.buildActionContainer()
|
||||||
|
}
|
||||||
|
let actions = []
|
||||||
|
if (!this.actions?.length) {
|
||||||
|
let nodHead = new NodHead()
|
||||||
|
const fun = (state) => {
|
||||||
|
if (state === 'success') {
|
||||||
|
this.takePhoto()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let straightenHead = new StraightenHead(10, fun)
|
||||||
|
let straightenHead2 = new StraightenHead(10)
|
||||||
|
let shakeHead = new ShakeHead()
|
||||||
|
actions = [straightenHead2, nodHead, shakeHead, straightenHead]
|
||||||
|
} else {
|
||||||
|
actions = this.actions
|
||||||
|
}
|
||||||
|
let act = new ActionContainer(actions)
|
||||||
|
act.end(() => {
|
||||||
|
this.detectOver()
|
||||||
|
}).fail(() => {
|
||||||
|
this.cameraError()
|
||||||
|
})
|
||||||
|
return act
|
||||||
|
},
|
||||||
|
|
||||||
|
// 初始化人脸检测
|
||||||
|
faceDetect() {
|
||||||
|
this.show = true
|
||||||
|
this.actionsList = this.buildActions()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.modal {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
z-index: 1110;
|
||||||
|
opacity: 0;
|
||||||
|
outline: 0;
|
||||||
|
text-align: center;
|
||||||
|
/* -ms-transform: scale(1.185);
|
||||||
|
transform: scale(1.185); */
|
||||||
|
backface-visibility: hidden;
|
||||||
|
perspective: 2000rpx;
|
||||||
|
background: rgba(0, 0, 0, 0.6);
|
||||||
|
/* transition: all 0.3s ease-in-out 0s; */
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal::before {
|
||||||
|
content: "\200B";
|
||||||
|
display: inline-block;
|
||||||
|
height: 100%;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal.show {
|
||||||
|
opacity: 1;
|
||||||
|
overflow-x: hidden;
|
||||||
|
overflow-y: auto;
|
||||||
|
pointer-events: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal.bottom-modal::before {
|
||||||
|
vertical-align: bottom;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal.bottom-modal .dialog {
|
||||||
|
width: 100%;
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal.bottom-modal {
|
||||||
|
margin-bottom: -1000rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal.bottom-modal.show {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog {
|
||||||
|
position: fixed;
|
||||||
|
display: inline-block;
|
||||||
|
vertical-align: middle;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
/* width: 680rpx; */
|
||||||
|
height: 100vh;
|
||||||
|
width: 100vw;
|
||||||
|
max-width: 100%;
|
||||||
|
background-color: #f8f8f8;
|
||||||
|
border-radius: 10rpx;
|
||||||
|
overflow: hidden;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bar {
|
||||||
|
display: flex;
|
||||||
|
position: relative;
|
||||||
|
align-items: center;
|
||||||
|
min-height: 180rpx;
|
||||||
|
height: 180rpx;
|
||||||
|
padding: 0rpx 40rpx;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bg-white {
|
||||||
|
background-color: #ffffff;
|
||||||
|
color: #666666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action {
|
||||||
|
color: #0081ff;
|
||||||
|
font-size: 35rpx;
|
||||||
|
padding: 10rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detectInfo {
|
||||||
|
padding: 20rpx 0rpx;
|
||||||
|
font-size: 34rpx;
|
||||||
|
text-align: center;
|
||||||
|
animation-duration: 1.5s;
|
||||||
|
color: #000000;
|
||||||
|
z-index: 999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.faceContent {
|
||||||
|
height: 700rpx;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.successImage {
|
||||||
|
overflow: hidden;
|
||||||
|
width: 600rpx;
|
||||||
|
height: 600rpx;
|
||||||
|
border-radius: 50%;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 50%;
|
||||||
|
z-index: 999;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tipsTextCss {
|
||||||
|
animation: 1.5s tipsTextAnimation;
|
||||||
|
animation-duration: 1.5s;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes tipsTextAnimation {
|
||||||
|
0% {
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
20% {
|
||||||
|
transform: scale(1.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
70% {
|
||||||
|
transform: scale(1.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.cover {
|
||||||
|
width: 100vw;
|
||||||
|
height: 100vh;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cover-item {
|
||||||
|
flex: 1;
|
||||||
|
width: 100%;
|
||||||
|
background-color: #FFFFFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
.camera {
|
||||||
|
width: 100vw;
|
||||||
|
height: 100vw;
|
||||||
|
transform: scale(1.05);
|
||||||
|
z-index: 999;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
81
uni_modules/face-bio-assay/package.json
Normal file
81
uni_modules/face-bio-assay/package.json
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
{
|
||||||
|
"id": "face-bio-assay",
|
||||||
|
"displayName": "face-bio-assay",
|
||||||
|
"version": "1.1.1",
|
||||||
|
"description": "纯前端简易版活体检测,易扩展,开发者可通过自定义动作 添加到该容器中管理。也可使用已有动作(点头、摇头、平视)进行排列组合创建动作组",
|
||||||
|
"keywords": [
|
||||||
|
"人脸识别",
|
||||||
|
"活体检测",
|
||||||
|
"易扩展"
|
||||||
|
],
|
||||||
|
"repository": "",
|
||||||
|
"engines": {
|
||||||
|
},
|
||||||
|
"dcloudext": {
|
||||||
|
"category": [
|
||||||
|
"前端组件",
|
||||||
|
"通用组件"
|
||||||
|
],
|
||||||
|
"sale": {
|
||||||
|
"regular": {
|
||||||
|
"price": "0.00"
|
||||||
|
},
|
||||||
|
"sourcecode": {
|
||||||
|
"price": "0.00"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"contact": {
|
||||||
|
"qq": ""
|
||||||
|
},
|
||||||
|
"declaration": {
|
||||||
|
"ads": "无",
|
||||||
|
"data": "无",
|
||||||
|
"permissions": "无"
|
||||||
|
},
|
||||||
|
"npmurl": ""
|
||||||
|
},
|
||||||
|
"uni_modules": {
|
||||||
|
"dependencies": [],
|
||||||
|
"encrypt": [],
|
||||||
|
"platforms": {
|
||||||
|
"cloud": {
|
||||||
|
"tcb": "y",
|
||||||
|
"aliyun": "y"
|
||||||
|
},
|
||||||
|
"client": {
|
||||||
|
"Vue": {
|
||||||
|
"vue2": "y",
|
||||||
|
"vue3": "u"
|
||||||
|
},
|
||||||
|
"App": {
|
||||||
|
"app-vue": "u",
|
||||||
|
"app-nvue": "u"
|
||||||
|
},
|
||||||
|
"H5-mobile": {
|
||||||
|
"Safari": "u",
|
||||||
|
"Android Browser": "u",
|
||||||
|
"微信浏览器(Android)": "u",
|
||||||
|
"QQ浏览器(Android)": "u"
|
||||||
|
},
|
||||||
|
"H5-pc": {
|
||||||
|
"Chrome": "u",
|
||||||
|
"IE": "u",
|
||||||
|
"Edge": "u",
|
||||||
|
"Firefox": "u",
|
||||||
|
"Safari": "u"
|
||||||
|
},
|
||||||
|
"小程序": {
|
||||||
|
"微信": "y",
|
||||||
|
"阿里": "u",
|
||||||
|
"百度": "u",
|
||||||
|
"字节跳动": "u",
|
||||||
|
"QQ": "u"
|
||||||
|
},
|
||||||
|
"快应用": {
|
||||||
|
"华为": "u",
|
||||||
|
"联盟": "u"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
185
uni_modules/face-bio-assay/readme.md
Normal file
185
uni_modules/face-bio-assay/readme.md
Normal file
@ -0,0 +1,185 @@
|
|||||||
|
# props
|
||||||
|
|参数名|类型|默认值|可选择值|描述|
|
||||||
|
|----|----|----|---|---|
|
||||||
|
|buildActionContainer|Function|null|""|创建ActionContainer类的函数,要求返回值必需是ActionContainer类|
|
||||||
|
|actions|Array|[]|""|动作组(目前已有动作:点头,摇头,平视)|
|
||||||
|
|isDev|boolen|false|true|是否是开发者模式,开启后可显示人脸的三个角度|
|
||||||
|
|
||||||
|
# emits
|
||||||
|
|方法名|参数|描述|
|
||||||
|
|----|---|---|
|
||||||
|
|detectFailed|[]|核验失败|
|
||||||
|
|photoChange|[url:'照片路径']|拍照后的回调|
|
||||||
|
|detectOver|[]|检测完成|
|
||||||
|
|showData|[faceData:'人脸数据']|每一帧的人脸数据|
|
||||||
|
# slots
|
||||||
|
|插槽名|参数|描述|
|
||||||
|
|----|---|---|
|
||||||
|
|default|无|用户可配合showData钩子展示人脸数据方便调试|
|
||||||
|
|
||||||
|
# 方法
|
||||||
|
## initData
|
||||||
|
开始进行人脸核验 使用案例:
|
||||||
|
```
|
||||||
|
//html
|
||||||
|
<face-bio-assay ref="faceDetect" ></face-bio-assay>
|
||||||
|
//js
|
||||||
|
//调用
|
||||||
|
this.$refs.faceDetect.initData()
|
||||||
|
```
|
||||||
|
## takePhoto
|
||||||
|
拍照获取照片,配合动作使用:
|
||||||
|
```
|
||||||
|
在每个动作创建时第二个参数是动作完成的回调 如平视动作的使用:
|
||||||
|
const fun = (state) => {
|
||||||
|
//state 有成功success和fail,ing(进行时不会触发该函数,忽略)
|
||||||
|
if (state === 'success') {
|
||||||
|
this.$refs.faceDetect.takePhoto() //调用拍照方法
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let straightenHead = new StraightenHead(10, fun)
|
||||||
|
```
|
||||||
|
|
||||||
|
# 使用建议
|
||||||
|
```
|
||||||
|
ios中bug解决方案:
|
||||||
|
在ios中,二次进入使用该组件有问题,解决办法:单独将该组件作为一个页面(或者下载demo查看),代码如下:
|
||||||
|
|
||||||
|
//主页面
|
||||||
|
<template>
|
||||||
|
<view>
|
||||||
|
<button type="default" @click="init">人脸检测</button>
|
||||||
|
<image mode="aspectFit" :src="imgSrc" />
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
imgSrc: '',
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
init(){
|
||||||
|
uni.navigateTo({
|
||||||
|
url:"/pages/face/face",
|
||||||
|
events:{
|
||||||
|
data: (path) => {
|
||||||
|
this.imgSrc = path
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//face.vue页
|
||||||
|
<template>
|
||||||
|
<view>
|
||||||
|
<face-bio-assay :isDev="false" ref="faceDetect" @detectFailed="detectFailed" @photoChange="photoChange">
|
||||||
|
</face-bio-assay>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import faceBioAssay from '@/uni_modules/face-bio-assay/components/face-bio-assay/face-bio-assay.vue'
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
faceBioAssay,
|
||||||
|
},
|
||||||
|
onLoad(option) { //一定要onLoad,onShow在进入相机授权页面退回时会再次触发
|
||||||
|
this.$refs.faceDetect.initData()
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
detectFailed() {
|
||||||
|
uni.showToast({
|
||||||
|
title: "人脸核验失败~",
|
||||||
|
icon: 'none'
|
||||||
|
})
|
||||||
|
uni.navigateBack()
|
||||||
|
},
|
||||||
|
photoChange(path) {
|
||||||
|
uni.navigateBack()
|
||||||
|
this.getOpenerEventChannel().emit('data',path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
# 类的使用
|
||||||
|
## Action
|
||||||
|
动作类,开发者可继承该类重写 takeFrame 方法 如:
|
||||||
|
```
|
||||||
|
//点头动作
|
||||||
|
import Action from "./Action.js"
|
||||||
|
class NodHead extends Action {
|
||||||
|
constructor(second=10,fun) {
|
||||||
|
//时间限制(s),结束时回调,完成次数(limit),基本提示
|
||||||
|
super(second,fun,1,'请点头')
|
||||||
|
this.maxPitch = 0
|
||||||
|
this.minPitch = 0
|
||||||
|
}
|
||||||
|
takeFrame(faceData){
|
||||||
|
let face = faceData.faceInfo[0]
|
||||||
|
if(face.angleArray.pitch>this.maxPitch){
|
||||||
|
this.maxPitch = face.angleArray.pitch
|
||||||
|
}
|
||||||
|
if(face.angleArray.pitch<this.minPitch){
|
||||||
|
this.minPitch = face.angleArray.pitch
|
||||||
|
}
|
||||||
|
if(Math.abs(this.minPitch-this.maxPitch)>0.45 && (this.minPitch || this.maxPitch) ){
|
||||||
|
this.frames.push('点头') //frames 完成的帧数组 根据该数组长度和limit判断是否完成
|
||||||
|
this.maxPitch = 0
|
||||||
|
this.minPitch = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default NodHead
|
||||||
|
```
|
||||||
|
## ActionContainer
|
||||||
|
动作容器的使用案例
|
||||||
|
```
|
||||||
|
buildActions() {
|
||||||
|
if (this.buildActionContainer) {
|
||||||
|
return this.buildActionContainer()
|
||||||
|
}
|
||||||
|
let actions = []
|
||||||
|
if (!this.actions?.length) {
|
||||||
|
let nodHead = new NodHead()
|
||||||
|
const fun = (state) => {
|
||||||
|
if (state === 'success') {
|
||||||
|
this.takePhoto() //拍照
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let straightenHead = new StraightenHead(10, fun) //平视 结束拍照
|
||||||
|
let straightenHead2 = new StraightenHead(10) //平视
|
||||||
|
let shakeHead = new ShakeHead()
|
||||||
|
actions = [straightenHead2, nodHead, shakeHead, straightenHead]
|
||||||
|
} else {
|
||||||
|
actions = this.actions
|
||||||
|
}
|
||||||
|
//new ActionContainer(actions,...)
|
||||||
|
//动作组 actions
|
||||||
|
//起始动作下标 index
|
||||||
|
//初始提示 tip
|
||||||
|
//绑定 完成所有动作的回调 endFun
|
||||||
|
//绑定 动作进行中 ingFun
|
||||||
|
//绑定 动作完成时 successFun
|
||||||
|
//绑定 动作失败时 failFun
|
||||||
|
let act = new ActionContainer(actions)
|
||||||
|
act.end(() => { //也可用该方法绑定endFun方法
|
||||||
|
this.detectOver()
|
||||||
|
}).fail(() => { //也可用该方法绑定failFun方法
|
||||||
|
this.cameraError()
|
||||||
|
})
|
||||||
|
//....其他方法类似
|
||||||
|
return act
|
||||||
|
}
|
||||||
|
```
|
||||||
BIN
uni_modules/face-bio-assay/static/images/cover.png
Normal file
BIN
uni_modules/face-bio-assay/static/images/cover.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.8 KiB |
Reference in New Issue
Block a user