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

背景 实习期间有个需求,需要前端调用算法模型,封装成npm包,供视频会议组去用,从而在视频会议中实现背景虚化,背景替换功能。后续可能会进一步加入一些好玩的功能,如面部特效(胡子,一字眉),头发颜色替换等。
实现效果应类似于下面这样
腾讯会议界面:
前端使用tensorflow.js模型实现浏览器摄像头视频流人像识别,背景虚化&背景替换
文章图片

?
为了给需求方演示,先采用google的TensorFlow.js的 BodyPix 模型做了一个小demo,先实现背景虚化和背景替换功能,模型的效果较为满意,显示画面流畅。
TensorFlow.js 是一个 JavaScript 库。 我们可以借助于它,来直接用 JavaScript 去创建新的机器学习模型和部署现有模型。对于前端人员入门机器学习十分友好。
TensorFlow.js 提供了很多开箱即用的预训练模型(见下图):
这里选用了图像处理类别里面的BodyPix模型
前端使用tensorflow.js模型实现浏览器摄像头视频流人像识别,背景虚化&背景替换
文章图片

这是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.起始界面:视频在开启摄像头后会在下方展示,拍的照片会展示在视频的下方
前端使用tensorflow.js模型实现浏览器摄像头视频流人像识别,背景虚化&背景替换
文章图片

  1. 背景虚化:可选择中,高,低三种虚化程度
前端使用tensorflow.js模型实现浏览器摄像头视频流人像识别,背景虚化&背景替换
文章图片

  1. 背景替换:模式切换为背景替换后,展示背景图列表,可切换背景
前端使用tensorflow.js模型实现浏览器摄像头视频流人像识别,背景虚化&背景替换
文章图片

核心过程 一. 引入模型 有两种方法
  1. 引入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
前端使用tensorflow.js模型实现浏览器摄像头视频流人像识别,背景虚化&背景替换
文章图片

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

三. 背景虚化 官网中的示例:
前端使用tensorflow.js模型实现浏览器摄像头视频流人像识别,背景虚化&背景替换
文章图片

其中,net.segmentPerson(img)返回的是对图像像素分析的结果, 如下图,
前端使用tensorflow.js模型实现浏览器摄像头视频流人像识别,背景虚化&背景替换
文章图片

采用的现有的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没有提供现成的背景替换的方法,但有个方法是返回一个遮罩对象,人像部分为传入的前景色,背景部分为传入的背景色(见下图)
前端使用tensorflow.js模型实现浏览器摄像头视频流人像识别,背景虚化&背景替换
文章图片

可以用canvas的 globalCompositeOperation 属性设置要在绘制新形状时应用的合成操作的类型, 对遮罩进行处理来达成替换背景的目的。
globalCompositeOperation有非常多的类型,供我们在之前的画布上 设置新图形的画上去时的操作(如并交差操作,绘制的层级,色调和亮度的保留),默认值为source-over, 在现有画布上下文之上绘制新图形。
这里用到了source-indestination-over
前端使用tensorflow.js模型实现浏览器摄像头视频流人像识别,背景虚化&背景替换
文章图片

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

  1. 绘制背景图
souce-in用于绘制要替换的新背景图。
事先将人像部分(前景色)设为透明,globalCompositeOperation 为 source-in 类型时,背景图将只在背景色区绘制,如下图:
前端使用tensorflow.js模型实现浏览器摄像头视频流人像识别,背景虚化&背景替换
文章图片

  1. 绘制人像
接下来只需切换为destination-over,将人像绘制到画布现有内容后面即可。这样背景会挡住之前的背景,而人像将显示出来。
前端使用tensorflow.js模型实现浏览器摄像头视频流人像识别,背景虚化&背景替换
文章图片

背景替换代码:
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 动态切换类
.flipHorizontal { transform: rotateY(180deg); }

参考 【前端使用tensorflow.js模型实现浏览器摄像头视频流人像识别,背景虚化&背景替换】开启摄像头: 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...

    推荐阅读