乐趣区

关于vue.js:前端使用tensorflowjs模型实现浏览器摄像头视频流人像识别背景虚化背景替换

背景

实习期间有个需要,须要前端调用算法模型,封装成 npm 包,供视频会议组去用,从而在视频会议中实现背景虚化,背景替换性能。后续可能会进一步退出一些好玩的性能,如面部特效(胡子,一字眉),头发色彩替换等。

实现成果应相似于上面这样

腾讯会议界面:

为了给需求方演示,先采纳 google 的 TensorFlow.jsBodyPix 模型做了一个小 demo,先实现背景虚化和背景替换性能,模型的成果较为称心,显示画面晦涩。

TensorFlow.js 是一个 JavaScript 库。咱们能够借助于它,来间接用 JavaScript 去创立新的机器学习模型和部署现有模型。对于前端人员入门机器学习非常敌对。

TensorFlow.js 提供了很多开箱即用的预训练模型(见下图):
这里选用了图像处理类别外面的 BodyPix 模型

这是 BodyPix 的官网演示 demo https://storage.googleapis.co…

demo 里的性能对咱们的需要来说有些过于简单,也没有背景替换性能。因而,我本人写了一个针对于背景虚化,背景替换场景的 demo。

介绍

  • 思路:在浏览器中关上摄像头,获取视频流图片,调用 tensorflow.jsbody-pix 模型的办法,来绘制后果。其中背景虚化比拟容易实现,可间接用模型提供的 drawBokehEffect 办法;模型没有现成的背景替换的接口,用 canvas 的绘制办法对模型的 toMask 办法返回的遮罩对象 (由前景色 & 背景色的像素点数组,其中前景色代表人像区域,背景色代表其余区域) 进行了一些解决,从而实现背景替换(前面会具体介绍)。
  • 用到的技术:vue+element ui, tensorflow.js(无需特意学习,间接用其中的示例即可) 以及一些 canvas 的简略操作
  • 本我的项目的代码已放到 github https://github.com/SprinaLF/f…

实现成果

先上一下最终的成果:

1. 起始界面:视频在开启摄像头后会在下方展现,拍的照片会展现在视频的下方

  1. 背景虚化:可抉择中,高,低三种虚化水平

  1. 背景替换:模式切换为背景替换后,展现背景图列表,可切换背景

外围过程

一. 引入模型

有两种办法

  1. 引入 script
<!-- Load TensorFlow.js -->
<script src="https://cdn.jsdelivr.net/npm/@tensorflow/tfjs@1.2"></script>
<!-- Load BodyPix -->
<script src="https://cdn.jsdelivr.net/npm/@tensorflow-models/body-pix@2.0"></script>
  1. 装置,用如下命令 (我的我的项目中曾经装置了 tensorflow.js 和 bodyPix,运行时间接yarn install 装置依赖即可)
$ npm install @tensorflow/tfjs 或 yarn add @tensorflow/tfjs
$ npm install @tensorflow-models/body-pix

二. 加载模型

body-pix 有两种算法模型架构,MobileNetV1 and ResNet50。

经本地尝试,MobileNetV1 启动速度十分慢,对 GPU 的要求比拟高,不适宜个别电脑及挪动设施,这里只思考 MobileNetV1

初始时调用 loadAndPredict 办法事后加载模型,参数预设为:

model: {
          architecture: 'MobileNetV1',
          outputStride: 16,   //8,16  值越小,输入分辨率越大,模型越准确,速度越慢
          multiplier: 0.75,   // 0.5,0.75,1  值越大,层越大,模型越准确,速度越慢
          quantBytes: 2    /* 1,2,4  此参数管制用于权重量化的字节  
                             '4. 每个浮点数 4 个字节(无量化)。最高精度 & 原始模型尺寸',
                             '2. 每个浮点数 2 个字节。精度略低,模型尺寸减小 2 倍',
                             '1. 每个浮点数 1 个字节。精度升高, 模型尺寸缩小 4 倍' 
                           */
      },
 async loadAndPredict(model) {
   // 加载模型
   this.net = await bodyPix.load(model);
 }

三. 背景虚化

官网中的示例:

其中,net.segmentPerson(img)返回的是对图像像素剖析的后果, 如下图,

采纳的现有的 bodyPix.drawBokehEffect 办法,传入要虚化的图片和要绘制的 canvas 对象,segmentation以及一些虚化水平的参数, 即可将后果绘制到传入的 canvas。

