В предыдущий раз я переводил краткую теорию цвета и описание управления цветом для формата PNG. Если пересказывать своими словами, то цветовые модели делятся на физические (XYZ) и логические (RGB или YUV). В форматах хранения изображений и видео используются логические форматы (потому что они ограничены в диапазоне значений), иногда с добавлением метаданных, описывающих правила конвертации из логической модели в физическую. В то время, как логическая модель обычно хранит значения в диапазоне от 0 до 255 или от 0 до 1, физическая модель оперирует комбинацией трех чисел, каждое из которых представляет взвешенную сумму энергий излучения по всему спектру видимого цвета, взятую с разными весами.
Что касается дисплеев, для них производитель указывает характеристики, описывающие то, как цифровой сигнал из, например, RGB преобразуется в значения XYZ, излучаемые этими самыми дисплеями. Такими характеристиками является точка белого (т.е. какому физическому цвету соответствует RGB-сигнал с компонентами max/max/max), основные цвета (максимумы RGB при остальных минимумах), гамма или передаточная функция, а также охват (gamut), который описывает всё множество физических цветов, которые в принципе может отобразить дисплей.
Гамма, или передаточная функция
Согласно википедии, гамма-коррекция - это процесс нелинейного преобразования значений сигнала с целью лучшей передачи темных полутонов. На пальцах можно объяснить так: пусть есть сигнал в диапазоне от 0 до 255. Яркость точки 2 отличается от яркости точки 1 в 2 раза, а яркость 255 от 254 - в "нисколько" раз, глаз не различит. Поэтому близкие темные полутона такой сигнал передать физически не может. Теперь договоримся, что будем рассчитывать значение яркости как 2^C
, где C как раз в диапазоне от 0 до 255. Тогда в примере выше обе пары точек будут соответствовать изменению яркости в 2 раза. Ура, теперь мы одинаково точно передаем и светлые, и темные полутона. Правда, человеческий глаз устроен немного иначе, и классическая формула выглядит как A * C ^ 2.2
, где 2.2 - это значение гаммы.
Конечно, вся эта теория уже мхом обросла, и современная версия стандарта H.264 описывает аж целых 10 функций преобразования (Таблица E4 - Transfer characteristics [1]), их которых только 2 соответствуют значению гаммы 2.2 и 2.8. Но в любом случае, передаточная функция - это очень важная часть стандарта, и ошибка ее использования при отображении цвета приведет к очень заметным искажениям.
YUV
В модели YUV за яркость отвечает Y - взвешенная сумма компонентов RGB. U и V соответствуют разностям яркости и синего/красного каналов. За счет того, что человеческий глаз гораздо лучше отличает яркость, нежели оттенок цвета (ну, помните, из школы, бульбочки и колбочки), становится возможным экономить биты при работе в YUV-модели: в пиксельном формате yuv420 блок 2х2 пикселя кодируется четырьмя значениями яркости Y и лишь двумя значениями цветности (по одному для U и V) - экономия! Тот же блок 2х2 в yuv422 соответствует четырем значениям яркости и по двум значениям UV (блоки 2х1 по горизонтали). Больше форматов веселых и разных описаны в вики к плееру VLC, а нам достаточно того, что yuv420p встречается чаще всего в видеопотоке H.264.
Есть еще один момент: во всей этой математике преобразований, особенно реализованной на целочисленной арифметике, есть побочка в виде ошибок округления: например, формула floor(1.1*x)
будет давать идентичные значения для 254 и 255, так что для перестраховки используется не полный диапазон значений байта, а, например, от 16 до 235 [2] - глядишь, до границы не дойдет. Страдает количество передаваемых цветов, зато арифметика проще. Это потихоньку уходит в прошлое, потому что, например, стандарт JPEG использует весь диапазон от 0 до 255 и называет этот формат J420.
Метаданные H.264
На этот раз не станем долго листать стандарт, а воспользуемся программой MediaInfo (практика - наше всё).
Video
ID : 1
Color space : YUV
Chroma subsampling : 4:2:0
Bit depth : 8 bits
Scan type : Progressive
Color range : Limited
Color primaries : BT.709
Transfer characteristics : BT.709
Matrix coefficients : BT.709
По отредактированному выводу программы можно понять следующее:
Color space" YUV
нам говорит, что в каналах изображения хранятся яркость и UV-компоненты цветностиChroma subsampling: 4:2:0
утверждает, что на каждый пиксель U или V приходится по 4 пикселя YBit depth: 8 bits
подсказывает что значения каналов хранятся в виде 8-битных чиселScan type: Progressive
соответствует хранению полного кадра - безо всякой черезстрочной развертки из древних телековColor range: Limited
ограничивает задействованные значения диапазоном 16-235.
Вот это всё пока что описывает формат хранения логических цветов, без привязки к физической цветовой модели.
Декодирование битового формата
Дальше мы попробуем мысленно декодировать данные, хранящиеся в видеопотоке. Этап с декодером мы пропустим, потому что сжатие видео - это про другое. Нашими входными данными будут кадры, для которых на каждые 2х2 пикселя в наличии имеются: 4 байта яркости, 1 байт U-компоненты и 1 байт V-компоненты.
Избавляемся от
4:2:0
: 1 байт UV-компонент используем для всех четырех пикселей. Теперь у нас на каждый пиксель приходится по 3 байта YUV-компонент.Приводим значения из диапазона 16-235 к диапазону 0-1: так мы избавились от
Limited
, а компоненты теперь лежат в нормированном диапазоне.
Перевод в RGB
На данном этапе нам необходимо трех-компонентный вектор YUV привести к трех-компонентному вектору RGB. Формулировки знакомы? Первый курс, линейная алгебра, умножение матрицы на вектор. Поле Matrix coefficients
подсказывет нам, что надо использовать формулу из Таблицы E-5 - Matrix coefficients [1], указанную ниже:
K[R] = 0.2126; K[B] = 0.0722
Эти коэффициенты упомянуты в википедии и используются в формуле получения Y-компоненты из RGB:
Y = K[R] * R + (1 - K[R] - K[B]) * G + K[B] * B
Пристально взглянув на эту и остальные формулы, замечаем, что это линейное преобразование, которое описывается матрицей 3х3.
Вычисляем обратную матрицу (ага, для каждого пикселя в видео - конечно же нет, просто хардкодим ее в исходниках).
умножаем YUV на обратную матрицу
M[-1]
, получаем RGB.Нормируем, как указано в той же статье вики, получаем RGB, где каждая компонента лежит в диапазоне от 0 до 1.
Коррекция тональности
Дальше по классике: чтобы аккуратно передавать светлые и темные полутона, используем передаточную функцию из Таблицы E-4 - Transfer characteristics [1] на каждом из трех каналов:
Заметьте, мы всё еще не знаем, какие физические цвета обрабатываем: пока мы просто занимались преобразованием логических значений.
Отображение на дисплее
Допустим, мы передаем логическое RGB-значение цвета S
на дисплей. Тот что-то мутит с напряжениями, транзисторами и прочей электроникой, в результате чего испускает фотоны, воспроизводящие цвет D
. Вот это электронное "что-то" производитель дисплея описывает в характеристиках как преобразование D = Fd(S)
.
Мы же в свою очередь имеем логическое значение V
, декодированное из видеофайла, но (пока что) понятия не имеем, какой физический цвет имел ввиду автор данного видео. Если мы просто отправим на дисплей V
вместо S
, то увидим "сферического коня в вакууме".
Однако, чтобы наш золотой видеофильский кабель не зря пылился под столом, нам очень хочется, чтобы цвет D
на дисплее соответствовал физическому цвету C
видеокамеры, на которую снято данное видео. Поэтому нам надо построить функцию преобразования для дисплея: S = Fs(V)
. Тут нам поможет последнее оставшееся незадействованным поле метаданных Color primaries
.
Таблица E-3 – Colour primaries [1]
primary |
x |
y |
---|---|---|
green |
0.300 |
0.600 |
blue |
0.150 |
0.060 |
red |
0.640 |
0.330 |
white D65 |
0.3127 |
0.3290 |
В этой таблицы указаны физические цвета, соответствующие точке белого и основным логическим цветам (RGB), использованным при кодировании видео. Хотя скорее можно понимать наоборот: физический цвет white D65
записывается видеокамерой как значение 255,255,255 в RGB.
Так вот, указанные физические координаты описывают функцию преобразования C = Fc(V)
из логического цвета V
в видеофайле в физический C
, который имел ввиду автор видео. Зная, что мы хотим, чтобы авторский физический цвет совпадал с отображенным на дисплее (C == D
), мы можем построить функцию преобразования декодированного цвета в сигнал для дисплея: S = Fs(V) = Fd[-1](Fc(V))
, где Fd[-1]
- это функция обратного преобразования физического света от дисплея ко входному сигналу.
В итоге
Распаковав изображение в формате yuv420p
и отнормировав его из Color Space: Limited
, мы использовали Matrix coefficients
для преобразования YUV в RGB, затем скорректировали полутона с помощью Transfer characteristics
и конвертировали цвет в сигнал для имеющегося дисплея, построив функцию преобразования на основе физических характеристик дисплея и Color primaries
, описывающих ту же концепцию, но для видеофайла. Теперь наше видео отображается так же, как это задумал его автор.
Ссылки
Rec. ITU-T H.264 (04/2013) - описание стандарта H.264, https://www.itu.int/rec/dologin_pub.asp?lang=e&id=T-REC-H.264-201304-S!!PDF-E&type=items.
Rec. ITU-R BT.601-7 - описание стандарта сигнала для телевидения, https://www.itu.int/rec/R-REC-BT.601-7-201103-I/en