Эксперименты с квадратными паттернами в dlib

Почитывая раздел Хабрахабра под названием «Ненормальное программирование» я наткнулся на интересную статью про узор, который как я понял, порождается определенными закономерностями во фрактале Герасимова.

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

Если интересно, что получилось, то добро пожаловать в эту статью.

Меня заинтерсовали сами паттерны, которые выглядели достаточно просто:

Но, я не стал перечитывать упоминавшуюся статью, а решил пойти своим путем и попробовать получить из них какой-нибудь узор, для чего воспользовался библиотекой dlib.

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

Объединяя выше сказанное имеем следующий код, который использует некоторые математические предположения относительно самих линий в «квадратиках» (предполагаем, что линии которые дают образ конверта в паттернах идут точно до середины прямоугольника, и используем этот факт при рисовании):

import std.stdio;
import dlib.image;

// рисование прямоугольника
void drawRectangle(ref SuperImage simg, Color4f color, int x, int y, uint w, uint h)
{
	for (uint a = 0; a < h; a++)
	{
		simg[x, y + a] = color;
	}
	
	for (uint b = 0; b < w; b++)
	{
		simg[x + b, y + h] = color;
	}
	
	for (uint c = 0; c < h; c++)
	{
		simg[x + w, y + c] = color;
	}
	
	
	for (uint d = 0; d < w; d++) { simg[x + d, y] = color; } } // рисование линии по алгоритму Брезенхэма void drawBresenhamLine(ref SuperImage simg, Color4f color, int x1, int y1, int x2, int y2) { import std.algorithm : max; import std.math : abs; int dx = (x2 - x1 >= 0 ? 1 : -1);
	int dy = (y2 - y1 >= 0 ? 1 : -1);
	
	int lengthX = abs(x2 - x1);
	int lengthY= abs(y2 - y1);
	int length = max(lengthX, lengthY);
	
	if (length == 0)
	{
		simg[x1, y1] = color;
	}
	
	if (lengthY <= lengthX)
	{
		int x = x1;
		float y = y1;
	
		length++;
		while (length--)
		{
			simg[x, cast(int) y] = color;
			x += dx;
			y += dy * (cast(float) (lengthY)) / lengthX;
		}
	}
	else
	{
		float x = x1;
		int y = y1;
	
		length++;
		while(length--)
		{
			simg[cast(int) x, y] = color;
			x += dx * (cast(float) (lengthX)) / lengthY;
			y += dy;
		}
	}
	
}

enum SquarePattern : int
{
	UP,
	DOWN,
	NOP
}

auto drawSquarePattern
(
	SuperImage superImage, Color4f color, SquarePattern pattern, int x, int y, int d
)
{
	superImage.drawRectangle(color, x, y, d, d);
	superImage.drawBresenhamLine(color, x, y, x + (d / 2), y + (d / 2));
	
	final switch (pattern) with (SquarePattern)
	{
		case UP:
			superImage.drawBresenhamLine(color, x + d, y, x + (d / 2), y + (d / 2));
			break;
		case DOWN:
			superImage.drawBresenhamLine(color, x + (d / 2), y + (d / 2), x, y + d);
			break;
		case NOP:
			break;
	}
	
}

Также, на всякий случай, я поместил в начале кода уже упоминавшиеся процедуры построения прямоугольника и линии, поскольку (пока ?) методов построения этих объектов непосредственно в составе dlib нет.

А теперь, возьмем некоторое пустое изображение, а затем разделим его на квадраты со стороной, равной стороне одного «квадратика», пройдемся в цикле по всем получившимся областям и случайным образом выберем вид «квадратика»:

auto drawPattern(SuperImage superImage, Color4f color, int size)
{
	auto w = superImage.width;
	auto h = superImage.height;

	import std.random : uniform;

	for (int x = 0; x < w; x += size)
	{
		for (int y = 0; y < h; y += size)
		{
			int q = uniform(0,3);
			
			superImage.drawSquarePattern(color, cast(SquarePattern) q, x, y, size);
		}
	}
}

Испытаем, создав для примера десять картинок размера 513 на 513, а сторону «квадратика» сделаем равным 8:

void main()
{
	import std.conv : to;
	for (int i = 0; i < 10; i++)
	{
		auto img = image(513, 513);
		img.drawPattern(Color4f(0.5f, 0.0f, 0.85f), 8);
		img.savePNG("test" ~ to!string(i) ~ ".png");
	}
}

Вот пример одного такого изображения:

