为什么B站的弹幕可以不挡人物

lxf2023-02-16 15:49:50

文中已经参与「 . 」

演试

//一键复制浏览
https://code.Admin.net/pen/7156805234864422943
序言

之前我也一直好奇心B站视频弹幕为何不遮挡角色,看过钱得乐教师的%20《为什么B站的弹幕可以不挡人物》的帖子知道里边的完成基本原理比较简单,就是通过-webkit-mask-image加一张人物透明图片就马上搞定。

知道完成基本原理之后我就在想,这个图片怎么获得?假如一帧一帧的从后面端获得,那样云服务器工作压力一定特别大,隐私功能又是一个极小的作用,服务器端解决很有可能不是一个最好解决方案,今天我就要说如何不依靠后面在手机客户端上进行一个这样子的作用。

如何做到?

根据观看图片我们都知道,只需要在视频在线观看时把角色的轮廓添充全透明别的地方填充色就可以,完成这样的一个作用我们就要引进tensorflowjs,应用官方网练习好一点的身体切分(Body%20Segmentation)实体模型,在视频在线观看时把每一帧形成一直照片,放进视频弹幕的父元素上。

架构和定义

抽象化整体上的完成构思如下所示

graph TD
A[读取Video界面] --> B[应用tensorflow载入面部识别实体模型形成MediaPipe] --> C[获得隔开人物图片]

引进

import * as bodySegmentation from '@tensorflow-models/body-segmentation';
import '@tensorflow/tfjs-core';
import '@tensorflow/tfjs-backend-webgl';
import '@mediapipe/selfie_segmentation';

建立身体分割模型

实体模型有 landscape(144x256 x3 )和 general(256x256 x3)二种,规格越多,鉴别越精确,另外特性也很差

//实体模型复位
async bodySegmentationInit(){
    try {
        const toast = this.$toast.loading({
            duration: 0, // 不断展现 toast
            forbidClick: true,
            message: '实体模型加载中',
        });
        const model = bodySegmentation.SupportedModels.MediaPipeSelfieSegmentation;
        const segmenterConfig = {
            runtime:'mediapipe',
            modelType:'landscape', 
            solutionPath:'https://cdn.jsdelivr.net/npm/@mediapipe/selfie_segmentation',
        };
        this.segmenter = await bodySegmentation.createSegmenter(model, segmenterConfig);
        toast.clear();
        this.dp && this.dp.notice('实体模型加载完成');
        this.dp && this.dp.play();
    } catch (error) {
        this.showDialog('实体模型加载失败-' error);
    }
}

生成图片

将图象制作到画板上,并制作包括具备特定不透明度的子网掩码的ImageDataImageData通常使用toBinaryMasktoColoredMask形成。

  • canvas 要绘制的画板。
  • image 运用口罩初始图象。
  • maskImage 包括子网掩码的图像信息。理想化前提下,这需要由toBinaryMasktoColoredMask.
  • maskOpacity 在图象顶端制作防护口罩时的不透明度。初始值为0.7。应当是0和1间的波动。
  • maskBlurAmount 模糊不清面罩的清晰度总数。初始值为0。应当是0到20间的整数金额。
  • flipHorizontal 如果结果应当水平翻转。默认false。
//鉴别
async recognition(){
    const canvas = document.createElement("canvas");
    const context = canvas.getContext('2d');
    //压缩视频规格(视频尺寸越大处理的时间越久,那应该缩小一下视频)
    const imageData = await this.compressionImage(this.dp.video);
    const segmentationConfig = {
        flipHorizontal: false,
        multiSegmentation: false,
        segmentBodyParts: true,
        segmentationThreshold:1,
    };
    const people = await this.segmenter.segmentPeople(imageData, segmentationConfig);
    const foregroundColor = {r: 0, g: 0, b: 0, a: 0}; //用以数据可视化归属于人像素的前景色 (r,g,b,a)。
    const backgroundColor = {r: 0, g: 0, b: 0, a: 255}; //用以数据可视化并不属于人像素的背景色 (r,g,b,a)。
    const drawContour = false; //是不是在每一个人切分蒙板周边制作轮廊。
    const foregroundThresholdProbability = this.foregroundThresholdProbability; //将清晰度上色为市场前景而非环境的最小几率。
    const backgroundDarkeningMask = await bodySegmentation.toBinaryMask(people, foregroundColor, backgroundColor, drawContour, foregroundThresholdProbability);
    // console.log('[backgroundDarkeningMask]',backgroundDarkeningMask);
    canvas.width = backgroundDarkeningMask.width;
    canvas.height = backgroundDarkeningMask.height;
    context.putImageData(backgroundDarkeningMask,0,0);
    const Base64 = canvas.toDataURL("image/png");
    this.maskImageUrl = Base64;
    const {width,height} = this.dp.video.getBoundingClientRect();
    //上传图片到缓存文件中(假如不载入到缓存文件中,也会导致mask-image无效,因为图片都还没载入到页面中,新的图片早已加上上来了,也会导致照片一直是个空缺)
    await this.imgLoad(Base64);
}

上边显示应用drawMask在图像和画板上制作toBinaryMask形成面具。在这样的情况下,segmentationThreshold设为0.25的较低值易耗,使子网掩码包括大量清晰度。前二张图像显示在图象顶端制作面具,后二张图像显示,在制作到图象上以前,将maskBlurAmount设为9,使面罩越来越模糊不清,进而在与蒙板环境中间完成更稳定的衔接。

将即时产生的照片放进画面中

这里有一个留意一个点,所有的图片形成之后都需要参与到缓存文件中,假如不载入到缓存文件中,也会导致mask-image无效,因为图片都还没载入到页面中,新的图片早已加上上来了,也会导致照片一直是个空缺

 danmaku.style = `-webkit-mask-image: url(${Base64});-webkit-mask-size: ${width}px ${height}px;`
文本文档

github.com/tensorflow/…

文中已经参与「 . 」