步骤
- 打开相机
- 识别是否是人像
- 是人像再拍照(将照片传给后端)
实现方式: wx.faceDetect
参考文件:
- AI / 人脸检测 / wx.faceDetect (qq.com)
- 微信小程序人脸识别/采集改进版(wx.faceDetect)-支持人脸中心位置校验,人脸图片采集(速度更快),人脸搜索_微信小程序人脸采集-CSDN博客
face.wxml
<view>
<!-- 选择服务对象 -->
<view class="bg-white pt-32 pr-32 pb-26 radius-25" wx:if="{{sourceType==1}}">
<van-field required value="{{serviceObjectName }}" is-link readonly label="服务对象" placeholder="请选择服务对象" bind:tap="onServiceObjectClick" />
<view bindtap="faceEnter" wx:if="{{serviceObjectName}}" class="blue-bg mg-20 pd-20 bg-blue radius-20 center">确认录入人脸
</view>
</view>
<!-- 服务对象下拉 -->
<van-popup show="{{ serviceObjectShow }}" bind:close="handleClose" round position="bottom">
<van-search value="{{ nameKeyWord }}" placeholder="请输入名称" use-action-slot bind:change="onSearch" bind:search="getObjectList">
</van-search>
<van-cascader value="{{ serviceObjectId }}" title="请选择对象" options="{{ serviceObjectList }}" bind:close="onServiceObjectClose" bind:change="onServiceObjectFinish" />
</van-popup>
<!--
**注意点:camera或camera外层view需要加wx:if,否则会出现黑屏的情况,报错信息是'[Component] <camera>: 一个页面只能插入一个',其次注意写法,wx:if="{{(isShowCamera&&sourceType==1)||sourceType!=1}}"IOS手机会出现黑屏,需要改为wx:if="{{isShowCamera&&(sourceType==1||sourceType!=1)}}"**
-->
<view class="camera-div" wx:if="{{isShowCamera&&(sourceType==1||sourceType!=1)}}">
<view class="flex_r white pd-32">
<!-- 镜头翻转 -->
<view bindtap="reverse">镜头翻转</view>
<!-- <view bindtap="clickScanCode">扫二维码</view> -->
</view>
<!-- resolution:获取人脸图片后的清晰度 low:低 -->
<camera device-position="{{devicePosition ?'front': 'back'}}" class="camera" flash="off" resolution='low' />
<view class="title white" wx:if="{{tipsText}}">识别提示:{{ tipsText }}</view>
临时文件:
<image src="{{faceImage}}" mode="" />
base64文件:
<image src="data:image/png;base64,{{base64}}" style="width:{{screenWidth}}px;height:{{screenHeight}}px" />
后端返回的图片:
<image src="{{info.facePicture}}" />
</view>
<van-toast id="van-toast" />
</view>
face.js
import Toast from '@vant/weapp/toast/toast';
Page({
/**
* 页面的初始数据
*/
data() {
return {
info: {},
sourceType: '',//1添加人脸 2比对人脸 3人脸支付 4签退
// 选择服务对象
nameKeyWord: '',
serviceObjectId: '',
serviceObjectList: [],
isShowCamera: false,
// 经纬度
latitude: '',
longitude: '',
isShow: false,
tipsText: '', // 错误文案提示
tempImg: '', // 本地图片路径
cameraEngine: null, // 相机引擎
devicePosition: false, // 摄像头朝向
isAuthCamera: true, // 是否拥有相机权限
isVerify: false,
translateY: -24,
timer: null,
base64: "",
personId: "",
isFlag2: true,
screenWidth: 375,
screenHeight: 640,
faceImage: '', //人脸图片
// 订单ID
orderId: '',
pictureUrl: '',
}
},
onLoad(options) {
this.clearTimer();
this.setData({
cameraEngine: wx.createCameraContext(),
isShow: true,
sourceType: options.sourceType,
pictureURL: pictureURL,
})
if (options.sourceType == 3) {
this.setData({
orderId: options.orderId,
serviceObjectId: options.serviceObjectId,
pictureUrl: options.pictureUrl
})
}
this.getBaiduLatLon().then(res => {
this.setData({
latitude: res.lat,
longitude: res.lon,
})
}).catch(err => {
console.error(err);
});
if (options.sourceType != 1) {
this.setData({ isShowCamera: true }, () => {
this.initData();
this.lineAnimation();
});
}
},
onServiceObjectClick() {
this.setData({
nameKeyWord: '',
isVerify: false
})
this.getObjectList()
},
// 选择服务对象
onSearch() {
this.setData({
nameKeyWord: e.detail
})
this.getObjectList()
},
handleClose() { },
getObject() {
var that = this
return new Promise((resolve, reject) => {
setTimeout(() => {
let newArr = []
wx.http('/', { serviceObjectName: this.data.nameKeyWord }).then(data => {
newArr = data.rows.map((item) => {
return {
text: `${item.serviceObjectName}(${item.phone})`,
value: String(item.serviceObjectId),
serviceObjectType: item.serviceObjectType
}
})
that.setData({
serviceObjectList: newArr,
})
})
resolve({ code: 200, data: newArr });
}, 1000);
});
},
// 获取服务对象列表
async getObjectList() {
let info = await this.getObject()
if (info.code == 200) {
this.setData({
serviceObjectList: info.data,
serviceObjectShow: true
});
}
},
onServiceObjectClose() {
this.setData({
serviceObjectShow: false,
});
},
onServiceObjectFinish(e) {
const {
selectedOptions,
value
} = e.detail;
const text = selectedOptions[0].text
this.setData({
serviceObjectName: text,
serviceObjectId: value,
serviceObjectShow: false
})
},
//获取经纬度(百度)
getBaiduLatLon() {
this.setData({
latitude: '',
longitude: '',
})
var that = this
return new Promise((resolve, reject) => {
var util = require('/utils/WSCoordinate.js')
let params = {}
wx.getLocation({
type: 'gcj02', //gcj02 wgs84
success(res) {
params.lon = util.transformFromGCJToBaidu(res.latitude, res.longitude).longitude
params.lat = util.transformFromGCJToBaidu(res.latitude, res.longitude).latitude
resolve({
code: 200,
lat: params.lat,
lon: params.lon
}) //成功回调
that.setData({
latitude: params.lat,
longitude: params.lon,
})
},
fail(err) {
if (err.errCode == 2) {
Toast.fail('请打开手机定位!');
} else if (!err.errCode) {
Toast.fail('获取定位失败!');
}
}
})
})
},
onUnload() {
this.clearTimer();
},
onHide() {
this.clearTimer();
},
clearTimer() {
if (this.data.timer) {
clearInterval(this.data.timer);
this.setData({
timer: null,
})
}
this.setData({
isFlag2: false,
})
},
faceEnter() {
this.setData({ isShowCamera: true }, () => {
this.initData();
this.lineAnimation();
});
},
// 初始化相机引擎
initData() {
// 1、初始化人脸识别
wx.initFaceDetect();
// 2、创建 camera 上下文 CameraContext 对象
// 在faceEnter或onLoad中
// 3、获取 Camera 实时帧数据
const listener = this.data.cameraEngine.onCameraFrame((frame) => {
console.log(888888888, frame.data, frame.width, frame.height)
if (this.data.isVerify) return
//动态设置canvas的宽高,不设置会导致部分机型人脸不完整导致不能识别-----很重要!很重要!很重要!
if (this.data.isFlag2) {
this.setData({
isFlag2: false,
screenWidth: frame.width,
screenHeight: frame.height,
})
}
// 4、人脸识别,使用前需要通过 wx.initFaceDetect 进行一次初始化,推荐使用相机接口返回的帧数据
wx.faceDetect({
frameBuffer: frame.data,
width: frame.width,
height: frame.height,
enablePoint: true,
enableConf: true,
enableAngle: true,
enableMultiFace: true,
success: async (faceData) => {
console.log(1111, faceData)
let face = faceData.faceInfo[0]
if (face.x == -1 || face.y == -1) {
this.setData({
tipsText: '检测不到人'
})
}
if (faceData.faceInfo.length > 1) {
this.setData({
tipsText: '请保证只有一个人'
})
} else {
const {
pitch,
roll,
yaw
} = face.angleArray;
const standard = 0.5
if (Math.abs(pitch) >= standard || Math.abs(roll) >= standard ||
Math.abs(yaw) >= standard) {
this.setData({
tipsText: '请平视摄像头'
})
} else if (face.confArray.global <= 0.8 || face.confArray.leftEye <=
0.8 || face.confArray.mouth <= 0.8 || face.confArray.nose <= 0.8 ||
face.confArray.rightEye <= 0.8) {
this.setData({
tipsText: '请勿遮挡五官'
})
} else {
if (this.data.isVerify) return
//人脸位置校验
var centerWidth = 250;
var centerHeight = 250;
if (face.x > (frame.width - centerWidth) / 2 && face.x < (frame
.width - centerWidth) / 2 + centerWidth && face.y > (frame
.height - centerHeight) / 2 && face.y < (frame.height -
centerHeight) / 2 + centerHeight) {
this.setData({
tipsText: '校验中...',
isVerify: true
})
// 太快获取的人脸可能比较抽象,给用户一个准备时间
setTimeout(async () => {
let img = await this.takePhoto();
// let img = await this.takePhoto(frame);
this.setData({
base64: img
})
this.searchUserFace();
}, 500)
} else {
this.setData({
tipsText: '请将人脸对准中心位置'
})
}
}
}
},
fail: (err) => {
console.log(2222, err)
if (this.data.isVerify) return
if (err.x == -1 || err.y == -1) {
this.setData({
tipsText: '检测不到人'
})
} else {
this.setData({
tipsText: err.errMsg || '网络错误,请退出页面重试'
})
}
},
})
})
// 5、开始监听帧数据
listener.start()
this.setData({
listener: listener
})
},
reverse() {
let a = this.data.devicePosition
this.setData({
devicePosition: !a,
isVerify: false
})
},
clickScanCode() {
// 只允许通过相机扫码
// #ifdef MP-WEIXIN
wx.scanCode({
onlyFromCamera: true,
success: (res) => {
var data = JSON.parse(res.result.replace(/\ufeff/g, ""));
}
});
// #endif
},
// 获取图片
async takePhoto() {
return new Promise((resolve, reject) => {
// const ctx = wx.createCameraContext();
const ctx = this.data.cameraEngine
ctx.takePhoto({
quality: 'high',
success: (res) => {
console.log(res, 'takePhoto')
let url = res.tempImagePath
this.setData({
faceImage: url
})
// resolve(url);
// 调用函数将图片转换为Base64
const fs = wx.getFileSystemManager();
fs.readFile({
filePath: url,
encoding: 'base64', // 指定编码格式为base64
success: function (res) {
console.log('图片的Base64编码是: ', res.data);
resolve(res.data);
},
fail: function (err) {
console.error('转换Base64失败', err);
}
});
},
fail: (err) => {
resolve({ code: 500 });
}
})
})
},
searchUserFace() {
// 1.人脸识别错误 把isVerify设置为false继续识别
// 2.人脸识别成功,做对应的逻辑
let { sourceType, orderId, base64, serviceObjectId, pictureUrl, latitude, longitude } = this.data
let params = {
faceBase: base64,
serviceObjectId: serviceObjectId,
latitude: latitude,
longitude: longitude
}
let url = sourceType == 1 ? '/add' : '/check'
wx.http(url, params, "POST").then(res => {
wx.stopFaceDetect(); //关闭人脸识别
this.data.listener.stop(); //关闭camera摄像头监听
this.setData({
tipsText: '校验成功'
})
}).catch(err => {
this.setData({
tipsText: err.msg
})
setTimeout(() => {
this.setData({
isVerify: false
})
}, 500)
})
},
clickPushDetail() {
// 跳转到其他页面
},
lineAnimation() {
if (this.data.timer) return
this.setData({
timer: setInterval(() => {
this.setData({
translateY: this.data.translateY + 8
})
if (this.data.translateY >= 460) {
this.setData({
translateY: 10
})
}
}, 40)
})
},
/**
* 页面相关事件处理函数--监听用户下拉动作
*/
onPullDownRefresh() {
},
/**
* 页面上拉触底事件的处理函数
*/
onReachBottom() {
},
/**
* 用户点击右上角分享
*/
onShareAppMessage() {
}
})