Поддержка JPEG в dlib

Коллекция библиотек dlib обзавелась начальной поддержкой декодирования формата JPEG (dlib.image.io.jpeg). Пока поддерживается только baseline-часть стандарта, декодер читает только изображения с прореживанием 4:2:0 и не загружает метаданные EXIF (эти ограничения постепенно будут исправлены). Как и другие декодеры графических форматов в dlib, модуль работает на основе абстрактных потоков ввода/вывода (dlib.core.stream).
Поддержка сохранения в JPEG в ближайшем будущем не планируется.

Chroma Key с использованием dlib

Эффект Chroma Key (“цветовой ключ”) заключается в сегментации изображения с тем, чтобы отделить объект переднего плана от фона. При этом цвет фона должен быть сплошным и равномерным – как правило, выбирают либо зеленый, либо синий, в зависимости от того, какой цвет отсутствует на объекте. Отделенное изображение затем накладывается на другой фон – например, на фотографию или рендер виртуальной сцены.

Существуют различные алгоритмы подобной сегментации, мы рассмотрим один из самых простых. Несмотря на простоту, он достаточно эффективен. Метод основан на нахождении евклидового расстояния в пространстве RGB – между цветом исходного пикселя и цветом фона. Если рассматривать цвета как точки в трехмерном пространстве, то пиксели, например, зеленого фона будут представлять собой облако точек, сосредоточенное вокруг “абсолютно зеленой” точки – (0, 1, 0). Чтобы получить значение альфа-канала (0 – пиксель принадлежит фону, 1 – не принадлежит), мы просто нормируем расстояние в заранее выбранном диапазоне.

import dlib.math.vector;
import dlib.math.utils;
import dlib.image.image;
import dlib.image.color;

SuperImage chromaKey(
SuperImage img,
Color4f keyColor,
float minDist,
float maxDist)
{
auto res = new ImageRGBA8(img.width, img.height);

foreach(y; img.col)
foreach(x; img.row)
{
Color4f col = img[x, y];

Color4f delta = col - keyColor;
float distSqr = dot(delta, delta);
col.a = clamp(
(distSqr - minDist) / (maxDist - minDist),
0.0f, 1.0f);
res[x, y] = col;
}

return res;
}

Вот пример использования этой функции:

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;
}

dlib 0.3

Состоялся релиз коллекции библиотек 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, то это изменение не должно повлечь никаких проблем.

Более полный чейнджлог, а также исходники релиза вы можете найти на GitHub:
https://github.com/gecko0307/dlib/releases/tag/v0.3.0

Распараллеливание обработки изображений

API dlib.image позволяет создавать фильтры, которые легко распараллеливать на несколько процессоров. Изображение условно разбивается на несколько блоков заданного размера, которые затем обрабатываются фильтром через std.parallelism.

import std.parallelism;
import dlib.functional.range;
import dlib.image.image;

struct Block
{
uint x1, y1;
uint x2, y2;
}

alias Range!uint PixRange;

void parallelFilter(
SuperImage img,
void delegate(PixRange blockRow, PixRange blockCol) ffunc,
uint bw = 100,
uint bh = 100)
{
if (bw > img.width)
bw = img.width;
if (bh > img.height)
bh = img.height;

uint numBlocksX = img.width / bw + ((img.width % bw) > 0);
uint numBlocksY = img.height / bh + ((img.height % bh) > 0);

Block[] blocks = new Block[numBlocksX * numBlocksY];
foreach(x; 0..numBlocksX)
foreach(y; 0..numBlocksY)
{
uint bx = x * bw;
uint by = y * bh;

uint bw1 = bw;
uint bh1 = bh;

if ((img.width - bx) < bw)
bw1 = img.width - bx;
if ((img.height - by) < bh)
bh1 = img.height - by;

blocks[y * numBlocksX + x] = Block(bx, by, bx + bw1, by + bh1);
}

foreach(i, ref b; taskPool.parallel(blocks))
{
ffunc(range!uint(b.x1, b.x2),
range!uint(b.y1, b.y2));
}
}


Пример (закрашивание сплошным цветом):

SuperImage filterTestMultithreaded(SuperImage img)
{
auto res = img.dup;

img.parallelFilter((PixRange row, PixRange col)
{
foreach(x; row)
foreach(y; col)
{
res[x, y] = hsv(180.0f, 1.0f, 0.5f);
}
});

return res;
}


Для сравнения - однопоточный вариант:

SuperImage filterTestSinglethreaded(SuperImage img)
{
auto res = img.dup;

foreach(x; img.row)
foreach(y; img.col)
{
res[x, y] = hsv(180.0f, 1.0f, 0.5f);
}

return res;
}


На двухъядерном Intel Dual Core T2390 (1.86 ГГц) многопоточный вариант показывает прирост производительности на 70%.

Итоги 2013 года

Завершился 2013 год, в течение которого я всеми силами старался выкроить свободное время для работы над Atrium и сопутствующими инструментами. Подведу итоги: что было сделано, какие в прошедшем году произошли важные релизы и достижения.

  • Сециально для Atrium был разработан игровой физический движок dmech с поддержкой нескольких видов геометрических тел и сочленений. Он еще далек от совершенства, но уже пригоден для использования в простых задачах игровой динамики;
  • Было выпущено 6 номеров электронно-познавательного журнала “FPS” (№№ 22, 23, 24, 25, 26, 27). Кстати, в феврале 2014 года журналу исполняется 6 лет!
  • Состоялось серьезное обновление dlib: в частности, пакетов dlib.math и dlib.image. Библиотека обогатилась новой функциональностью, переехала на GitHub и обзавелась поддержкой DUB;
  • Вышла Cook2, экспериментальная ветка программы сборки проектов Cook со значительными изменениями и улучшениями;
  • Вышла альфа-версия Arrow – тетрисоподобной игры-головоломки с оригинальной механикой.

Огромное спасибо всем, кто так или иначе помогал мне в течение года:

  • Андрею Пенечко (MrSmith33) – за багрепорты и багфиксы в dlib;
  • Наталии Чумаковой (d_o_r_i_a_n_a) – за помощь по матчасти и тестирование всех программ на Windows 7, а также за сотрудничество по журналу;
  • Александру Санникову (Suslik) – за советы и помощь по физике.