视频ToneMapping(HDR转SDR)中的颜色空间转换问题(BT2020转BT709,YCbCrYUV和RGB)

Posted q274488181

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了视频ToneMapping(HDR转SDR)中的颜色空间转换问题(BT2020转BT709,YCbCrYUV和RGB)相关的知识,希望对你有一定的参考价值。

笔者按,最近在做视频TM的相关工作,具体是给定一个HDR视频(10bit的YUV420格式),要对其进行TM,写了一个算法但总是有非常离谱的色差,尤其是红色和蓝色通道。仔细检查之后发现是拿到的HDR视频的颜色空间是BT2020的,但转换后的SDR视频是BT709的,需要先对颜色空间进行降级才可以进行处理。这里记录一下处理过程。

首先贴两个地址

[1] ​​​​​​RECOMMENDATION ITU-R BT.2087-0 - Colour conversion from Recommendation ITU-R BT.709 to Recommendation ITU-R BT.2020

[2] REPORT ITU-R BT.2407-0 - Colour gamut conversion from Recommendation ITU-R BT.2020 to Recommendation ITU-R BT.709

这两个都是ITU搞出来的标准纲领性文件。第一个文件讲了怎么把BT709转到2020(包括YUV、RGB的处理办法),第二个讲了怎么把BT2020转换到BT709(但只有RGB信号)。

在文献[2]中,简单粗暴地给出了一个示意图

E'RGB指的是“归一化的非线性RGB值”,而E_RGB指的是“归一化的线性RGB值”。

但在算法中,我拿到的数据是10bit的YUV420数据,所以需要先把10bitYUV420数据转换到E'RGB。怎么转?文献没提。但文献[1]中将如何将709转换到2020时,给了一个图

这里除了 E'RGB和E_RGB之外,还出现了E'YCBCR和D'YCBCR。其中E'YCBCR指的是“归一化的YCBCR值”,而D'YCBCR指的是“量化的YCBCR值”

所以我这里明白了,我在算法输入端所拿到的所谓10bitYUV420数据,实际上就是这个D'YCBCR值。那么接下来,我们结合两篇文献,就可以推导出相应过程了。

Step 1. 将D’YCbCr(2020)转为E’YCbCr(2020)

其实就是如下过程的反过程

 简单推导,得

        E_Y = ((D_Y / 4) - 16) / 219
        E_Cb = ((D_Cb / 4) - 128) / 224
        E_Cr = ((D_Cr / 4) - 128) / 224

Step 2. 将E’YCbCr(2020)转为E’RGB(2020)

文献[1]中给出了反过程

所以这里只要做一个简单的矩阵求逆(这里直接给出了逆)

需要注意的是,下面给出的代码中的E_YCbCr和E_RGB这样的变量,其形状都是height x width x 3的,所以这里讨巧做了一个线性代数的转换,将左乘变成了右乘。下面很多地方都利用了这个方法,后续就不再做解释说明了。

        m_YCbCr2020_to_RGB2020 = np.array([[1.00000000e+00, -7.82308321e-18, 1.47460000e+00],
                                           [1.00000000e+00, -1.64553127e-01, -5.71353127e-01],
                                           [1.00000000e+00, 1.88140000e+00, 2.38961873e-17]])
        E_RGB = np.matmul(E_YCbCr, m_YCbCr2020_to_RGB2020.T)

Step 3. 将E’RGB(2020)转为E RGB(2020)

        E_RGB = np.power(E_RGB, 2)
        # 另一个版本是
        # E_RGB = np.power(E_RGB, 2.4)

Step 4. 将E RGB(2020)转为E RGB(709)

        m_RGB2020_to_RGB709 = np.array([[1.66051121, -0.58771059, -0.07280062],
                                        [-0.12456141,  1.13296051, -0.00839911],
                                        [-0.01816769, -0.1005606,  1.11872828]])
        E_RGB = np.matmul(E_RGB, m_RGB2020_to_RGB709.T)

Step 5. 将E RGB(709)转为E’RGB(709)

        E_RGB = np.power(E_RGB, 1/2)
        # 另一个版本是
        # E_RGB = np.power(E_RGB, 1/2.4)

Step 6.  E’RGB(709)转为D’RGB(709)

【这里是以10bit为例的,如果是8bit,去掉后面的*4即可】

