253 lines
8.1 KiB
HTML
253 lines
8.1 KiB
HTML
|
<!doctype html>
|
|||
|
<html>
|
|||
|
<head>
|
|||
|
<meta charset="utf-8">
|
|||
|
<title>人脸识别</title>
|
|||
|
<script src="./js/vue.js"></script>
|
|||
|
<script type="text/javascript" src="./js/jquery-3.3.1.min.js"></script>
|
|||
|
<script src="../config.js"></script>
|
|||
|
<script src="build/tracking-min.js"></script>
|
|||
|
<script src="build/data/face-min.js"></script>
|
|||
|
|
|||
|
<style>
|
|||
|
#video {
|
|||
|
transform: rotateY(180deg);
|
|||
|
}
|
|||
|
.face-capture {
|
|||
|
display: flex;
|
|||
|
flex-direction: column;
|
|||
|
align-items: center;
|
|||
|
justify-content: center;
|
|||
|
}
|
|||
|
|
|||
|
.face-capture video, .face-capture canvas {
|
|||
|
position: fixed;
|
|||
|
top: 0;
|
|||
|
bottom: 0;
|
|||
|
left: 0;
|
|||
|
right: 0;
|
|||
|
width: 100%;
|
|||
|
height: 100%;
|
|||
|
object-fit: cover;
|
|||
|
z-index: 2;
|
|||
|
background-repeat: no-repeat;
|
|||
|
background-size: 100% 100%;
|
|||
|
}
|
|||
|
|
|||
|
.face-capture canvas {
|
|||
|
z-index: 2;
|
|||
|
}
|
|||
|
|
|||
|
.face-capture .img-cover {
|
|||
|
position: fixed;
|
|||
|
top: 0;
|
|||
|
bottom: 0;
|
|||
|
left: 0;
|
|||
|
right: 0;
|
|||
|
width: 100%;
|
|||
|
height: 100%;
|
|||
|
object-fit: cover;
|
|||
|
z-index: 2;
|
|||
|
background-repeat: no-repeat;
|
|||
|
background-size: 100% 100%;
|
|||
|
}
|
|||
|
|
|||
|
.face-capture .rect {
|
|||
|
border: 2px solid #0aeb08;
|
|||
|
position: fixed;
|
|||
|
z-index: 3;
|
|||
|
}
|
|||
|
|
|||
|
.face-capture .control-container {
|
|||
|
margin-top: 10rem;
|
|||
|
position: relative;
|
|||
|
width: 100%;
|
|||
|
height: 100%;
|
|||
|
object-fit: cover;
|
|||
|
z-index: 4;
|
|||
|
background-repeat: no-repeat;
|
|||
|
background-size: 100% 100%;
|
|||
|
}
|
|||
|
|
|||
|
.face-capture .title {
|
|||
|
text-align: center;
|
|||
|
color: white;
|
|||
|
margin: 1.6rem auto;
|
|||
|
font-size: 28px;
|
|||
|
}
|
|||
|
|
|||
|
.face-capture .close {
|
|||
|
width: 0.8rem;
|
|||
|
height: 0.8rem;
|
|||
|
}
|
|||
|
</style>
|
|||
|
</head>
|
|||
|
<body>
|
|||
|
<div id="app">
|
|||
|
<div v-show="showContainer" class="face-capture" id="face-capture">
|
|||
|
<video ref="refVideo" id="video" autoplay></video>
|
|||
|
<!-- <img src="./../images/video-cover.png" alt="cover" class="img-cover"/> -->
|
|||
|
<div class="control-container face-capture">
|
|||
|
<h2 class="title">{{scanTip}}</h2>
|
|||
|
<h2 class="title">{{msg}}</h2>
|
|||
|
<!-- <img class="close" src="./../images/address_edit_clear.png" alt=""/> -->
|
|||
|
<canvas ref="refCanvas" :width="screenSize.width" :height="screenSize.height" :style="{opacity: 0}"></canvas>
|
|||
|
</div>
|
|||
|
<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>
|
|||
|
</div>
|
|||
|
<div v-show="!showContainer">验证成功</div>
|
|||
|
</div>
|
|||
|
</body>
|
|||
|
</html>
|
|||
|
<script type="text/javascript">
|
|||
|
var vm = new Vue({
|
|||
|
el: '#app',
|
|||
|
|
|||
|
data: {
|
|||
|
USER_ID: null,
|
|||
|
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: '', // base64格式图片
|
|||
|
weburl: config.weburl,
|
|||
|
msg: '',
|
|||
|
countdown: 0,
|
|||
|
timer: null
|
|||
|
},
|
|||
|
beforeDestroy() {
|
|||
|
if (this.timer) {
|
|||
|
clearInterval(this.timer)
|
|||
|
this.timer = null
|
|||
|
}
|
|||
|
},
|
|||
|
mounted() {
|
|||
|
this.USER_ID = this.getUrlKey('USER_ID')
|
|||
|
// 这段代 主要是获取摄像头的视频流并显示在Video 签中
|
|||
|
window.addEventListener('DOMContentLoaded', function() {
|
|||
|
// 拍照按钮
|
|||
|
// $("#snap").click(function () {
|
|||
|
// context.drawImage(video, 0, 0, 330, 250);
|
|||
|
// })
|
|||
|
// 拍照每秒一次
|
|||
|
// setInterval(function(){
|
|||
|
// context.drawImage(video, 0, 0, 330, 250)
|
|||
|
// },1000);
|
|||
|
var constraints = { 'audio': false, 'video': { 'facingMode': 'user' }}
|
|||
|
|
|||
|
// 老的浏览器可能根本没有实现 mediaDevices,所以我们可以先设置一个空的对象
|
|||
|
if (navigator.mediaDevices === undefined) {
|
|||
|
navigator.mediaDevices = {}
|
|||
|
}
|
|||
|
// 一些浏览器部分支持 mediaDevices。我们不能直接给对象设置 getUserMedia
|
|||
|
// 因为这样可能会覆盖已有的属性。这里我们只会在没有getUserMedia属性的时候添加它。
|
|||
|
if (navigator.mediaDevices.getUserMedia === undefined) {
|
|||
|
navigator.mediaDevices.getUserMedia = function(constraints) {
|
|||
|
// 首先,如果有getUserMedia的话,就获得它
|
|||
|
var getUserMedia = navigator.webkitGetUserMedia || navigator.mozGetUserMedia
|
|||
|
// 一些浏览器根本没实现它 - 那么就返回一个error到promise的reject来保持一个统一的接口
|
|||
|
if (!getUserMedia) {
|
|||
|
return Promise.reject(new Error('getUserMedia is not implemented in this browser'))
|
|||
|
}
|
|||
|
|
|||
|
// 否则,为老的navigator.getUserMedia方法包裹一个Promise
|
|||
|
return new Promise(function(resolve, reject) {
|
|||
|
getUserMedia.call(navigator, constraints, resolve, reject)
|
|||
|
})
|
|||
|
}
|
|||
|
}
|
|||
|
if (window.stream) {
|
|||
|
window.stream.getTracks().forEach(track => {
|
|||
|
track.stop()
|
|||
|
})
|
|||
|
}
|
|||
|
navigator.mediaDevices.getUserMedia(constraints)
|
|||
|
.then(function(stream) {
|
|||
|
var video = document.querySelector('video')
|
|||
|
video.setAttribute('playsinline', true)
|
|||
|
// 旧的浏览器可能没有srcObject
|
|||
|
window.stream = stream
|
|||
|
if ('srcObject' in video) {
|
|||
|
video.srcObject = stream
|
|||
|
} else {
|
|||
|
// 防止在新的浏览器里使用它,应为它已经不再支持了
|
|||
|
video.src = window.URL.createObjectURL(stream)
|
|||
|
}
|
|||
|
video.play()
|
|||
|
})
|
|||
|
.catch(function(err) {
|
|||
|
alert(err.name + ': ' + err.message)
|
|||
|
}) // 总是在最后检查错误
|
|||
|
}, false)
|
|||
|
this.tackPhoto()
|
|||
|
},
|
|||
|
methods: {
|
|||
|
// 拍照
|
|||
|
tackPhoto() {
|
|||
|
this.context = this.$refs.refCanvas.getContext('2d') // 画布
|
|||
|
this.timer = setInterval(() => {
|
|||
|
this.countdown += 1
|
|||
|
if (this.countdown > 30) {
|
|||
|
clearInterval(this.timer)
|
|||
|
this.timer = null
|
|||
|
this.msg = '验证失败,请重新打开此页面'
|
|||
|
}
|
|||
|
this.context.drawImage(this.$refs.refVideo, 0, 0, this.screenSize.width, this.screenSize.height)
|
|||
|
// // 保存为base64格式
|
|||
|
this.imgUrl = this.saveAsPNG(this.$refs.refCanvas)
|
|||
|
// /** 拿到base64格式图片之后就可以在this.compare方法中去调用后端接口比较了,也可以调用getBlobBydataURI方法转化成文件再去比较
|
|||
|
// * 我们项目里有一个设置个人头像的地方,先保存一下用户的图片,然后去拿这个图片的地址和当前拍照图片给后端接口去比较。
|
|||
|
// * */
|
|||
|
var _this = this
|
|||
|
var USERAVATARURL = this.imgUrl.substring(this.imgUrl.indexOf('base64,') + 7)
|
|||
|
$.ajax({
|
|||
|
xhrFields: {
|
|||
|
withCredentials: true
|
|||
|
},
|
|||
|
type: 'POST',
|
|||
|
url: config.httpurl + '/app/user/compareFaceForH5',
|
|||
|
data: {
|
|||
|
USERAVATARURL: USERAVATARURL,
|
|||
|
USER_ID: this.USER_ID
|
|||
|
},
|
|||
|
dataType: 'json',
|
|||
|
success: function(data) {
|
|||
|
var result = data.result
|
|||
|
if (result == 'success') {
|
|||
|
clearInterval(_this.timer)
|
|||
|
_this.timer = null
|
|||
|
_this.msg = '验证成功'
|
|||
|
_this.showContainer = false
|
|||
|
} else if (result == 'error') {
|
|||
|
_this.msg = data.msg
|
|||
|
}
|
|||
|
},
|
|||
|
fail: function(data) {
|
|||
|
_this.msg = '验证失败'
|
|||
|
}
|
|||
|
})
|
|||
|
// this.msg = USERAVATARURL
|
|||
|
}, 2000)
|
|||
|
},
|
|||
|
// 保存为png,base64格式图片
|
|||
|
saveAsPNG(c) {
|
|||
|
return c.toDataURL('image/png', 0.3)
|
|||
|
},
|
|||
|
// 根据url参数名称获取参数值
|
|||
|
getUrlKey: function(name) {
|
|||
|
return decodeURIComponent(
|
|||
|
(new RegExp('[?|&]' + name + '=' + '([^&;]+?)(&|#|;|$)').exec(location.href) || [, ''])[1].replace(/\+/g, '%20')) || null
|
|||
|
}
|
|||
|
}
|
|||
|
})
|
|||
|
</script>
|
|||
|
|