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 */
}

我有三个问题:

  1. 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?

  2. 重载 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;
  1. 并行化 我如何并行化 blurX?基于来自 CVPR'15 herebrighten.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 或者需要非平面存储布局时,还有其他方法可能更有意义。