【首发干货】优酷世界杯直播50帧极清算法解析

简介: 我们的FRUC算法同样具备非整倍数的帧率上变换能力,除了25到50之外,还支持24到50、25到60、30到60的上变换,包括了所有主流视频帧率的排列组合,近期也已经在优酷各种影剧综漫内容中大面积覆盖各端,欢迎大家体验~

image.png

导语

世界杯期间,优酷独家上线了50帧极清直播间,将央视的25帧/秒的世界杯直播信号通过算法转换为50帧/秒的高帧率视频,带给球迷独一无二的世界杯视听新体验,在这背后的算法原理又是什么?

“优酷技术”邀请到了50帧极清的算法负责人后藤(一位技术能力和文采同样过硬的小哥哥)为我们揭开“让小球平滑连贯地飞翔在绿茵场,世界细腻流畅地旋转于镜头前”的秘密。以下是正文:

本次世界杯期间,优酷将25帧转换为50帧的技术,也就是帧率上变换(Frame Rate Up-Conversion,FRUC)算法。

此前,李安导演的《比利林恩的中场战事》第一次使用120帧、4K、3D技术进行拍摄,给观众带来极度真实震撼的视觉感受的同时,也带来了“真实得不像电影”的争议:部分影评家认为“高帧率带来的太过清晰,让商业大片看起来像纪录片般索然无味”。

不难看出,高帧率对于视频内容的观看感受的影响是非常大的,

某种程度来说,电影或许有电影语言的表达和约束,以“艺术”之名去理解影评家对高帧率的反感是完全能够理解的,不过对于体育赛事的视频直播点播,则是绝对要追求沉浸感和真实感的。

人眼和大脑的联动是很神奇的,你看到的东西越真实,你的大脑就越会脑补它的细节让它更真实,直到难辨真假的地步——一些非常精美的IMAX 2D电影会让观众有立体感就是因为此,反例比如VR现阶段的问题就是让大脑能过于容易地判断看到的东西是假的,所以在视频内容与人的感受之间始终留有明显的落差,这是题外话。

那么对于足球比赛来说,高帧率带来的是什么?

足球的飞行速度可以超过每小时100公里。普通电视和互联网上的每秒25帧的视频里,两帧之间,足球位移已逾一米,比如射门时,25帧时观众看到的是这样的:
image.png

而50帧时观众看到的是这样的:

image.png

可以注意看足球的运动轨迹,25帧时观众看到的是球闪闪闪就闪进球门了,50帧时观众看到的是球划过一条连贯的轨迹进入球门。虽然大家多年来已经非常习惯于看前者,不过看到后者的时候还是觉得很惊艳的,尤其是50帧看多了,再回过头看25帧的时候就会觉得很不习惯,所谓由俭入奢易,由奢入俭难(笑)。

回到算法来。FRUC算法的问题定义是:已知上一帧画面F(n-1)和下一帧F(n+1),求当前帧F(n)。具体到从25帧到50帧的过程如下图,在每两帧原始帧之间插入一个新的帧。

image.png

比较常见的FRUC算法是基于运动估计和插值的。
当需要计算F(n)上点P的像素值时,先把F(n-1)和F(n+1)都拆成8x8的小块,然后将F(n-1)中包含点P的小块在F(n+1)中找跟它最像的块,这两个块之间的位置差就是运动向量MV_正向(Motion Vector),就得到了点P在F(n-1)中的运动速度和方向。再将F(n+1)中包含点P的小块在F(n-1)中找跟它最像的块,,得到MV_反向,即点P在F(n+1)中的运动速度和方向。如果是理想的刚体匀速运动,这两个MV是恰好相反的,所谓“最像的块”跟原本的块也严格一致。
然后,我们认为新生成的帧F(n)正好在F(n-1)和F(n+1)之间,所以我们将F(n-1)帧里MV_前向除2对应位置的块里的像素值,和F(n+1)帧里MV_后向除2对应位置的块里的像素值求加权平均,即可得F(n)里对应这个块的像素值。当理想刚体匀速运动如下图时,显然可见前后两帧里取到的像素块会是一样的,生成的图像也最锐最理想。

