Убираем «лишние» цвета

Увлекшись цифровой обработкой изображений, неожиданно для себя, я открыл интересный графический эффект, который как выяснилось, делается весьма и весьма просто (даже без свертки и прочих математических диковинок), однако, если постараться его поискать, то поисковик выдает лишь то, как выполнить подобное в Photoshop и других монструозных графических программах…

Это решительно меня не устраивало, да и вообще, почему описания эффекта для программистов отсутствуют в рунете ? Сейчас я это исправлю!

Эффект, который так неожиданно ворвался в мои опыты по обработке изображений, называется «выборочное обесцвечивание», которое представляет собой наглядную демонстрацию того факта, что такие математические абстракции как «расстояние» применимы не только к точкам плоскости/пространства/населенным пунктам (и не населенным тоже), но и к гораздо более чувственным и образным понятиям, таким, как например, цвет.

Что такое «выборочное обесцвечивание»?

Выборочное обесцвечивание — это когда берется некоторый цвет, а также все его цветовые «родственники» (оттенки), и оставляются в изображении неизменными, а остальные цвета, присутствующие в исходной картинке, заменяются на собственные аналоги из палитры серого, т.е. теряют цветность или «обесцвечиваются» (в кавычках, потому, что под бесцветными телами мы привыкли понимать объекты, которые выглядят как вода или стекло, но не объекты, которые несут на себе оттенок цветности из палитры Grayscale). Соответственно, для достижения подобного эффекта, необходима функция, которая будет принимать как минимум два аргумента: объект изображения, роль которого в dlib прекрасно выполняет объект класса SuperImage, и цвет, который мы хотим сильно выделить среди остальных, роль которого будет исполнять уже знакомая нам структура dlib с именем Color4f.

Но, как вы уже заметили, это минимальный спектр аргументов, которые функция может принять, и следовательно, у функции должны быть еще аргументы…

Чтобы понять, какие аргументы еще нужны, необходимо поставить хороший и качественный вопрос, который поможет одновременно разобраться и с алгоритмом функционирования выборочного обесцвечивания, и это вопрос должен звучать примерно так: «А каким образом, функция проходя по всему изображению отличает цвет, который решено было оставить от других присутствующих?».

Ответ на вопрос очень прост: используя понятие Евклидового расстояния до выбранного нами цвета, функция решает оставить или заменить на серый аналог попавший под обработку оттенок, если цвет из картинки находиться достаточно рядом или достаточно близко, то он остается без каких-либо изменений; в противном же случае происходит «обесцвечивание» (Это мотивировано графическим представлением модели цветов RGB, которое, как известно, представляет собой куб с главными осями R, G, и B соответственно. Если представить, что цвет — это точка в пространстве этого куба, то ближайшие соседи, сходные по цветности, и соответственно, по «координатности», лежат в небольшой окрестности, определяемой минимальным евклидовым расстоянием до этих самых соседей).

Исходя из подобного предположения, становиться ясно, что функции необходим третий аргумент, который определяет насколько близко расположены точки с «одинаковым» по сравнению с нашим оттенком цвета, и что наиболее удобно выражать этот аргумент в долях единицы, т.е. третий аргумент принимает значения из интервала [0;1].

Вспоминая о том, что такое Евклидовое расстояние, а также тот факт, что структура Color4f содержит в себе поля r, g, b создадим функцию, которая выглядит примерно так:

auto excludeAnotherColor(SuperImage source, Color4f color, float distance = 0.51f)
{
	auto imageWidth = source.width;
	auto imageHeight = source.height;
	auto simg = image(imageWidth, imageHeight);


	float colorDistance(Color4f first, Color4f second)
	{
		import std.math : sqrt;

		auto R = (first.r - second.r) ^^ 2;
		auto G = (first.g - second.g) ^^ 2;
		auto B = (first.b - second.b) ^^ 2;

		return sqrt(R+G+B);
	}

	
	for (int i = 0; i < imageWidth; i++)
	{
		for (int j = 0; j < imageHeight; j++)
		{
			auto currentPixel = source[i,j];

			if (colorDistance(currentPixel, color) < distance)
			{
				simg[i,j] = currentPixel;
			}
			else
			{
				auto I = 0.2126 * currentPixel.r + 0.7152 * currentPixel.g + 0.0722 * currentPixel.b;
				simg[i,j] = Color4f(I, I, I);
			}
		}
	}

	return simg;
	
}

Ну и соответственно, возьмем, к примеру, вот такую картинку для испытаний:

И примерно такие параметры:

	// выборочное обесцвечивание
	auto img = load(`berry.jpg`)
	img.excludeAnotherColor(Color4f(0.9f, 0.0f, 0.1f), 0.65).savePNG("test.png");

И в результате, мы увидим, что ягоды остались выделенными, а все остальное поблекло и как бы потеряло цвет!

Фантастика, не правда ли?

Под конец, хотелось бы пояснить кое-что: третий аргумент удобно воспринимать не как расстояние или «близость», а как некоторую степень детализации по цвету, т.е. насколько детально будет выделен предмет, обладающий этим цветом. Согласитесь, что это более удобная, хотя определенно и не с первого раза, интерпретация!

2 Комментарии “Убираем «лишние» цвета

  1. Очень круто! В dlib, кстати, есть основанный на том же принципе эффект chroma key (dlib.image.chromakey.chromaKeyEuclidean), но выборочное обесцвечивание там до сих пор работает на старом алгоритме, в пространстве HSV. Надо будет это исправить…

  2. Спасибо )) Но пока не стоит переводить на новый алгоритм — мы его не до конца изучили. Кроме того, разрабатываем и функциональный вариант…

Добавить комментарий