Создание гистограмм изображений

В ходе увлекательнейшей работы над одним из проектов, внутри нашей коллаборации началось повальное увлечение темой Digital Image Processing (DIP) , ну а если сказать по-русски название тематики, то дословно это будет звучать, как  «цифровая обработка изображений» (ЦОИ).

Действительно, тема обработки изображений действительно сильно заманчивая и интересная, а также включает в себя большой объем тем для работ, а поскольку, работы непочатый край, то наверняка, для уменьшения ее величины существуют инструменты (коих действительно тьма тьмущая)…

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

Как видите, сама по себе гистограмма довольно проста и помогает быстро проинтерпретировать некоторые характеристики изображения, что в общем-то и делает ее ценным инструментом при разработке алгоритмов DIP или просто при оценке цветового качества изображения.

Для построения гистограммы нам сначала нужно загрузить любое доступное изображение (в чем нам поможет мощная и компактная библиотека dlib), после чего пройтись по каждому пикселю и рассчитать его яркость, полагая, что она лежит в интервале от 0 до 255 (что не совсем так, в dlib свойство luminance, т.е. яркость дает интервал от 0 до 1, если не ошибаюсь, и именно поэтому нам потребуется нечто вроде операции перенормировки). После расчета яркостей (а еще лучше, не после, а во время) нужно посчитать количество каждого вычисленного значения яркости, для чего используется обычный массив-счетчик, вначале процедуры заполненный нулями.

Вот так выглядит мой вариант построения гистограммы, учитывающий возможность выбора пользователем цветового канала под построение гистограммы (ALL — суммарная яркость, RED — яркость по красному каналу, GREEN — яркость по зеленому каналу, BLUE — яркость по синему каналу, ALPHA — «яркость» по каналу прозрачности):

// канал под построение гистограммы
enum ColorChannel 
{
	ALL, 
	RED, 
	GREEN, 
	BLUE, 
	ALPHA
};

auto createHistogram(SuperImage source, ColorChannel colorChannel, Color4f color = Color4f(0.0f, 0.0f, 1.0f))
{
	SuperImage histogram = image(256, 256);
	
	histogram.fillColor(White);
	
	float[256] quantity = 0.0f;
	size_t index = 0;
	
	for (int i = 0; i < source.width; i++)
	{
		for (int j = 0; j < source.height; j++)
		{
			final switch (colorChannel) with (ColorChannel)
			{
				case ALL:
					index = cast(size_t) (255 * source[i,j].luminance);
					break;
				case RED:
					index = cast(size_t) (255 * source[i,j].r);
					break;
				case GREEN:
					index = cast(size_t) (255 * source[i,j].g);
					break;
				case BLUE:
					index = cast(size_t) (255 * source[i,j].b);
					break;
				case ALPHA:
					index = cast(size_t) (255 * source[i,j].a);
					break;
			}
			quantity[index]++;
		}
	}
	
	quantity[] /= 255.0f;
	
	foreach(number, colorPeak; quantity)
	{
		histogram.drawLine(color, cast(int) number, 255, cast(int) number, 255 - cast(int) colorPeak);
	}
	
	return histogram;
}

Самое интересное, что для реализации выбора канала используются перечисления или енумераторы (лично я, иногда их называю именно так, по кальке с их английского названия enums, что является сокращением от enumerations или enumerators), которые очень удобно использовать для реализации серии именованных констант. Более того, используемый синтаксис для перебора, а именно необычная форма final switch, не является стандартной — вместо этого был использован идиоматический синтаксис, а конструкция final switch обеспечивает гарантию того, что весь диапазон входных значений будет обработан (в данном случае такая надежность хоть и является избыточной, но необходимой).

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

Несмотря на эти два отличия, гистограммы, создаваемые этой нехитрой процедурой представляют собой очень ценный и полезный инструмент (в частности, с их помощью легко можно создавать черно-белый изображения, вычленяя нужные для такого преобразования пороги) и выглядят они вот так:

  • гистограмма для канала красного цвета (вариант ColorChannel.RED)

  • гистограмма для канала зеленого цвета (вариант ColorChannel.GREEN)

  • гистограмма для канала синего цвета (вариант ColorChannel.BLUE)

  • гистограмма для альфа-канала (вариант ColorChannel.ALPHA)

  • гистограмма по яркости пикселов (вариант ColorChannel.ALL)

  • оригинал изображения:
Lena
Lenna

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

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