B站自研色彩空间转换引擎

本期作者

1.   背景

色彩空间(Color Space)是一种数学模型,用于描述和表示颜色的方式。不同的色彩空间有不同的用途和特点,可以用于不同的应用,如图像处理、计算机图形、印刷、摄影等领域。它一般用于描述设备的色彩能力,或者视频、图片的色彩范围。比如现在大部分人用的手机的屏幕,色彩空间大多为sRGB或者DCI-P3;网页上常见的图片或者视频内容,大多属于sRGB色彩空间;B站客户端播放的HDR视频,则处于BT.2020色彩空间。

目前B站UP主投稿视频是千变万化的,它们包含各种不同的色彩参数, 比如不同的色深,不同的色度采样坐标,不同的色彩空间,不同的亮度传递函数等等(如下图),而且每个用户观看这些视频的终端也不一样,这样就产生了各种各样的兼容性问题——很多时候用户的设备并不能完全正确显示UP主投稿的“原片”,而且老旧的设备或者浏览器也并不支持广色域或者HDR视频。为了考虑终端设备的兼容性和B站服务的完整性,我们会对用户投稿的不同色彩空间的视频进行统一处理。但是如何将各种不同色彩参数的视频进行准确地统一处理,同时又能保证视频的处理速度,这对于我们来说是一个巨大的挑战。

图片

色彩空间转换中需要处理的参数

2.   引擎架构

为了处理各种不同色彩参数的视频,我们研发了一套色彩空间的转换引擎。该引擎会对各种不同色深、色彩空间、亮度传递函数的普通视频统一转换为标准的SDR视频。除了SDR视频, B站也会接收很多HDR视频投稿,对于HDR投稿,B站会统一转出一路标准的HDR视频和一路标准的SDR视频。

该图像转换引擎分为三层,包括滤镜层,引擎层,和设备层。其中滤镜层包含各种滤镜,用于处理不同的色彩参数。引擎层的主要目的是做一些预计算与计算路径优化,比如有一些矩阵乘法只需要计算一次,那么就可以预先计算出来,而不是在像素处理的时候再去做。有一些GPU需要用到的矩阵参数,则也可以进行预拷贝,防止出现CPU与GPU之间出现频繁拷贝的情况,计算路径优化,则是用来去除一些不必要的步骤,来减少计算量。最后,设备层主要包含每种滤镜在不同平台的实现,该引擎包含一个指令集优化的X86版本和一个CUDA版本。接下来我介绍一下该滤镜层的各种色彩转换能力,包括坐标变换、量化、色度采样,色调映射等等。

图片

色彩空间处理引擎的基本架构图

2.1 坐标变换

很多时候,用户投稿的视频会有显示矩阵(Display matrix),它表示视频被旋转、被翻转、或者被转置。如果我们在服务端检测到这种视频,那么就需要对输入视频按照显示矩阵进行坐标变换。无论是软解的CPU数据帧还是硬解的CUDA帧,该引擎都能将其准确地渲染出来。

2.2 量化与反量化

信号的量化主要用于连续的YUV信号转为离散的Y’CbCr,而反量化是将输入视频不同的色深转换为连续信号的过程。该引擎支持8bit、10bit、12bit等不同量化精度的Y’CbCr信号与YUV信号的互相转换,同时也支持pc range、tv range的转换。

2.3 色度采样

色度采样(Chroma subsampling)是一种用于减少图像或视频中色度信息采样率的技术。在数字图像和视频编码中,颜色信息通常由亮度分量(Y)和两个色度分量(U和V)组成。由于人眼对亮度信息更加敏感,而对色度信息的感知相对较弱,因此可以通过减少色度分量的采样率来降低数据量,同时尽量保持图像或视频的质量。下图为常见的像素格式YUV420的采样坐标。

为了色度采样尽可能平滑,我们采用了双线性(Bilinear)插值策略进行上采样和下采样。例如对YUV420像素格式的视频进行色度的上采样, 我们并不会简单地将一个色度像素共享给4个亮度像素, 而是对其进行插值,否则最后输出的视频会有色度过渡的不均匀。同理,对于色度的下采样,我们也会采用同样的插值方法。

图片

常见的色度采样坐标(4:2:0 chroma format)

