Как-то я выложил рецепт по использованию палитр, которые были вытащены из Matlab, но не объяснил как этим пользоваться, хотя выложенный пакет цветовых палитр сам по себе довольно интересная штука.
Сегодня я покажу любопытный метод, как перекрасить некоторое заданное изображение, используя известный, но ограниченный набор цветов на примере создания простой «термокарты» из картинки.
Для того, чтобы получить «термокарту» изображения нам нужно приблизить все теплые тона на исходной картинке к красному цвету (поскольку, он является самым «горячим»), а все холодные — к фиолетовому. Однако, набор цветов на произвольном изображении довольно широк, что приводит нас к проблеме того, каким образом выполнить искомое приближение, приводящее к созданию «термокарты».
В этом случае, нам не обязательно думать о том, как выполнить приближение, достаточно вспомнить о том, что есть ряд замечательных палитр Matlab, которые отвечают необходимому условию перевода цветов (теплые переводятся в оттенки красного, а холодные — в оттенки фиолетового). Одной из таких палитр является известная палитра Jet, полный спектр которой напоминает «тепловой фон», который можно увидеть на экранах тепловизоров.
Для реализации создания «термокарты» нам потребуется готовая палитра Jet, которая представляет собой простой csv-файл с перечислением всех ее основных цветов в формате RGB.
В нашем случае палитра выглядит примерно так:
0;0;132 0;0;136 0;0;140 0;0;144 0;0;148 0;0;152 0;0;156 0;0;160
где первое число — это красный компонент, второе — зеленый, а третье — синий компонент в описании RGB.
(скачать все палитры (в том числе, и используемую нами Jet) вы можете из статьи «Загрузка сторонних палитр в QtE5» )
После скачивания палитры, нам необходимо ее загрузить в программу для чего воспользуемся библиотекой dlib, а саму палитру после загрузки представим в виде массива цветов Color4f:
Color4f[] getPalette(string filename) { Color4f[] palette; Color4f extractField(string triplet) { Color4f color; auto content = triplet.split(";"); color.r = parse!float(content[0]) / 255.0f; color.g = parse!float(content[1]) / 255.0f; color.b = parse!float(content[2]) / 255.0f; return color; } palette = (cast(string)(read(filename))) .splitLines .map!(a => extractField(a)) .array; return palette; }
Данная функция построчно считывает csv-файл палитры, превращая каждую строку в RGB-триплет и возвращает массив этих триплетов. Извлечение производится с помощью разбиения строки с компонентами по разделителю «;» и перевода полученных значений в числа с плавающей запятой (т.е. приведение к типу float) с последующей нормировкой (деление на 255.0f необходимо для попадания числа в интервал от 0 до 1, поскольку именно на такие значения рассчитан тип Color4f).
После того, как мы получили палитру, необходимо обработать исходное изображение, таким образом, чтобы каждый пиксель изображения имел какой-либо цвет, принадлежащий палитре Jet. Для этого, нам надо пройтись по каждой точке изображения и подобрать ей наиболее «близкий» оттенок цвета из нашей палитры, что можно сделать используя тот же прием, что мы использовали при создании эффекта с выборочным обесцвечиванием — измерение расстояния между цветами.
Как при создании «термокарты» использовать расстояние между цветами? Очень просто: для каждого пикселя исходного изображения нам нужно выбрать из палитры Jet такой цвет, расстояние до которого от цвета исходного пикселя будет наименьшим.
Сделаем это следующим образом:
auto selectColor(ref Color4f[] colors, Color4f color) { auto colorDistance(Color4f a, Color4f b) { auto dx = ((a.r - b.r) ^^ 2) + ((a.g - b.g) ^^ 2) + ((a.b - b.b) ^^ 2); return sqrt(dx); } auto distance = float.max; auto currentColor = Color4f(1.0f, 1.0f, 1.0f); foreach (paletteColor; colors) { if (colorDistance(color, paletteColor) < distance) { distance = colorDistance(color, paletteColor); currentColor = paletteColor; } } return currentColor; }
Теперь, нужно просто осуществить замену цвета по такому принципу на всей картинке, для чего достаточно просто пройти с помощью функции selectColor по всему изображению:
auto createThermocard(SuperImage superImage, ref Color4f[] palette) { SuperImage newImage = image(superImage.width, superImage.height); foreach (x; 0..superImage.width) { foreach (y; 0..superImage.height) { newImage[x,y] = selectColor(palette, superImage[x,y]); } } return newImage; }
А теперь протестируем createThermocard на многострадальном изображении Lenna:
void main() { // source image auto img = load(`Lenna.png`); auto palette = getPalette(PALETTE_JET); // create a thermal-image and save to a file createThermocard(img, palette).savePNG("Lenna_Jet.png"); }
«Термокарта» получилось такой:
Вот в общем-то, и все. Не знаю, как можно использовать данный эффект и упоминал ли его еще кто-нибудь, но по-моему получилось очень классно, кроме того, никто не мешает вам попробовать повторить его и с другими палитрами (что должно также выглядеть эффектно), и поэтому оставляю код на GitHub: ThermalImageMap.
P.S: Будем стараться и впредь публиковать код из статей в виде репозиториев GitHub.