资讯专栏INFORMATION COLUMN

【小案例】基于色键技术的纯客户端实时蒙版弹幕

muzhuyu / 472人阅读

摘要:组件提供了一系列的操作接口以方便用户对弹幕的相关特性进行定制。对于这种类型的图像,我们可以使用色键的方式进行抠图生成蒙版。其中,用于更新蒙版的接口为。

导读:本文内容是笔者最近实现的 web 端弹幕组件—— Barrage UI 的一个延伸。在阅读本文的实例和相关代码之前,不妨先浏览项目文档,对组件的使用方式和相关接口进行了解。

各位童鞋如果经常上 B 站(bilibili.com) ,应该对 蒙版弹幕 这个概念并不陌生。

蒙版弹幕 是由知名弹幕视频网站 bilibili 于 2018 年中推出的一种弹幕渲染效果,可以有效减少弹幕文字对视频主体信息的干扰。

关于 B 站蒙版弹幕的实现原理,其实网上已经有很多细致的讨论和研究。个人总结了一下,大致要点如下:

基于用户数据和一些机器学习的相关应用,可以提炼出视频的关键主体

服务端预先对视频进行处理,并生成相应的蒙版数据

客户端播放视频时,实时地加载对应资源

通过一些前端的技术手段,实现弹幕的蒙版处理

客户端方面,由于 B 站弹幕是基于 div+css 的实现,因而采用了 svg 格式来传输矢量蒙版(至少目前是这样),通过 CSS 遮罩的方式实现渲染。

逼乎上有一篇关于这个方案的讨论,感兴趣的童鞋可以移步 这里 进行了解。

Barrage UI

Barrage UI 是个人最近实现的一个前端弹幕组件,主要用于在前端页面中挂载弹幕动画。

组件提供了一系列的操作接口以方便用户对弹幕的相关特性进行定制。你也可以在渲染层面对动画中的每一帧图像进行处理,比如:

实时读取视频信息

对每一帧视频图像进行实时处理,计算出抠图蒙版

将计算出的蒙版传给弹幕组件,以实现实时的蒙版弹幕

下面是基于 Barrage UI 组件实现的蒙版弹幕效果:

由于文中不方便嵌入视频,Demo 的实际效果请移步到 此处 查看。

下面我们来介绍如何实现上图的动画效果。

色键(色度键控)

Demo 中使用了初音小姐姐跳舞的视频。最主要的特点是除了人物外,视频的背景是比较一致的纯色。对于这种类型的图像,我们可以使用 色键 的方式进行抠图(生成“蒙版”)。

色度键控,又称色彩嵌空,是一种去背合成技术。Chroma 为纯色之意,Key 则是抽离颜色之意。把被拍摄的人物或物体放置于绿幕的前面,并进行去背后,将其替换成其他的背景。此技术在电影、电视剧及游戏制作中被大量使用,色键也是虚拟摄影棚(Virtual studio)与视觉效果(Visual effects)当中的一个重要环节。

下图是色键技术的一个示例:在绿幕前穿着蓝色衣服的小姐姐,左图为去背前,右图为去背后的新背景。

如何扣取视频图像

在浏览器环境中,我们可以通过 canvas 画布实时地绘制视频的每一帧,并从画布中读取到图像中每个像素的 RGBA 信息,检测每个点的 R(red)、G(green)、B(blue) 值是否满足要求,最终将需要扣除的像素的 A(alpha) 值置为 0,即可得到用于合成蒙版弹幕的蒙版图像。

注意:

Barrage UI 组件的蒙版功能是基于 Canvas 2D API 的 CanvasRenderingContext2D.globalCompositeOperation 属性实现的(使用了 source-in 的混合模式),因而只需将不需要的像素设置为透明(alpha=0)即可,并不需要改变图像的 RGB 色值。

下面介绍此案例的代码实现。

具体实现 安装 Barrage UI 组件

直接使用 yarn 或 npm 安装此组件:

yarn add barrage-ui or npm install --save barrage-ui

HTML + CSS

准备一个 video 元素用于播放视频,video 的父级元素用于挂载弹幕:

根据视频的实际尺寸(880×540)设置 #container#video 的样式:

html,
body {
  font: 14px/18px Helvetica, Arial, "Microsoft Yahei", Verdana, sans-serif;
  width: 100%;
  margin: 0;
  padding: 0;
  background: #eee;
  overflow: hidden;
}

#container,
#video {
  width: 880px;
  height: 540px;
}

#container {
  margin: 0 auto;
  margin-top: 50vh;
  margin-left: 50vw;
  transform: translate(-50%, -50%);
  background-color: #ffffd;
}
创建弹幕
import Barrage from "barrage-ui";
import data from "utils/mockData";

// 获取父级容器
const container = document.getElementById("container");

// 创建弹幕实例
const barrage = new Barrage({
  container: container,
});

// 重置画布高度,避免弹幕遮挡视频播放控件
barrage.canvas.height = container.clientHeight - 80;

// 装填弹幕数据
barrage.setData(data);

其中,mockData 是用于生成随机弹幕数据的方法。

关于弹幕数据的内容与格式,详见 Barrage UI 项目文档
实时获取视频图像
// 获取 video 元素
const video = document.getElementById("video");

