Есть ли шанс ускорить билинейную интерполяцию?

Сначала я хочу представить вам некоторый контекст.

У меня есть два вида изображений, которые мне нужно объединить. Первое изображение — это фоновое изображение с форматом 8BppGrey и разрешением 320х240. Второе изображение — переднее изображение в формате 32BppRGBA и разрешением 64х48.

Обновить
Github репо с MVP находится в нижней части вопроса.

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

Мне нужно сделать это как можно быстрее, поэтому моя идея заключалась в том, чтобы объединить процесс изменения размера и слияния / наложения.

Для этого я использовал функцию изменения размера из репозиторий writeablebitmapex и добавил слияние / смешивание.

Все работает как положено, но я хочу уменьшить время выполнения.

Это текущие сроки отладки:

// CPU: Intel(R) Core(TM) i7-4810MQ CPU @ 2.80GHz

MediaServer: Execution time in c++ 5 ms
MediaServer: Resizing took 4 ms.
MediaServer: Execution time in c++ 5 ms
MediaServer: Resizing took 5 ms.
MediaServer: Execution time in c++ 4 ms
MediaServer: Resizing took 4 ms.
MediaServer: Execution time in c++ 3 ms
MediaServer: Resizing took 3 ms.
MediaServer: Execution time in c++ 4 ms
MediaServer: Resizing took 4 ms.
MediaServer: Execution time in c++ 5 ms
MediaServer: Resizing took 4 ms.
MediaServer: Execution time in c++ 6 ms
MediaServer: Resizing took 6 ms.
MediaServer: Execution time in c++ 3 ms
MediaServer: Resizing took 3 ms.

Есть ли у меня шанс повысить производительность и уменьшить время выполнения процесса изменения размера / слияния / наложения?

Есть ли некоторые части, которые я могу распараллелить?

Могу ли я использовать некоторые функции процессора?

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

Я хотел бы достичь 1 или 2 мс для всего процесса. Это вообще возможно?

Вот модифицированная визуальная функция C ++, которую я использую.

  • pd — это буфер для записываемого растрового изображения, которое я использую для отображения
    результат в wpf. Я использую формат по умолчанию 32BppRGBA.
  • пикселей это массив int [] изображения 64×48 32BppRGBA
  • widthSource и heightSource — размер изображения в пикселях.
  • ширина и высота — целевой размер выходного изображения
  • baseImage — массив int [] изображения 320×240 8BppGray

Код VC ++:

unsigned int Resize(int* pd, int* pixels, int widthSource, int heightSource, int width, int height, byte* baseImage)
{
unsigned int start = clock();

float xs = (float)widthSource / width;
float ys = (float)heightSource / height;

float fracx, fracy, ifracx, ifracy, sx, sy, l0, l1, rf, gf, bf;
int c, x0, x1, y0, y1;
byte c1a, c1r, c1g, c1b, c2a, c2r, c2g, c2b, c3a, c3r, c3g, c3b, c4a, c4r, c4g, c4b;
byte a, r, g, b;

// Bilinear
int srcIdx = 0;

for (int y = 0; y < height; y++)
{
for (int x = 0; x < width; x++)
{
sx = x * xs;
sy = y * ys;
x0 = (int)sx;
y0 = (int)sy;

// Calculate coordinates of the 4 interpolation points
fracx = sx - x0;
fracy = sy - y0;
ifracx = 1.0f - fracx;
ifracy = 1.0f - fracy;
x1 = x0 + 1;
if (x1 >= widthSource)
{
x1 = x0;
}
y1 = y0 + 1;
if (y1 >= heightSource)
{
y1 = y0;
}

// Read source color
c = pixels[y0 * widthSource + x0];
c1a = (byte)(c >> 24);
c1r = (byte)(c >> 16);
c1g = (byte)(c >> 8);
c1b = (byte)(c);

c = pixels[y0 * widthSource + x1];
c2a = (byte)(c >> 24);
c2r = (byte)(c >> 16);
c2g = (byte)(c >> 8);
c2b = (byte)(c);

c = pixels[y1 * widthSource + x0];
c3a = (byte)(c >> 24);
c3r = (byte)(c >> 16);
c3g = (byte)(c >> 8);
c3b = (byte)(c);

c = pixels[y1 * widthSource + x1];
c4a = (byte)(c >> 24);
c4r = (byte)(c >> 16);
c4g = (byte)(c >> 8);
c4b = (byte)(c);

// Calculate colors
// Alpha
l0 = ifracx * c1a + fracx * c2a;
l1 = ifracx * c3a + fracx * c4a;
a = (byte)(ifracy * l0 + fracy * l1);

// Write destination
if (a > 0)
{
// Red
l0 = ifracx * c1r + fracx * c2r;
l1 = ifracx * c3r + fracx * c4r;
rf = ifracy * l0 + fracy * l1;

// Green
l0 = ifracx * c1g + fracx * c2g;
l1 = ifracx * c3g + fracx * c4g;
gf = ifracy * l0 + fracy * l1;

// Blue
l0 = ifracx * c1b + fracx * c2b;
l1 = ifracx * c3b + fracx * c4b;
bf = ifracy * l0 + fracy * l1;

// Cast to byte
float alpha = a / 255.0f;
r = (byte)((rf * alpha) + (baseImage[srcIdx] * (1.0f - alpha)));
g = (byte)((gf * alpha) + (baseImage[srcIdx] * (1.0f - alpha)));
b = (byte)((bf * alpha) + (baseImage[srcIdx] * (1.0f - alpha)));

pd[srcIdx++] = (255 << 24) | (r << 16) | (g << 8) | b;
}
else
{
// Alpha, Red, Green, Blue
pd[srcIdx++] = (255 << 24) | (baseImage[srcIdx] << 16) | (baseImage[srcIdx] << 8) | baseImage[srcIdx];
}
}
}

unsigned int end = clock() - start;
return end;
}