2.4 色彩矩阵转换

视频处理中的一个关键参数是色彩矩阵(Color matrix),有时也称为色彩空间(Color space)。该参数主要用于确定信号如何转换为RGB,色彩矩阵转换是将输入信号通过色彩矩阵映射到RGB色彩空间的过程。

举例来说,常见的BT.709标准就属于一种色彩矩阵,它指导了如何将YUV信号转换为RGB,其转换方式如下:

图片

为什么选择了上述的矩阵而不是其他的呢?这是因为BT.709协议所指定的色彩空间与sRGB相对应,通过sRGB的色域坐标可以推导出上述的转换矩阵。不同的色彩空间对应不同的转换矩阵,具体的矩阵根据输入的色彩矩阵而定。如果需要将R'G'B'转换为Y'U'V',则需要对上述矩阵进行逆运算。

而对于特殊信号,例如ICtCp信号,我们会首先将其转换为L'M'S'信号,然后再进行后续的转换操作。

2.5 亮度传递函数转换

亮度传递函数是一种用于调整图像或视频信号亮度级别的函数,分为两种OETF(Opto-Electronic Transfer Function)和EOTF(Electro-Optical Transfer Function)。由于人眼对不同光线区域的敏感度不同,人眼对暗部细微亮度变化的敏锐程度较高,而对于高亮度的细微变化则较难察觉。为了以一种均匀的方式呈现信号,以符合人眼的感知,并且为了能够编码更多的信息,我们需要对信号进行OETF压缩。相反地,如果我们需要解压信号,则需要进行相反的处理,即EOTF。常见的EOTF曲线有Gamma2.4曲线和感知量化曲线(Perceptual Quantization,PQ),如下图。感知量化曲线是一种最接近人眼感知的量化方法,因此它也被广泛用作HDR视频的主要亮度传递函数。

图片

Gamma2.4和PQ的EOTF曲线

在该引擎中,我们实现了十几种亮度传递函数的转换,包括Gamma2.2,Gamma2.4,PQ,HLG等等。

2.6色域映射

当涉及到色彩转换或图像处理时,色域映射是一个重要的概念。它指的是将一个色彩空间中的颜色值映射到另一个色彩空间中的过程。

假设有一个图像,它使用BT.2020(一种广色域)色彩空间进行编码,但你希望在一个只支持sRGB色彩空间的设备上显示这个图像。由于这两个色彩空间具有不同的色域范围,直接将图像的颜色值转换为sRGB可能会导致颜色失真。在这种情况下,色域映射就派上用场了。色域映射算法会根据源色彩空间和目标色彩空间的特性,将图像的颜色值进行适当的转换,以确保在目标设备上显示的图像颜色尽可能接近原始图像的颜色。

色域映射的方法一般有两种,一种是基于Clip的方法,一种是基于压缩的方法。首先介绍一下基于Clip方法的色域映射。想要做色域映射,首先需要将RGB线性信号转换到XYZ色彩空间,具体转换矩阵可以由r177协议得到。

比如BT.2020色域的RGB转XYZ的公式如下:

图片

要将XYZ信号转换为BT.709 RGB信号,使用BT.709的XYZ到RGB的转换矩阵就可以了,如下:

图片

上述两步过程就完成了最简单的色域映射,它将广色域的BT.2020 RGB转换为了窄色域的BT.709 RGB。但是该方法有一个缺点,当广色域转换到窄色域会对信号进行截断(Clip),从而丢失一些色彩信息,导致视频的有些区域会过于饱和。一般来说,更好的色域映射方法是通过色域压缩的方式进行。

色域压缩可以选择一个人眼看起来相对均匀的色彩空间进行压缩,比如CIE Lab,CIE Luv,在饱和度为某个阈值内的色彩不进行压缩,过于饱和的色彩才进行压缩。在色域压缩的时候,还需要考虑到色相映射(Hue mapping),如果不考虑色相,信号处理的过程可能会有色偏。有些色域压缩的算法的复杂度会非常高,甚至需要用GPU来加速。

下图为BT.2407协议中一种色域映射后的效果图,左图为基于Clip的色域映射,右图为基于压缩方法的色域映射,可以看到优秀的的色域映射算法可以呈现更丰富的细节,它对最后的效果呈现影响非常大。

