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

А уж тот, кто плотно работал с графикой, знает эти цифры буквально наизусть — как в былые времена эникейщики запоминали серийники 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, который дает отправные точки для дальнейшего чтения.
Если изображение у вас в другом цветовом пространстве (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
Получилось, конечно, точнее.
А уж тот, кто плотно работал с графикой, знает эти цифры буквально наизусть — как в былые времена эникейщики запоминали серийники 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, который дает отправные точки для дальнейшего чтения.
Если изображение у вас в другом цветовом пространстве (Adobe RGB, ProPhoto RGB и т.д.) — коэффициенты будут свои; их можно найти в вышеупомянутой таблице Брюса Линдблума.
Почему меня это волнует?
Как уже было сказано выше, за многие годы формула растиражирована на несметном количестве сайтов, и они сидят в топе всех поисковиков (например). Источники посерьезнее часто приводят обе формулы, но не делают между ними должного различия, преподнося их как равноправные альтернативы. Характерный пример на 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 — кидайте автору ссылку на эту заметку.
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
Получилось, конечно, точнее.