Pull to refresh

Об относительной яркости, или насколько живучим бывает легаси

Reading time6 min
Views40K
Я уверен, что многим программистам знакома формула:

Y = 0.299 R + 0.587 G + 0.114 B

А уж тот, кто плотно работал с графикой, знает эти цифры буквально наизусть — как в былые времена эникейщики запоминали серийники Windows. Иногда коэффициенты округляют до второго знака, иногда уточняют до четвертого, но каноническая форма именно такая.

Вычисляет она относительную яркость цвета (relative luminance или в некоторых контекстах luma; не путать с lightness и brightness) и широко применяется для преобразования цветного RGB-изображения в Grayscale и связанных с этим задач.

Формула растиражирована и процитирована в тысячах статей, форумных обсуждений и ответов на StackOverflow… Но дело в том, что единственно-правильное её место — на свалке истории. Использовать её нельзя. Однако же используют.

Но почему нельзя? И откуда же взялись именно такие коэффициенты?

Мини-экскурс в историю


Есть такая международная организация, которая разрабатывает рекомендации (де-факто стандарты) для сферы телерадиокоммуникаций — ITU.

Интересующие нас параметры прописаны в рекомендациях ITU-R BT.601, принятых в 1982 году (по ссылке обновленная редакция). Уже на этом моменте можно слегка удивиться — где мы и где 82-ой год? Но это только начало.

Циферки перекочевали туда из рекомендаций ITU-R BT.470 от 1970 года (по ссылке также обновленная редакция).

А они, в свою очередь — наследие цветовой модели YIQ, которая была разработана для североамериканской системы телевещания NTSC в 1953 году! К нынешним компьютерам и гаджетам она имеет отношение чуть более, чем никакое.

Никому не напоминает байку про связь космических кораблей с шириной древнеримской лошадиной задницы?

Современные колориметрические параметеры начали выкристаллизовываться в 1970 году с модернизацией систем PAL/SECAM. Примерно в это же время американцы придумали свою спецификацию SMPTE-C на аналогичные люминофоры, но NTSC перешла на них только в 1987 году. Я не знаю наверняка, но подозреваю, что именно этой задержкой объясняется сам факт рождения пресловутых Rec.601 — ведь по большому счету, они морально устарели уже к моменту своего появления.

Потом в 1990 году случились новые рекомендации ITU-R BT.709, а в 1996 на их основе придумали стандарт sRGB, который захватил мир и царствует (в потребительском секторе) по сей день. Альтернативы ему существуют, но все они востребованы в узкоспецифичных областях. И прошло уже, ни много ни мало, 20 лет — не пора бы уже избавиться от атавизмов окончательно?

Так в чем же конкретно проблема?


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

Любое RGB-пространство (а YIQ это преобразование над моделью RGB) определяется тремя базовыми параметрами:

1. Хроматическими координатами трех основных цветов (они называются primaries);
2. Хроматическими координатами белой точки (white point или reference white);
3. Гамма-коррекцией.

Хроматические координаты принято задавать в системе CIE xyY. Регистр букв в данном случае важен: cтрочные xy соответствуют координатам на хроматической диаграмме (всем известная «подкова»), а заглавный Y — это яркость из вектора CIE XYZ.

Теперь посмотрим на компоненту Y у всех первичных цветов NTSC (я пометил их розовым):


* Оригинал таблицы со многими другими пространствами на сайте Брюса Линдблума.

Знакомая цифирь, правда? Вот и ответ на вопрос «откуда взялось?»

А проблема в том, что используемое сегодня пространство sRGB существенно отличается от системы 60-летней давности. И дело даже не в том, что из них лучше или хуже — они просто разные:



Треугольник шире и смещён в сторону. Другая белая точка. К слову сказать, иллюминант C уже очень давно признан deprecated в пользу иллюминантов серии D вообще и наиболее популярного D65 в частности. Тело цветового охвата другое — соответственно, результаты вычислений яркости окажутся неадекватны реальности.

Вы можете спросить: а зачем древнему NTSC охват (практически совпадает с охватом Adobe RGB 1998!) настолько больше, чем у современного sRGB? Я не знаю. Совершенно очевидно, что кинескопы того времени покрыть его не могли. Быть может, хотели сделать задел на будущее?

Как правильно?


Относительные яркости первичных цветов в пространстве sRGB приведены в таблице выше (помечены зеленым) — их и нужно использовать. На практике обычно делают округление до 4-х знаков:

