VUE项目-H5端人脸识别功能实现

需求:

安卓原生app端接入腾讯人脸核身sdk,采集人脸信息和账号绑定。用户使用同一账号登录微信公众号h5端,h5采集人脸信息,调用腾讯人脸比对接口,和原生app端采集的人脸信息比对,是否是同一个人,然后再进行下一步操作。

H5实现思路:

1.如何调用移动端设备摄像头

2.使用tracking.js捕捉人脸

3.对人脸进行拍照,描绘到canvas画布上面

4.将照片转换成base64格式

5.调用后台接口,后台调用腾讯人脸比对接口进行比对。(由于调用腾讯人脸比对接口需要签名,签名在后台做比较合适)

代码以及注释

1.下载tracking.js 网址:https://trackingjs.com   将需要的js文件复制到静态资源文件夹


静态资源引入

2.使用video标签调用手机摄像头,playsinline、webkit-playsinline属性非常重要,如果不写会掉不起来苹果手机的摄像头。微信开发者文档上面有明确的说明,喜欢钻研的同学可以到微信开发者文档中查看文档。video和canvas的宽高的写法是为了适配手机屏幕的大小。

```

<template>

  <div id="app">

    <div v-show="showContainer" class="face-capture" id="face-capture">

      <p class="tip">请保持人像在取景框内</p>

      <video

        id="video"

        :width="vwidth"

        :height="vheight"

        playsinline

        webkit-playsinline

      ></video>

      <canvas id="refCanvas" :width="cwidth" :height="cheight"></canvas>

      <img

        class="img-cover"

        src="@/assets/img/face/yuanxingtouming.png"

        alt=""

      />

      <p class="contentp">{{ scanTip }}</p>

    </div>

    <div v-if="!showContainer" class="img-face">

      <img class="imgurl" :src="imgUrl" />

    </div>

  </div>

</template>

```

3.获取媒体设备,video开始播放

```

import tracking from "@/assets/js/tracking-min.js";

import "@/assets/js/data/face-min.js";

import "@/assets/js/data/eye-min.js";

import "@/assets/js/data/mouth-min.js";

import { faceInfo, conFace } from "@/request/api/my.js";

export default {

  data() {

    return {

      screenSize: {

        width: window.screen.width,

        height: window.screen.height,

      },

      URL: null,

      streamIns: null, // 视频流

      showContainer: true, // 显示

      tracker: null,

      tipFlag: false, // 提示用户已经检测到

      flag: false, // 判断是否已经拍照

      context: null, // canvas上下文

      profile: [], // 轮廓

      removePhotoID: null, // 停止转换图片

      scanTip: "人脸识别中...", // 提示文字

      imgUrl: "",

      canvas: null,

      trackertask: null,

      vwidth: "266",

      vheight: "266",

      cwidth: "266",

      cheight: "266",

      userInfo: {},

      orderData: {},

    };

  },

mounted() {

    //设置video canvas宽高

    const scale = this.screenSize.width / 375;

    this.vwidth = 266 * scale;

    this.vheight = 266 * scale;

    this.cwidth = 266 * scale;

    this.cheight = 266 * scale;

    this.playVideo();

  },

methods: {

playVideo() {

      this.getUserMedia(

        {

          //摄像头拍摄的区域

          video: {

            width: 500,

            height: 500,

            facingMode: "user",

          } /* 前置优先 */,

        },

        this.success,

        this.error

      );

    },

    // 访问用户媒体设备

    getUserMedia(constrains, success, error) {

      if (navigator.mediaDevices.getUserMedia) {

        //最新标准API

        navigator.mediaDevices

          .getUserMedia(constrains)

          .then(success)

          .catch(error);

      } else if (navigator.webkitGetUserMedia) {

        //webkit内核浏览器

        navigator.webkitGetUserMedia(constrains).then(success).catch(error);

      } else if (navigator.mozGetUserMedia) {

        //Firefox浏览器

        navagator.mozGetUserMedia(constrains).then(success).catch(error);

      } else if (navigator.getUserMedia) {

        //旧版API

        navigator.getUserMedia(constrains).then(success).catch(error);

      } else {

        this.scanTip = "你的浏览器不支持访问用户媒体设备";

      }

    },

}

```

