В этой статье я снова расскажу немного про цифровую обработку изображений с помощью языка программирования D на примере одной простой операции с изображением, которая называется соляризацией. Данная операция является частным случаем более общей операции под названием препарирование изображения и которая оперирует над цветовой плоскостью, изменяя динамический диапазон изображения и давая интересный графический эффект.
Если вам любопытно, что получилось, то добро пожаловать в эту статью.
Над соляризацией я задумался, когда разрабатывал модуль processing для библиотеки цифровой обработки изображений rip, поэтому не удивляйтесь интересному и не совсем соответствующему для dlib коду. Дело в том, что я решил рассказать здесь про эффект соляризации для иллюстрации интересного приема обработки фотографий и для описания своего подхода к разработке методов препарирования изображений.
Прежде всего, я должен рассказать вам, что соляризация — это очень старый графический трюк, связанный с еще нецифровыми фотографиями, которые можно было легко передержать или переэкспонировать. Именно эта особенность старых методов получения фотоизображения приводила к тому, что иногда можно было наблюдать необычное явление: при слишком большой экспозиции происходит снижение оптической плотности изображения, в результате чего происходит эффект «обращения» — светлые участки могут стать частично темными.
Вообще сам эффект соляризации (иногда и псевдосоляризации, называемой также эффект Сабатье) описывается весьма простой формулой:
y' = y * k * (y_max - y)
где y’ — новое значение цвета, y — текущее значение цвета, k — некий коэффициент описывается весьма простой формулой: (назовем его коэффициентом соляризации), y_max — значение цвета с максимальной для изображения яркостью.
Для реализации соляризации мы воспользуемся уже знакомой нам библиотекой dlib, которая обеспечит нас необходимыми типами данных и методами работы. Создав проект dub и включив в него dlib, мы прежде всего сделаем ряд тонких «надстроек», которые помогут использовать любой арифметический тип в качестве коэффициента. Для этой цели введем шаблон allArithmetic, который позволит на этапе компиляции произвести проверку — числовой или нечисловый тип будет использован в качестве аргумента функции соляризации:
private { import std.algorithm; import std.range; import std.meta : allSatisfy; import std.traits : isIntegral, isFloatingPoint, Unqual; } // является ли тип арифметическим (числовым) ? template allArithmetic(T...) if (T.length >= 1) { // числовой тип (т.е. является либо целым типом, либо с плавающей точкой)? template isNumberType(T) { enum bool isNumberType = isIntegral!(Unqual!T) || isFloatingPoint!(Unqual!T); } // удовлетворяет ли всем условиям ? enum bool allArithmetic = allSatisfy!(isNumberType, T); }
Далее, опишем саму функцию соляризации, с учетом того, что в качестве коэффициента соляризационной процедуры может быть использован любой числовой тип:
// Соляризация изображения auto solarizateImage(T)(SuperImage superImage, T coefficient) if (allArithmetic!T) { // буфер под новую картинку SuperImage newImage = image(superImage.width, superImage.height); // приводим тип к float (будем предполагать, что этот тип универсален) float modifiedCoefficient = cast(float) coefficient; // псевдоним для функции сравнения яркостей отдельных пикселей alias compareLuminance = (a, b) => a.luminance > b.luminance; // буфер под пиксели Color4f[] colors; // накапливаем пиксели для будущих вычислений foreach (x; 0..superImage.width) { foreach (y; 0..superImage.height) { colors ~= superImage[x, y]; } } // цвет с максимальной по изображению яркостью auto maximalColor = colors.sort!compareLuminance.front; // сама соляризация изображения foreach (x; 0..superImage.width) { foreach (y; 0..superImage.height) { newImage[x,y] = superImage[x,y] * modifiedCoefficient * (maximalColor - superImage[x,y]); } } return newImage; }
Приведенный здесь код сначала создаем новое изображение с размерами точно такими же, как и у картинки под преобразование (в общем, наш стандартный прием в делах обработки изображений), затем приводим полученный коэффициент к типу float, как наиболее удобный для расчетов (спорный момент, конечно, но в целом это почти всегда удобно). Далее, создаем псевдоним под простую лямбда-функцию для сравнения пикселей исходя из их яркости и формируем массив из всех пикселей изображения (надеюсь, в dlib когда-нибудь появится диапазон для прохода по всем пикселям изображения без необходимости формирования массива), который далее сортируем по убыванию с помощью уже созданной функции compareLuminance и с помощью метода front первый элемент, который отвечает пикселю с максимальным значением яркости по изображению. Дальнейший обход по изображению с помощью двух циклов — это стандартная версия процедуры трансформации каждого отдельно взятого пикселя, для которой в нашем случае используется формула соляризации (даже без каких-либо изменений, кроме как переменных). В совокупности, вышеописанный код выдает новое изображение, в котором соляризация уже проведена.
Для испытания эффекта необходимо просто загрузить картинку с помощью стандартной для dlib процедуры load и непосредственным применением функции solarizateImage c соответствующим коэффициентом:
void main() { auto img = load("Lenna_full.png"); img.solarizateImage(0.74).savePNG("Lenna_0.74.png"); img.solarizateImage(0.5).savePNG("Lenna_0.5.png"); img.solarizateImage(0.1).savePNG("Lenna_0.1.png"); img.solarizateImage(1.7).savePNG("Lenna_1.7.png"); }
Вот примеры изображений, которые генерируется испытательным кодом (не включено одно изображение для коэффициента 0.1, т.к. получается просто черный фон вместо изображения):
А вот оригинал:
Таким образом, мы расширили копилку интересных эффектов, реализованных с помощью D/dlib, а также рассмотрели интересную схему разработки: сначала мы разрабатываем инструменты проверки идеи, причем делаем это в самом общем виде, а затем создаем само программное описание интересующей нас проблемы.
Надеюсь, данная статья задаст новую планку в программировании цифровой обработки изображений, а также подтолкнет вас к созданию своих любопытных цифровых эффектов.