Ааа - это звучит довольно эффективно.

читалBT.709 спецификация количество раз, и вещь, которая просто не ясна, состоит в том, должен ли закодированный поток битов H.264 фактически применить любую гамма-кривую к закодированным данным? Обратите внимание на конкретное упоминание гамма-подобной формулы в спецификации BT.709. Apple предоставила примеры шейдеров OpenGL или Metal, которые считывают YUV-данные из CoreVideo, при условии, что буферы не выполняют какую-либо настройку гаммы. Значения YUV читаются и обрабатываются так, как если бы они были простыми линейными значениями. Я также изучил исходный код ffmpeg и не обнаружил никаких настроек гаммы после шага масштабирования BT.709. После, ясоздал тестовое видео только с двумя линейными оттенками серого 5 и 26, соответствующими уровням 2% и 10%. При преобразовании в H.264 с использованием ffmpeg и iMovie выходные значения BT.709 равны (YCbCr) (20 128 128) и (38 128 128), и эти значения точно соответствуют выходным данным матрицы преобразования BT.709 без какой-либо гаммы регулировка.

Большой фон по этой теме можно найти наQuicktime Gamma Bug, Кажется, что некоторые исторические проблемы с кодировщиками Quicktime и Adobe неправильно выполняли различные настройки гаммы, и в результате видеопотоки выглядели ужасно на разных проигрывателях. Это действительно сбивает с толку, потому что если вы сравните сSRGB, он четко указывает, как применить гамма-кодирование, а затем декодировать его для преобразования между sRGB и линейным. Почему BT.709 так подробно описывает кривую регулировки гаммы такого же типа, если регулировка гаммы не выполняется после шага матрицы при создании потока данных h.264? Все цветовые шаги в потоке h.264 должны быть закодированы как прямые линейные (гамма 1.0) значения?

Если конкретный пример ввода прояснит ситуацию, я прилагаю 3 изображения с цветными полосами, точные значения разных цветов могут отображаться в редакторе изображений с этими файлами изображений.

Это первое изображение находится в цветовом пространстве sRGB и помечено как sRGB.

Это второе изображение было преобразовано в линейное цветовое пространство RGB и помечено линейным профилем RGB.

Это третье изображение было преобразовано в уровни профиля REC.709 с помощью Rec709-elle-V4-rec709.icc изelles_icc_profiles, Похоже, это то, что нужно сделать для имитации гамма-камеры, как описано в BT.709.

Обратите внимание, как значение sRGB в нижнем правом углу (0x555555) становится линейным RGB (0x171717), а значение, закодированное в гамма-коде BT.709, становится (0x464646). Что неясно, так это то, должен ли я передавать линейное значение RGB в ffmpeg или если я должен передавать уже кодированное значение BT.709, которое затем необходимо будет декодировать на клиенте до шага матрицы линейного преобразования, чтобы вернуться к RGB ,

Обновить:

Основываясь на отзывах, я обновил свою реализацию на основе C и металлический шейдер и загрузил ее на github в качестве примера проекта для iOS.MetalBT709Decoder.

Кодирование нормализованного линейного значения 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;
}

Я считаю, что эта логика реализована правильно, но мне очень трудно проверить результаты. Когда я генерирую файл .m4v, который содержит значения цвета, скорректированные по гамме (osxcolor_test_image_24bit_BT709.m4v), результат получается ожидаемым. Но тестовый пример типа (bars_709_Frame01.m4v), который я нашелВот не работает, так как значения цветовой шкалы закодированы как линейные (без регулировки гаммы).

Для тестового шаблона SMPTE уровень 0,75 является линейным RGB (191 191 191), если этот RGB кодируется без регулировки гаммы, как (Y Cb Cr) (180 128 128), или если значение в битовом потоке отображается как настроенная гамма (Y Cb Cr) (206 128 128)?

(продолжение) После дополнительных исследований этой проблемы гаммы стало ясно, что Apple фактически занимается AVFoundation с использованием гамма-функции 1.961. Это имеет место при кодировании с помощью AVAssetWriterInputPixelBufferAdaptor, при использовании vImage или с API-интерфейсами CoreVideo. Эта кусочно-гамма-функция определяется следующим образом:

#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;
}

Ответы на вопрос(1)

Ваш ответ на вопрос