总篇112篇 2021年第3篇
全文5986字,阅读约需10分钟
弹幕是显示在视频上的评论,可以以滚动、停留甚至更多动作特效方式出现在视频上,是观看视频的人发送的简短评论。
各大视频网站目前都有弹幕功能,之家也于2020年5月正式上线视频弹幕功能,受到了广大网友的喜爱,大家在观看视频的同时,也能通过弹幕进行互动。
但密集的弹幕,遮挡视频画面,严重影响用户观看体验,如何解决?
有人可能会说:可以关弹幕啊。但,把弹幕关闭看视频,那就完全失去了乐趣,其实弹幕也是视频的一部分。如何在用户既想看弹幕又不影响视频观看的体验呢?
我们查阅了相关视频网站,发现B站推出了一种蒙版弹幕技术,可以让弹幕自动躲避人形区域,达到弹幕不挡人的效果。
原理如下:通过AI计算机视觉的技术,对视频内容进行分析,并将之前已经定义好的“视频主体内容”进行识别,生成蒙版并分发给客户端后,让客户端利用 CSS3 的特性进行渲染从而达成最终的效果。这样就形成了我们最终看到的,“不挡脸”弹幕效果。
实现方法就正如 PS 中的“蒙版“一样,实心区域允许,空白区域拒绝,从而达到弹幕不挡人的效果。而技术的核心就在蒙版的生成上,所以将这个功能称之为‘蒙版弹幕’。
蒙版弹幕原理已经了解了,但如何实现呢?我们调研了以下几个功能点:
实例分割方法和指标对比:
Mask R-CNN:
BlendMask:
我们的选择:BlendMask。原因:BlendMask是一种自顶向下和自底向上设计策略相结合的实例分割算法,在精度上超越了MaskR-CNN,速度上快20%,可谓完美替代Mask R-CNN,是实例分割新标杆!
我们的选择:Detectron2。原因:基于PyTorch;FAIR出品;R-CNN系列最强实现;高度模块化,扩展性好;训练速度更快。BlendMask算法在Detectron2上也有实现:AdelaiDet组件。
思路一:
蒙版任务生产端:对视频进行分析,将视频按照时长分段,各个分段视频信息包装成任务的形式存储在数据库中。比如一个视频时长为20分钟,我们可以将该视频分为两个任务:0-10分钟为第一个任务,11-20分钟为第二个任务。
蒙版任务调度端:对蒙版任务进行调度,并对任务进行监控,如果任务有异常情况,进行报警。
蒙版任务消费端:向蒙版任务调度端请求任务,执行蒙版生成任务,将各个蒙版图片压缩打包,存入之家FastDFS文件系统。
思路二:
蒙版任务消费端进行蒙版生成的流程如下:
根据蒙版任务包含的视频URL,蒙版任务对应的视频开始时间,蒙版任务对应的视频结束时间,使用FFmpeg依次提取视频每帧的视频画面,对提取的视频画面,使用Detectron2组件,进行实例分割,获得该视频画面对应的人像区域,将该人像区域使用potrace组件保存成SVG图像。并对多张SVG图片进行zlib压缩打包成单一文件,调用之家FastDFS上传文件接口将该文件存入之家FastDFS文件系统,将FastDFS返回的文件URL和视频信息存入数据库中。
思路三:
蒙版文件数据交互协议:
我们把多张蒙版SVG图片进行打包成单一文件,每张图片需要有对应视频的显示时间和其他的相关信息,而且需要支持压缩,所以,我们设计了一套私有二进制蒙版文件协议,方便数据传输和存储,该协议也能支持长连接传输。协议如下:
名称 | 类型 | 长度 | 偏移量 | 描述 |
Mask Header | 包头 | |||
Tag | int32 | 4 | 0 | 标识,固定值“ATHM”或者"MHTA",如果值是"ATHM"表示高字节序(网络字节序),如果值是"MHTA"表示低字节序 |
Version | int16 | 2 | 4 | 协议版本(目前是103) 协议100:原始svg图片文本 协议101: svg去除换行符(\r\n),并只返回d=""里面的内容,多个d=""以"|"分隔 svg:<g transform="translate(0.000000,56.000000) scale(0.100000,-0.100000)" fill="#000000" stroke="none"> 协议102: 蒙层图片路径扩大 1倍, 高斯模糊边缘虚化 大小设置为 8 , svg : <g transform="translate(0.000000,56.000000) scale(0.100000,-0.100000)" fill="#000000" stroke="none"> 协议103: 蒙层图片路径扩大 1倍, 高斯模糊边缘虚化 大小设置为 8 , svg : <g transform="scale(0.100000,0.100000)" fill="#000000" stroke="none"> |
Safe Code | int16 | 2 | 6 | 安全校验码(目前是65535) |
Segments | int16 | 2 | 8 | 该数据包里有多少个段数据 |
Media Type | int8 | 1 | 10 | 多媒体类型 1: FLV 2:HLS(M3U8) 3:RTMP |
Etc. | int8 | 1 | 11 | 扩展字段位,目前:1表示Segment Data ZLiB压缩 255 表示Segment Data 无压缩 |
FPS(video frames per seconds) | int8 | 1 | 12 | 视频帧率 |
MPS(video masks per seconds) | int8 | 1 | 13 | 视频一秒几帧蒙版 |
Mask Meta Data | 12*Segments | 每段的元数据(一个Mask Meta Data: 12字节) | ||
Segment Start MilliSecond | int32 | 4 | 该段数据里开始的毫秒数 | |
Segment End MilliSecond | int32 | 4 | 该段数据里结束的毫秒数 | |
Segment Length | int32 | 4 | 该段数据长度 | |
Body | 包体 | |||
Segment... | 段数据 | |||
Time | int32 | 4 | 该张图蒙版对应的时间(毫秒数) | |
Data Length | int32 | 4 | 该张图蒙版数据长度 | |
Data | byte[] | 该张图蒙版数据(svg数据) | ||
1.提供如下接口,供蒙版任务消费端调用:
a. 获得待执行的蒙版生成任务
b. 领取蒙版生成任务
c. 上报任务执行状态
d. 上报生成好的蒙版文件
a. 调用蒙版任务调度端接口,获得当前待领取的任务
b. 调用蒙版任务调度端接口,领取该任务
c. 如未领取任务成功,程序休眠1分钟,然后继续领取任务
a. 根据任务定义的视频开始时间和结束时间,使用FFmpeg截取相关时段视频,并保存在本地。
b. 使用python的cv2库,依次读取该时间段视频的所有帧。
c. 使用Detectron2提供的实例分割技术,对视频帧的人像区域进行预测,对一些识别评分低的,区域太大或太小的人像舍弃,仅保留我们需要的人像区域,评分太低的人像有可能该物体不是人像,区域太大的人像会“镂空”过多的弹幕,影响弹幕观看体验。人像区域太小,弹幕是否遮挡意义不大,所以也舍弃之。
d. 对预测的人像生成蒙版png图片,使用potrace组件,将该png图片转换成SVG(1KB左右)。
e. 多张SVG图片进行压缩,打包,打包时需要将每张图片对应的视频时间(毫秒级)也一并打包,生成单个二进制文件。
f. 将该二进制文件上传到之家云服务器(FastDFS)中。
g. 文件上传后将获得的文件url调用蒙版任务调度端接口,将该文件url存储到数据库中。
h. 定时向蒙版任务调度端上报任务执行情况。任务调度端会定期扫描所有正在执行的任务,如果任务消费端程序长时间未上报任务状态,说明该程序可能卡死,或者进程已被kill,任务调度端会根据该任务生成一个新的任务,供其他任务消费端程序领取执行,同时,任务调度端会进行短信报警。
a. 任务结束,调用蒙版任务调度端接口,告知该任务已完成。
b. 执行任务清理工作:删除本地视频,临时文件等。
原创视频,格式为:m3u8。
与原创视频领取任务一致。
a. 读取重播视频m3u8文件列表。
b. 使用PyAV组件,依次读取每个ts文件的开始和结束pts时间。
c. 根据任务定义的视频开始时间和结束时间定位到具体ts文件的位置。
d. 依次读取该时间段视频的所有帧。
e. 使用Detectron2的实例分割技术,对视频帧的人像进行预测。
f. 之后的流程与原创视频执行任务一致。
3. 结束任务
与原创视频结束任务一致。
4.3.3 直播场景 (开发中)
直播场景下的防遮挡弹幕目前正在开发中,由于直播场景的实时性要求,不管是前端还是云端服务,也遇到了更多的挑战。
同时,为了烘托直播的氛围,提升用户停留时长,我们将加入智能弹幕,丰富弹幕的内容。
后续,我们会出专门的文章,分别讲解前端和云端在直播场景下防遮挡弹幕和智能弹幕的实践,敬请期待。
问题:刚上线的时候,程序经常报cuda runtime error(2): out of memory。
优化过程:
这是因为PyTorch 使用缓存内存分配器来加速内存分配,有时候不会正确的释放所有cuda内存,需手动调用torch.cuda.empty_cache()来释放cuda内存。我们定义了一个阈值,在处理100张图片后调用一次torch.cuda.empty_cache(),手动释放cuda内存。
结果:cuda内存占用由15G左右降低到900M左右。
问题:预测单张图片时间过长,单张图片200多ms。
优化过程:
a. 更换模型,由Mask-RCNN更换成最新的BlendMask模型
b. 预测前,对图片进行等比缩小(如果该图宽或高大于320px):如果该图宽>高,将宽度缩小到320px,高等比缩小到对应尺寸;如果该图宽<高,将高度缩小到320px,宽等比缩小到对应尺寸。
c. GPU硬件由Nvidia K80替换成V100。
结果:由预测一帧200多ms优化到35ms左右。
问题:预测图片的结果生成PNG图片耗时过长。
优化过程:
a. 我们刚开始是直接使用Detectron2中demo.py的代码的来处理预测结果生成PNG图片的,该部分代码有很多我们不需要的操作:对识别区域分类,对识别区域画线框和显示评分,对各个识别区域着色等等操作。
b. 我们将该部分代码舍弃,重新封装成我们自己需要的方法:获得预测结果后,只使用该预测结果的pred_masks字段,不进行其他操作,生成一张对应的PNG图片。
结果:预测图片的结果生成PNG图片耗时由130多ms优化到1ms左右。
问题:预测结果PNG图片转换成SVG图片耗时过长。
优化过程:
a. 刚开始我们使用的是potrace软件,通过linux管道命令的方式,将PNG图转换成SVG,每张图片耗时平均80多ms。
b. 我们使用pypotrace组件,修改部分源码重新安装编译,适配我们的项目。上线后,一张PNG图转化成SVG图耗时1ms左右。
结果:一张PNG图转化成SVG图耗时由80多ms优化到1ms左右。
问题:刚开始我们统一使用FFmpeg来对m3u8进行seek到任务对应的视频开始时间和结束时间,但只要m3u8视频的时长过长,比如几个小时的视频,FFmpegseek到视频末尾时,FFmpeg假死,一直不返回数据,或者返回的时间出错,PyAV组件读取m3u8文件时进行seek操作也可能会出错。
优化过程:
解析m3u8文件,依次读取m3u8文件对应的ts文件,获得每个ts文件开始时间和结束时间,这样,就能知道整个m3u8视频的具体每个视频帧对应时间的信息。
但等我们兴高采烈的执行此方案的时候,又遇到了一个新的问题,m3u8文件中如果遇到EXT-X-DISCONTINUITY这个tag的时候,该tag后面的ts文件里的pts会reset。所以,我们在读取m3u8文件的时候,遇到EXT-X-DISCONTINUITY这个tag,需要记录上该EXT-X-DISCONTINUITY tag上一个ts最后一帧的pts(姑且定义名称为:last_discontinuity_ts_pts),这样以后的ts文件的视频帧的pts都需要加上这个last_discontinuity_ts_pts。
结果:对m3u8文件seek操作正常,不会出错。
(汽车之家原创视频)
(汽车之家重播视频)
(蒙版弹幕消费端深度学习部署平台)
(蒙版弹幕消费端深度学习日志平台)
(蒙版弹幕消费端深度学习GPU监控平台)
本文主要介绍了AI防挡弹幕-蒙版弹幕的设计与实践,利用分布式任务调度系统,对视频进行蒙版生成。使用了相关AI计算机视觉相关算法对视频帧画面进行人像预测,并保存成蒙版文件,供客户端进行蒙版弹幕渲染。
开发过程中,遇到了很多问题,走了很多弯路,希望该文章对大家有所帮助,如有错误之处,敬请指正。
作者