實習期間有個需求,須要前端調用算法模型,封裝成npm包,供視頻會議組去用,從而在視頻會議中實現背景虛化,背景替換功能。後續可能會進一步加入一些好玩的功能,如面部特效(鬍子,一字眉),頭髮顏色替換等。css
實現效果應相似於下面這樣html
騰訊會議界面:前端
爲了給需求方演示,先採用google的TensorFlow.js的 BodyPix 模型作了一個小demo,先實現背景虛化和背景替換功能,模型的效果較爲滿意,顯示畫面流暢。vue
TensorFlow.js 是一個 JavaScript 庫。 咱們能夠藉助於它,來直接用 JavaScript 去建立新的機器學習模型和部署現有模型。對於前端人員入門機器學習十分友好。css3
TensorFlow.js 提供了不少開箱即用的預訓練模型(見下圖):
這裏選用了圖像處理類別裏面的BodyPix模型git
這是BodyPix的官方演示demo https://storage.googleapis.co...github
demo裏的功能對咱們的需求來講有些過於複雜,也沒有背景替換功能。所以,我本身寫了一個針對於背景虛化,背景替換場景的demo。算法
drawBokehEffect
方法;模型沒有現成的背景替換的接口,用canvas的繪製方法對模型的toMask
方法返回的遮罩對象 (由前景色&背景色的像素點數組,其中前景色表明人像區域,背景色表明其餘區域) 進行了一些處理,從而實現背景替換(後面會詳細介紹)。先上一下最終的效果:npm
1.起始界面:視頻在開啓攝像頭後會在下方展現,拍的照片會展現在視頻的下方canvas
有兩種方法
<!-- 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>
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-in
和destination-over
souce-in用於繪製要替換的新背景圖。
事先將人像部分(前景色)設爲透明,globalCompositeOperation 爲 source-in 類型時,背景圖將只在背景色區繪製,以下圖:
接下來只需切換爲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...