虚化背景代码:

 async blurBackground () {const img = this.$refs['video']   // 获取视频帧
      const segmentation = await this.net.segmentPerson(img);
      bodyPix.drawBokehEffect(
        this.videoCanvas, img, segmentation, this.backgroundBlurAmount,
        this.edgeBlurAmount, this.flipHorizontal);
   
      if(this.radio===2) {    // 入选中背景虚化时,用 requestAnimationFrame 一直调用 blurBackground
        requestAnimationFrame(this.blurBackground)
      } else this.clearCanvas(this.videoCanvas)

      // this.timer = setInterval(async() => {//   this.segmentation = await this.net.segmentPerson(img);
      //   bodyPix.drawBokehEffect(
      //     this.videoCanvas, img, this.segmentation, 3,
      //     this.edgeBlurAmount, this.flipHorizontal);
      // }, 60)
   },

补充:

这里须要一直的对视频帧进行解决,绘制到 canvas,能力保障晦涩的体验。

最后设置了一个定时器,每隔 60ms 就执行相应办法,然而成果并不好,能显著感到卡顿,性能也不好。于是我看了一下 bodyPix 的 demo 代码,外面用了 window.requestAnimationFrame来代替定时器。将 timer 换为此办法后,性能和晦涩度有了很大的晋升。

四. 背景替换

bodyPix 没有提供现成的背景替换的办法,但有个办法是返回一个遮罩对象,人像局部为传入的前景色,背景局部为传入的背景色(见下图)

能够用 canvas 的 globalCompositeOperation 属性设置要在绘制新形态时利用的合成操作的类型, 对遮罩进行解决来达成替换背景的目标。

globalCompositeOperation有十分多的类型,供咱们在之前的画布上 设置新图形的画下来时的操作(如并交差操作,绘制的层级,色调和亮度的保留),默认值为source-over, 在现有画布上下文之上绘制新图形。

这里用到了 source-indestination-over

  1. 绘制背景图

souce-in 用于绘制要替换的新背景图。

当时将人像局部 (前景色) 设为通明,globalCompositeOperation 为 source-in 类型时,背景图将只在背景色区绘制,如下图:

  1. 绘制人像

接下来只需切换为destination-over,将人像绘制到画布现有内容前面即可。这样背景会挡住之前的背景,而人像将显示进去。

背景替换代码:

  async replaceBackground() {if(!this.isOpen) return
    const img = this.$refs['video']
    const segmentation = await this.net.segmentPerson(img);

    const foregroundColor = {r: 0, g: 0, b: 0, a: 0}    // 前景色  设为齐全通明
    const backgroundColor = {r: 0, g: 0, b: 0, a: 255}   // 背景色
    let backgroundDarkeningMask = bodyPix.toMask(
      segmentation,
      foregroundColor,
      backgroundColor
    )
    if (backgroundDarkeningMask) {let context = this.videoCanvas.getContext('2d')
      // 合成
      context.putImageData(backgroundDarkeningMask, 0, 0)
      context.globalCompositeOperation = 'source-in' // 新图形只在重合区域绘制
      context.drawImage(this.backgroundImg, 0, 0, this.videoCanvas.width, this.videoCanvas.height)
      context.globalCompositeOperation = 'destination-over' // 新图形只在不重合的区域绘制
      context.drawImage(img, 0, 0, this.videoCanvas.width, this.videoCanvas.height)
      context.globalCompositeOperation = 'source-over' // 复原
    }
    if(this.radio===3) {
      requestAnimationFrame(this.replaceBackground)
    } else {this.clearCanvas(this.videoCanvas)
    }
  },

其余:镜像

镜像没有用到 bodyPix 的办法,只管它为咱们提供了这样的操作

间接是通过 css3 实现的,借助 vue 的 v-bind 动静切换类

<canvas v-bind:class="{flipHorizontal: isFlipHorizontal}" id="videoCanvas" width="400" height="300"></canvas>
  
.flipHorizontal {transform: rotateY(180deg);
  }

参考

开启摄像头:https://www.cnblogs.com/ljx20…
TensorFlow.js 模型:https://github.com/tensorflow…
canvas:https://developer.mozilla.org…
JS 统计函数执行工夫:https://blog.csdn.net/K346K34…
开启摄像头:https://www.cnblogs.com/ljx20…
bodyPix 实现实时摄像头背景含糊 / 背景替换 https://www.tytion.net/archiv…

退出移动版