4.安卓手机在允许调用摄像头之后正常调用和播放,苹果手机第一次允许之后调不出来摄像头,返回到上个页面再次进入人脸识别页面,可以正常调用。这个问题想了很多方向,使用了微信api里面的一些监听方法,依然无效。想了一些策略,在前一个页面获取媒体设备等,都不是完美方案。 耽误了两天时间,这天中午午休,我做梦梦到一个美女。那美女竟然穿着女仆装,超短的蕾丝花边裙,修长光溜溜的美腿,洁白秀美的脸蛋,乌黑发亮的发丝,圆润丰满的胸脯,简直是梦想中的女朋友。我对着那个美女抛了一个媚眼,那美女便婀娜多姿地眨着眼睛回应我。我咽了一口口水,忍不住便问:你如何才能做我女朋友。那美女张开樱桃小口,突出几个调皮的字来:你抓到我,我便以身相许。此话一听,我哪能安奈住自己激动的内心。迅速向她扑去,可是我向她跑去,她就转身向后跑,我停下来,她就停下来对我抛飞吻。我追呀追,她跑呀跑,她始终跟我保持着一定的距离,我是怎么都追不上她,气的我吐血。突然,我心想,上次做梦,我得到了一件短暂时间穿越能力。只要我集中精力发功,我就能穿越到未来1秒以后的时间。想到此我就开始集中精力,我一追她便跑,我一停她便停。突然这个世界相对与我静止了1秒钟,我利用这1秒的时间,迅速接近女仆装美女。1秒1秒又1秒,我终于抱住了女仆装美女,那美女对我眉开眼笑,还用樱桃小口往我脸上亲......突然,音乐响起,午休时间结束,我意犹未尽的醒来,发现抱枕上面都是口水,赶快趁人不注意用纸擦掉。又是突然,我想到了是否可以在调用video.play方法的时候,给它加一个定时器,让它1秒钟之后再执行。  没想到加上之后,苹果手机上面的问题解决了。自此,我猜想:当我们询问是否可以调用手机摄像头的时候,苹果的系统弹框阻碍了js的执行,在那一刻js代码停止执行,但是当我们点击允许之后,js代码没有继续执行,因为系统没有手段或者说事件去触发代码继续走。我们加上setTimeout函数之后,系统有了可以让代码继续执行的触发手段,所以js代码继续执行,我们就可以进行下一步人脸捕捉了。如果同学们有更好的解释,欢迎留言。


点击允许





点击允许苹果手机并不掉起摄像头

代码

```

success(stream) {

      this.streamIns = stream;

      const video = document.getElementById("video");

      // webkit内核浏览器

      this.URL = window.URL || window.webkitURL;

      if ("srcObject" in video) {

        video.srcObject = stream;

      } else {

        video.src = this.URL.createObjectURL(stream);

      }

      // 苹果手机的系统弹框会阻止js的线程的继续执行 手动0.1秒之后自动执行代码

      setTimeout(() => {

        video.play();

        this.initTracker();// 人脸捕捉

      }, 100);

    },

    error(e) {

      this.scanTip = "访问用户媒体失败";

    },

```

5.设置各种参数 实例化人脸捕捉实例对象


识别中

```

// 人脸捕捉 设置各种参数 实例化人脸捕捉实例对象,注意canvas上面的动画效果。

    initTracker() {

      this.context = document.getElementById("refCanvas").getContext("2d"); // 画布

      this.canvas = document.getElementById("refCanvas");

      this.tracker = new window.tracking.ObjectTracker("face"); // tracker实例

      this.tracker.setInitialScale(4);

      this.tracker.setStepSize(2); // 设置步长

      this.tracker.setEdgesDensity(0.1);

      try {

        this.trackertask = window.tracking.track("#video", this.tracker); // 开始追踪

      } catch (e) {

        this.scanTip = "访问用户媒体失败,请重试";

      }

      //开始捕捉方法 一直不停的检测人脸直到检测到人脸

      this.tracker.on("track", (e) => {

        //画布描绘之前清空画布

        this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);

        if (e.data.length === 0) {

          this.scanTip = "未检测到人脸";

        } else {

          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.scanTip = "检测成功,正在拍照,请保持不动2秒";

          }

          // 1.5秒后拍照,仅拍一次 给用户一个准备时间

          // falg 限制一直捕捉人脸,只要拍照之后就停止检测

          if (!this.flag) {

            this.scanTip = "拍照中...";

            this.flag = true;

            this.removePhotoID = setTimeout(() => {

              this.tackPhoto();

              document.getElementById("video").pause();

              this.tipFlag = true;

            }, 1500);

          }

        }

      });

    },

```

