halide编程技术指南(连载三)

本文是halide编程指南的连载,已同步至公众号

目录

第六章 在任意域上实现函数

第七章 多级管道


 

 

第六章 在任意域上实现函数

本课演示如何在不从(0,0)开始的域上计算Func。

#include "Halide.h"
#include <stdio.h>
using namespace Halide;
int main(int argc, char **argv) {
    // 上一课涉及的内容很多,复杂的多级管道调度就在前面。作为一个插曲,让我们考虑一些简单的事情:在不从原点开始的矩形域上计算函数。
    // 我们定义我们熟悉的梯度函数.
    Func gradient("gradient");
    Var x("x"), y("y");
    gradient(x, y) = x + y;

    // 打开跟踪,这样我们就可以看到它是如何被评估的。
    gradient.trace_stores();

    // 以前我们已经认识到梯度是这样的:
    // gradient.realize(8, 8);
    // 这里面有三件事:
    // 1) 生成的代码无法计算任意矩形上的渐变。
    // 2) 分配新的8 x 8图像.
    // 3) 运行生成的代码来计算从(0,0)到(7,7)的所有x,y的梯度,并将结果放入图像中。
    // 4) 作为实现调用的结果返回新图像。

    // 如果我们小心地管理内存,不想让Halide为我们分配一个新的图像呢?我们可以用另一种方式来实现。我们可以给它一个我们想要它来填充的图像。下面将函数计算为现有图像:
    printf("Evaluating gradient from (0, 0) to (7, 7)\n");
    Buffer<int> result(8, 8);
    gradient.realize(result);
-----------------------------------------------------------------------
 > Begin pipeline gradient.0()
 > Store gradient.0(0, 0) = 0
 > Store gradient.0(1, 0) = 1
 > Store gradient.0(2, 0) = 2
 > Store gradient.0(3, 0) = 3
 > Store gradient.0(4, 0) = 4
 > Store gradient.0(5, 0) = 5
 > Store gradient.0(6, 0) = 6
 > Store gradient.0(7, 0) = 7
 > Store gradient.0(0, 1) = 1
 > Store gradient.0(1, 1) = 2
 > Store gradient.0(2, 1) = 3
 > Store gradient.0(3, 1) = 4
 > Store gradient.0(4, 1) = 5
 > Store gradient.0(5, 1) = 6
 > Store gradient.0(6, 1) = 7
 > Store gradient.0(7, 1) = 8
 > Store gradient.0(0, 2) = 2
 > Store gradient.0(1, 2) = 3
 > Store gradient.0(2, 2) = 4
 > Store gradient.0(3, 2) = 5
 > Store gradient.0(4, 2) = 6
 > Store gradient.0(5, 2) = 7
 > Store gradient.0(6, 2) = 8
 > Store gradient.0(7, 2) = 9
 > Store gradient.0(0, 3) = 3
 > Store gradient.0(1, 3) = 4
 > Store gradient.0(2, 3) = 5
 > Store gradient.0(3, 3) = 6
 > Store gradient.0(4, 3) = 7
 > Store gradient.0(5, 3) = 8
 > Store gradient.0(6, 3) = 9
 > Store gradient.0(7, 3) = 10
 > Store gradient.0(0, 4) = 4
 > Store gradient.0(1, 4) = 5
 > Store gradient.0(2, 4) = 6
 > Store gradient.0(3, 4) = 7
 > Store gradient.0(4, 4) = 8
 > Store gradient.0(5, 4) = 9
 > Store gradient.0(6, 4) = 10
 > Store gradient.0(7, 4) = 11
 > Store gradient.0(0, 5) = 5
 > Store gradient.0(1, 5) = 6
 > Store gradient.0(2, 5) = 7
 > Store gradient.0(3, 5) = 8
 > Store gradient.0(4, 5) = 9
 > Store gradient.0(5, 5) = 10
 > Store gradient.0(6, 5) = 11
 > Store gradient.0(7, 5) = 12
 > Store gradient.0(0, 6) = 6
 > Store gradient.0(1, 6) = 7
 > Store gradient.0(2, 6) = 8
 > Store gradient.0(3, 6) = 9
 > Store gradient.0(4, 6) = 10
 > Store gradient.0(5, 6) = 11
 > Store gradient.0(6, 6) = 12
 > Store gradient.0(7, 6) = 13
 > Store gradient.0(0, 7) = 7
 > Store gradient.0(1, 7) = 8
 > Store gradient.0(2, 7) = 9
 > Store gradient.0(3, 7) = 10
 > Store gradient.0(4, 7) = 11
 > Store gradient.0(5, 7) = 12
 > Store gradient.0(6, 7) = 13
 > Store gradient.0(7, 7) = 14
 > End pipeline gradient.0()
-----------------------------------------------------------------------

    // 让我们检查一下它是否达到了我们的预期:
    for (int y = 0; y < 8; y++) {
        for (int x = 0; x < 8; x++) {
            if (result(x, y) != x + y) {
                printf("Something went wrong!\n");
                return -1;
            }
        }
    }

    // 现在让我们计算一个从其他地方开始的5x 7矩形上的梯度——位置(100,50)。所以x和y从(100,50)到(104,56)。

    // 我们首先创建一个表示矩形的图像:
    Buffer<int> shifted(5, 7); // 在构造器中我们告诉它大小.
    shifted.set_min(100, 50); // 然后我们在左上角告诉它.

    printf("Evaluating gradient from (100, 50) to (104, 56)\n");

    // 注意,这不需要编译任何新代码,因为当我们第一次实现它时,我们生成了能够计算任意矩形上的梯度的代码。
    gradient.realize(shifted);
--------------------------------------------------------------------------
 > Begin pipeline gradient.0()
 > Store gradient.0(100, 50) = 150
 > Store gradient.0(101, 50) = 151
 > Store gradient.0(102, 50) = 152
 > Store gradient.0(103, 50) = 153
 > Store gradient.0(104, 50) = 154
 > Store gradient.0(100, 51) = 151
 > Store gradient.0(101, 51) = 152
 > Store gradient.0(102, 51) = 153
 > Store gradient.0(103, 51) = 154
 > Store gradient.0(104, 51) = 155
 > Store gradient.0(100, 52) = 152
 > Store gradient.0(101, 52) = 153
 > Store gradient.0(102, 52) = 154
 > Store gradient.0(103, 52) = 155
 > Store gradient.0(104, 52) = 156
 > Store gradient.0(100, 53) = 153
 > Store gradient.0(101, 53) = 154
 > Store gradient.0(102, 53) = 155
 > Store gradient.0(103, 53) = 156
 > Store gradient.0(104, 53) = 157
 > Store gradient.0(100, 54) = 154
 > Store gradient.0(101, 54) = 155
 > Store gradient.0(102, 54) = 156
 > Store gradient.0(103, 54) = 157
 > Store gradient.0(104, 54) = 158
 > Store gradient.0(100, 55) = 155
 > Store gradient.0(101, 55) = 156
 > Store gradient.0(102, 55) = 157
 > Store gradient.0(103, 55) = 158
 > Store gradient.0(104, 55) = 159
 > Store gradient.0(100, 56) = 156
 > Store gradient.0(101, 56) = 157
 > Store gradient.0(102, 56) = 158
 > Store gradient.0(103, 56) = 159
 > Store gradient.0(104, 56) = 160
 > End pipeline gradient.0()
-------------------------------------------------------------------------


    // 从C++,我们也使用从(100, 50)开始的坐标访问图像对象。
    for (int y = 50; y < 57; y++) {
        for (int x = 100; x < 105; x++) {
            if (shifted(x, y) != x + y) {
                printf("Something went wrong!\n");
                return -1;
            }
        }
    }
    // 图像“shifted”将Func的值存储在一个从(100,50)开始的域上,因此请求shifted(0,0)实际上会读取越界,并可能崩溃。

    // 如果我们想计算某个非矩形区域上的Func怎么办?太糟糕了。halide只做矩形。

    printf("Success!\n");
    return 0;
}

