vue使用tracking实现人脸识别/人脸侦测完整代码
作者:jiangzhihao0515
1、安装依赖
npm install tracking.js --save
2、完整代码(人脸识别功能)
以下代码实现打开摄像头识别人脸
注:
1、安卓设备的人脸识别实现规则: 打开设备摄像机后,在相机的拍摄下实时进行人脸识别,如果识别到人脸后,1.5秒后自动拍照(可自行调整拍照时间)。
2、IOS设备的人脸识别实现规则:是利用input file的方式来达到打开IOS设备摄像机的目的,此时IOS设备的相机是全屏状态,所以只能在相机拍摄以后,再利用input的change(changePic)事件来得到拍摄的照片,然后再对照片进行人脸识别(检测是否存在人脸),如果检测通过,会保留该图片并且绘制在页面上,如果未通过人脸检测,则会提示未检测到人脸。
<template> <div class="camera_outer"> <!-- 此处代码请勿随意删除: input兼容ios无法调用摄像头的问题 accept属性兼容某些华为手机调用摄像头,打开的是文件管理器的问题 capture="user" 调用前置摄像头 camera 调用后置摄像头 如果使用 style="display: none" 隐藏input后,可能会出现无法吊起相册等问题 --> <input type="file" id="file" accept="image/*" capture="user" style="opacity: 0;" @change="changePic" /> <video id="videoCamera" :width="videoWidth" :height="videoHeight" autoplay class="img_bg_camera" /> <!-- 如果使用 style="display: none" 隐藏canvas后,将不会显示出人脸检测的识别框 如需要人脸识别框显示 video与canvas 样式是需要相同(目的是保持同一位置) --> <canvas id="canvasCamera" :width="videoWidth" :height="videoHeight" class="img_bg_camera" /> <div v-if="imgSrc" class="img_bg_camera" :class="[isDisplay ? 'displayBlock' : 'displayNone']"> <img id="imgId" :src="imgSrc" alt class="tx_img" /> </div> <div class="bottomButton"> <van-button id="open" type="warning" @click="getCamers()" class="marginRight10" >打开摄像机</van-button> </div> </div> </template> <script> // npm install tracking.js --save require("tracking/build/tracking-min.js"); require("tracking/build/data/face-min.js"); require("tracking/build/data/eye-min.js"); require("tracking/build/data/mouth-min.js"); require("tracking/examples/assets/stats.min.js"); export default { data() { return { videoWidth: 300, //摄像机宽度 videoHeight: 300, //摄像机高度 imgSrc: "", //生成图片链接 canvas: null, //canvas context: null, //context video: null, //video isFlag: false, //非正常拍照 isDisplay: false, //生成的照片是否显示 } }, mounted() { // this.getCompetence(); }, destroyed() { this.stopNavigator(); }, methods: { //调用权限(打开摄像头功能) getCompetence() { var _this = this; //得到canvasCamera的元素 this.canvas = document.getElementById("canvasCamera"); this.context = this.canvas.getContext("2d"); // 画布 this.video = document.getElementById("videoCamera"); // 旧版本浏览器可能根本不支持mediaDevices,我们首先设置一个空对象 if (navigator.mediaDevices === undefined) { Object.defineProperty(navigator, "mediaDevices", { value: {}, writable: true, configurable: true, enumerable: true, }); } // 一些浏览器实现了部分mediaDevices,我们不能只分配一个对象,如果使用getUserMedia,因为它会覆盖现有的属性。如果缺少getUserMedia属性,就添加它。 if (navigator.mediaDevices.getUserMedia === undefined) { navigator.mediaDevices.getUserMedia = function (constraints) { // 首先获取现存的getUserMedia(如果存在) var getUserMedia = navigator.getUserMedia || navigator.mediaDevices.getUserMedia; // 有些浏览器不支持,会返回错误信息 if (!getUserMedia) { this.$toast("getUserMedia is not implemented in this browser"); return Promise.reject( new Error( "getUserMedia is not implemented in this browser" ) ); } // 否则,使用Promise将调用包装到旧的navigator.getUserMedia return new Promise(function (resolve, reject) { getUserMedia.call( navigator, constraints, resolve, reject ); }); }; } var constraints = { audio: false, video: { width: this.videoWidth, height: this.videoHeight, transform: "scaleX(-1)", facingMode: "user", // user 安卓前置摄像头 {exact: 'environment} 后置摄像头 }, } //使苹果手机和苹果ipad支持打开摄像机 if ( navigator.userAgent.toLowerCase().indexOf("iphone") != -1 || navigator.userAgent.toLowerCase().indexOf("ipad") != -1 ) { //使得file一定可以获取到 document.getElementById("file").click(); } else { // (安卓/浏览器(除safari浏览器)) 在用户允许的情况下,打开相机,得到相关的流 navigator.mediaDevices .getUserMedia(constraints) .then(function (stream) { // 旧的浏览器可能没有srcObject if (!_this.video) { _this.video = {}; } try { _this.video.srcObject = stream; } catch (err) { _this.video.src = window.URL.createObjectURL(stream); // window.URL || window.webkitURL } _this.isFlag = true; _this.video.onloadedmetadata = () => { _this.video.play(); _this.initTracker();// 人脸捕捉 } }) .catch((err) => { this.$toast('访问用户媒体权限失败,请重试'); }); } }, // 人脸捕捉 设置各种参数 实例化人脸捕捉实例对象,注意canvas上面的动画效果。 // 使用 domId 来控制监听那个容器 Android 使用容器 #videoCamera,IOS使用容器 #imgId initTracker(domId) { // this.tracker = new window.tracking.ObjectTracker("face"); // tracker实例 this.tracker = new window.tracking.ObjectTracker(['face', 'eye', 'mouth']); // tracker实例 this.tracker.setInitialScale(4); this.tracker.setStepSize(2); // 设置步长 this.tracker.setEdgesDensity(0.1); try { this.trackertask = window.tracking.track(domId ? domId :"#videoCamera", this.tracker); // 开始追踪 } catch (e) { this.$toast("访问用户媒体失败,请重试") } //开始捕捉方法 一直不停的检测人脸直到检测到人脸 this.tracker.on("track", (e) => { //画布描绘之前清空画布 this.context.clearRect(0, 0, this.canvas.width, this.canvas.height); if (e.data.length === 0) { if(domId) this.$toast("未检测到人脸,请重新拍照或上传") } else { if(!domId){ // 安卓设备 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.$toast("检测成功,正在拍照,请保持不动2秒") } }else{ // IOS设备或safari浏览器 if (!this.tipFlag) { this.$toast("检测成功,正在生成,请稍等") } } // 1.5秒后拍照,仅拍一次 给用户一个准备时间 // falg 限制一直捕捉人脸,只要拍照之后就停止检测 if (!this.flag) { this.tipFlag = true this.flag = true; this.removePhotoID = setTimeout(() => { this.$toast("图像生成中···") this.setImage(domId ? true:false); this.stopNavigator() // 关闭摄像头 this.flag = false this.tipFlag = false; clearInterval(this.removePhotoID) this.removePhotoID = null }, 1500); } } }); }, //苹果手机获取图片并且保存图片 changePic(event) { this.isDisplay = false; // 隐藏已拍照片的展示 var reader = new FileReader(); var f = (document.getElementById("file")).files; reader.readAsDataURL(f[0]); reader.onload = () => { var re = reader.result; this.canvasDataURL(re, { quality: 1 }, (base64Codes) => { if (base64Codes) { this.imgSrc = base64Codes; // 此方式是为了检测图片中是否有人脸 this.$nextTick(()=>{ this.isFlag = true; this.initTracker('#imgId') }) // 如果不需要检验拍照或上传图片中是否有人脸,可注释上方人脸检测代码,使用此种方式 // this.isDisplay = true; // this.submitCollectInfo(); } else { this.$toast('拍照失败'); } event.target.value = ""; // 解决上传相同文件不触发change事件问题 }); }; }, //压缩图片 canvasDataURL(path, obj, callback) { let img = new Image(); img.src = path; const that = this; img.onload = () => { // 默认按比例压缩 var w = that.videoWidth, h = that.videoHeight, scale = w / h; // 使用人脸识别video高度 // w = obj.width || w; // h = obj.height || w / scale; // 使用图片真实高度(像素)图像更加清晰 w = img.width; h = img.height; var quality = 0.5; // 默认图片质量为0.5 //生成canvas var canvas = document.createElement("canvas"); // canvas 设置宽高 使用默认宽高图片会变形、失真 canvas.width = w canvas.height = h var ctx = canvas.getContext("2d"); // 创建属性节点 ctx.drawImage(img, 0, 0, w, h); // 图像质量 数值范围(0 ~ 1) 1表示最好品质,0基本不被辨析但有比较小的文件大小; if (obj.quality && obj.quality <= 1 && obj.quality > 0) { quality = obj.quality; } // quality值越小,所绘制出的图像越模糊 var base64 = canvas.toDataURL("image/jpeg", quality); // 回调函数返回base64的值 callback(base64); }; }, //绘制图片(拍照功能) setImage(flag) { if(!this.context){ this.$toast('请打开摄像机') return; } this.context.drawImage( flag ? document.getElementById('imgId') : this.video, 0, 0, this.videoWidth, this.videoHeight ); // 获取图片base64链接 var image = this.canvas.toDataURL("image/png", 0.5); if (this.isFlag) { if (image) { this.imgSrc = image; this.isDisplay = true; this.submitCollectInfo(); } else { this.$toast("图像生成失败"); } } else { this.$toast("图像生成失败"); } }, //保存图片 async submitCollectInfo() { //其中可以和后端做一些交互 console.log('与后端交互'); }, // 关闭摄像头 并且停止人脸检测 stopNavigator() { if (this.video && this.video.srcObject) { this.video.srcObject.getTracks()[0].stop(); } if(this.trackertask) this.trackertask.stop(); this.tracker = null; this.isFlag = false }, //打开摄像机 getCamers() { this.isDisplay = false; // 隐藏已拍照片的展示 this.getCompetence(); }, // 以下是提供的几种可能在优化或者与后端交互时需要使用的方法 // //返回 // goBack() { // this.stopNavigator(); // //可以写相应的返回的一些操作,携带一些需要携带的参数 // }, // // 访问用户媒体设备 // getUserMedias(constrains, success, error) { // if (navigator && navigator.mediaDevices && navigator.mediaDevices.getUserMedia) { //最新标准API // navigator.mediaDevices.getUserMedia(constrains).then(success).catch(error); // } else if (navigator && navigator.webkitGetUserMedia) { //webkit内核浏览器 // navigator.webkitGetUserMedia(constrains).then(success).catch(error); // } else if (navigator && navigator.mozGetUserMedia) { //Firefox浏览器 // navagator.mozGetUserMedia(constrains).then(success).catch(error); // } else if (navigator && navigator.getUserMedia) { //旧版API // navigator.getUserMedia(constrains).then(success).catch(error); // } else { // this.$toast("你的浏览器不支持访问用户媒体设备") // // error("访问用户媒体失败") // } // }, // // 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 得到文件后进行人脸识别 // }, } } </script> <style lang="scss" scoped> .camera_outer { position: relative; overflow: hidden; background-size: cover; background: white; width: 100%; height: 100%; } video, canvas, .tx_img { -moz-transform: scaleX(-1); -webkit-transform: scaleX(-1); -ms-transform: scaleX(-1); -o-transform: scaleX(-1); transform: scaleX(-1); } .img_bg_camera { position: absolute; bottom: 25%; top: 25%; left: 50%; margin-left: -150px; border-radius: 50%; -webkit-border-radius: 50%; -moz-border-radius: 50%; -ms-border-radius: 50%; -o-border-radius: 50%; } .img_bg_camera img { width: 300px; height: 300px; border-radius: 50%; -webkit-border-radius: 50%; -moz-border-radius: 50%; -ms-border-radius: 50%; -o-border-radius: 50%; } .displayNone { // display: none; opacity: 0; } .displayBlock { // display: block; opacity: 1; } .marginRight10 { margin-right: 20px; } .bottomButton { position: fixed; bottom: 20px; width: 100%; text-align: center; } </style>
该项目在调试时,tracking.js相关依赖会在检测人脸时,会有两个警告频繁出现,但不会影响项目的运行
第一个问题:[Violation] 'requestAnimationFrame' handler took <N>ms
这个警告通常是因为你的canvas或者video渲染过于复杂或者数据量过大,导致在浏览器的一帧内渲染超时。但本人目前没有太好的解决方法,如广大网友有了解或者是解决办法,希望可以在评论区讨论一下
第二个问题:Canvas2D: Multiple readback operations using getImageData are faster with the willReadFrequently attribute set to true.
这个警告大概的意思是 使用getImageData的多次读回操作会更快,建议将willReadFrequency属性设置为true。目前的解决方法是将 node_modules 依赖中的 tracking.js 和 tracking-min.js 这两个文件中的 getContext("2d") 和 getContext('2d') 整体替换为 getContext("2d",{ willReadFrequently: true })
另外如果不使用npm方式下载,也可以使用本地文件导入的方式 提供tracking.js相关文件 链接: https://pan.baidu.com/s/1JjABqkjgkszBLuJTuzHpvw?pwd=meae 提取码: meae
3、调试代码(无人脸识别,可自行拍照和上传任意图片)
<template> <div class="camera_outer"> <!-- 此处代码请勿随意删除: input兼容ios无法调用摄像头的问题 accept属性兼容某些华为手机调用摄像头,打开的是文件管理器的问题 --> <input type="file" id="file" accept="image/*" capture="camera" style="display: none" @change="changePic" /> <video id="videoCamera" :width="videoWidth" :height="videoHeight" autoplay class="img_bg_camera" /> <canvas style="display: none" id="canvasCamera" :width="videoWidth" :height="videoHeight" class="img_bg_camera" /> <div v-if="imgSrc" class="img_bg_camera" :class="[isDisplay ? 'displayBlock' : 'displayNone']" > <img :src="imgSrc" alt class="tx_img" /> </div> <div class="bottomButton"> <van-button color="#aaaaaa" @click="stopNavigator()" class="marginRight10" >关闭摄像头</van-button > <van-button id="open" type="warning" @click="getCamers()" class="marginRight10" >打开摄像机</van-button > <van-button type="warning" @click="setImage()">拍照</van-button> </div> </div> </template> <script> export default { data() { return { videoWidth: 300, //摄像机宽度 videoHeight: 300, //摄像机高度 imgSrc: "", //生成图片链接 canvas: null, //canvas context: null, //context video: null, //video isFlag: false, //非正常拍照 isDisplay: false, //生成的照片是否显示 } }, mounted() { // this.getCompetence(); }, destroyed() { this.stopNavigator(); }, methods: { //调用权限(打开摄像头功能) getCompetence() { var _this = this; //得到canvasCamera的元素 this.canvas = document.getElementById("canvasCamera"); this.context = this.canvas.getContext("2d"); this.video = document.getElementById("videoCamera"); // 旧版本浏览器可能根本不支持mediaDevices,我们首先设置一个空对象 if (navigator.mediaDevices === undefined) { Object.defineProperty(navigator, "mediaDevices", { value: {}, writable: true, configurable: true, enumerable: true, }); } // 一些浏览器实现了部分mediaDevices,我们不能只分配一个对象,如果使用getUserMedia,因为它会覆盖现有的属性。如果缺少getUserMedia属性,就添加它。 if (navigator.mediaDevices.getUserMedia === undefined) { navigator.mediaDevices.getUserMedia = function (constraints) { // 首先获取现存的getUserMedia(如果存在) var getUserMedia = navigator.getUserMedia || navigator.mediaDevices.getUserMedia; // 有些浏览器不支持,会返回错误信息 if (!getUserMedia) { console.log('getUserMedia is not implemented in this browser'); return Promise.reject( new Error( "getUserMedia is not implemented in this browser" ) ); } // 否则,使用Promise将调用包装到旧的navigator.getUserMedia return new Promise(function (resolve, reject) { getUserMedia.call( navigator, constraints, resolve, reject ); }); }; } var constraints = { audio: false, video: { width: this.videoWidth, height: this.videoHeight, transform: "scaleX(-1)", }, } //使苹果手机和苹果ipad支持打开摄像机 if ( navigator.userAgent.toLowerCase().indexOf("iphone") != -1 || navigator.userAgent.toLowerCase().indexOf("ipad") != -1 ) { //使得file一定可以获取到 document.getElementById("file").click(); } else { //在用户允许的情况下,打开相机,得到相关的流 navigator.mediaDevices .getUserMedia(constraints) .then(function (stream) { // 旧的浏览器可能没有srcObject if (!_this.video) { _this.video = {}; } try { _this.video.srcObject = stream; } catch (err) { _this.video.src = window.URL.createObjectURL(stream); } _this.isFlag = true; _this.video.onloadedmetadata = () => _this.video.play(); }) .catch((err) => { console.log(err); }); } }, //苹果手机获取图片并且保存图片 changePic() { var reader = new FileReader(); var f = (document.getElementById("file")).files; reader.readAsDataURL(f[0]); reader.onload = () => { var re = reader.result; this.canvasDataURL(re, { quality: 0.5 }, (base64Codes) => { if (base64Codes) { this.imgSrc = base64Codes; this.isDisplay = true; this.submitCollectInfo(); } else { this.$toast('拍照失败'); } }); }; }, //压缩图片 canvasDataURL(path, obj, callback) { let img = new Image(); img.src = path; const that = this; img.onload = () => { // 默认按比例压缩 var w = that.videoWidth, h = that.videoHeight, scale = w / h; // w = obj.width || w; // h = obj.height || w / scale; w = img.width || w; h = img.height || w / scale; var quality = 0.5; // 默认图片质量为0.5 //生成canvas var canvas = document.createElement("canvas"); // canvas 设置宽高 使用默认宽高图片会变形、失真 canvas.width = w canvas.height = h var ctx = canvas.getContext("2d"); // 创建属性节点 ctx.drawImage(img, 0, 0, w, h); // 图像质量 if (obj.quality && obj.quality <= 1 && obj.quality > 0) { quality = obj.quality; } // quality值越小,所绘制出的图像越模糊 var base64 = canvas.toDataURL("image/jpeg", quality); // 回调函数返回base64的值 callback(base64); }; }, //绘制图片(拍照功能) setImage() { this.context.drawImage( this.video, 0, 0, this.videoWidth, this.videoHeight ); // 获取图片base64链接 var image = this.canvas.toDataURL("image/png", 0.5); if (this.isFlag) { if (image) { this.imgSrc = image; this.isDisplay = true; this.submitCollectInfo(); } else { console.log("拍照失败"); } } else { console.log("拍照失败"); } }, //保存图片 async submitCollectInfo() { //其中可以和后端做一些交互 console.log('与后端交互'); }, // 关闭摄像头 stopNavigator() { if (this.video && this.video.srcObject) { this.video.srcObject.getTracks()[0].stop(); } }, //打开摄像机 getCamers() { this.isDisplay = false; // 隐藏已拍照片的展示 this.getCompetence(); }, } } </script> <style lang="scss" scoped> .camera_outer { position: relative; overflow: hidden; background-size: cover; background: white; width: 100%; height: 100%; } video, canvas, .tx_img { -moz-transform: scaleX(-1); -webkit-transform: scaleX(-1); -ms-transform: scaleX(-1); -o-transform: scaleX(-1); transform: scaleX(-1); } .img_bg_camera { position: absolute; bottom: 25%; top: 25%; left: 50%; margin-left: -150px; border-radius: 50%; -webkit-border-radius: 50%; -moz-border-radius: 50%; -ms-border-radius: 50%; -o-border-radius: 50%; } .img_bg_camera img { width: 300px; height: 300px; border-radius: 50%; -webkit-border-radius: 50%; -moz-border-radius: 50%; -ms-border-radius: 50%; -o-border-radius: 50%; } .displayNone { display: none; } .displayBlock { display: block; } .marginRight10 { margin-right: 20px; } .bottomButton { position: fixed; bottom: 20px; width: 100%; text-align: center; } </style>
总结
到此这篇关于vue使用tracking实现人脸识别/人脸侦测的文章就介绍到这了,更多相关vue实现人脸识别内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!