D_RGB = ((E_RGB * 219 + 16) * 4).astype("uint16")

这样转换过后,就没有了色偏问题。

HDR&ToneMapping

HDR

high dynamic range.

很多程序朋友(包括我)都是从dxsdk上看到和学习这个概念,开始学习的更多的是一整套hdr sample的流程:

  • 在float render target上去render scene
    • 后面很多console上的游戏使用rgbm等编码方式来节省内存和bandwidth
  • 通过down sample去计算亮度
    • treyarch是cook到场景数据里面,省了这个down sample的过程
  • 根据亮度对场景做一个矫正(tone mapping)最后输出到一个rgb8的render target上
但是随着游戏业的画面水准开始向电影水准发展,就需要我们有更好的理解HDR,来进一步把游戏像电影画质推进,这也是近几年的GDC和siggraph上都有一线studio在推HDR相关的技术,比如naughty dog10年的filmic tone mapping,11年crytek在siggraph里面提到的physically based hdr等。 首先看一下概念:
  • dynamic range:reinhard的<tone mapping>论文中定义:一个场景中最高亮度与最低亮度的比是dynamic range。
  • low dynamic range : 之所以出现这种情况是图像存储介质(打印纸,照片,电脑屏幕等)精度有限造成的,导致在range上没法完全记录一个场景的亮度信息,只能记录有限的一部分,比如游戏里常见的在rgba8上渲染,每个channel大于1的部分就被截取到1了。
  • high dynamic range : 准确讲是high dynamic range imaging,是指一种图像技术,它能让图像表示一个比原有技术(之前的LowDynamicRange)更大(greater)的dynamic range
    • 这样就可以更加准确和真实的描绘一个场景
进一步说,hdr的存在在于图像存储与表达介质的精度有限,这也是为何游戏引擎大佬在提的都是CG和电影画质,而不是和现实一样的:因为存储介质在这里摆着呢,没法和现实一样,cg是上限。 实际应用中,我们也看到了,hdr除了在range上发力,在精度上也可以更好,比如描绘发暗的场景,可以把亮度矫正到可以充分显示暗部细节。                                                                                                                                                                                                                     ToneMapping&ExposureAdjustment 这里除了数学上正确的亮度计算以外,要考虑到人眼视网膜的特点,比如之前的gamma就是一个根据视网膜特点,重新分配亮度信息,来让人眼在有限的显示精度下获得最大的信息量。视网膜有两个典型特点,这里通过tone mapping和exposure adjustment来解决:
  • 自动根据亮度矫正明暗:让我们晚上看东西也能比较清楚,一开灯眼睛要矫正一会回来
  • 局部适应性:比较经典的图是:
这里我们遇到两个问题,各用两个方案解决:
  • 曝光率问题---解决:exposure adjustment。和照相时候曝光原理差不多,白天亮曝光就短一些,晚上曝光长一些,编程时候就是计算render target的平均亮度,然后矫正,这样沙漠的白天和丛林的夜晚都可以在游戏中的rgba8上有一个良好的体现。在hable的论文里,这个属于不同的场景之间的处理问题范畴。
  • 压缩的过程中不可避免的涉及到重新分配亮度值,怎么做来得到更好的尽可能不失真的画面这个解决方案就是tone mapping
实际中也有把这个exposure adjustment并到tone mapping中去,其实还是分开比较好,因为这些概念是从摄像技术中来的,曝光就是曝光,tone mapping就是tone mapping。

                                                                                                                                                                                                                   

ReinhardToneMapping

tone mapping方面比较著名的reinhard哥:

reinhard主页

http://www.cs.ucf.edu/~reinhard/cdrom/

paper link:

http://www.cs.ucf.edu/~reinhard/cdrom/tonemap.pdf

tone mapping干什么的?

dxsdk里面也有说,本来是摄影中提出的概念,解决怎么把场景中范围巨大的亮度值放到范围有限的存储空间中来(照片,打印机。。。),达到一个让人喜欢的结果。

这里面一点是“让人喜欢的结果”,它是一个含有主观意味的东西,没有一个绝对的标准,也没有说什么是绝对的对和错,根据游戏类型和开发者,玩家口味,大可选择自己喜欢的结果,tonemapping是达到这一结果的方法而已。

tonemapping相关的研究是从摄影技术中发展过来的,只不过digital imaging有比摄影洗相片更好的一个优势,可以进一步发展:

