基于浏览器的人脸识别标记

最近,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代码

 

在这个 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代码

 

本 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 大致相同。

posted @ 2017-09-25 20:22  x_x!!!  阅读(2871)  评论(3编辑  收藏  举报