// 新建一个画布来实时绘制视频(纯绘图,不用添加进页面)
const vCanvas = document.createElement("canvas");
vCanvas.width = video.clientWidth;
vCanvas.height = video.clientHeight;
const vContext = vCanvas.getContext("2d");

// 实时绘制视频到画布
barrage.afterRender = () => {
  vContext.drawImage(video, 0, 0, vCanvas.width, vCanvas.height);
};

使用组件提供的渲染周期钩子 .afterRender() 可以在弹幕动画的每一帧图像渲染后,将视频图像绘制到中间画布 vCanvas 上。注意这里的 vCanvas 画布主要用于实时地获取视频图像,并不需要添加到页面中。

实时计算蒙版信息
// 渲染前读取画布 vCanvas 的数据,并处理为蒙版图像
barrage.beforeRender = () => {
  // 读取图像
  const frame = vContext.getImageData(0, 0, vCanvas.width, vCanvas.height);

  // 图像总像素个数
  const pxCount = frame.data.length / 4;

  // 将 frame 构造成我们需要的蒙版图像
  for (let i = 0; i < pxCount; i++) {
    // 这里不用 ES6 解构赋值的写法,主要为了保证性能
    // PS: 这里如果用解构赋值语法将导致大量新对象的创建,是个很耗时的过程
    const r = frame.data[i * 4 + 0];
    const g = frame.data[i * 4 + 1];
    const b = frame.data[i * 4 + 2];

    // 将黑色区域以外的内容设为透明
    if (r > 15 || g > 15 || b > 15) {
      frame.data[4 * i + 3] = 0;
    }
  }

  // 设置蒙版
  barrage.setMask(frame);
};

使用组件提供的渲染周期钩子 .beforeRender() 可以在弹幕动画的每一帧图像渲染前计算出蒙版图像。其中,用于更新蒙版的接口为 .setMask()

视频、弹幕的操作绑定

最后,为了让弹幕的行为与视频播放的操作协同,还需要进行一些绑定的操作:

// 绑定播放事件
video.addEventListener(
  "play",
  () => {
    barrage.play();
  },
  false
);

// 绑定暂停事件
video.addEventListener(
  "pause",
  () => {
    barrage.pause();
  },
  false
);

// 切换播放进度
video.addEventListener(
  "seeked",
  () => {
    barrage.goto(video.currentTime * 1000);
  },
  false
);

这里分别用到 Brrage UI 组件的 .play() .pause .goto() 三个接口,分别用于播放暂停切换弹幕动画的进度。需要注意的是,通过 video.currentTime 属性获取到的视频播放进度是一个单位为 的浮点数,需要转换为 毫秒数 再传给弹幕组件。

源码奉上

本文的案例已上传 github,感兴趣的童鞋可以点击 这里 查看源码细节。

关于 Barrage UI 组件如果有什么建议和疑问,欢迎大家在项目中提 issue 给我,帮助我持续改进和迭代,更欢迎 star 和 PR。

感谢您能耐心读到此处,如果觉得有趣或有用,不妨 点赞/评论/转发 此文,再谢。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/101217.html

相关文章

  • B站智能防挡弹幕的一种python实现

    摘要:将图片的处理方法放到视频中的每一帧,再加上弹幕飞过的效果,就完成了版的智能防挡弹幕。不知道站的实现方法是怎样,是否有人工干预,是否有预计算。 某天代码写得老眼昏花,去B站上摸鱼,突然发现奇怪的现象: showImg(https://segmentfault.com/img/remote/1460000017911829?w=600&h=284); 哟呵,B站竟然做了 视频前景提取 ,把...

    jzzlee 评论0 收藏0
  • 20181012微信程序填坑手册~

    摘要:全屏蒙版弹窗遮不住的层级还是很高的,当出现全屏蒙版弹窗时,是无法盖住的,可以调用微信的,不过需要注意兼容低版本在类设置的颜色并没有变化。 从6月份开始到现在,写小程序将近4个月了开发时给自己埋了不少坑~给大家分享下我的填坑经验~~ 开发部分 1.小程序的组件修改不能触发页面刷新?需要在父级文件上保存下才会触发(使用wepy开发) 2.接口请求出现的问题?记得勾选调试开发工具上 不校验...

    刘玉平 评论0 收藏0
  • 只需四个步骤几行代码,即可快速实现直播弹幕功能

    摘要:想要快速实现这些功能,需要进行怎样的操作呢其实,实现直播弹幕功能只需四个步骤和几行代码现在,我们使用云巴,手把手教你实现直播弹幕功能吧步骤一注册云巴账号打开网址,或直接搜索云巴,进行注册并登录。 在视频直播中,互动是不可缺少、不容小觑的内容,主要包括发送弹幕、打赏、点赞等。想要快速实现这些功能,需要进行怎样的操作呢? 其实,实现直播弹幕功能只需四个步骤和几行代码! 现在,我们使用云巴 ...

    guyan0319 评论0 收藏0
  • 2016年云巴产品更新合集

    摘要:实时弹幕使用云巴,直播平台可快速实现视频直播中发送弹幕打赏点赞等实时互动功能。云巴聊天室支持图片上传文件发送文档评论系统正式上线新增搜索功能,我们会做得更好。 SDK 篇 Android SDK 更新 Release 1.6.3后台进程相互拉起的特殊版本 Release 1.6.4增加 so 文件 Release 1.8.0支持小米、华为推送,无需注册第三方账号 Release 1....

    CoffeX 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<