vue使用tracking實現(xiàn)人臉識別/人臉偵測完整代碼
1、安裝依賴
npm install tracking.js --save
2、完整代碼(人臉識別功能)
以下代碼實現(xiàn)打開攝像頭識別人臉
注:
1、安卓設(shè)備的人臉識別實現(xiàn)規(guī)則: 打開設(shè)備攝像機后,在相機的拍攝下實時進行人臉識別,如果識別到人臉后,1.5秒后自動拍照(可自行調(diào)整拍照時間)。
2、IOS設(shè)備的人臉識別實現(xiàn)規(guī)則:是利用input file的方式來達到打開IOS設(shè)備攝像機的目的,此時IOS設(shè)備的相機是全屏狀態(tài),所以只能在相機拍攝以后,再利用input的change(changePic)事件來得到拍攝的照片,然后再對照片進行人臉識別(檢測是否存在人臉),如果檢測通過,會保留該圖片并且繪制在頁面上,如果未通過人臉檢測,則會提示未檢測到人臉。
<template> <div class="camera_outer"> <!-- 此處代碼請勿隨意刪除: input兼容ios無法調(diào)用攝像頭的問題 accept屬性兼容某些華為手機調(diào)用攝像頭,打開的是文件管理器的問題 capture="user" 調(diào)用前置攝像頭 camera 調(diào)用后置攝像頭 如果使用 style="display: none" 隱藏input后,可能會出現(xiàn)無法吊起相冊等問題 --> <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: { //調(diào)用權(quán)限(打開攝像頭功能) getCompetence() { var _this = this; //得到canvasCamera的元素 this.canvas = document.getElementById("canvasCamera"); this.context = this.canvas.getContext("2d"); // 畫布 this.video = document.getElementById("videoCamera"); // 舊版本瀏覽器可能根本不支持mediaDevices,我們首先設(shè)置一個空對象 if (navigator.mediaDevices === undefined) { Object.defineProperty(navigator, "mediaDevices", { value: {}, writable: true, configurable: true, enumerable: true, }); } // 一些瀏覽器實現(xiàn)了部分mediaDevices,我們不能只分配一個對象,如果使用getUserMedia,因為它會覆蓋現(xiàn)有的屬性。如果缺少getUserMedia屬性,就添加它。 if (navigator.mediaDevices.getUserMedia === undefined) { navigator.mediaDevices.getUserMedia = function (constraints) { // 首先獲取現(xiàn)存的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將調(diào)用包裝到舊的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瀏覽器)) 在用戶允許的情況下,打開相機,得到相關(guān)的流 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('訪問用戶媒體權(quán)限失敗,請重試'); }); } }, // 人臉捕捉 設(shè)置各種參數(shù) 實例化人臉捕捉實例對象,注意canvas上面的動畫效果。 // 使用 domId 來控制監(jiān)聽那個容器 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); // 設(shè)置步長 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){ // 安卓設(shè)備 e.data.forEach((rect) => { //設(shè)置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設(shè)備或safari瀏覽器 if (!this.tipFlag) { this.$toast("檢測成功,正在生成,請稍等") } } // 1.5秒后拍照,僅拍一次 給用戶一個準(zhǔn)備時間 // falg 限制一直捕捉人臉,只要拍照之后就停止檢測 if (!this.flag) { this.tipFlag = true this.flag = true; this.removePhotoID = setTimeout(() => { this.$toast("圖像生成中···") this.setImage(domId ? true:false); this.stopNavigator() // 關(guān)閉攝像頭 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 = ""; // 解決上傳相同文件不觸發(fā)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; // 默認圖片質(zhì)量為0.5 //生成canvas var canvas = document.createElement("canvas"); // canvas 設(shè)置寬高 使用默認寬高圖片會變形、失真 canvas.width = w canvas.height = h var ctx = canvas.getContext("2d"); // 創(chuàng)建屬性節(jié)點 ctx.drawImage(img, 0, 0, w, h); // 圖像質(zhì)量 數(shù)值范圍(0 ~ 1) 1表示最好品質(zhì),0基本不被辨析但有比較小的文件大小; if (obj.quality && obj.quality <= 1 && obj.quality > 0) { quality = obj.quality; } // quality值越小,所繪制出的圖像越模糊 var base64 = canvas.toDataURL("image/jpeg", quality); // 回調(diào)函數(shù)返回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('與后端交互'); }, // 關(guān)閉攝像頭 并且停止人臉檢測 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(); }, // 以下是提供的幾種可能在優(yōu)化或者與后端交互時需要使用的方法 // //返回 // goBack() { // this.stopNavigator(); // //可以寫相應(yīng)的返回的一些操作,攜帶一些需要攜帶的參數(shù) // }, // // 訪問用戶媒體設(shè)備 // getUserMedias(constrains, success, error) { // if (navigator && navigator.mediaDevices && navigator.mediaDevices.getUserMedia) { //最新標(biāo)準(zhǔn)API // navigator.mediaDevices.getUserMedia(constrains).then(success).catch(error); // } else if (navigator && navigator.webkitGetUserMedia) { //webkit內(nèi)核瀏覽器 // 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("你的瀏覽器不支持訪問用戶媒體設(shè)備") // // error("訪問用戶媒體失敗") // } // }, // // Base64轉(zhuǎn)文件 // 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>
該項目在調(diào)試時,tracking.js相關(guān)依賴會在檢測人臉時,會有兩個警告頻繁出現(xiàn),但不會影響項目的運行
第一個問題:[Violation] 'requestAnimationFrame' handler took <N>ms
這個警告通常是因為你的canvas或者video渲染過于復(fù)雜或者數(shù)據(jù)量過大,導(dǎo)致在瀏覽器的一幀內(nèi)渲染超時。但本人目前沒有太好的解決方法,如廣大網(wǎng)友有了解或者是解決辦法,希望可以在評論區(qū)討論一下
第二個問題:Canvas2D: Multiple readback operations using getImageData are faster with the willReadFrequently attribute set to true.
這個警告大概的意思是 使用getImageData的多次讀回操作會更快,建議將willReadFrequency屬性設(shè)置為true。目前的解決方法是將 node_modules 依賴中的 tracking.js 和 tracking-min.js 這兩個文件中的 getContext("2d") 和 getContext('2d') 整體替換為 getContext("2d",{ willReadFrequently: true })
另外如果不使用npm方式下載,也可以使用本地文件導(dǎo)入的方式 提供tracking.js相關(guān)文件 鏈接: https://pan.baidu.com/s/1JjABqkjgkszBLuJTuzHpvw?pwd=meae 提取碼: meae
3、調(diào)試代碼(無人臉識別,可自行拍照和上傳任意圖片)
<template> <div class="camera_outer"> <!-- 此處代碼請勿隨意刪除: input兼容ios無法調(diào)用攝像頭的問題 accept屬性兼容某些華為手機調(diào)用攝像頭,打開的是文件管理器的問題 --> <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" >關(guān)閉攝像頭</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: { //調(diào)用權(quán)限(打開攝像頭功能) getCompetence() { var _this = this; //得到canvasCamera的元素 this.canvas = document.getElementById("canvasCamera"); this.context = this.canvas.getContext("2d"); this.video = document.getElementById("videoCamera"); // 舊版本瀏覽器可能根本不支持mediaDevices,我們首先設(shè)置一個空對象 if (navigator.mediaDevices === undefined) { Object.defineProperty(navigator, "mediaDevices", { value: {}, writable: true, configurable: true, enumerable: true, }); } // 一些瀏覽器實現(xiàn)了部分mediaDevices,我們不能只分配一個對象,如果使用getUserMedia,因為它會覆蓋現(xiàn)有的屬性。如果缺少getUserMedia屬性,就添加它。 if (navigator.mediaDevices.getUserMedia === undefined) { navigator.mediaDevices.getUserMedia = function (constraints) { // 首先獲取現(xiàn)存的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將調(diào)用包裝到舊的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 { //在用戶允許的情況下,打開相機,得到相關(guān)的流 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; // 默認圖片質(zhì)量為0.5 //生成canvas var canvas = document.createElement("canvas"); // canvas 設(shè)置寬高 使用默認寬高圖片會變形、失真 canvas.width = w canvas.height = h var ctx = canvas.getContext("2d"); // 創(chuàng)建屬性節(jié)點 ctx.drawImage(img, 0, 0, w, h); // 圖像質(zhì)量 if (obj.quality && obj.quality <= 1 && obj.quality > 0) { quality = obj.quality; } // quality值越小,所繪制出的圖像越模糊 var base64 = canvas.toDataURL("image/jpeg", quality); // 回調(diào)函數(shù)返回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('與后端交互'); }, // 關(guān)閉攝像頭 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>
總結(jié)
到此這篇關(guān)于vue使用tracking實現(xiàn)人臉識別/人臉偵測的文章就介紹到這了,更多相關(guān)vue實現(xiàn)人臉識別內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
elementui[el-table]toggleRowSelection默認多選事件無法選中問題
這篇文章主要介紹了elementui[el-table]toggleRowSelection默認多選事件無法選中問題及解決方案,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2023-11-11vue中前端如何實現(xiàn)pdf預(yù)覽功能(含vue-pdf插件用法)
這篇文章主要給大家介紹了vue中前端如何實現(xiàn)pdf預(yù)覽功能的相關(guān)資料,文中包含vue-pdf插件用法,在前端開發(fā)中,很多時候我們需要進行pdf文件的預(yù)覽操作,需要的朋友可以參考下2023-07-07Vue中的assets和static目錄:使用場景及區(qū)別說明
這篇文章主要介紹了Vue中的assets和static目錄:使用場景及區(qū)別說明,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-06-06vue3+vite引入插件unplugin-auto-import的方法
這篇文章主要介紹了vue3+vite引入插件unplugin-auto-import的相關(guān)知識,本文通過實例代碼給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值?,需要的朋友可以參考下2022-10-10element-plus/element-ui走馬燈配置圖片及圖片自適應(yīng)的最簡便方法
走馬燈功能在展示圖片時經(jīng)常用到,下面這篇文章主要給大家介紹了關(guān)于element-plus/element-ui走馬燈配置圖片及圖片自適應(yīng)的最簡便方法,文中通過實例代碼介紹的非常詳細,需要的朋友可以參考下2023-03-03