图片

Clip方法和色域压缩方法效果对比图

我们的转换引擎也实现了一套自研的色域压缩算法,可以将广色域的视频压缩至sRGB而不会产生色彩溢出,同时也会保留了高饱和区域的细节。

2.7 色调映射(Tone mapping)

HDR图像包含比传统SDR图像更大的亮度范围,可以捕捉到更多的细节和光照信息。然而,SDR显示设备(如计算机显示器或手机屏幕)的亮度范围有限,无法直接显示HDR图像中的所有细节和对比度。这就需要通过色调映射(Tone mapping)技术将HDR图像转换为SDR图像,使其在常规显示设备上呈现出较好的视觉效果。

下图展示了不同的Tone mapping算法对图像的影响,b为原始HDR图片的色阶图,含有低亮度区域和高光区域,c为归一化(Normalization)方法的进行Tone mapping,虽然c的高光被压缩了,但是它的暗部细节也被等比例压缩,造成暗部细节丢失。d则是简单地将高光进行截断(Clipping),虽然暗部细节保留了,但是高光细节丢失了。e表征了一种更高级的方法,既可以保证暗部细节丢失很少,又可以保留一定的高光细节。

图片

不同算法对HDR 高光区域渲染的效果

图片来源:ITU-R BT.2390-3

图片

图片

基于Clipping和基于压缩算法的Tone mapping效果

图片来源:https://www.bilibili.com/video/BV1WA411E7i7

我们对比了几种常用的Tone mapping算法,并最终选择了BT.2390协议中的一种曲线作为我们Tone mapping的指导算法。BT.2390算法的主要思想是将输入的线性信号转换为PQ空间,以使输入信号更符合人眼的感知特性。然后设定了一个阈值,保证小于该阈值的信号尽可能接近原始信号,大于该阈值的信号通过Hermite样条曲线将其压缩,从而保证了暗部和高光都丢失比较少的细节。

图片

通过BT.2390将线性信号压缩至不同范围的曲线

图片来源:ITU-R BT.2390-3

通过色域映射和色调映射,B站最终实现的HDR转SDR效果如下,可以看到,对比于VLC的效果,B站的效果做到了比较好的色相还原,并且保证了饱和度和亮度不溢出,暗部的细节也还原得较好。

图片

图片

图片

图片

B站自研HDR转SDR效果(上)与VLC的效果(下)对比

2.8 HDR动态元数据渲染

动态元数据是相对于静态元数据的一个定义,首先介绍一下静态元数据。静态元数据主要包含了整个播放序列的一些参数,包括最高亮度,最大平均亮度,色域信息等。

HDR视频中静态元数据不随着视频场景的变化而变化,整个播放序列维持着同一个值,终端可以根据静态元数据来判断自身的显示能力能否显示视频内容,一般地,优秀的显示软件会根据自身的显示能力对视频信号进行Tone mapping处理,来保证不丢失高光细节。

静态元数据的缺点是显而易见的,通过静态元数据进行Tone mapping的方法在整个播放序列中是不变的,以至于它不能适配复杂的场景。有时候它显示高光细节是优秀的,但却无法还原暗部细节;有时候它显示暗部细节不错,但显示高光场景会过曝。总而言之,通过静态元数据进行Tone mapping,它无法识别视频中每个场景的亮度,以至于无法有效地去针对每一个场景进行Tone mapping。

对比静态元数据,动态元数据则灵活很多。在拥有动态元数据的视频中,视频中的每个场景甚至每帧都会包含一组元数据,来标识视频的亮度信息,它还会对每帧内容生成几组调色曲线来适配不同亮度的显示终端。终端在显示该场景的时候,会根据自身的显示能力来渲染调色曲线,从而让每个场景,无论是暗部还是亮部,都能得到较完美的显示,从而避免了暗部细节和高光细节的丢失。在服务端的色彩空间转换模块中,我们也会根据HDR的动态元数据来生成下变换后的SDR视频,这样生成的SDR视频也会比全局的Tone mapping算法好很多。

包含HDR动态元数据的视频主要包括杜比视界和国产高动态范围视频HDR Vivid,目前我们正在推动HDR Vivid在B站的上线。对于HDR Vivid在服务端的处理,我们除了正常编码一路HDR Vivid,还会借助引擎中动态元数据渲染的能力,来生成一路SDR视频,以适配不支持HDR 的手机。

