Halide: Как обработать изображение в (перекрывающихся) блоках?
Я открываю Halide и добился определенного успеха с конвейером, выполняющим различные преобразования. Большинство из них основаны на примерах в источниках (цветовые преобразования, различные фильтры, Hist-EQ).
Мой следующий шаг должен обработать изображение в блоках. В более общем видечастично перекрывающиеся блоки.
Примеры
Входные данные:
[ 1, 2, 3, 4, 5, 6, 7, 8,
9, 10, 11, 12, 13, 14, 15, 16,
17, 18, 19, 20, 21, 22, 23, 24,
25, 26, 27, 28, 29, 30, 31, 32]
Неперекрывающиеся блоки:Размер: 2х4
[ 1, 2, 3, 4,
9, 10, 11, 12]
[ 5, 6, 7, 8,
13, 14, 15, 16]
[ 17, 18, 19, 20,
25, 26, 27, 28]
[ 21, 22, 23, 24,
29, 30, 31, 32]
Блоки перекрытия:Размер: 2x4 с перекрытием 50% (обе оси)
[ 1, 2, 3, 4,
9, 10, 11, 12]
[ 3, 4, 5, 6,
11, 12, 13, 14]
[ 5, 6, 7, 8,
13, 14, 15, 16]
-
[ 9, 10, 11, 12,
17, 18, 19, 20]
[11, 12, 13, 14,
19, 20, 21, 22]
...
Я подозреваю, что должен быть хороший способ выразить это, поскольку они также довольно распространены во многих алгоритмах (например, макроблоках).
Что я проверил
Я попытался собрать идеи из учебника и примеров приложений и обнаружил следующее, что, кажется, связано с тем, что я хочу реализовать:
- Учебное пособие Halide 6: реализация функций в произвольных областях
// We start by creating an image that represents that rectangle
Image<int> shifted(5, 7); // In the constructor we tell it the size
shifted.set_min(100, 50); // Then we tell it the top-left corner
- Проблема у меня есть: как обобщить это на несколько сдвинутых доменов без зацикливания?
- Галидный урок 9: Многоходовые функции, обновления определений и сокращения
- Вот
RDom
вводится, который выглядит красиво, чтобы создать блочный вид - Большинство примеров использования
RDom
кажется, что скользящие окна похожи на подходы, где нет прыжков
- Вот
цель
В общем, я спрашиваю, как реализовать блочное представление, которое затем может быть обработано другими шагами.
Было бы неплохо, если бы подход был достаточно общим, чтобы реализовать как перекрывающиеся, так и не перекрывающиеся
- Каким-то образом сначала генерировать верхние левые индексы?
В моем случае размер изображения известен во время компиляции, что упрощает это
- Но я все еще хотел бы иметь какую-то компактную форму, с которой приятно работать с точки зрения Halide (никаких вещей с ручным кодированием, таких как примеры с небольшими фильтрами)
- Используемый подход может зависеть от вывода на блок, который в моем случае является скаляром
Может быть, кто-то может дать мне несколько идей и / или несколько примеров (которые были бы очень полезны).
Прошу прощения за то, что не предоставил код, так как не думаю, что смог бы создать что-то полезное
Редактировать: Решение
После ответа dsharlet и небольшой крошечной отладки / обсуждения здесь работает следующий очень упрощенный самодостаточный код (при условии, что 1-канальный вход 64x128, как этот, я создал).
#include "Halide.h"
#include "Halide/tools/halide_image_io.h"
#include <iostream>
int main(int argc, char **argv) {
Halide::Buffer<uint8_t> input = Halide::Tools::load_image("TestImages/block_example.png");
// This is a simple example assuming an input of 64x128
std::cout << "dim 0: " << input.width() << std::endl;
std::cout << "dim 1: " << input.height() << std::endl;
// The "outer" (block) and "inner" (pixel) indices that describe a pixel in a tile.
Halide::Var xo, yo, xi, yi, x, y;
// The distance between the start of each tile in the input.
int tile_stride_x = 32;
int tile_stride_y = 64;
int tile_size_x = 32;
int tile_size_y = 64;
Halide::Func tiled_f;
tiled_f(xi, yi, xo, yo) = input(xo * tile_stride_x + xi, yo * tile_stride_y + yi);
Halide::RDom tile_dom(0, tile_size_x, 0, tile_size_y);
Halide::Func tile_means;
tile_means(xo, yo) = sum(Halide::cast<uint32_t>(tiled_f(tile_dom.x, tile_dom.y, xo, yo))) / (tile_size_x * tile_size_y);
Halide::Func output;
output(xo, yo) = Halide::cast<uint8_t>(tile_means(xo, yo));
Halide::Buffer<uint8_t> output_(2, 2);
output.realize(output_);
Halide::Tools::save_image(output_, "block_based_stuff.png");
}
1 ответ
Вот пример, который разбивает Func на блоки произвольного шага и размера:
Func f = ... // The thing being blocked
// The "outer" (block) and "inner" (pixel) indices that describe a pixel in a tile.
Var xo, yo, xi, yi;
// The distance between the start of each tile in the input.
int tile_stride_x, tile_stride_y;
Func tiled_f;
tiled_f(xi, yi, xo, yo) = f(xo * tile_stride_x + xi, yo * tile_stride_y + yi);
Func tiled_output;
tiled_output(xi, yi, xo, yo) = ... // Your tiled processing here
Чтобы вычислить некоторое сокращение (например, статистику) для каждого блока, вы можете сделать следующее:
RDom tile_dom(0, tile_size_x, 0, tile_size_y);
Func tile_means;
tile_means(xo, yo) = sum(tiled_output(tile_dom.x, tile_dom.y, xo, yo)) / (tile_size_x * tile_size_y);
Свести плитки обратно в результат немного сложно. Вероятно, это зависит от вашего метода объединения результатов в перекрывающихся областях. Если вы хотите добавить перекрывающиеся листы, возможно, самый простой способ - использовать RDom:
RDom tiles_dom(
0, tile_size_x,
0, tile_size_y,
min_tile_xo, extent_tile_xo,
min_tile_yo, extent_tile_yo);
Func output;
Expr output_x = tiles_dom[2] * tile_stride_x + tiles_dom[0];
Expr output_y = tiles_dom[3] * tile_stride_y + tiles_dom[1];
output(x, y) = 0;
output(output_x, output_y) += tiled_output(tiles_dom[0], tiles_dom[1], tiles_dom[2], tiles_dom[3]);
Обратите внимание, что в приведенных выше двух блоках кода tile_stride_x и tile_size_x являются независимыми параметрами, допускающими любой размер и перекрытие тайлов.
В обоих ваших примерах tile_size_x = 4
, а также tile_size_y = 2
, Чтобы получить неперекрывающиеся плитки, установите шаг плитки, равный размеру плитки. Чтобы получить 50% перекрывающихся плиток, установите tile_stride_x = 2
, а также tile_stride_y = 1
,
Полезный график для такого алгоритма:
// Compute tiles as needed by the output.
tiled_output.compute_at(output, tile_dom[2]);
// or
tiled_output.compute_at(tile_means, xo);
Есть и другие варианты, например, использование чистого func (без обновления /RDom), в котором используется оператор mod для определения внутренних и внешних индексов тайлов. Однако такой подход может быть сложным для эффективного планирования с перекрывающимися плитками (в зависимости от обработки, которую вы выполняете на каждой плитке). Я использую подход RDom, когда возникает эта проблема.
Обратите внимание, что с подходом RDom, вы должны предоставить границы индексов плитки, которые вы хотите вычислить (min_tile_xo
, extent_tile_xo
,...), что может быть сложно для перекрывающихся плиток.