Не так давно один из участников сообщества D провел любопытный тест, сравнив скорости загрузки изображения в формате PNG с различными библиотеками. В сравнении участвовали D-библиотеки (dlib и imageformats), а также C#.
Использовалось RGB-изображение размером 2048х2048, компилятор DMD 2.0.67 c флагами -release -inline -O. Задачей была загрузка из PNG, отражение по горизонтали и сохранение в PNG. Результат получился следующий:
Не так давно состоялось очередное крупное обновление коллекции библиотек dlib — вышла версия 0.5, наиболее значительным нововведением которой стала поддержка ручного управления памятью (РУП). Но — обо всем по порядку…
Новый модуль dlib.core.memory предоставляет средства для ручного выделения и высвобождения динамической памяти, независимые от сборщика мусора и основанные на malloc/free. Имеется поддержка структур, классов и массивов. При использовании классов рекомендуется использовать интерфейс ManuallyAllocatable и перегружать метод free, который ответственен за удаление объекта — в противном случае корректное удаление в некоторых случаях не гарантировано (например, при доступе через интерфейс или родительский класс).
Началась работа по переводу всей dlib на РУП. Так, загрузчики изрбражений (PNG, JPEG, TGA, BMP) в новой версии полностью независимы от сборщика мусора. Для этого активно используется паттерн абстрактной фабрики, ответственный за создание изображений в памяти. Кстати, в загрузчике PNG значительно улучшена поддержка индексированных изображений, для них добавлена поддержка альфа-канала.
Кроме того, на РУП переведены некоторые контейнеры из dlib.container — BST, ассоциативный массив. Реализован полностью ручной динамический массив (dlib.container.array).
Еще одна новинка — ООП для структур (dlib.core.oop). Это экспериментальный модуль, реализующий для структур прототипный стиль ООП с поддержкой множественного наследования и параметрического полиморфизма. Полностью заменить классы он, конечно, не может, но окажется весьма полезен, если нужно создавать объекты с наследованием в стеке. В будущем планируется переписать некоторые внутренние механизмы dlib с использованием этой легковесной объектной системы.
В пакете dlib.math появилась поддержка дуальных кватернионов. Это частный случай алгербы Клиффорда, обобщение кватернионов на поле дуальных чисел. Их можно использовать, например, для описания движения тел в кинематике — один дуальный кватернион охватывает и перенос, и вращение. Кстати, реализация обычных кватернионов через инкапсуляцию теперь совместима с векторами.
Изменения коснулись и пакета вычислительной геометрии. Усеченная пирамида (dlib.geometry.frustum) теперь задается с нормалями ограничивающих плоскостей, указывающими наружу пирамиды. Подвергся изменению API проверки пересечения Frustum с AABB. Исправлены ошибки в реализации AABB и плоскости.
В поисках удобного и не слишком требовательного к ресурсам RAW-проявщика под Linux, я остановился на RAWTherapee и его бэкенде dcraw. Последний в виде консольной утилиты поддерживает RAW-файлы моей Sony ILCE-3000 и показывает неожиданно хорошие результаты, по сравнению с JPEG’ом из камеры:
«Невооруженным глазом» видны лучшие показатели яркости и практически полное отсутствие шума. dcraw способен сохранять в несжатый формат PPM, что позволяет проводить дальнейшую обработку в GIMP или другом редакторе, а уже потом экспериментировать со сжатием.
Коллекция библиотек dlib обзавелась начальной поддержкой декодирования формата JPEG (dlib.image.io.jpeg). Пока поддерживается только baseline-часть стандарта, декодер читает только изображения с прореживанием 4:2:0 и не загружает метаданные EXIF (эти ограничения постепенно будут исправлены). Как и другие декодеры графических форматов в dlib, модуль работает на основе абстрактных потоков ввода/вывода (dlib.core.stream). Поддержка сохранения в JPEG в ближайшем будущем не планируется.
Эффект Chroma Key (“цветовой ключ”) заключается в сегментации изображения с тем, чтобы отделить объект переднего плана от фона. При этом цвет фона должен быть сплошным и равномерным – как правило, выбирают либо зеленый, либо синий, в зависимости от того, какой цвет отсутствует на объекте. Отделенное изображение затем накладывается на другой фон – например, на фотографию или рендер виртуальной сцены.
Существуют различные алгоритмы подобной сегментации, мы рассмотрим один из самых простых. Несмотря на простоту, он достаточно эффективен. Метод основан на нахождении евклидового расстояния в пространстве RGB – между цветом исходного пикселя и цветом фона. Если рассматривать цвета как точки в трехмерном пространстве, то пиксели, например, зеленого фона будут представлять собой облако точек, сосредоточенное вокруг “абсолютно зеленой” точки – (0, 1, 0). Чтобы получить значение альфа-канала (0 – пиксель принадлежит фону, 1 – не принадлежит), мы просто нормируем расстояние в заранее выбранном диапазоне.
import dlib.image.io.io;
auto img = load("input.png");
auto res = img.chromaKey(Color4f(0, 1, 0), 0.3f, 0.7f);
res.save("output.png");
Как нетрудно заметить, результат не идеален – если наложить изображение на фон, вокруг актера наблюдается зеленоватый контур. От него можно избавиться путем эрозии альфа-канала: изображение пропускается через дискретный оконный фильтр 3х3, который присваивает пикселю наименьшее значение в окне. В результате, непрозрачная область “теряет” несколько пикселей контура, и зеленый ореол практически исчезает.
SuperImage erodeAlpha(SuperImage img)
{
uint kw = 3, kh = 3;
auto res = img.dup;
foreach(y; img.col)
foreach(x; img.row)
{
auto c = img[x, y];
foreach(ky; 0..kh)
foreach(kx; 0..kw)
{
int iy = y + (ky - kh/2);
int ix = x + (kx - kw/2);
if (ix < 0) ix = 0;
if (ix >= img.width) ix = img.width - 1;
if (iy < 0) iy = 0;
if (iy >= img.height) iy = img.height - 1;
float a = img[ix, iy].a;
if (a < c.a)
c.a = a;
}
res[x, y] = c;
}
return res;
}
Обновилась коллекция винтажных фильтров для GIMP: добавлены три новых фильтра (Amaro, Brannan, Toaster), а также поддержка виньетирования. Все фильтры теперь объединены в один: при запуске скрипта выводится диалоговое окно с выбором фильтра и другими опциями.
Состоялся релиз коллекции библиотек dlib 0.3. Нововведения этой версии:
Добавлены абстрактные потоки ввода/вывода (dlib.core.stream), независимые от Phobos, а также интерфейс файловой системы (dlib.filesystem) с готовыми реализациями для POSIX и Windows — этот интерфейс можно использовать, например, для построения виртуальных ФС.
Добавлена начальная поддержка HDRI в dlib.image (реализация формата изображений с плавающей запятой в dlib.image.hdri). Кроме того, обеспечена поддержка распараллеливания обработки изображений (dlib.image.parallel), добавлена поддержка чтения форматов TGA и BMP. Чтение/запись графических форматов теперь основаны на потоках, поэтому имеется возможность загружать изображения, например, напрямую из архивов.
Элементы матриц (dlib.math.matrix) теперь располагаются по столбцам, а не по строкам. Это серьезно нарушило обратную совместимость, но если вы не используете внутренние данные матриц и пользуетесь только внешним API, то это изменение не должно повлечь никаких проблем.
API dlib.image позволяет создавать фильтры, которые легко распараллеливать на несколько процессоров. Изображение условно разбивается на несколько блоков заданного размера, которые затем обрабатываются фильтром через std.parallelism.
В dlib.image появилась возможность отслеживать прогресс во время работы фильтров. Для этого используется многопоточность – необходимо создать класс-враппер, наследующий от FilteringThread. Прогресс (от 0 до 1) считывается из свойства progress для SuperImage. В данном примере показано, как использовать эту функциональность для вывода прогресса свертки в консоль: