最近,Chrome在Chrome中集成了一套與圖形識別相關的 API——Shape Detector API,只需要少量代碼就能夠實現人臉識別、二維碼/條形碼識別和文本識別。雖然這些 API 還處於實驗階段,只要條件允許,還是可以一下的。在 Windows 10 Chrome Canary 和 安卓 Chrome 等應用中,開啟 chrome://flags/#enable-experimental-web-platform-features 功能,在控制台下輸入
window.FaceDetector
window.BarcodeDetector
window.TextDetector
若得到“[native code]”,那么就可以使用這些 API 了。親測 Windows7 Chrome 61 開啟了 enable-experimental-web-platform-features 之后,雖然 window 包含以上三個 API,但調用 new FaceDetector.detector 時會報錯:Face detection service unavailable.
本來只是想簡單的體驗一下這個 API,沒想到一不小心就寫了這么多。。。
Demo1
在這個 demo 中,一共有三個類:
(1)FaceTag:所有的邏輯操作(人臉識別、搜索面部、面部標記)都在該類中實現。
(2)ShowImg:實現圖片按原比例繪制、縮放圖片、獲取 base64、清除功能。
(3)SignTool:實現繪制方框、繪制文字、清除功能。
實際上,ShowImg 完全可以用 <img> 代替的,我只是為了能夠縮小圖片,縮短人臉檢測的時間。由於圖片一旦確定后,在檢測和標記階段是不變的,為了方便操作,ShowImg 和 SignTool 各包含一個 canvas。為了減少代碼的重復,將創建 canvas 和獲取 ctx 的任務交給了 FaceTag 。
面部檢測
FaceDetector 真的很簡單。
創建實例的時候,可以給它傳遞FaceDetectorOptions,這個對象只含兩個有效參數:
maxDetectedFaces:檢測人臉的最大數目。
fastMode:是否優先考慮速度。
而它只提供了 detect(img) 一個方法,傳入待檢測的圖像,結果以 Promise 的形式返回,是包含一組 DetectedFace 元素的數組,若檢測不到則返回一個空數組。DetectedFace 的相關信息:x,y,left,top,right,bottom 等都包含在 boundingBox 中。
至於檢測結果的話,正臉的精確度挺高的,但是側臉基本檢測不到。
class FaceTag {
constructor(opt={}){
...
this.detector = new FaceDetector(orignOpt.detectorOpt);
...
}
...
faceDetector(aspect = 1) { return new Promise((resolve, reject) => { let img = this.img.getImage(); this.detector.detect(img) .then(faces => { if(faces.length === 0) { reject('檢測不到面部哦'); }else { this.faces = faces; resolve(this.faces); } }) .catch(err => { reject(err); }) }) }
...
}
根據檢測到的結果信息在 signTool 的 canvas 中繪制出來。
class SignTool { ... /* * 標識面部 * param {Array} faces 需要標識的部分 */ drawFaces(faces=[], aspect = 1) { faces.length > 0 && faces.forEach(face => { this.drawRect(face.boundingBox, aspect); }) } /* * 繪制 rect * param {object} rect 需要繪制的 rect */ drawRect(rect={}, aspect = 1) { this.ctx.save() this.ctx.beginPath(); this.ctx.lineWidth = rect.lineWidth || 2; this.ctx.strokeStyle = rect.color || 'red'; this.ctx.rect(Math.floor(rect.x * aspect), Math.floor(rect.y * aspect), Math.floor(rect.width * aspect), Math.floor(rect.height * aspect) ); this.ctx.stroke(); this.ctx.restore(); } ... }
選擇面部
本 demo 中為 signTool 的 canvas添加點擊事件,通過鼠標坐標與檢測到的所有面部信息進行比較,判斷是否選中面部。選中之后,重新繪制一遍所有的面部,在將選中面部高亮。
class FaceTag { ... /* * signTool 的 canvas 添加點擊事件 */ addEvent() { if(!this.signTool.canvas) { return; } let canvas = this.signTool.canvas; let canvasBox = canvas.getBoundingClientRect(); canvas.addEventListener('click', e => { // 計算鼠標在canvas中的位置 let eX = e.clientX - canvasBox.left; let eY = e.clientY - canvasBox.top; this.findFace(this.img.getImage(), eX, eY) .then(face => { this.signTool.drawFaces(this.faces); this.heightLighRect(face); // 保存選中的面部 this.beTagFace = face; }) }) }
... }
最后,beTagFace 的信息,將文字標注在方框附近 ,對面部的標記就完成啦。
Demo2
本 demo 結合了 WebRTC,並將 面部檢測部分移到了 Web Worker 中計算。
/* * 獲取視頻流 * @param {Object} opt video 的參數 */ getUserMedia(opt) { navigator.mediaDevices.getUserMedia({ video: opt }).then(stream => { this.createVideo(stream); resizeCanvas(); }).catch(err => { alert(err); }) } /* * 創建 video 標簽 * @param {Object} stream 視頻流 */ createVideo(stream) { this.video = document.createElement('video'); this.video.autoplay = 'autoplay'; this.video.src = window.URL.createObjectURL(stream); this.container.appendChild(this.video); }
通過 navigator.mediaDevices.getUserMedia 獲取權限打開攝像頭,獲取視頻流。
由於 detect()需要傳入 ImageBitmapSource 對象,因此將視頻流繪制到 canvas 中,通過 canvas 獲取圖像信息,將圖像信息傳遞給 Web Worker,進行面部檢測。之后的就和 demo1 大致相同。