В ходе увлекательнейшей работы над одним из проектов, внутри нашей коллаборации началось повальное увлечение темой 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)
- оригинал изображения:

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