上一篇实现了一对多直播模式,但在实际场景中,有时会遇到使用虚拟背景的需求。现在就看看如何在前端实现给视频流添加虚拟背景,后面再将虚拟背景和直播以及视频会议组合起来,逐步实现一个功能完整的音视频会议系统
认识虚拟背景
在生活应该都见过虚拟背景,尤其是现在微信视频通话过程中新增的模糊背景功能。这个过程还挺复杂的,整个实现逻辑涉及到人物动态计算、人像抠图、背景填充(增加马赛克或者其他的色彩)等从而才能实现模糊背景这个看似简单的功能。
但是,我们不需要做这么多事情,只需要把人家训练好的人工智能模型拿来使用即可。
实现模糊背景需要的几个核心步骤:
- 识别视频中的人物 (模型实现)
- 动态从这个视频中扣出人物画面 (模型实现)
- 给非人部分增加马赛克或者其他的背景 (我们实现)
两个最关键最难的步骤由模型来实现,我们只需要将模型实现出来的效果再稍微修改一下即可。只要会使用模型,不必涉及到深层次的算法学习,没必要。
这里使用谷歌开源的一个机器学习框架 MediaPipe
实现虚拟背景的功能,官网地址在这里
所使用的模型是这个
利用上述框架中的图像分割模型,就可以实现我们在摄像头中的画面人物和背景分割的目标。分割完成后,还可以利用其他强大的功能,对已经分割识别的动态流自定义处理,进而实现背景自定义。
代码实现
- 首先在本地运行下官网示例,因为接下来就是要使用这个代码示例来完成虚拟背景。
- 运行没问题之后,安装依赖包
"@mediapipe/tasks-vision": "^0.10.10"
,然后直接复制示例中的代码到新建文件中(没有看错,就是直接复制,简单粗暴) - 复制完,就相当于实现了核心步骤的前两个步骤了,再来看看第三个步骤的实现,注意到官网中的这段话
再结合代码看看:
js
function callbackForVideo(result: ImageSegmenterResult) {
let imageData = canvasCtx.getImageData(
0,
0,
video.videoWidth,
video.videoHeight
).data;
const mask: Number[] = result.categoryMask.getAsFloat32Array();
let j = 0;
for (let i = 0; i < mask.length; ++i) {
const maskVal = Math.round(mask[i] * 255.0);
const legendColor = legendColors[maskVal % legendColors.length];
imageData[j] = (legendColor[0] + imageData[j]) / 2;
imageData[j + 1] = (legendColor[1] + imageData[j + 1]) / 2;
imageData[j + 2] = (legendColor[2] + imageData[j + 2]) / 2;
imageData[j + 3] = (legendColor[3] + imageData[j + 3]) / 2;
j += 4;
}
const uint8Array = new Uint8ClampedArray(imageData.buffer);
const dataNew = new ImageData(
uint8Array,
video.videoWidth,
video.videoHeight
);
canvasCtx.putImageData(dataNew, 0, 0);
if (webcamRunning === true) {
window.requestAnimationFrame(predictWebcam);
}
}
注意看mask
数组,里面就是模型切割人物和背景之后的数据了,这个数据里用0和1来区分了背景和人物。所以我们要做的就是在这里取出人物,然后再结合自定义图片的数据,就可以生成新的具有虚拟自定义背景的视频了。
- 那么先需要将自定义图片通过
canvas
绘制,然后拿到绘制后的图片数据,代码如下:
js
const createCustomImgData = () => {
return new Promise((resolve, reject) => {
const img = new Image();
img.onerror = (e) => {
reject("加载失败" + e);
};
img.onload = () => {
const canvas = document.createElement("canvas");
const ctx = canvas.getContext("2d");
canvas.width = videoDom.videoWidth;
canvas.height = videoDom.videoHeight;
ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
const data = ctx.getImageData(0, 0, canvas.width, canvas.height).data;
resolve(data); // 将获取到的 customImgData 传递给 resolve
};
img.src = "https://127.0.0.1:8088/src/assets/imgs/bg.jpg";
});
};
- 将拿到的自定义图片数据与
mask
数据进行混合,就可以生成虚拟自定义背景的视频了。重点在于for循环中
,代码如下:
js
const callbackForVideo = async (result) => {
if (!customImgData.value) {
customImgData.value = await createCustomImgData();
}
let imageData = canvasCtx.getImageData(
0,
0,
videoDom.videoWidth,
videoDom.videoHeight
).data;
const mask = result.categoryMask.getAsFloat32Array();
let j = 0;
for (let i = 0; i < mask.length; ++i) {
if (mask[i] === 0) {
const maskVal = Math.round(mask[i] * 255.0);
const legendColor = legendColors[maskVal % legendColors.length];
imageData[j] = (legendColor[0] + imageData[j]) / 2;
imageData[j + 1] = (legendColor[1] + imageData[j + 1]) / 2;
imageData[j + 2] = (legendColor[2] + imageData[j + 2]) / 2;
imageData[j + 3] = (legendColor[3] + imageData[j + 3]) / 2;
} else {
// 这里用自定义图片数据替换原来的mask数据
imageData[j] = customImgData.value[j];
imageData[j + 1] = customImgData.value[j + 1];
imageData[j + 2] = customImgData.value[j + 2];
imageData[j + 3] = customImgData.value[j + 3];
}
j += 4;
}
const uint8Array = new Uint8ClampedArray(imageData.buffer);
const dataNew = new ImageData(
uint8Array,
videoDom.videoWidth,
videoDom.videoHeight
);
canvasCtx.putImageData(dataNew, 0, 0);
if (webcamRunning.value === true) {
window.requestAnimationFrame(predictWebcam);
}
};
到这里,功能已经没啥问题了。我们只是实现了用自定义背景替换模型分割出来的背景,其他的都由模型来实现。总的来说还是挺简单的。当然,如果需要实现更多复杂的定制的功能,也可以在此基础上进行拓展。比如人脸识别,手势识别等等,这里的模型是可以叠加使用的,就不在此赘述了。
项目操作
项目地址
前端:gitee.com/yoboom/webr... 后端:gitee.com/yoboom/webr...
直接点开项目中的虚拟视频背景
即可,属于是有手就行。
下一章节会将虚拟背景加入到一对多直播模式中
另外, 推荐一下我的另一个开源项目:问卷平台
使用的技术栈为react + ts + echarts + 高德地图 + webrtc 目前正在持续开发中。有想要学习的小伙伴可以加入进来,一起交流学习