最近刷脸支付很火, 老板们当然要追赶时代潮流, 于是就有了刷脸支付这个项目. 前端实现关键的技术是摄像头录像, 拍照和人脸比对, 本文来探讨一下如何在 html5 环境中如何实现刷脸支付以及开发过程中遇到的问题.
1. 摄像头
1.1 input 获取摄像头
html5 中获取手机上的图片, 有两种方式, 使用 input, 如下可以打开摄像头拍照:
<input type="file" capture="camera" accept="image/*"/>
另外如果想打开相册, 可以这样:
<input type="file" accept="img/*">
但是这两种方式都会有兼容性问题, 用过的同学可能都知道.
1.2 getUserMedia 获取摄像头
getUserMedia 是 html5 一个新的 API, 官方一点的定义是:
MediaDevices.getUserMedia() 会提示用户给予使用媒体输入的许可, 媒体输入会产生一个, 里面包含了请求的媒体类型的轨道. 此流可以包含一个视频轨道 (来自硬件或者虚拟视频源, 比如相机, 视频采集设备和屏幕共享服务等等), 一个音频轨道 (同样来自硬件或虚拟音频源, 比如麦克风, A/D 转换器等等), 也可能是其它轨道类型.
简单一点说就是可以获取到用户摄像头.
同上面 input 一样, 这种方式也有兼容性问题, 不过可以使用其他方式解决, 这里可以参考 MediaDevices.getUserMedia(), 文档中有介绍 "在旧的浏览器中使用新的 API". 我这里在网上也找了一些参考, 总结出一个相对全面的 getUserMedia 版本, 代码如下:
- // 访问用户媒体设备
- 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 = "你的浏览器不支持访问用户媒体设备"
- }
- }
1.3 播放视屏
获取设备方法有两个回调函数, 一个是成功, 一个是失败. 成功了就开始播放视频, 播放视屏其实就是给 video 设置一个 url, 并调用 play 方法, 这里设置 url 要考虑不同浏览器兼容性, 代码如下:
- success(stream) {
- this.streamIns = stream
- // 设置播放地址, webkit 内核浏览器
- this.URL = Windows.URL || Windows.webkitURL
- if ("srcObject" in this.$refs.refVideo) {
- this.$refs.refVideo.srcObject = stream
- } else {
- this.$refs.refVideo.src = this.URL.createObjectURL(stream)
- }
- this.$refs.refVideo.onloadedmetadata = e => {
- // 播放视频
- this.$refs.refVideo.play()
- this.initTracker()
- }
- },
- error(e) {
- this.scanTip = "访问用户媒体失败" + e.name + "," + e.message
- }
注意:
播放视屏方法最好写在 onloadmetadata 回调函数中, 否则可能会报错.
播放视频的时候出于安全性考虑, 必须在本地环境中测试, 也就是 http://localhost/xxxx 中测试, 或者带有 https://xxxxx 环境中测试, 不然的话或有跨域问题.
下面用到的 initTracker() 方法也好放在这个 onloadedmetadata 回调函数里, 不然也会报错.
2. 捕捉人脸
2.1 使用 tracking.JS 捕捉人脸
视屏在 video 中播放成功之后就开始识别人脸了, 这里使用到一个第三方的功能 tracking.JS https://trackingjs.com/ , 是国外的大神写的 JavaScript 图像识别插件. 关键代码如下:
- // 人脸捕捉
- initTracker() {
- this.context = this.$refs.refCanvas.getContext("2d") // 画布
- this.tracker = new tracking.ObjectTracker(['face']) // tracker 实例
- this.tracker.setStepSize(1.7) // 设置步长
- this.tracker.on('track', this.handleTracked) // 绑定监听方法
- try {
- tracking.track('#video', this.tracker) // 开始追踪
- } catch (e) {
- this.scanTip = "访问用户媒体失败, 请重试"
- }
- }
捕获到人脸之后, 可以在页面上用一个小方框标注出来, 这样有点交互效果.
- // 追踪事件
- handleTracked(e) {
- if (e.data.length === 0) {
- this.scanTip = '未检测到人脸'
- } else {
- if (!this.tipFlag) {
- this.scanTip = '检测成功, 正在拍照, 请保持不动 2 秒'
- }
- // 1 秒后拍照, 仅拍一次
- if (!this.flag) {
- this.scanTip = '拍照中...'
- this.flag = true
- this.removePhotoID = setTimeout(() => {
- this.tackPhoto()
- this.tipFlag = true
- }, 2000)
- }
- e.data.forEach(this.plot)
- }
- }
在页面中画一些方框, 标识出人脸:
- <div class="rect" v-for="item in profile"
- :style="{ width: item.width +'px', height: item.height +'px', left: item.left +'px', top: item.top +'px'}"></div>
- // 绘制跟踪框
- plot({x, y, width: w, height: h}) {
- // 创建框对象
- this.profile.push({ width: w, height: h, left: x, top: y })
- }
2.2 拍照
拍照, 就是使用 video 作为图片源, 在 canvas 中保存一张图片下来, 注意这里使用 toDataURL 方法的时候可以设置第二个参数 quality, 从 0 到 1,0 表示图片比较粗糙, 但是文件比较小, 1 表示品质最好.
- // 拍照
- tackPhoto() {
- this.context.drawImage(this.$refs.refVideo, 0, 0, this.screenSize.width, this.screenSize.height)
- // 保存为 base64 格式
- this.imgUrl = this.saveAsPNG(this.$refs.refCanvas)
- // this.compare(imgUrl)
- this.close()
- },
- // Base64 转文件
- getBlobBydataURI(dataURI, type) {
- var binary = Windows.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
- });
- },
- // 保存为 PNG,base64 格式图片
- saveAsPNG(c) {
- return c.toDataURL('image/png', 0.3)
- }
拍照完成之后就可以把文件发送给后端, 让后端进行对比验证, 这里后端使用的是阿里云的接口.
3. 最后效果
3.1 参考代码 demo
最后, demo 我已经放在 GitHub 上了, 感兴趣可以打开看一下.
效果如下:
3.2 在项目中落地
最后放在项目中, 无非就是最后一个步骤, 去调用接口比对, 根据比对结果成功是成功还是失败, 决定是人脸支付还是继续使用原来的密码支付, 效果如下:
ps: 这里人脸比对失败了, 是因为我带着口罩, 就不呲牙露脸了. 后端调用阿里云的接口地址:
来源: https://www.cnblogs.com/tylerdonet/p/12711086.html