本文已同步至公众号,欢迎关注。
Halide是一种编程语言,使得在现代机器上编写高性能图像和数组处理代码更加容易。Halide支持如下的平台:
CPU体系结构:X86,ARM,MIPS,Hexagon,PowerPC
操作系统:Linux,Windows,macOS,Android,iOS,Qualcomm QuRT
GPU计算API:CUDA,OpenCL,OpenGL,OpenGL计算着色器,Apple Metal,Microsoft Direct X 12
Halide并不是独立的编程语言,而是嵌入在C ++中。 这意味可以像编写c++代码一样编写halide代码,然后,可以将该表示形式编译为目标文件,或对其进行JIT编译并在同一过程中运行。
如果你想用普通的CPU做加速,又不想去优化算法,那么halide将是非常优秀的选择。唯一要做的,就是把算法用halide重写一遍即可。而且,halide重写的算法,就跟你正常理解的是完全一样的,不需要拐弯抹角。这在图像处理中非常有用。
笔者使用halide已经一年有余了,觉得很有必要对其做个介绍,尤其是使用方法的介绍。Halide的英文教程可以参考:
https://halide-lang.org/tutorials/tutorial_introduction.html
API可以参考:
https://halide-lang.org/docs/
但是,对于初学者来说,知道怎么用,然后去看API才是重点。
笔者针对官方教程做定向翻译,难懂的地方再加标注。最后手把手教你写一个图像高斯模糊的halide程序。如果你觉得中文的就是垃圾,可以去参考英文的原版。
第一章 基本定义
如果想用halide,只需要包含<halide.h>这个头文件即可。
#include “Halide.h”
当然,在halide中,也可以包含C语言的头文件。例如包含<stdio.h>,使用printf函数
#include <stdio.h>
我们用一个main函数来展开。
int mainint argc, char **argv) { // 这个主函数定义了一个图像处理通道,用于获得灰度图像的对角梯度 // ‘Func’对象代表一个处理管道,它规定了每个像素因该是怎么样的数据,可以理解为就是一幅处理后的图。例如opencv中的cv::mat Halide::Func gradient; // ‘Var’对象实际上就是类型,就像我们常说的int,float等。它本身是没有任何含义的。 Halide::Var x, y; // 我们用x和y来表示图像的x轴和y轴,按照我们处理图像的惯常思维,x就是图像列的索引,y就是图像行的索引。 // 就这些变量和其他函数而言,Func在其变量的任何整数坐标处定义为Expr。 在这里,我们将定义一个Expr,其值为x + y。 Var具有适当的运算符重载,因此“ x + y”之类的表达式成为“ Expr”对象。可以理解为表达式,或数学函数。 Halide::Expr e = x + y;// 现在我们为’Func’对象gradient进行定义。在x,y像素处,图像的像素值是Expr e的值。在等式的左边,就是我们定义的Func gradient对象和Vars变量x,y,在等式的右边就是用相同的Vars x,y定义的Expr e对象, gradientx, y) = e; // 同样可以写为: // gradientx, y) = x + y; // 这是更为一般的形式,但是为了完整起见,在此显示中间量Expr e。 // 该行代码定义了Func,但实际上并没有计算输出图像。在此阶段,内存中只有Funcs,Exprs和Vars,代表了我们成像管道的结构。我们正在元编程。这个C ++程序正在内存中构造一个Halide程序。而真正的像素数据计算紧随其后。 // 现在我们“实现” Func,JIT编译一些代码以实现我们定义的管道,然后运行它。我们还需要告诉Halide评估Func的域,该域确定上述x和y的范围以及输出图像的分辨率。Halide.h还提供了我们可以使用的基本模板图像类型。例如,我们生成一个800 x 600的图像,就需要定义一个域,也就是一个模板buffer<int32_t>。 Halide::Buffer<int32_t> output = gradient.realize800, 600); // Halide确实为您键入推断。Var对象代表32位整数,因此Expr对象’x + y’也代表32位整数,因此’gradient’定义了32位图像,因此当出现以下情况时,我们得到了32位有符号整数图像 我们称之为“实现”。halide类型和类型转换规则等效于C。 // 下面这个是验证程序,以期上面的结果跟下面展开的结果是一样的。 for int j = 0; j < output.height); j++) { for int i = 0; i < output.width); i++) { // 我们可以使用类似的语法来定义和使用函数,来访问Buffer对象的像素。 if outputi, j) != i + j) { printf”Something went wrong!\n” “Pixel %d, %d was supposed to be %d, but instead it’s %d\n”, i, j, i+j, outputi, j)); return -1; } } } // 一切正常! 我们定义了一个Func,然后在其上调用“ realize”以生成并运行产生Buffer的机器代码。(暂且不说) printf”Success!\n”); return 0;}