前端使用tensorflow.js模型實現瀏覽器攝像頭視頻流人像識別,背景虛化&背景替換

背景

實習期間有個需求,須要前端調用算法模型,封裝成npm包,供視頻會議組去用,從而在視頻會議中實現背景虛化,背景替換功能。後續可能會進一步加入一些好玩的功能,如面部特效(鬍子,一字眉),頭髮顏色替換等。css

實現效果應相似於下面這樣html

騰訊會議界面:前端

image-20210603181106629

爲了給需求方演示,先採用google的TensorFlow.jsBodyPix 模型作了一個小demo,先實現背景虛化和背景替換功能,模型的效果較爲滿意,顯示畫面流暢。vue

TensorFlow.js 是一個 JavaScript 庫。 咱們能夠藉助於它,來直接用 JavaScript 去建立新的機器學習模型和部署現有模型。對於前端人員入門機器學習十分友好。css3

TensorFlow.js 提供了不少開箱即用的預訓練模型(見下圖):
這裏選用了圖像處理類別裏面的BodyPix模型git

image-20210604145627620

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

demo裏的功能對咱們的需求來講有些過於複雜,也沒有背景替換功能。所以,我本身寫了一個針對於背景虛化,背景替換場景的demo。算法

介紹

  • 思路: 在瀏覽器中打開攝像頭,獲取視頻流圖片,調用tensorflow.jsbody-pix 模型的方法,來繪製結果。 其中背景虛化比較容易實現,可直接用模型提供的drawBokehEffect方法;模型沒有現成的背景替換的接口,用canvas的繪製方法對模型的toMask方法返回的遮罩對象 (由前景色&背景色的像素點數組,其中前景色表明人像區域,背景色表明其餘區域) 進行了一些處理,從而實現背景替換(後面會詳細介紹)。
  • 用到的技術:vue+element ui, tensorflow.js(無需特地學習,直接用其中的示例便可) 以及一些canvas的簡單操做
  • 本項目的代碼已放到github https://github.com/SprinaLF/f...

實現效果

先上一下最終的效果:npm

1.起始界面:視頻在開啓攝像頭後會在下方展現,拍的照片會展現在視頻的下方canvas

image-20210604164908989

  1. 背景虛化:可選擇中,高,低三種虛化程度

image-20210604165329606

  1. 背景替換:模式切換爲背景替換後,展現背景圖列表,可切換背景

image-20210604170532654

核心過程

一. 引入模型

有兩種方法

  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

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

相關文章
相關標籤/搜索