需求:
安卓原生app端接入腾讯人脸核身sdk,采集人脸信息和账号绑定。用户使用同一账号登录微信公众号h5端,h5采集人脸信息,调用腾讯人脸比对接口,和原生app端采集的人脸信息比对,是否是同一个人,然后再进行下一步操作。
H5实现思路:
1.如何调用移动端设备摄像头
2.使用tracking.js捕捉人脸
3.对人脸进行拍照,描绘到canvas画布上面
4.将照片转换成base64格式
5.调用后台接口,后台调用腾讯人脸比对接口进行比对。(由于调用腾讯人脸比对接口需要签名,签名在后台做比较合适)
代码以及注释
1.下载tracking.js 网址:https://trackingjs.com 将需要的js文件复制到静态资源文件夹
2.使用video标签调用手机摄像头,playsinline、webkit-playsinline属性非常重要,如果不写会掉不起来苹果手机的摄像头。微信开发者文档上面有明确的说明,喜欢钻研的同学可以到微信开发者文档中查看文档。video和canvas的宽高的写法是为了适配手机屏幕的大小。
```
请保持人像在取景框内
class="img-cover"
src="@/assets/img/face/yuanxingtouming.png"
alt=""
/>
{{ scanTip }}
```
3.获取媒体设备,video开始播放
```
import tracking from "@/assets/js/tracking-min.js";
import "@/assets/js/data/face-min.js";
import "@/assets/js/data/eye-min.js";
import "@/assets/js/data/mouth-min.js";
import { faceInfo, conFace } from "@/request/api/my.js";
export default {
data() {
return {
screenSize: {
width: window.screen.width,
height: window.screen.height,
},
URL: null,
streamIns: null, // 视频流
showContainer: true, // 显示
tracker: null,
tipFlag: false, // 提示用户已经检测到
flag: false, // 判断是否已经拍照
context: null, // canvas上下文
profile: [], // 轮廓
removePhotoID: null, // 停止转换图片
scanTip: "人脸识别中...", // 提示文字
imgUrl: "",
canvas: null,
trackertask: null,
vwidth: "266",
vheight: "266",
cwidth: "266",
cheight: "266",
userInfo: {},
orderData: {},
};
},
mounted() {
//设置video canvas宽高
const scale = this.screenSize.width / 375;
this.vwidth = 266 * scale;
this.vheight = 266 * scale;
this.cwidth = 266 * scale;
this.cheight = 266 * scale;
this.playVideo();
},
methods: {
playVideo() {
this.getUserMedia(
{
//摄像头拍摄的区域
video: {
width: 500,
height: 500,
facingMode: "user",
} /* 前置优先 */,
},
this.success,
this.error
);
},
// 访问用户媒体设备
getUserMedia(constrains, success, error) {
if (navigator.mediaDevices.getUserMedia) {
//最新标准API
navigator.mediaDevices
.getUserMedia(constrains)
.then(success)
.catch(error);
} else if (navigator.webkitGetUserMedia) {
//webkit内核浏览器
navigator.webkitGetUserMedia(constrains).then(success).catch(error);
} else if (navigator.mozGetUserMedia) {
//Firefox浏览器
navagator.mozGetUserMedia(constrains).then(success).catch(error);
} else if (navigator.getUserMedia) {
//旧版API
navigator.getUserMedia(constrains).then(success).catch(error);
} else {
this.scanTip = "你的浏览器不支持访问用户媒体设备";
}
},
}
```
4.安卓手机在允许调用摄像头之后正常调用和播放,苹果手机第一次允许之后调不出来摄像头,返回到上个页面再次进入人脸识别页面,可以正常调用。这个问题想了很多方向,使用了微信api里面的一些监听方法,依然无效。想了一些策略,在前一个页面获取媒体设备等,都不是完美方案。 耽误了两天时间,这天中午午休,我做梦梦到一个美女。那美女竟然穿着女仆装,超短的蕾丝花边裙,修长光溜溜的美腿,洁白秀美的脸蛋,乌黑发亮的发丝,圆润丰满的胸脯,简直是梦想中的女朋友。我对着那个美女抛了一个媚眼,那美女便婀娜多姿地眨着眼睛回应我。我咽了一口口水,忍不住便问:你如何才能做我女朋友。那美女张开樱桃小口,突出几个调皮的字来:你抓到我,我便以身相许。此话一听,我哪能安奈住自己激动的内心。迅速向她扑去,可是我向她跑去,她就转身向后跑,我停下来,她就停下来对我抛飞吻。我追呀追,她跑呀跑,她始终跟我保持着一定的距离,我是怎么都追不上她,气的我吐血。突然,我心想,上次做梦,我得到了一件短暂时间穿越能力。只要我集中精力发功,我就能穿越到未来1秒以后的时间。想到此我就开始集中精力,我一追她便跑,我一停她便停。突然这个世界相对与我静止了1秒钟,我利用这1秒的时间,迅速接近女仆装美女。1秒1秒又1秒,我终于抱住了女仆装美女,那美女对我眉开眼笑,还用樱桃小口往我脸上亲......突然,音乐响起,午休时间结束,我意犹未尽的醒来,发现抱枕上面都是口水,赶快趁人不注意用纸擦掉。又是突然,我想到了是否可以在调用video.play方法的时候,给它加一个定时器,让它1秒钟之后再执行。 没想到加上之后,苹果手机上面的问题解决了。自此,我猜想:当我们询问是否可以调用手机摄像头的时候,苹果的系统弹框阻碍了js的执行,在那一刻js代码停止执行,但是当我们点击允许之后,js代码没有继续执行,因为系统没有手段或者说事件去触发代码继续走。我们加上setTimeout函数之后,系统有了可以让代码继续执行的触发手段,所以js代码继续执行,我们就可以进行下一步人脸捕捉了。如果同学们有更好的解释,欢迎留言。
代码
```
success(stream) {
this.streamIns = stream;
const video = document.getElementById("video");
// webkit内核浏览器
this.URL = window.URL || window.webkitURL;
if ("srcObject" in video) {
video.srcObject = stream;
} else {
video.src = this.URL.createObjectURL(stream);
}
// 苹果手机的系统弹框会阻止js的线程的继续执行 手动0.1秒之后自动执行代码
setTimeout(() => {
video.play();
this.initTracker();// 人脸捕捉
}, 100);
},
error(e) {
this.scanTip = "访问用户媒体失败";
},
```
5.设置各种参数 实例化人脸捕捉实例对象
```
// 人脸捕捉 设置各种参数 实例化人脸捕捉实例对象,注意canvas上面的动画效果。
initTracker() {
this.context = document.getElementById("refCanvas").getContext("2d"); // 画布
this.canvas = document.getElementById("refCanvas");
this.tracker = new window.tracking.ObjectTracker("face"); // tracker实例
this.tracker.setInitialScale(4);
this.tracker.setStepSize(2); // 设置步长
this.tracker.setEdgesDensity(0.1);
try {
this.trackertask = window.tracking.track("#video", this.tracker); // 开始追踪
} catch (e) {
this.scanTip = "访问用户媒体失败,请重试";
}
//开始捕捉方法 一直不停的检测人脸直到检测到人脸
this.tracker.on("track", (e) => {
//画布描绘之前清空画布
this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);
if (e.data.length === 0) {
this.scanTip = "未检测到人脸";
} else {
e.data.forEach((rect) => {
//设置canvas 方框的颜色大小
this.context.strokeStyle = "#42e365";
this.context.lineWidth = 2;
this.context.strokeRect(rect.x, rect.y, rect.width, rect.height);
});
if (!this.tipFlag) {
this.scanTip = "检测成功,正在拍照,请保持不动2秒";
}
// 1.5秒后拍照,仅拍一次 给用户一个准备时间
// falg 限制一直捕捉人脸,只要拍照之后就停止检测
if (!this.flag) {
this.scanTip = "拍照中...";
this.flag = true;
this.removePhotoID = setTimeout(() => {
this.tackPhoto();
document.getElementById("video").pause();
this.tipFlag = true;
}, 1500);
}
}
});
},
```
6.拍照 绘制照片,计算照片大小,腾讯人脸比对接口对照片大小和格式有要求。
```
// 拍照
tackPhoto() {
// 在画布上面绘制拍到的照片
this.context.drawImage(
document.getElementById("video"),
0,
0,
this.vwidth,
this.vwidth
);
// 保存为base64格式
this.imgUrl = this.saveAsPNG(document.getElementById("refCanvas"));
/** 拿到base64格式图片之后就可以在this.compare方法中去调用后端接口比较了,也可以调用getBlobBydataURI方法转化成文件再去比较
* 我们项目里有一个设置个人头像的地方,先保存一下用户的图片,然后去拿这个图片的地址和当前拍照图片给后端接口去比较。
* */
// this.compare(imgUrl)
//判断图片大小
this.imgSize();
this.faceToTengXun(); // 人脸比对
this.close();
},
imgSize() {
if (this.imgUrl) {
// 获取base64图片byte大小
const equalIndex = this.imgUrl.indexOf("="); // 获取=号下标
let size;
if (equalIndex > 0) {
const str = this.imgUrl.substring(0, equalIndex); // 去除=号
const strLength = str.length;
const fileLength = strLength - (strLength / 8) * 2; // 真实的图片byte大小
size = Math.floor(fileLength / 1024); // 向下取整
console.log("size", size + "KB");
} else {
const strLength = this.imgUrl.length;
const fileLength = strLength - (strLength / 8) * 2;
size = Math.floor(fileLength / 1024); // 向下取整
console.log("size", size + "KB");
}
if (size > 1024) {
// 图片超过1M 按比例压缩
this.imgUrl = document
.getElementById("refCanvas")
.toDataURL("image/png", 1024 / size);
}
}
},
// Base64转文件
getBlobBydataURI(dataURI, type) {
var binary = window.atob(dataURI.split(",")[1]);
var array = [];
for (var i = 0; i < binary.length; i++) {
array.push(binary.charCodeAt(i));
}
return new Blob([new Uint8Array(array)], {
type: type,
});
},
// compare(url) {
// let blob = this.getBlobBydataURI(url, 'image/png')
// let formData = new FormData()
// formData.append("file", blob, "file_" + Date.parse(new Date()) + ".png")
// // TODO 得到文件后进行人脸识别
// },
// 保存为png,base64格式图片
saveAsPNG(c) {
return c.toDataURL("image/png", 0.4);
},
```
7. 人脸采集之后,移除实例化对象,初始化参数和css样式部分代码
```
close() {
this.flag = false;
this.tipFlag = false;
this.showContainer = false;
this.context = null;
this.scanTip = "人脸识别中...";
clearTimeout(this.removePhotoID);
if (this.streamIns) {
this.streamIns.enabled = false;
this.streamIns.getTracks()[0].stop();
this.streamIns.getVideoTracks()[0].stop();
}
this.streamIns = null;
this.trackertask.stop();
this.tracker = null;
}
.face-capture {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.tip {
position: fixed;
top: 48px;
z-index: 5;
font-size: 18px;
font-family: PingFangSC-Medium, PingFang SC;
font-weight: 500;
color: #333333;
line-height: 25px;
}
.face-capture video,
.face-capture canvas {
position: fixed;
top: 117.5px;
object-fit: cover;
z-index: 2;
background-repeat: no-repeat;
background-size: 100% 100%;
}
.face-capture .img-cover {
position: fixed;
top: 63px;
width: 375px;
height: 375px;
object-fit: cover;
z-index: 3;
background-repeat: no-repeat;
background-size: 100% 100%;
}
.face-capture .contentp {
position: fixed;
top: 438px;
font-size: 18px;
font-weight: 500;
color: #333333;
}
.face-capture .rect {
border: 2px solid #0aeb08;
position: fixed;
z-index: 4;
}
.img-face {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.img-face .imgurl {
position: fixed;
top: 117.5px;
width: 266px;
height: 266px;
border-radius: 133px;
}
```
总结:
1.人脸捕捉技术使用的tracking.js,关键是要理解它的运作原理和一下参数配置。
2.因为要调用手机媒体设备,兼容性问题是大问题,特别是苹果手机的问题,往往不知道如何下手,需要有丰富的开发经验和各种曲线救国的开发思想。
3.那个梦真的很重要。
4.对,美女更重要。