首先明确和定义一些概念:

  • zone:存储空间的亮度阶这么一个概念,比如print只有11个zone
  • middle grey:中间的亮度
  • dynamic range:指场景中最高亮度与最低亮度的比值
    • 这是一个最学术派的定义,具体上摄像师一般会追求细节还可以明辨的range
  • key:描述整个场景亮度的数值
  • dodging and burning:把高亮度的东西亮度降低为dodging,把低亮度的部分加亮为burning
luminance mapping 首先是把场景亮度map到image里,这一步是luminance mapping: luminance计算: 这个计算使用了log。 step 1: simplest result = x/(1+x); 最简单的一个情况,就把任意大的亮度encode到0到1之间。 虽然效果不那么好,但这也算一个luminance mapping。 step2:more control L(white)定义的是在画面上表示纯白的亮度。 dodging and burning 一般来讲像dxsdk里面做的都是全屏统一的一个量度矫正,这个也是可以的,但是reinhard这里面说有时候这样并不太好,比如整个场景很亮,里面一部分很暗的情况(比如亮背景下的一个树),全屏统一做矫正(也就是dodging and burning),效果就不好。 这时可能在一个较小的区域来进行dodging and burning可以进一步提升local的对比度,可以达到某些情况下的“令人喜欢”。 这里的基本思路就是在一个scale内去搜集亮度等信息,来做进一步计算,operator也很多。 基本上知道这里已经够了。                                                                                                                                                                                                                     FilmicToneMapping gdc10上当时在naughty dog工作的hable的presentation: http://cmpmedia.vo.llnwd.net/o1/vault/gdc10/slides/Hable_John_Uncharted2_HDRLighting.pptx 后来他又在自己blog上说了更多的细节:(甚至说更重要,ppt上的内容但看的话会有很多不解的地方) http://filmicgames.com/archives/75

tone mapping就是一个原始颜色向目标颜色映射的过程,不同的函数呈现一些不同的特点,这里列一些,看下对比:

ppt中有更多的一些对比,这里直接总结filmic tone mapping的好处就是:

  • 向暗色过渡的更“脆”
  • 高亮部分更柔和
  • 在input color的match上也更接近linear
最后简单讲,hable把cg工业中的这个operator拿过来用就是因为在实际应用中这个看起来更棒。 这个也是很多电影在用的operator,它的mapping曲线这样的: 实现类似filmic tone mapping的mapping的时候一般是把映射关系放到texture里,然后sample texture,不过有牛人把mapping搞到一个公式里了,hable还加了参数可以让美术调:
A = Shoulder Strength
B = Linear Strength
C = Linear Angle
D = Toe Strength
E = Toe Numerator
F = Toe Denominator
Note: E/F = Toe Angle
LinearWhite = Linear White Point Value
F(x) = ((x*(A*x+C*B)+D*E)/(x*(A*x+B)+D*F)) - E/F;
FinalColor = F(LinearColor)/F(LinearWhite)‏
在具体的代码看hable的blog更好了:
float A = 0.15;
float B = 0.50;
float C = 0.10;
float D = 0.20;
float E = 0.02;
float F = 0.30;
float W = 11.2;

float3 Uncharted2Tonemap(float3 x)

   return ((x*(A*x+C*B)+D*E)/(x*(A*x+B)+D*F))-E/F;


float4 ps_main( float2 texCoord  : TEXCOORD0 ) : COLOR

   float3 texColor = tex2D(Texture0, texCoord );
   texColor *= 16;  // Hardcoded Exposure Adjustment

   float ExposureBias = 2.0f;
   float3 curr = Uncharted2Tonemap(ExposureBias*texColor);

   float3 whiteScale = 1.0f/Uncharted2Tonemap(W);
   float3 color = curr*whiteScale;

   float3 retColor = pow(color,1/2.2);
   return float4(retColor,1);





以上是关于视频ToneMapping(HDR转SDR)中的颜色空间转换问题(BT2020转BT709,YCbCrYUV和RGB)的主要内容,如果未能解决你的问题,请参考以下文章

HDR&ToneMapping

快手宣布全链路支持iPhone12系列 HDR视频拍摄编辑上传和播放

海思Hi3798MV310芯片处理器参数介绍

LDR和HDR

LDR和HDR

LDR和HDR