Интересный эффект можно получить просто избавившись от строки с отрисовкой прямоугольной области, для этого необходимо убрать или закоментировать строку с superImage.drawRectangle(color, x, y, d, d) из процедуры drawSquarePattern:

Ачто если мы возьмем некоторое изображение, пройдем по каждому из его пикселей, вычисляя его цвет и используя полученное значение для того, чтобы отобразить каждый пиксель в виде случайно выбранного «квадратика» в новом изображении ?

Сказано — сделано:

auto drawWithPatterns(SuperImage source, int size)
{
	auto width = source.width;
	auto height = source.height;
	auto img = image(width * size, height * size);

	import std.random : dice, uniform;

	foreach (x; 0..width)
	{
		foreach (y; 0..height)
		{
			auto nx = x * size;
			auto ny = y * size;
			auto q = dice(33,33,33);

			Color4f current = source[x, y];

			img.drawSquarePattern(current, cast(SquarePattern) q, nx, ny, size);
			
		}
	}

	return img;
}

Код прост , и единственное, что мы использовали нового: это приведение целого числа к типу перечисления. В итоге, это избавляет от final…switch для выбора типа «квадратика» в зависимости от выпавшего случайного числа.

Испытаем, используя стандартное изображение Lenna:

void main()
{
	auto img = load(`/home/aquareji/Downloads/220px-Lenna.png`);
	img.drawWithPatterns(8).savePNG(`Lenna_exp.png`);
}

Результат:

И вот сейчас, я предложу кое-что любопытное: давайте вместо цвета некоторого пикселя из исходного изображения возьмем его яркость по стандарту 601 (также известному, как luminance) и умножим это значение на случайно выбранный цвет из палитры RGB. Полученный таким образом цвет будем использовать как цвет выбранного квадратика, осуществляя тем самым замену пикселей на «квадратики».

Процедура замены:

auto drawWithPatterns(SuperImage source, int size)
{
	auto width = source.width;
	auto height = source.height;
	auto img = image(width * size, height * size);

	import std.random : dice, uniform;

	foreach (x; 0..width)
	{
		foreach (y; 0..height)
		{
			auto nx = x * size;
			auto ny = y * size;
			auto q = dice(33,33,33);

			
			Color4f current = Color4f(
				uniform(0.0f, 1.0f),
				uniform(0.0f, 1.0f),
				uniform(0.0f, 1.0f)
				) * source[x, y].luminance601;

			img.drawSquarePattern(current, cast(SquarePattern) q, nx, ny, size);
			
		}
	}

	return img;
}

А теперь результат для «квадратика» со стороной 4 пикселя:

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

А вот еще одно изображение, обработанное схожим образом:

Гепард застрял в текстурах утонул в море шума.

Попробуем применить процедуру замены на только полученном изображении Лены (исходная картинка для преобразования на одну выше):

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

В детстве, я очень увлекался физикой и моими пособиями были книги вроде «Опыты без приборов» Ф.Рабизы. Так вот, в таких книгах описывался очень занятный и простой опыт, который я даже делал сам и показывал родителям:

  • берем лист бумаги (лучше лист белого картона) и вырезаем из него круг диаметров примерно сантиметров 5;
  • делим его на 7 одинаковых секторов и раскрашиваем каждый из них в свой цвет, взятый из спектральной палитры (цвета радуги: красный, оранжевый, желтый, зеленый, голубой, синий и фиолетовый);
  • после чего берем обычную спичку и заостряем один из ее концов;
  • проделываем в центре круга спичкой отверстие;
  • насаживаем круг на спичку, получая тем самым самодельный волчок
  • запускаем волчок

Если волчок достаточно быстро раскрутить, то цвета смешаются в один, который будет близок к белому, но скорее всего получится какой-то из оттенков серого. То, что мы наблюдаем в этом опыте называется аддитивным смешением в белый цвет, и я полагаю, что в эксперименте с квадаратиками мы видим тоже самое, но на несколько иной основе.

Дело в том, что человеческое зрение не идеально и имеет ряд дефектов, как физиологических, так и психологических, именно из-за них, а также благодаря распределению генератора случайных чисел, мы наблюдаем оттенки серого. У нас просто сам мозг производит слияние «квадратиков», а их обилие провоцирует некое «размывание» картинки, сглаживая «разноцветность» и отфильтровывая часть шума.

На этом все, а я надеюсь что наш скромный эксперимент с dlib вас заинтересовал и сподвиг на новые изыскания в области цифровых экспериментов.

 

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