将颜色从RGB转换为NV12。

3
我正在开发一个应用程序,使用媒体基础 h264 编码器对视频进行编码。使用 VRAM 中的 RGB 输入时,Sink writer 在 Windows 7 上崩溃,显示“0x8876086C D3DERR_INVALIDCALL”,因此我在 GPU 上实现了自己的 RGB->NV12 转换,节省了超过 60% 的 PCI Express 带宽。

这是我的媒体类型中的内容,包括输入(NV12)和输出(h264):

mt->SetUINT32( MF_MT_VIDEO_CHROMA_SITING, MFVideoChromaSubsampling_MPEG2 ); // Specifies the chroma encoding scheme for MPEG-2 video. Chroma samples are aligned horizontally with the luma samples, but are not aligned vertically. The U and V planes are aligned vertically.
mt->SetUINT32( MF_MT_YUV_MATRIX, MFVideoTransferMatrix_BT709 ); // ITU-R BT.709 transfer matrix.
mt->SetUINT32( MF_MT_VIDEO_NOMINAL_RANGE, MFNominalRange_0_255 ); // The normalized range [0...1] maps to [0...255] for 8-bit samples or [0...1023] for 10-bit samples.
mt->SetUINT32( MF_MT_TRANSFER_FUNCTION, MFVideoTransFunc_10 );  // Linear RGB (gamma = 1.0).

到目前为止,我用这个公式得到的最好结果是:
inline float3 yuvFromRgb(float3 rgba)
{
    float3 res;
    res.x = dot( rgba, float3( 0.182585880, 0.614230573, 0.0620070584 ) );
    res.y = dot( rgba, float3( -0.121760942, -0.409611613, 0.531372547 ) );
    res.z = dot( rgba, float3( 0.531372547, -0.482648790, -0.0487237722 ) );
    res += float3( 0.0627451017, 0.500000000, 0.500000000 );
    return saturate( res );
}

我的担忧是这个公式与我在互联网、代码示例和ITU官方规范中读到的一切相矛盾。
对于Y,公式没问题,我使用了BT.709系数,并将它们线性缩放以将[0..255]映射到[16..235],正如规范中所写的那样。亮度是正常的。
规范要求我必须将U和V缩放以将[0..255]映射到[16..240]。然而,我的眼睛告诉我它色彩不饱和。为了得到正确的颜色,我必须将U和V反向缩放,从[0..255]映射到[-8,255 + 8]之类的值。
为什么我需要反向缩放才能在h264编码和解码后获得正确的颜色?这段代码能在其他人的电脑上运行吗?

你读过这个吗:https://learn.microsoft.com/zh-cn/windows-hardware/drivers/display/yuv-format-ranges https://learn.microsoft.com/zh-cn/windows/desktop/medfound/about-yuv-video https://learn.microsoft.com/zh-cn/windows/desktop/medfound/recommended-8-bit-yuv-formats-for-video-rendering - mofo77
1个回答

1
问题是色度采样伪影。当我提出这个问题时,我正在查看彩色控制台文本。
今天我尝试了更好的图像编码,这个:enter image description here 通过这张图片,正确的公式变得明显,就是这些标准中指定的公式。
因此,这里是正确的系数:
// Convert RGB color into ITU-R BT.709 YUV color
inline float3 yuvFromRgb( float3 rgb )
{
    float3 res;
    res.x = dot( rgb, float3( 0.18258588, 0.61423057, 0.06200706 ) );
    res.y = dot( rgb, float3( -0.10064373, -0.33857197, 0.43921569 ) );
    res.z = dot( rgb, float3( 0.43921569, -0.39894217, -0.04027352 ) );
    res += float3( 0.06274510, 0.50196081, 0.50196081 );
    return res;
}

尽管仍然存在一些偏差,但对于我的特定问题来说,0.39%的误差是可以接受的。


那么什么是色度采样伪影?公式标准在哪里?0.39%来自哪里?你只是发现使用标准公式是正确的做法了吗? - mofo77
@mofo77 顺便说一句,尽管h264很普遍,但互联网上没有正确的YUV公式代码。这就是我回答自己问题而不是删除它的主要原因。 - Soonts
所以你的公式是针对8位编码而不是10位编码的。或许需要明确一下。仅仅做一些基本的数学运算并不能解释你的值从哪里来。 - mofo77
@mofo77 我的公式在GPU上运行,并且操作浮点数值。如果你需要10位的输出,可以使用R16_UNORM和R16G16_UNORM渲染目标,但是你还需要一个额外的步骤将16位值打包成10位,比如使用计算着色器。这些值是从标准的两个部分中得出的,“亮度和颜色差信号的推导”定义了颜色模型,另一个部分“量化级别”定义了范围。我将它们相乘以产生一个单一的公式。 - Soonts
1
经过多次公式计算和结果比对,似乎大多数在线YUV转换示例都使用了T871标准,该标准是为JPEG图像设计的,而不适用于视频显示。因此,这个答案非常适合那些想要快速将RGB转换为BT.709的开发人员。 - Jorma Rebane
显示剩余3条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接