Y = 0.2126 R + 0.7152 G + 0.0722 B

Внимательный читатель заметит, что коэффициент при R округлен не по правилам (в меньшую сторону), но это не ошибка. Дело в том, что сумма всех трех чисел должна равняться единице, и «правильное» округление внесло бы погрешность. Педанты могут взять все шесть знаков после запятой и не беспокоиться.

Этой формулы хватит для 99% типичных случаев. Её использует во всех своих спецификациях W3C (например, матричные фильтры в SVG).
Если вам нужна бОльшая точность, придется вычислять L*, но это отдельная большая тема. Неплохой ответ на StackOverflow, который дает отправные точки для дальнейшего чтения.
Если изображение у вас в другом цветовом пространстве (Adobe RGB, ProPhoto RGB и т.д.) — коэффициенты будут свои; их можно найти в вышеупомянутой таблице Брюса Линдблума.

Почему меня это волнует?


Как уже было сказано выше, за многие годы формула растиражирована на несметном количестве сайтов, и они сидят в топе всех поисковиков (например). Источники посерьезнее часто приводят обе формулы, но не делают между ними должного различия, преподнося их как равноправные альтернативы. Характерный пример на StackОverflow: Formula to determine brightness of RGB color — ответы довольно подробные, но человеку не в теме сложно сделать осознанный выбор.

Справедливости ради, серьезные проекты такими ошибками почти не страдают — авторы не брезгуют сверяться со стандартами, да и фидбек аудитории работает (хотя и там не обходится без исключений). А вот рядовой программист, которому нужно побыстрее решить задачу, вбивает в поисковик что-то типа «rgb to grayscale», и тот подсовывает ему сами знаете что. Формулу продолжают находить и копипастить до сих пор! Феноменальная живучесть.

На розыск этих примеров я потратил около 20 минут:


Обратите внимание, что наряду со старенькими проектами в списке немало упоминаний самых свежих и модных технологий, то есть код писался/копипастился совсем недавно.

А поводом для написания этой заметки стал слайд из доклада Василики Климовой с HolyJS-2016 — с той же самой доисторической формулой. Понятно, что формула не повлияла на основной смысл выступления, но наглядно продемонстрировала ваши шансы ненароком её нагуглить в 2016 году.

Подытоживая: если увидите в чьем-то действующем коде последовательность 299/587/114 — кидайте автору ссылку на эту заметку.

update 1
В комментариях настоятельно просят примеры. Но тут не всё так просто, как кажется.
Если взять произвольную картинку и перевести её в ч/б двумя способами — это не даст вообще ничего. Картинки будут просто немного разными. Зритель сможет только оценить, какой вариант ему субъективно симпатичнее. А дело-то не в этом! Дело в том, какой вариант правильнее, точнее.

Немного поразмыслив, я набросал вот такую штуку: codepen.io/dom1n1k/pen/LZWjbj

Скрипт генерирует 2 раза по 100 случайных цветов, подбирая компоненты так, чтобы яркость всех кубиков была теоретически одинакова (Y = 0.5). То есть всё поле целиком должно субъективно восприниматься как можно более однородно (однородно именно с точки зрения яркости, не учитывая разные тона).
Слева старая «неправильная» формула, справа новая «правильная». Справа однородность действительно заметно выше. Хотя и не идеальна, конечно — для бОльшей точности нужно вычислять перцептивную светлоту L*.

update 2.1
Ещё возник вопрос по поводу гаммы. Его подняло уже минимум 3 человека, поэтому тоже вынесу в апдейт. Вопрос на самом деле непростой и отчасти даже философский (вполне потянет на отдельную статью).

Если говорить строго — да, для перевода картинки в ч/б вид гамму нужно декодировать. Но на практике (в задачах, не связанных с точной колориметрией) этот шаг часто опускают ради простоты и производительности. Например, Photoshop при конвертации в grayscale гамму учитывает, а одноименный CSS-фильтр (MDN) — нет.

С точки зрения корректности результата, выбор весовых коэффициентов и пересчёт гаммы — взаимодополняющие вещи. Влияет и то, и другое. Разница только в том, что гамма требует дополнительных вычислений, а вот правильные коэффициенты обходятся бесплатно.

Вторая версия демо с учетом гаммы (первая никуда не делась): codepen.io/dom1n1k/pen/PzpEQX
Получилось, конечно, точнее.
Tags:
Hubs:
+87
Comments130

Articles