图片

HDR Vivid 视频在服务端的转换

3.   工程化加速

对于该引擎的工程化加速,我们尝试了很多办法,具体大致有如下图几种:

图片

工程化加速用到的方法

色彩空间转换中,有很多参数是需要预先计算的,比如各种矩阵的参数,图像插值的数值,亮度传递函数中的一些常数等等。如果这些参数在像素处理的时候再去计算会极大地浪费计算资源,所以在引擎初始化的时候就将其计算出来是一个不错的选择。除此,在CUDA计算中,矩阵参数和插值参数也是需要进行预拷贝处理,这样可以避免程序在像素处理中进行频繁拷贝而影响性能。

计算路径优化的主要目的是为了消除重复计算,以找到一个色彩空间到另外一个色彩空间的最优转换路径。举例来说,如果一个视频只需要进行range转换,那么我们无需整个转换路径走一遍,从而避免了额外的色彩转换和电光转换,如下图。

图片

pc range信号转tv range计算路径优化示意图

对于CPU优化来说,色彩空间转换中的幂运算和矩阵乘法对CPU的消耗很大。为了提高程序的性能,在CPU处理中,我们对频繁使用的矩阵乘法和幂运算进行了指令集优化。这样的优化显著提升了程序的性能。

在对幂运算进行指令集优化的过程中,存在一个难点。常见的编译器并没有提供针对幂运算的向量化函数,因此我们需要手动编写一套幂运算函数。为了实现幂运算的向量化优化,首先将pow函数写成形如pow(x, m) = exp(m * log(x))的形式。然后,通过泰勒级数展开逐步实现了经过AVX指令集加速的log(x)和exp(x)函数。

这种优化策略充分利用了指令集的特性,以提高幂运算和矩阵乘法的计算效率。通过手动编写幂运算函数并进行向量化优化,我们能够更好地利用CPU的计算能力,从而加速色彩空间转换的处理过程。

除了使用指令集加速,我们还采用了查找表进行优化,通过将复杂的计算提前存储在查找表中,可以显著减少计算量。

通过使用指令集优化和使用查找表优化策略,CPU端的性能提升达到如下

图片

CPU加速策略对性能的提升

将CPU端的代码进行一系列优化后,通过4K视频在AMD Ryzen 9 5950X上的测试,我们的色彩空间转换引擎的性能达到了如下指标:

图片

CPU端色彩空间转换性能实验数据

对于CUDA端优化来说,加速手段主要有C++模板和Kernel融合技术。由于色彩空间的属性和种类非常多,需要写很多if else分支,但是CUDA是天然不支持分支预测功能的,所以我们没有将很多if else放在核函数内部,而是使用C++模板来编译kernel函数,这样可以防止kernel中出现很多分支,从而大大加快了GPU的处理速度。

此外,由于CUDA在执行全局内存(Gobal memory)的I/O操作时非常耗时,因此我们应避免为每个细小的操作编写单独的核函数(Kernel)。相反,我们可以将一些操作合并到一个核函数中以减少I/O开销。例如,我们可以将range转换和量化处理合并为一个核函数,将色彩矩阵转换和亮度传递函数的转换也可以合并为一个核函数(如下图所示)。通过合并核函数,我们在一定程度上牺牲了一些通用性,但却能大大节省GPU的I/O开销。这样做是一个明智的选择,特别是在涉及I/O操作频繁的情况下。

图片

色彩空间转换引擎中kernel融合示意图

通过使用C++模板和使用Kernel融合策略,CUDA端的性能提升达到如下

图片

CUDA 加速策略对性能的提升

将CUDA端的代码进行一系列优化后,通过4K视频在RTX2060上的测试,我们的色彩空间引擎转换性能达到了225FPS+(未编码),对比于CPU端,处理速度得到了很大的提升,详细数据如下表

图片

CUDA端色彩空间转换性能实验数据(未包含编解码耗时)

4.   总结与展望

目前,我们的色彩空间转换引擎已经稳定地在线上为成千上万的用户提供服务。在用户的观看体验中,视频的色彩质量至关重要。因此,将视频的色彩准确呈现在用户眼前一直是我们追求的目标。我们将继续致力于优化色彩转换效果,以提供更好的用户体验。