第七章 多级管道

#include "Halide.h"
#include <stdio.h>
using namespace Halide;
// 加载PNG的支持代码.
#include "halide_image_io.h"
using namespace Halide::Tools;

int main(int argc, char **argv) {
    // 首先,我们将在下面声明一些要使用的变量.
    Var x("x"), y("y"), c("c");

    // 现在我们将表示一个多级管道,它先水平模糊图像,然后垂直模糊图像.
    {
        // 获取8位彩色输入
        Buffer<uint8_t> input = load_image("images/rgb.png");

        // 把它升级到16位,这样我们就可以计算而不会溢出.
        Func input_16("input_16");
        input_16(x, y, c) = cast<uint16_t>(input(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 blur_y("blur_y");
        blur_y(x, y, c) = (blur_x(x, y-1, c) +
                           2 * blur_x(x, y, c) +
                           blur_x(x, y+1, c)) / 4;

        // 转换回8位.
        Func output("output");
        output(x, y, c) = cast<uint8_t>(blur_y(x, y, c));

        // 此管道中的每个Func都使用熟悉的函数调用语法调用前一个Func(我们在Func对象上重载了operator())。Func可以调用已给出定义的任何其他Func。此限制可防止管道中包含循环。halide管道总是Funcs的前向图.
        // 现在让我们实现它...
        // Buffer<uint8_t> result = output.realize(input.width(), input.height(), 3);

        // 只是上面这一行行不通。取消注释以查看发生了什么。

        // 在与输入图像相同的域上实现此管道需要读取超出输入边界的像素,因为blur_xstage水平向外延伸,blur_ystage垂直向外延伸。Halide通过在管道顶部注入一段代码来检测这一点,该代码计算将在其上读取输入的区域。当它开始运行管道时,它首先运行此代码,确定将读取超出界限的输入,并拒绝继续。在内部循环中没有实际的边界检查;这会很慢。
        //也就是说要注意图像边界问题,防止越界
        // 那我们该怎么办?有几个选择。如果我们意识到在一个域上向内移动了一个像素,我们就不会要求halide程序读取越界。我们在上一课中看到了如何做到这一点:
        Buffer<uint8_t> result(input.width()-2, input.height()-2, 3);
        result.set_min(1, 1);
        output.realize(result);

        // 保存结果。它应该看起来像一只略带模糊的鹦鹉,并且应该比输入图像窄两个像素,短两个像素(这是因为边界的问题)。
        save_image(result, "blurry_parrot_1.png");

        //这通常是处理边界的最快方法:不要编写读取越界的代码:)下一个示例是更一般的解决方案。
    }

    // 相同的管道,在输入端有一个边界条件.
    {
        // 获取8位彩色输入
        Buffer<uint8_t> input = load_image("images/rgb.png");

        // 这次,我们将把输入包装在一个Func中,以防止读取超出界限:
        Func clamped("clamped");

        // 定义一个表达式,将x钳制在[0,input.width()-1]范围内。
        Expr clamped_x = clamp(x, 0, input.width()-1);
        // clamp(x, a, b) 等价于 max(min(x, b), a).

        // 类似的对y进行限制.
        Expr clamped_y = clamp(y, 0, input.height()-1);
        // 在限制的范围内读取图像。这意味着无论我们如何计算Func‘clapped’,我们永远不会读取输入的越界值。这是一个钳制到边的边界条件,是用halide表示的最简单的边界条件。
        clamped(x, y, c) = input(clamped_x, clamped_y, c);

        // 使用BoundaryConditions命名空间中的helper函数可以更简洁地定义“clamped”,如下所示:
        // clamped = BoundaryConditions::repeat_edge(input);
        // 这些对于其他边界条件的使用很重要,因为它们以halide能够最好地理解和优化的方式表示。如果使用正确,它们和没有边界条件一样好用。

        // 将它升级到16位,这样我们就可以在不溢出的情况下进行计算。这次我们将引用我们的新函数“clamped”,而不是直接引用输入图像。
        Func input_16("input_16");
        input_16(x, y, c) = cast<uint16_t>(clamped(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 blur_y("blur_y");
        blur_y(x, y, c) = (blur_x(x, y-1, c) +
                           2 * blur_x(x, y, c) +
                           blur_x(x, y+1, c)) / 4;

        // 转换为 8-bit.
        Func output("output");
        output(x, y, c) = cast<uint8_t>(blur_y(x, y, c));

        // 这一次可以安全地计算某个域上的输出作为输入,因为我们有一个边界条件。
        Buffer<uint8_t> result = output.realize(input.width(), input.height(), 3);

        // 保存结果。它看起来像一只略带模糊的鹦鹉,但这次它的大小与输入的大小相同。
        save_image(result, "blurry_parrot_2.png");
    }

    printf("Success!\n");
    return 0;
}

 

 

 

 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值