Github репо

3

Решение

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

Что-то вроде этого:

for (int y = 0; y < height; y++)
{
for (int x = 0; x < width; x++)
{
int sx1 = x * widthSource ;
int x0 = sx1 / width;
int fracx = (sx1 % width) ; // range 0..width - 1

который превращается в нечто вроде

        l0 = (fracx * c2a + (width - fracx) * c1a) / width ;

И так далее. Немного сложно, но выполнимо

3

Другие решения

Спасибо за помощь, но проблема заключалась в управляемом проекте c ++. Теперь я перенес эту функцию в свою родную библиотеку c ++ и использовал управляемую часть c ++ только в качестве оболочки для приложения c #.

После оптимизации компилятора функция завершается за 1 мс.

Редактировать:

Я пока обозначу свой ответ как решение, потому что оптимизация от @marom приводит к повреждению изображения.

0

Обычный способ ускорить операцию изменения размера с билинейной интерполяцией заключается в следующем:

  1. Используйте тот факт, что x0 а также fracx не зависят от ряда и y0а также fracy не зависят от столбца. Даже если вы не вытащили вычисление y0 а также fracy вне х-цикла об этом должна позаботиться оптимизация компилятора. Однако для x0 а также fracxнеобходимо предварительно вычислить значения для всех столбцов и сохранить их в массиве. Сложность для вычислений x0 а также fracx становится O (ширина) по сравнению с O (ширина * высота) без предварительного вычисления.

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

Для лучшей читабельности я не реализовал предварительное вычисление x0 а также fracx в следующем коде. Предварительные вычисления в любом случае просты.

Обратите внимание, что FACTOR = 2048 это максимум, который вы можете сделать с 32-битными знаковыми целыми числами здесь (2048 * 2048 * 255 — это нормально). Для более высокой точности следует переключиться на int64_t и затем увеличьте ФАКТОР и СДВИГ, соответственно.

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

Если кому-то интересно, что + (FACTOR * FACTOR / 2) для, это для округления в сочетании с последующим делением.

Наконец, обратите внимание, что (FACTOR * FACTOR / 2) а также 2 * SHIFT оцениваются во время компиляции.

#define FACTOR      2048
#define SHIFT       11

const int xs = (int) ((double) FACTOR * widthSource / width + 0.5);
const int ys = (int) ((double) FACTOR * heightSource / height + 0.5);

for (int y = 0; y < height; y++)
{
const int sy = y * ys;
const int y0 = sy >> SHIFT;
const int fracy = sy - (y0 << SHIFT);

for (int x = 0; x < width; x++)
{
const int sx = x * xs;
const int x0 = sx >> SHIFT;
const int fracx = sx - (x0 << SHIFT);

if (x0 >= widthSource - 1 || y0 >= heightSource - 1)
{
// insert special handling here
continue;
}

const int offset = y0 * widthSource + x0;

target[y * width + x] = (unsigned char)
((source[offset] * (FACTOR - fracx) * (FACTOR - fracy) +
source[offset + 1] * fracx * (FACTOR - fracy) +
source[offset + widthSource] * (FACTOR - fracx) * fracy +
source[offset + widthSource + 1] * fracx * fracy +
(FACTOR * FACTOR / 2)) >> (2 * SHIFT));
}
}

Для пояснения, чтобы соответствовать переменным, используемым OP, например, в случае альфа-канала это:

a = (unsigned char)
((c1a * (FACTOR - fracx) * (FACTOR - fracy) +
c2a * fracx * (FACTOR - fracy) +
c3a * (FACTOR - fracx) * fracy +
c4a * fracx * fracy +
(FACTOR * FACTOR / 2)) >> (2 * SHIFT));
0
По вопросам рекламы [email protected]