我们实现了一套HDR转SDR的算法,但是当下并没有一个好的算法可以准确评估HDR转SDR的效果,像HDR-VDP(一种客观的图像质量评估方法),它只能评估亮度、对比度、细节上的质量,并无法评估两张图像在色度上的差异。色差公式CIEDE2000可以比较两个像素在亮度、色度、饱和度上的差异,但是由于HDR和SDR之间存在巨大的亮度范围差异,仅仅使用CIEDE2000公式计算的色差值无法准确反映HDR转SDR后的差异。在实际应用中,评估HDR转SDR的效果通常需要结合其他指标和方法,如亮部细节、暗部细节、色相差异等。结合这些指标可以更全面地衡量HDR与SDR之间的差异,以便更好地评估转换后的图像质量和观看体验。

随着高动态范围的视频内容越来越普及,HDR协议也在扩充新的标准,最新的高动态范围标准主要有杜比视界以及HDR Vivid。其中,HDR Vivid是中国超高清视频产业联盟发布的国产高动态范围的视频技术标准,目前最新的开源框架FFmpeg已经集成了HDR Vivid元数据的解码,我们正在将HDR Vivid元数据的渲染集成到我们自己的色彩转换引擎中,来支持国产的动态范围标准,相信未来的不久,大家就可以在B站客户端上欣赏到来自国产高动态范围标准的视频了。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/429999.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

javaWebssh药品进销存信息管理系统myeclipse开发mysql数据库MVC模式java编程计算机网页设计

一、源码特点 java ssh药品进销存信息管理系统是一套完善的web设计系统(系统采用ssh框架进行设计开发),对理解JSP java编程开发语言有帮助,系统具有完整的源代码和数据库,系统主要采用B/S模式开发。开发环境为TOM…

Unity2023.1.19_DOTS_JobSystem

Unity2023.1.19_DOTS_JobSystem 上篇我们知道了DOTS是包含Entity Component System,Job System,Burst compiler三者的。接下来看下JobSystem的工作原理和具体实现。 简介: 官方介绍说:JobSystem允许您编写简单而安全的多线程代…

【Docker】Docker:解析容器化技术的利器与在Linux中的关键作用

🍎个人博客:个人主页 🏆个人专栏:Linux ⛳️ 功不唐捐,玉汝于成 目录 前言 正文 Docker 是什么? Docker 的作用 Docker 在 Linux 中的重要性 结语 我的其他博客 前言 随着软件开发的不断发展&…

虚拟化之CPU

一 cpu 1 如何查看内核版本:uname -r 2 如何查看操作系统的发行版本:cat /etc/redhat-release 3 计算机系统子的系统 cpu处理器memory内存storage存储network 网络Display显示 4 进程模式 用户模式(user mode)主要处理I/O的模…

2024年【G1工业锅炉司炉】考试报名及G1工业锅炉司炉模拟考试

题库来源:安全生产模拟考试一点通公众号小程序 G1工业锅炉司炉考试报名是安全生产模拟考试一点通生成的,G1工业锅炉司炉证模拟考试题库是根据G1工业锅炉司炉最新版教材汇编出G1工业锅炉司炉仿真模拟考试。2024年【G1工业锅炉司炉】考试报名及G1工业锅炉…

HarmonyOS 获取位置信息

