Я уверен, что многим программистам знакома формула:
А уж тот, кто плотно работал с графикой, знает эти цифры буквально наизусть — как в былые времена эникейщики запоминали серийники 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-х знаков:
Внимательный читатель заметит, что коэффициент при R округлен не по правилам (в меньшую сторону), но это не ошибка. Дело в том, что сумма всех трех чисел должна равняться единице, и «правильное» округление внесло бы погрешность. Педанты могут взять все шесть знаков после запятой и не беспокоиться.
Этой формулы хватит для 99% обычных случаев. Её использует во всех своих спецификациях W3C (например, матричные фильтры в SVG). Если вам нужна бОльшая точность, придется вычислять L*, но это отдельная большая тема. Неплохой ответ на StackOverflow, который дает отправные точки для дальнейшего чтения.
Почему меня это волнует?
Как уже было сказано выше, за многие годы формула растиражирована на несметном количестве сайтов, и они сидят в топе всех поисковиков (например). Источники посерьезнее часто приводят обе формулы, но не делают между ними должного различия, преподнося их как равноправные альтернативы. Характерный пример на StackОverflow: Formula to determine brightness of RGB color — ответы довольно подробные, но человеку не в теме сложно сделать осознанный выбор.
Справедливости ради, серьезные проекты такими ошибками почти не страдают — авторы не брезгуют сверяться со стандартами, да и фидбек аудитории работает (хотя и там не обходится без исключений). А вот рядовой программист, которому нужно побыстрее решить задачу, вбивает в поисковик что-то типа «rgb to grayscale», и тот подсовывает ему сами знаете что. Формулу продолжают находить и копипастить до сих пор! Феноменальная живучесть.
На розыск этих примеров я потратил около 20 минут:
- документация Microsoft
- документация Matlab
- стандартная библиотека Go
- модные колор-пикеры для React-а (>2k звезд)
- Chart.js (>23k звезд!)
- библиотека CImg для обработки изображений
- какие-то модули в OpenCV (похоже, что второстепенные)
- коллекция демок на WebGL
- что-то внутри Gimp-а и утилитка к Inkscape
- чьи-то небольшие проекты на Swift, Go, Three.js, JS
Обратите внимание, что наряду со старенькими проектами в списке немало упоминаний самых свежих и модных технологий, то есть код писался/копипастился совсем недавно.
А поводом для написания этой заметки стал слайд из доклада Василики Климовой с HolyJS-2016 — с той же самой доисторической формулой. Понятно, что формула не повлияла на основной смысл выступления, но наглядно продемонстрировала ваши шансы ненароком её нагуглить в 2016 году.
Подытоживая: если увидите в чьем-то действующем коде последовательность 299/587/114 — кидайте автору ссылку на эту заметку.
Автор: dom1n1k