Возьмем известную всем задачу преобразования из цветного в черно-белое изображение. Цель — получить черно-белое изображение, при просмотре которого воспринимаемые яркости пикселей будут пропорциональны воспринимаемой яркости пикселей цветного изображения. Нас интересует не художественный эффект, а фотометрическая точность. Обычно задачу решают либо отбрасыванием одного из трех каналов (получается плохо), либо смешиванием каналов в равных пропорциях (получается лучше, но есть противоречия с экспериментальными результатами). В этот момент обычно на просторах интернета поиском находится формула (например, в OpenCV). Но почему коэффициенты именно такие? И верны ли они?
Любое цветовое пространство RGB должно быть связано с эталонным цветовым пространством CIE XYZ. Для CIE XYZ известно соответствие между значением коэффициентов (x,y,z) пикселя и фактическим значением яркости эталонных источников света, смешением которых добиваются получения нужного воспринимаемого цвета. Для каждого RGB-изображения явно или неявно такое преобразование в XYZ должно быть определено, иначе оно может быть корректно показано только на том мониторе, на котором создавалось. Назовем такую информацию цветовым профилем изображения (подробнее здесь и здесь). Если такой профиль есть у монитора, то путем преобразования и затем преобразования $ latex XYZ_{image}rightarrow RGB_{display} $ монитора можно получить на другом мониторе цвета, соответствующие исходным (с учетом ограничений цветопередачи).
В пространстве XYZ координата Y по определению соответствует воспринимаемой яркости цвета. Это то, что нам нужно. Чтобы получить из полноцветного изображения монохромное, необходимо преобразовать каждый пиксель в XYZ и взять компоненту Y в качестве результата. Известно, что преобразование между любыми аддитивными цветовыми системами линейное (в силу линейности восприятия цвета человеком). Следовательно, может быть описано матрицей , такой, что
Итак, разгадка. Коэффициенты — это округленная до второго знака строчка из матрицы преобразования
из системы NTSC RGB (стандарт для американского телевидения) в XYZ, а “типичная” формула
вычисляет воспринимаемую яркость Y для изображения, закодированного в NTSC RGB! Полная матрица преобразования выглядит так (взято отсюда):
Однако подавляющее большинство цифровых изображений на данный момент соответствуют стандарту sRGB, разработанному для компьютерных мониторов, поэтому типичная формула, как правило, неверна!
Для sRGB более корректным будет преобразование (матрицу M для sRGB смотреть здесь):
$
Конечно, самым верным будет использование профиля изображения, если он доступен.
Стоит обратить внимание, что sRGB-изображения имеют нелинейную кодировку яркости, так называемую гамма-коррекцию: , где
является также частью профиля изображения (для sRGB можно считать гамму равной 2.2, хотя в стандарте функция несколько сложнее). Преобразование в черно-белое необходимо проводить в линейном пространстве, поэтому сначала надо выполнить гамма-преобразование, обратное гамма-коррекции
. Таким образом, корректное преобразование из цветного sRGB-изображения в монохромное выглядит так (цвета изображения лежат в диапазоне
):
Сравним результаты применения разных способов преобразования.
- Weighed (no gamma!):
, гамма-коррекция не применяется
- Old school (no gamma!):
, гамма-коррекция не применяется
- Correct (no gamma!):
- Weighted: аналогично 1), однако примеряется гамма-коррекция
- Old school: аналогично 2), однако примеряется гамма-коррекция
- Correct: аналогично 3), однако примеряется гамма-коррекция
Как проверить результаты? Если у вас хороший монитор, сравните воспринимаемую яркость предметов на цветном изображении и их яркость на черно-белом. Где более точно передано соотношение? В теории более правильный ответ на изображении от метода Correct.
Все результаты (картинки кликабельны до полного размера):
Рядом друг с другом:
Разница относительно метода 1) (линейная абсолютная разница ):
Обращаю внимание, что для задач, требующих алгоритмической работы с воспринимаемой яркостью (а не вывод на экран), лучше использовать цветовое пространства CIELAB.
Код правильного преобразования с использованием OpenCV:
Mat BGR2Gray(Mat input) { // 8 bit -> 32 bit float Mat fltImage; input.convertTo(fltImage, CV_32FC3, 1.0 / 255); // apply gamma (note: gamma simplified!) Mat fltLinear; pow(fltImage, 2.2, fltLinear); // convert to xyz (keep y) (note: bgr order!) Mat bgr2grayCorrent = (Mat_(1, 3) << 0.07, 0.72, 0.21); Mat fltGrayCorrect; transform(fltLinear, fltGrayCorrect, bgr2grayCorrent); // gamma transform Mat fltGamma; pow(fltGrayCorrect, 1 / 2.2, fltGamma); // back 8 bit Mat output; fltGamma.convertTo(output, CV_8UC3, 255); return output; }
Итак: для преобразования изображения в черно-белое для вывода на экран необходимо использовать преобразование и не забывать о гамма-коррекции.
Статья впервые (в сокращенном виде) опубликована в авторской колонке сетевого журнала «Компьютерная графика и мультимедиа» ).
Добавить комментарий