1. HarmonyOS 获取位置信息 1.1. 官方文档 权限申请 位置服务 1.2. 权限申请 1.2.1. 配置位置权限信息 "requestPermissions": [//API9之前只申请这个就可以米级定位{name: ohos.permission.LOCATION},//API9之前申请的权限//API9后两个权限同时申请才可以获取米…

链路负载均衡之DNS透明代理

一、DNS透明代理 一般来说,企业的客户端上都只能配置一个运营商的DNS服务器地址,DNS服务器通常会将域名解析成自己所在ISP内的Web服务器地址,这将导致内网用户的上网流量都集中在一个ISP的链路上转发,最终可能会造成链路拥塞&…

C++之智能指针

为什么会有智能指针 前面我们知道使用异常可能会导致部分资源没有被正常释放, 因为异常抛出之后会直接跳转到捕获异常的地方从而跳过了一些很重要的的代码, 比如说下面的情况: int div() {int a, b;cin >> a >> b;if (b 0)throw invalid_argument(&q…

windows下的反调试探究——原理

原理 我们在前面介绍了一些反调试的手段,基本上都是通过对内核的某个标志进行修改来达到反调试的效果,但是这里有一个问题就是,如果分析人员对我们的样本的API进行了hook,那么我们的反调试手段都将作废,也就是说我们还…

【LeetCode】升级打怪之路 Day 13:优先级队列的应用

今日题目: 23. 合并 K 个升序链表 | LeetCode378. 有序矩阵中第 K 小的元素 | LeetCode373. 查找和最小的 K 对数字 | LeetCode703. 数据流中的第 K 大元素 | LeetCode347. 前 K 个高频元素 | LeetCode 目录 Problem 1:合并多个有序链表 【classic】LC 2…

2核4G服务器支持多少人在线?腾讯云全访问测试

腾讯云轻量应用服务器2核4G5M配置一年优惠价165元、252元15个月、三年756元,100%CPU性能,5M带宽下载速度640KB/秒,60GB SSD系统盘,月流量500GB,折合每天16.6GB流量,超出月流量包的流量按照0.8元每GB的价格支…

WSL2安装Ubuntu18.04到指定路径(非C盘)

1 系统设置开启WSL 1.1 在搜索框搜索“启动或关闭Windows功能”或在“控制面板”->“程序”->“启用或关闭 windows 功能” 开启 Windows 虚拟化和 Linux 子系统(WSL2)以及Hyper-V 按照提示重启计算机,开启WSL。 2 将WSL2 设置为默认版本 wsl --se…

Mysql删除重复项:力扣196. 删除重复的电子邮箱

题目链接:196. 删除重复的电子邮箱 - 力扣(LeetCode) 题目描述 sql语句 # Write your MySQL query statement below delete a from person as a inner join person as b where a.email b.email and a.id > b.id 思路:内连接…

MySQL NDB Cluster 分布式架构搭建 自定义启动、重启和关闭集群Shell脚本

此次NDB Cluster使用三台虚拟机进行搭建,一台作为管理节点;而对于另外两台服务器,每一台都充当着数据节点和SQL节点的角色。注意不是MGR主从复制架构,而是分布式MySQL架构。 创建 /var/lib/mysql-cluster/config.ini Cluster全局…

uipath调用python代码获取网站验证码

用uipath自带的ocr读验证码不是很准确,选择调用python读验证码,需要导入ddddocr(3.8以下版本支持ddddocr) 用uipath程序将验证码图片保存到本地(也可以直接用python处理图片,保存到本地比较简单&#xff0…

xss.haozi.me:0X0D

alert(1) -> 记住要回车一下-->是js的一个注释符但是只能用在最前面前面有一个空格都不行

C++:String的模拟实现

模拟实现的节奏比较快,大家可以先去看看博主的关于string的使用,然后再来看这里的模拟实现过程 C:String类的使用-CSDN博客 String模拟实现大致框架迭代器以及迭代器的获取(public定义,要有可读可写的也要有可读不可写…

基于springboot+vue的医院药品管理系统

博主主页:猫头鹰源码 博主简介:Java领域优质创作者、CSDN博客专家、阿里云专家博主、公司架构师、全网粉丝5万、专注Java技术领域和毕业设计项目实战,欢迎高校老师\讲师\同行交流合作 ​主要内容:毕业设计(Javaweb项目|小程序|Pyt…

【Android】源码解析 Activity 的构成

本文是基于 Android 14 的源码解析。 当我们写 Activity 时会调用 setContentView() 方法来加载布局。现在来看看 setContentView() 方法是怎么实现的,源码如下所示: 路径:/frameworks/base/core/java/android/app/Activity.javapublic void…

Linux中服务端开发

1 创建socket,返回一个文件描述符lfd---socket(); 2 将lfd和IP,PROT进行绑定---bind(); 3 将lfd由主动变成被动监听---listen(); 4 接收一个新的连接,得到一个的文件描述符cfd--accept() --该文件描述符用于与客户端通信 5 while(1) { 接受数据&a…