image.png

当情况不这么理想时,如物体有形变、物体太小、运动太快等时,有各种各样的优化方法不赘述,其中一个最明显的问题在于基于8x8块的运动估计太不精细了,要细化到每一个像素点的运动情况时不够准确,所以我们首先把基于块的算法优化为基于光流的。光流可以简单看成每个像素点的运动方向和大小,比如当前后两帧画面F(n-1)和F(n+1)是如下面上图这样的时,从F(n-1)到F(n+1)的正向光流、F(n+1)到F(n-1)的反向光流分别是如下面下图这样的。

image.png

人影依稀可见,球在正中央是反色的代表其运动方向与画面相反。基于光流图能获得更精准的插帧结果,这很显然可见,我们替换算法之后也获得了更好的结果。
然后,真正的问题来了:所谓飞翔的小球与旋转的世界之间难以调和的矛盾。

飞翔的小球,指快速运动的小物体。当我们回到上面的例子,当画面中这个球变小、变快之后,出现一个很大的问题。当F(n-1)和F(n+1)如下图这样时,人很容易判断出中间F(n)里小球应该位于中间的位置。可是当我在无论前一帧还是后一帧里针对这个位置做运动估计也好光流也好时,取到的像素都是背景,取不到这个球了。那我在计算F(n)里这个位置的像素时,只会取到背景像素,那也只能得到背景像素的像素值了,虽然在前后两帧里球的MV都能正确得到,但用不上,导致的结果就是F(n)应该出现球的位置没有球了,球丢了。

image.png

针对这个bad case有没有解法呢?也有。我们来看人是如何判断F(n)这个位置应该有个球的:人是先在前一帧里找球的位置,再在后一帧里找球的位置,然后找这两个位置的中心点,作为F(n)里的这个球的位置,然后假设这个球完全没有形变来得到其像素值。
我们把这个方法转化成算法语言(块比较方便描述所以还是用运动估计的方法举例,光流同理),就是先在F(n-1)里找到这个球对应位置的前向MV,映射到这个MV除2对应的F(n)上的位置去,然后再在F(n+1)里找到这个球对应位置的后向MV,映射到这个MV除2对应的F(n)上的位置去,即得到这个球在F(n)上的正确位置了。

image.png

这种方法解决了飞翔的小球问题,那它有什么缺陷呢?我们注意到在使用这种方法时,F(n)里的像素不是依次顺序生成的,而是凭着前后两帧的MV位置按随机的位置生成,导致它可能会有没有被填充的空洞、过于明显的边界等情况,在镜头本身有平移或拉伸等运动时尤其突出,这时视频中的世界在随着镜头而旋转,暴露的这种问题也最大。

所以,我们现在手里有两种算法,其中一种对旋转的世界更优质,另一种对飞翔的小球更优质,二者只能取其一,就很尴尬了。在绝大多数视频里,这两种情况都是会出现的,我们无论选哪一种算法,都会面临特定的bad case。那有没有一种算法能兼顾二者呢?我们暂时还没有找到,所以,我们就只能同时使用这两种算法了。
我们FRUC算法的总流程图如下:

image.png

其中解决不了飞翔的小球的前一种算法是从中间的生成帧向前、向后取值,称为外插法;解决不了旋转的世界的后一种算法是从前、后帧向中间的生成帧映射,称为内插法。外插法和内插法生成的帧都是不完美的,将二者进行融合以获得最终的完美生成帧。
融合分两步,先手工融合,再深度学习融合。手工融合是使用先验知识的规则化融合,包括优先填补内插法空洞、对内插帧和外插帧差异大的区域进行加权平均、对因错误取值而得到的伪轮廓及融合边缘进行平滑等,这一步可以生成高画质的、无显著错误的帧,但是有时画面不够自然。将此帧再进行深度学习融合,将其与外插帧一起过形如下图的网络,可获得效果相当自然的、无肉眼可见瑕疵的生成帧了。