6.拍照 绘制照片,计算照片大小,腾讯人脸比对接口对照片大小和格式有要求。


```

// 拍照

    tackPhoto() {

      // 在画布上面绘制拍到的照片

      this.context.drawImage(

        document.getElementById("video"),

        0,

        0,

        this.vwidth,

        this.vwidth

      );

      // 保存为base64格式

      this.imgUrl = this.saveAsPNG(document.getElementById("refCanvas"));

      /** 拿到base64格式图片之后就可以在this.compare方法中去调用后端接口比较了,也可以调用getBlobBydataURI方法转化成文件再去比较

       * 我们项目里有一个设置个人头像的地方,先保存一下用户的图片,然后去拿这个图片的地址和当前拍照图片给后端接口去比较。

       * */

      // this.compare(imgUrl)

      //判断图片大小

      this.imgSize();

      this.faceToTengXun(); // 人脸比对

      this.close();

    },

    imgSize() {

      if (this.imgUrl) {

        // 获取base64图片byte大小

        const equalIndex = this.imgUrl.indexOf("="); // 获取=号下标

        let size;

        if (equalIndex > 0) {

          const str = this.imgUrl.substring(0, equalIndex); // 去除=号

          const strLength = str.length;

          const fileLength = strLength - (strLength / 8) * 2; // 真实的图片byte大小

          size = Math.floor(fileLength / 1024); // 向下取整

          console.log("size", size + "KB");

        } else {

          const strLength = this.imgUrl.length;

          const fileLength = strLength - (strLength / 8) * 2;

          size = Math.floor(fileLength / 1024); // 向下取整

          console.log("size", size + "KB");

        }

        if (size > 1024) {

          // 图片超过1M 按比例压缩

          this.imgUrl = document

            .getElementById("refCanvas")

            .toDataURL("image/png", 1024 / size);

        }

      }

    },

    // 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 得到文件后进行人脸识别

    // },

    // 保存为png,base64格式图片

    saveAsPNG(c) {

      return c.toDataURL("image/png", 0.4);

    },

```

7. 人脸采集之后,移除实例化对象,初始化参数和css样式部分代码

```

close() {

      this.flag = false;

      this.tipFlag = false;

      this.showContainer = false;

      this.context = null;

      this.scanTip = "人脸识别中...";

      clearTimeout(this.removePhotoID);

      if (this.streamIns) {

        this.streamIns.enabled = false;

        this.streamIns.getTracks()[0].stop();

        this.streamIns.getVideoTracks()[0].stop();

      }

      this.streamIns = null;

      this.trackertask.stop();

      this.tracker = null;

    }

<style>

.face-capture {

  display: flex;

  flex-direction: column;

  align-items: center;

  justify-content: center;

}

.tip {

  position: fixed;

  top: 48px;

  z-index: 5;

  font-size: 18px;

  font-family: PingFangSC-Medium, PingFang SC;

  font-weight: 500;

  color: #333333;

  line-height: 25px;

}

.face-capture video,

.face-capture canvas {

  position: fixed;

  top: 117.5px;

  object-fit: cover;

  z-index: 2;

  background-repeat: no-repeat;

  background-size: 100% 100%;

}

.face-capture .img-cover {

  position: fixed;

  top: 63px;

  width: 375px;

  height: 375px;

  object-fit: cover;

  z-index: 3;

  background-repeat: no-repeat;

  background-size: 100% 100%;

}

.face-capture .contentp {

  position: fixed;

  top: 438px;

  font-size: 18px;

  font-weight: 500;

  color: #333333;

}

.face-capture .rect {

  border: 2px solid #0aeb08;

  position: fixed;

  z-index: 4;

}

.img-face {

  display: flex;

  flex-direction: column;

  align-items: center;

  justify-content: center;

}

.img-face .imgurl {

  position: fixed;

  top: 117.5px;

  width: 266px;

  height: 266px;

  border-radius: 133px;

}

</style>

```

总结:

1.人脸捕捉技术使用的tracking.js,关键是要理解它的运作原理和一下参数配置。

2.因为要调用手机媒体设备,兼容性问题是大问题,特别是苹果手机的问题,往往不知道如何下手,需要有丰富的开发经验和各种曲线救国的开发思想。

3.那个梦真的很重要。

4.对,美女更重要。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,053评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,527评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,779评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,685评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,699评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,609评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,989评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,654评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,890评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,634评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,716评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,394评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,976评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,950评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,191评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,849评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,458评论 2 342

推荐阅读更多精彩内容