Halide:投射 RGB 图像和平行化模糊
Halide: casting RGB images and parallelising blur
以下代码改编自 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 */
}
我有三个问题:
Casting 是转换 cast<uint16_t>(clamped(x, y, c))
将每个 (x,y) 位置的 8 位 R G 和 B 值转换为 16 位整数,即什么cast returns 是一个 RGB 图像,可以对其进行索引,例如 img1Fun(x, y, 0) 以访问其 R 值?或者这是将图像中的每个 RGB 像素投射到每个 (x,y) 位置的 RGB 像素的 [0..1] 之间的亮度值,即 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
?基于来自 CVPR'15 here 的 brighten.cpp
示例,我可以使用 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 的抽象映射,它是这些坐标的数学函数。一般来说,运算符是直接的,没有任何成像特定行为,如将颜色元组转换为光度标量。 (要完成这样的转换,必须编写代码,因为系数取决于所使用的颜色 space。)
因此声明:
img1Fun(x, y, c) = cast<uint16_t>(clamped(x, y, c));
将 input_16
定义为与 clamped
具有相同数量的通道,但是是 16 位类型而不是 8 位类型。 Halide 中的算术与其最大操作数保持相同的位宽,并且与 C 不同,它不会隐式转换为标准 int 大小。这是因为对于矢量化,保持对通道大小的明确控制很重要。在这种情况下,需要使用 16 位中间类型以避免在对 8 位值求和时溢出。
除法后有相应的转换回8位类型。模糊结果保证适合 8 位类型,因为计算是标准化的(给定颜色通道在整个图像上的平均值不应改变)。上面的代码在两个地方同时进行了向上转型和向下转型,这是多余的。它可能不会对性能产生任何影响,因为编译器应该足够聪明,可以识别出外部转换集是 nop,但它不会产生特别可读的代码。
问题 2:实际上是相同的答案。我不会在这里使用术语 "overloading",但该定义适用于所有坐标。 Var "c" 在左手边和右手边提到,并且每个都有相同的值。 (我们有一个 shorthand 下划线 ('_') 表示法表示 "zero or more coordinates" 允许通过参数列表,但除此之外这些定义没有什么特别之处。)
问题 3:为此进行矢量化和并行化安排的最简单方法是使用平面布局(所有 R 值彼此相邻存储,然后是所有 G 等)并将矢量化为适当的大小对于 16 位数学。 (例如,"vectorize(x, natural_vector_size())" id 在生成器中工作。)沿行的线程并行度——“.parallel(y)”。根据行的长度,您可能需要向并行指令添加拆分参数。
此时间表也适用于半平面表示(一行 R、一行 G 和一行 B)。
当在实际管道的上下文中使用 blurX 或者需要非平面存储布局时,还有其他方法可能更有意义。
以下代码改编自 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 */
}
我有三个问题:
Casting 是转换
cast<uint16_t>(clamped(x, y, c))
将每个 (x,y) 位置的 8 位 R G 和 B 值转换为 16 位整数,即什么cast returns 是一个 RGB 图像,可以对其进行索引,例如 img1Fun(x, y, 0) 以访问其 R 值?或者这是将图像中的每个 RGB 像素投射到每个 (x,y) 位置的 RGB 像素的 [0..1] 之间的亮度值,即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
?基于来自 CVPR'15 here 的brighten.cpp
示例,我可以使用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 的抽象映射,它是这些坐标的数学函数。一般来说,运算符是直接的,没有任何成像特定行为,如将颜色元组转换为光度标量。 (要完成这样的转换,必须编写代码,因为系数取决于所使用的颜色 space。)
因此声明:
img1Fun(x, y, c) = cast<uint16_t>(clamped(x, y, c));
将 input_16
定义为与 clamped
具有相同数量的通道,但是是 16 位类型而不是 8 位类型。 Halide 中的算术与其最大操作数保持相同的位宽,并且与 C 不同,它不会隐式转换为标准 int 大小。这是因为对于矢量化,保持对通道大小的明确控制很重要。在这种情况下,需要使用 16 位中间类型以避免在对 8 位值求和时溢出。
除法后有相应的转换回8位类型。模糊结果保证适合 8 位类型,因为计算是标准化的(给定颜色通道在整个图像上的平均值不应改变)。上面的代码在两个地方同时进行了向上转型和向下转型,这是多余的。它可能不会对性能产生任何影响,因为编译器应该足够聪明,可以识别出外部转换集是 nop,但它不会产生特别可读的代码。
问题 2:实际上是相同的答案。我不会在这里使用术语 "overloading",但该定义适用于所有坐标。 Var "c" 在左手边和右手边提到,并且每个都有相同的值。 (我们有一个 shorthand 下划线 ('_') 表示法表示 "zero or more coordinates" 允许通过参数列表,但除此之外这些定义没有什么特别之处。)
问题 3:为此进行矢量化和并行化安排的最简单方法是使用平面布局(所有 R 值彼此相邻存储,然后是所有 G 等)并将矢量化为适当的大小对于 16 位数学。 (例如,"vectorize(x, natural_vector_size())" id 在生成器中工作。)沿行的线程并行度——“.parallel(y)”。根据行的长度,您可能需要向并行指令添加拆分参数。
此时间表也适用于半平面表示(一行 R、一行 G 和一行 B)。
当在实际管道的上下文中使用 blurX 或者需要非平面存储布局时,还有其他方法可能更有意义。