我们的FRUC算法同样具备非整倍数的帧率上变换能力,除了25到50之外,还支持24到50、25到60、30到60的上变换,包括了所有主流视频帧率的排列组合,近期也已经在优酷各种影剧综漫内容中大面积覆盖各端,欢迎大家体验~

相关文章
|
弹性计算 Cloud Native 关系型数据库
【官方首发】—阿里云云计算&数据库ACP认证的解析与实战系列电子书来啦!
重磅推出【云计算、数据仓库、关系型数据库】ACP认证的解析与实战电子书!认证备考必备学习资料,免费下载!
【官方首发】—阿里云云计算&数据库ACP认证的解析与实战系列电子书来啦!
|
前端开发 UED 运维
独家首发 | 900页阿里文娱技术实战,8大技术栈解析技术全景
这是一本全面且实用的一本技术合辑,既有技术知识又有业务应用。阿里文娱长期的技术实践与创新经验包罗其中,900页的丰富内容,8大技术栈全景揭秘文娱技术,相信能给技术开发者和文娱行业从业者带来直接的帮助和启发。
32788 0
独家首发 | 900页阿里文娱技术实战,8大技术栈解析技术全景
|
开发框架 开发者 前端开发
重磅首发 | 承载亿级流量的开发框架,闲鱼Flutter技术解析与实战大公开
去年闲鱼发布的《Flutter in action》为开发者带去一手的实践经验总结,现在《Flutter in action》全新升级啦!这本书并非基础知识的简单罗列,而是从一线问题出发,循序渐进,娓娓道来。不仅把Flutter的重要理念讲得极为清晰, 而且给开发者提供了应对眼前各种问题的实用方法。同时,书中还给出了详尽的可以融会贯通、举一反三的思路,理论陈述和问题分析面面俱到,力求让读者可以获得全面系统的技术知识。
67404 0
重磅首发 | 承载亿级流量的开发框架,闲鱼Flutter技术解析与实战大公开
|
14天前
|
XML Java Android开发
Android实现自定义进度条(源码+解析)
Android实现自定义进度条(源码+解析)
47 1
|
29天前
|
Python
区域代理分红商城系统开发源码片段示例规则解析
level = Column(Integer, default=1) # 代理等级,例如:1代表普通用户,2代表初级代理,3代表高级代理等 parent_id = Column(Integer, ForeignKey('user.id')) # 上级代理ID 【更全面的开发源码搭建可V or TG我昵称】 parent = relationship("User", remote_side=[id]) # 上级代理对象
|
1月前
|
存储 安全 Java
ArrayList源码全面解析
ArrayList源码全面解析
|
2月前
|
C语言
内核源码中遇到不会解析的宏怎么办?
内核源码中遇到不会解析的宏怎么办?
202 1
|
2月前
|
Java 关系型数据库 数据库连接
Spring源码解析--深入Spring事务原理
本文将带领大家领略Spring事务的风采,Spring事务是我们在日常开发中经常会遇到的,也是各种大小面试中的高频题,希望通过本文,能让大家对Spring事务有个深入的了解,无论开发还是面试,都不会让Spring事务成为拦路虎。
33 1
|
3月前
|
缓存 Dubbo Java
Dubbo 第三节_ Dubbo的可扩展机制SPI源码解析
Dubbo会对DubboProtocol对象进⾏依赖注⼊(也就是⾃动给属性赋值,属性的类型为⼀个接⼝,记为A接⼝),这个时候,对于Dubbo来说它并不知道该给这个属性赋什么值,换句话说,Dubbo并不知道在进⾏依赖注⼊时该找⼀个什么的的扩展点对象给这个属性,这时就会预先赋值⼀个A接⼝的⾃适应扩展点实例,也就是A接⼝的⼀个代理对象。在调⽤getExtension去获取⼀个扩展点实例后,会对实例进⾏缓存,下次再获取同样名字的扩展点实例时就会从缓存中拿了。Protocol是⼀个接。但是,不是只要在⽅法上加了。
|
1月前
|
存储 安全 Java
HashMap源码全面解析
HashMap源码全面解析

推荐镜像

更多