我已经多次读过BT.709 spec,但还不清楚的是,编码的H.264比特流是否应该对编码数据实际应用任何伽玛曲线?请注意,BT.709规范中特别提到了类似伽玛的公式。 Apple提供了从CoreVideo读取YUV数据的OpenGL或Metal着色器的示例,提供的缓冲区不进行任何类型的伽玛调整。 YUV值就像简单的线性值一样被读取和处理。我还检查了ffmpeg的源代码,发现在BT.709缩放步骤之后未应用任何伽玛调整。然后,我created a test video仅带有两个线性灰度颜色5和26,分别对应于2%和10%的水平。当同时使用ffmpeg和iMovie转换为H.264时,输出BT.709值为(YCbCr)(20 128 128)和(38 128 128),并且这些值与BT.709转换矩阵的输出完全匹配,没有任何伽马调整。
有关该主题的大量背景资料可以在Quicktime Gamma Bug中找到。似乎Quicktime和Adobe编码器存在一些历史问题,它们不正确地进行了不同的伽玛调整,结果使得视频流在不同播放器上看起来很糟糕。这确实令人困惑,因为如果与sRGB进行比较,它可以清楚地指示出如何应用伽玛编码,然后对其进行解码以在sRGB和线性之间进行转换。如果在创建h.264数据流时在矩阵步骤之后没有应用伽玛调整,为什么BT.709会详细介绍相同种类的伽玛调整曲线? h.264流中的所有色阶是否都应编码为直线(伽玛1.0)值?
如果要通过特定示例输入使情况更清楚,我将附加3个彩条图像,可以使用这些图像文件在图像编辑器中显示不同颜色的确切值。
该第一张图片位于sRGB色彩空间中,并标记为sRGB。
第二张图像已转换为线性RGB色彩空间,并带有线性RGB配置文件标记。
此第三张图像已使用elles_icc_profiles 中的Rec709-elle-V4-rec709.icc转换为REC.709配置文件级别。这似乎是模拟BT.709中描述的“相机”伽玛所需要做的。
请注意,右下角(0x555555)的sRGB值如何变为线性RGB(0x171717),而BT.709伽玛编码的值如何变为(0x464646)。尚不清楚是我应该将线性RGB值传递给ffmpeg,还是应该传递已经BT.709伽玛编码的值,然后在线性转换Matrix步骤返回到RGB之前需要在客户端中对其进行解码
更新:
根据反馈,我更新了基于C的实现和Metal shader,并将其作为iOS示例项目MetalBT709Decoder上传到github。
编码归一化线性RGB值是这样实现的:
static inline
int BT709_convertLinearRGBToYCbCr(
float Rn,
float Gn,
float Bn,
int *YPtr,
int *CbPtr,
int *CrPtr,
int applyGammaMap)
{
// Gamma adjustment to non-linear value
if (applyGammaMap) {
Rn = BT709_linearNormToNonLinear(Rn);
Gn = BT709_linearNormToNonLinear(Gn);
Bn = BT709_linearNormToNonLinear(Bn);
}
// https://www.itu.int/dms_pubrec/itu-r/rec/bt/R-REC-BT.709-6-201506-I!!PDF-E.pdf
float Ey = (Kr * Rn) + (Kg * Gn) + (Kb * Bn);
float Eb = (Bn - Ey) / Eb_minus_Ey_Range;
float Er = (Rn - Ey) / Er_minus_Ey_Range;
// Quant Y to range [16, 235] (inclusive 219 values)
// Quant Eb, Er to range [16, 240] (inclusive 224 values, centered at 128)
float AdjEy = (Ey * (YMax-YMin)) + 16;
float AdjEb = (Eb * (UVMax-UVMin)) + 128;
float AdjEr = (Er * (UVMax-UVMin)) + 128;
*YPtr = (int) round(AdjEy);
*CbPtr = (int) round(AdjEb);
*CrPtr = (int) round(AdjEr);
return 0;
}
从YCbCr到线性RGB的解码方式如下:
static inline
int BT709_convertYCbCrToLinearRGB(
int Y,
int Cb,
int Cr,
float *RPtr,
float *GPtr,
float *BPtr,
int applyGammaMap)
{
// https://en.wikipedia.org/wiki/YCbCr#ITU-R_BT.709_conversion
// http://www.niwa.nu/2013/05/understanding-yuv-values/
// Normalize Y to range [0 255]
//
// Note that the matrix multiply will adjust
// this byte normalized range to account for
// the limited range [16 235]
float Yn = (Y - 16) * (1.0f / 255.0f);
// Normalize Cb and CR with zero at 128 and range [0 255]
// Note that matrix will adjust to limited range [16 240]
float Cbn = (Cb - 128) * (1.0f / 255.0f);
float Crn = (Cr - 128) * (1.0f / 255.0f);
const float YScale = 255.0f / (YMax-YMin);
const float UVScale = 255.0f / (UVMax-UVMin);
const
float BT709Mat[] = {
YScale, 0.000f, (UVScale * Er_minus_Ey_Range),
YScale, (-1.0f * UVScale * Eb_minus_Ey_Range * Kb_over_Kg), (-1.0f * UVScale * Er_minus_Ey_Range * Kr_over_Kg),
YScale, (UVScale * Eb_minus_Ey_Range), 0.000f,
};
// Matrix multiply operation
//
// rgb = BT709Mat * YCbCr
// Convert input Y, Cb, Cr to normalized float values
float Rn = (Yn * BT709Mat[0]) + (Cbn * BT709Mat[1]) + (Crn * BT709Mat[2]);
float Gn = (Yn * BT709Mat[3]) + (Cbn * BT709Mat[4]) + (Crn * BT709Mat[5]);
float Bn = (Yn * BT709Mat[6]) + (Cbn * BT709Mat[7]) + (Crn * BT709Mat[8]);
// Saturate normalzied linear (R G B) to range [0.0, 1.0]
Rn = saturatef(Rn);
Gn = saturatef(Gn);
Bn = saturatef(Bn);
// Gamma adjustment for RGB components after matrix transform
if (applyGammaMap) {
Rn = BT709_nonLinearNormToLinear(Rn);
Gn = BT709_nonLinearNormToLinear(Gn);
Bn = BT709_nonLinearNormToLinear(Bn);
}
*RPtr = Rn;
*GPtr = Gn;
*BPtr = Bn;
return 0;
}
我相信此逻辑已正确实现,但是我很难验证结果。当我生成一个包含伽玛调整后的颜色值(osxcolor_test_image_24bit_BT709.m4v)的.m4v文件时,结果如预期的那样出来。但是我发现here这样的测试用例(bars_709_Frame01.m4v)似乎不起作用,因为彩条值似乎被编码为线性(无伽马调整)。
对于SMPTE测试图案,0.75灰度是线性RGB(191 191 191),如果此RGB在没有伽玛调整的情况下被编码为(Y Cb Cr)(180 128 128),或者位流中的值显示为γ校正后的(Y Cb Cr)(206 128 128)?
(跟进) 在对该gamma问题进行了进一步研究之后,很明显,Apple在AVFoundation中实际正在使用1.961 gamma函数。在使用AVAssetWriterInputPixelBufferAdaptor进行编码,使用vImage或CoreVideo API进行编码时,就是这种情况。此分段伽玛函数定义如下:
#define APPLE_GAMMA_196 (1.960938f)
static inline
float Apple196_nonLinearNormToLinear(float normV) {
const float xIntercept = 0.05583828f;
if (normV < xIntercept) {
normV *= (1.0f / 16.0f);
} else {
const float gamma = APPLE_GAMMA_196;
normV = pow(normV, gamma);
}
return normV;
}
static inline
float Apple196_linearNormToNonLinear(float normV) {
const float yIntercept = 0.00349f;
if (normV < yIntercept) {
normV *= 16.0f;
} else {
const float gamma = 1.0f / APPLE_GAMMA_196;
normV = pow(normV, gamma);
}
return normV;
}
答案 0 :(得分:1)
您最初的问题:带有BT.709矩阵的H.264编码视频是否包含任何伽马调整?
编码的视频仅包含伽玛调整-如果您输入编码器的伽玛调整值。
H.264编码器不关心传输特性。 因此,如果先线性压缩然后再解压缩-您将得到线性。 因此,如果先用gamma压缩然后再解压缩-您将获得gamma。
或者如果您的位是用Rec。 709传递函数-编码器不会更改伽玛。
但是您可以在H.264流中将传输特性指定为元数据。 (ITU-T H.264建议书(04/2017)E.1.1 VUI参数语法)。因此,编码后的流会携带色彩空间信息,但是不会在编码或解码中使用。
我会假设8位视频始终包含非线性传递函数。否则,您会非常不明智地使用8位。
如果转换为线性以产生效果和构图-建议您增加位深度或线性化为浮点数。
颜色空间由原色,传递函数和矩阵系数组成。 伽玛调整值在传递函数中编码(而不是在矩阵中)。