最近,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.
这是一个面部标记的 demo。代码。Ps:js 含有 ES6,没有转为 ES5,只能在支持 ES6 的浏览器上体验。
本来只是想简单的体验一下这个 API,没想到一不小心就写了这么多。。。
在这个 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 的信息,将文字标注在方框附近 ,对面部的标记就完成啦。