Следующий код адаптирован из учебников Halide.
Func blurX(Func continuation)
{ Var x("x"), y("y"), c("c");
Func input_16("input_16");
input_16(x, y, c) = cast<uint16_t>(continuation(x, y, c));
Func blur_x("blur_x");
blur_x(x, y, c) = (input_16(x-1, y, c) +
2 * input_16(x, y, c) +
input_16(x+1, y, c)) / 4;
Func output("outputBlurX");
output(x, y, c) = cast<uint8_t>(blur_x(x, y, c));
return output;
}
int main()
{ Var x("x"), y("y"), c("c");
Image<uint8_t> input = load_image("input.png");
Func clamped("clamped");
clamped = BoundaryConditions::repeat_edge(input);
Func img1Fun("img1Fun");
Func img2Fun = blurX(clamped);
Func outputFun("outputFun");
/* carry on */
}
У меня три вопроса:
Кастинг Является ли актерский состав cast<uint16_t>(clamped(x, y, c))
приведение 8-битных значений R G и B в каждой позиции (x, y) к 16-битному целому числу, то есть то, что возвращает приведение, является изображением RGB, которое можно проиндексировать, например, img1Fun (x, y, 0) для доступа к его значению R? Или это приведение каждого пикселя RGB в изображении к его значению яркости между [0..1] для пикселя RGB в каждой (x, y) позиции, т.е. r*0.3 + g*0.59 + b*0.11
?
Перегрузка RGB размытия являются арифметическими операциями на (x,y,c)
перегружен по всем показателям? Например.
(input_16(x-1, y, c) + 2 * input_16(x, y, c) + input_16(x+1, y, c)) / 4;
Это перегрузка:
(input_16(x-1, y, 0) + 2 * input_16(x, y, 0) + input_16(x+1, y, 0)) / 4;
(input_16(x-1, y, 1) + 2 * input_16(x, y, 1) + input_16(x+1, y, 1)) / 4;
(input_16(x-1, y, 2) + 2 * input_16(x, y, 2) + input_16(x+1, y, 2)) / 4;
blurX
? На основе brighten.cpp
пример из CVPR’15 Вот, Я мог бы использовать blur_x.vectorize(x, 4).parallel(y);
векторизовать строку по оси X, распараллеливая потоки в направлении Y .. как это?Func blurX(Func continuation)
{ Var x("x"), y("y"), c("c");
Func input_16("input_16");
input_16(x, y, c) = cast<uint16_t>(continuation(x, y, c));
Func blur_x("blur_x");
blur_x(x, y, c) = (input_16(x-1, y, c) +
2 * input_16(x, y, c) +
input_16(x+1, y, c)) / 4;
blur_x.vectorize(x, 4).parallel(y);
Func output("outputBlurX");
output(x, y, c) = cast<uint8_t>(blur_x(x, y, c));
return output;
}
Вопрос 1: Func определяет абстрактное отображение из набора координат в Expr, который является математической функцией этих координат. В общем, операторы просты и не имеют какого-либо специфического поведения при отображении, такого как преобразование цветового кортежа в скаляр светимости. (Чтобы выполнить такое преобразование, необходимо написать код, поскольку коэффициенты зависят от используемого цветового пространства.)
Отсюда и утверждение:
img1Fun(x, y, c) = cast<uint16_t>(clamped(x, y, c));
определяет input_16
с тем же количеством каналов, что и clamped
но 16-битный тип вместо 8-битного типа. Арифметика в Halide остается с той же самой битовой шириной, что и его самый большой операнд, и в отличие от C неявно преобразуется в стандартный размер int. Это связано с тем, что при векторизации важно поддерживать явный контроль над размером полосы движения. В этом случае использование 16-разрядного промежуточного типа требуется, чтобы избежать переполнения при суммировании 8-разрядных значений.
После деления происходит соответствующее приведение к 8-битному типу. Размытый результат гарантированно соответствует 8-битному типу, так как вычисление нормализовано (среднее значение данного цветового канала, полученное по всему изображению, не должно изменяться). Приведенный выше код выполняет как восходящую, так и нисходящую передачу в двух местах, что является избыточным. Скорее всего, это не повлияет на производительность, так как компилятор должен быть достаточно умен, чтобы распознавать внешний набор приведений, но это не приводит к особенно читабельному коду.
Вопрос 2: фактически тот же ответ. Я бы не использовал здесь термин «перегрузка», но это определение применимо ко всем координатам. Var «c» упоминается с левой и правой стороны и имеет одинаковое значение на каждом. (У нас есть сокращенное обозначение подчеркивания (‘_’), означающее «ноль или более координат», позволяющее проходить через список аргументов, но в остальном в этих определениях нет ничего особенного.)
Вопрос 3: Самый простой способ запланировать это для векторизации и распараллеливания — это использовать планарную компоновку (все значения R хранятся рядом друг с другом, затем все G и т. Д.) И векторизоваться до соответствующего размера для 16-битной математики. , (Например, «vectorize (x, natural_vector_size ())» id работает внутри генератора.) Параллелизм потока по строкам — «.parallel (y)». В зависимости от длины строк, вы можете захотеть добавить разделенные параметры в директиву Parallel.
Этот график также будет работать с полуплоскостным представлением (строка R, строка G и строка B).
Существуют и другие подходы, которые могут иметь больше смысла, если blurX используется в контексте реального конвейера или требуется непланарная схема хранения.
Других решений пока нет …