【问题标题】:Convert colors from RGB to NV12将颜色从 RGB 转换为 NV12
【发布时间】:2018-09-21 01:59:58
【问题描述】:

我正在开发一个使用媒体基础 h264 编码器对视频进行编码的应用。 Sink writer crashes on Windows 7 在 VRAM 中使用 RGB 输入,说“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 编码和解码后以其他方式缩放以获得正确的颜色?此代码可以在其他人的计算机上运行吗?

【问题讨论】:

标签: c++ windows h.264 hlsl ms-media-foundation


【解决方案1】:

问题在于色度采样伪影。当我问这个问题时,我正在查看彩色控制台文本。

今天我尝试编码更好的图像,这个: 使用该图像,很明显正确的公式是这些标准中指定的。

所以,这是正确的系数:

// 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% 是从哪里来的?您是否刚刚发现使用标准公式是正确的做法?
  • “什么是色度采样伪影?”尝试在黑色背景上对彩色文本进行 h264 编码,你会看到。 “公式标准在哪里?”在标准中。主要的是ITU-R BT.709。如果您愿意阅读它们并做一些基本的数学运算,您将在我的像素着色器中获得系数。 “0.39% 是从哪里来的?”在我的测试中,我看到了一些颜色的差异,即编码+解码后#aabccc 而不是#aabbcc。 1 / 255 = 0.39%。 “你刚刚发现使用标准公式是正确的做法吗?”是的,我做到了。
  • @mofo77 顺便说一句,尽管 h264 很普遍,但互联网上没有正确的 YUV 公式的代码。这就是我回答我的问题而不是直接删除它的主要原因。
  • 另外,在生产中我没有一个 YUV 着色器。它分为 2 个,一个用于从单个像素读取并写入 R8_UNORM 目标的亮度,另一个用于从 2 个像素读取并写入 R8G8_UNORM 目标的组合 U+V。我希望我的示例代码能帮助人们制作类似的东西。
  • 所以您的公式适用于 8 位编码,而不是 10 位编码。也许应该准确。做一些基本的数学运算并不能解释你的价值观来自哪里。