3

前端使用tensorflow.js模型实现浏览器摄像头视频流人像识别,背景虚化&背景替换

 2 years ago
source link: https://blog.p2hp.com/archives/8578
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

前端使用tensorflow.js模型实现浏览器摄像头视频流人像识别,背景虚化&背景替换 | Lenix Blog

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

实现效果应类似于下面这样

腾讯会议界面:

image-20210603181106629

为了给需求方演示,先采用google的TensorFlow.js的 BodyPix 模型做了一个小demo,先实现背景虚化和背景替换功能,模型的效果较为满意,显示画面流畅。

TensorFlow.js 是一个 JavaScript 库。 我们可以借助于它,来直接用 JavaScript 去创建新的机器学习模型和部署现有模型。对于前端人员入门机器学习十分友好。

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

image-20210604145627620

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

demo里的功能对我们的需求来说有些过于复杂,也没有背景替换功能。因此,我自己写了一个针对于背景虚化,背景替换场景的demo。

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

先上一下最终的效果:

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

image-20210604164908989
  1. 背景虚化:可选择中,高,低三种虚化程度
image-20210604165329606
  1. 背景替换:模式切换为背景替换后,展示背景图列表,可切换背景
image-20210604170532654

一. 引入模型

有两种方法

  1. 引入script
<!-- Load TensorFlow.js -->
<script src="https://cdn.jsdelivr.net/npm/@tensorflow/[email protected]"></script>
<!-- Load BodyPix -->
<script src="https://cdn.jsdelivr.net/npm/@tensorflow-models/[email protected]"></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。

经本地尝试,ResNet50 启动速度非常慢,加载时间很长,对GPU的要求比较高,不适合一般电脑及移动设备,这里只考虑 MobileNetV1

image-20210603193308457

初始时调用 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);
 }

三. 背景虚化

官网中的示例:

image-20210604102042195

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

image-20210604102538333

采用的现有的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没有提供现成的背景替换的方法,但有个方法是返回一个遮罩对象,人像部分为传入的前景色,背景部分为传入的背景色(见下图)

image-20210604154232811

可以用canvas的 globalCompositeOperation 属性设置要在绘制新形状时应用的合成操作的类型, 对遮罩进行处理来达成替换背景的目的。

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

这里用到了source-indestination-over

image-20210604161019750
image-20210604161052928
  1. 绘制背景图

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

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

image-20210604160344895
  1. 绘制人像

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

image-20210604164112027

背景替换代码:

  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...


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK