public MyPipeline(HSBuffer <byte> inBuf) { Input = inBuf; // For this lesson, we'll use a two-stage pipeline that sharpens // and then applies a look-up-table (LUT). // First we'll define the LUT. It will be a gamma curve. Lut[I] = HS.Cast <byte>(HS.Clamp(HSMath.Pow(I / 255.0f, 1.2f) * 255.0f, 0, 255)); // Augment the input with a boundary condition. Padded[X, Y, C] = Input[HS.Clamp(X, 0, Input.Width - 1), HS.Clamp(Y, 0, Input.Height - 1), C]; // Cast it to 16-bit to do the math. Padded16[X, Y, C] = HS.Cast <ushort>(Padded[X, Y, C]); // Next we sharpen it with a five-tap filter. Sharpen[X, Y, C] = (Padded16[X, Y, C] * 2 - (Padded16[X - 1, Y, C] + Padded16[X, Y - 1, C] + Padded16[X + 1, Y, C] + Padded16[X, Y + 1, C]) / 4); // Then apply the LUT. Curved[X, Y, C] = Lut[Sharpen[X, Y, C]]; }
public static int Main(string[] args) { // First we'll declare some Vars to use below. var x = new HSVar("x"); var y = new HSVar("y"); var c = new HSVar("c"); // Now we'll express a multi-stage pipeline that blurs an image // first horizontally, and then vertically. { // Take a color 8-bit input var input = HSBuffer <byte> .LoadImage("rgb.png"); // Upgrade it to 16-bit, so we can do math without it overflowing. var input_16 = new HSFunc("input_16"); input_16[x, y, c] = HS.Cast <ushort>(input[x, y, c]); // Blur it horizontally: var blur_x = new HSFunc("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 it vertically: var blur_y = new HSFunc("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; // Convert back to 8-bit. var output = new HSFunc("output"); output[x, y, c] = HS.Cast <byte>(blur_y[x, y, c]); // Each Func in this pipeline calls a previous one using // familiar function call syntax (we've overloaded operator() // on Func objects). A Func may call any other Func that has // been given a definition. This restriction prevents // pipelines with loops in them. Halide pipelines are always // feed-forward graphs of Funcs. // Now let's realize it... // Buffer<byte> result = output.realize(input.width(), input.height(), 3); // Except that the line above is not going to work. Uncomment // it to see what happens. // Realizing this pipeline over the same domain as the input // image requires reading pixels out of bounds in the input, // because the blur_x stage reaches outwards horizontally, and // the blur_y stage reaches outwards vertically. Halide // detects this by injecting a piece of code at the top of the // pipeline that computes the region over which the input will // be read. When it starts to run the pipeline it first runs // this code, determines that the input will be read out of // bounds, and refuses to continue. No actual bounds checks // occur in the inner loop; that would be slow. // // So what do we do? There are a few options. If we realize // over a domain shifted inwards by one pixel, we won't be // asking the Halide routine to read out of bounds. We saw how // to do this in the previous lesson: var result = new HSBuffer <byte>(input.Width - 2, input.Height - 2, 3); result.SetMin(1, 1); output.Realize(result); // Save the result. It should look like a slightly blurry // parrot, and it should be two pixels narrower and two pixels // shorter than the input image. result.SaveImage("blurry_parrot_1.png"); // This is usually the fastest way to deal with boundaries: // don't write code that reads out of bounds :) The more // general solution is our next example. } // The same pipeline, with a boundary condition on the input. { // Take a color 8-bit input var input = HSBuffer <byte> .LoadImage("rgb.png"); // This time, we'll wrap the input in a Func that prevents // reading out of bounds: var clamped = new HSFunc("clamped"); // Define an expression that clamps x to lie within the // range [0, input.width()-1]. var clamped_x = HS.Clamp(x, 0, input.Width - 1); // clamp(x, a, b) is equivalent to max(min(x, b), a). // Similarly clamp y. var clamped_y = HS.Clamp(y, 0, input.Height - 1); // Load from input at the clamped coordinates. This means that // no matter how we evaluated the Func 'clamped', we'll never // read out of bounds on the input. This is a clamp-to-edge // style boundary condition, and is the simplest boundary // condition to express in Halide. clamped[x, y, c] = input[clamped_x, clamped_y, c]; // Defining 'clamped' in that way can be done more concisely // using a helper function from the BoundaryConditions // namespace like so: // // clamped = BoundaryConditions::repeat_edge(input); // // These are important to use for other boundary conditions, // because they are expressed in the way that Halide can best // understand and optimize. When used correctly they are as // cheap as having no boundary condition at all. // Upgrade it to 16-bit, so we can do math without it // overflowing. This time we'll refer to our new Func // 'clamped', instead of referring to the input image // directly. var input_16 = new HSFunc("input_16"); input_16[x, y, c] = HS.Cast <ushort>(clamped[x, y, c]); // The rest of the pipeline will be the same... // Blur it horizontally: var blur_x = new HSFunc("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 it vertically: var blur_y = new HSFunc("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; // Convert back to 8-bit. var output = new HSFunc("output"); output[x, y, c] = HS.Cast <byte>(blur_y[x, y, c]); // This time it's safe to evaluate the output over the some // domain as the input, because we have a boundary condition. var result = output.Realize <byte>(input.Width, input.Height, 3); // Save the result. It should look like a slightly blurry // parrot, but this time it will be the same size as the // input. result.SaveImage("blurry_parrot_2.png"); } Console.WriteLine("Success!"); return(0); }
public static int Main(string[] args) { // This program defines a single-stage imaging pipeline that // brightens an image. // First we'll load the input image we wish to brighten. var input = HSBuffer <byte> .LoadImage("rgb.png"); // See figures/lesson_02_input.jpg for a smaller version. // Next we define our Func object that represents our one pipeline // stage. var brighter = new HSFunc("brighter"); // Our Func will have three arguments, representing the position // in the image and the color channel. Halide treats color // channels as an extra dimension of the image. var x = new HSVar("x"); var y = new HSVar("y"); var c = new HSVar("c"); // Normally we'd probably write the whole function definition on // one line. Here we'll break it apart so we can explain what // we're doing at every step. // For each pixel of the input image. var value = input[x, y, c]; // Cast it to a floating point value. value = HS.Cast <float>(value); // Multiply it by 1.5 to brighten it. Halide represents real // numbers as floats, not doubles, so we stick an 'f' on the end // of our constant. value = value * 1.5f; // Clamp it to be less than 255, so we don't get overflow when we // cast it back to an 8-bit unsigned int. value = HSMath.Min(value, 255.0f); // Cast it back to an 8-bit unsigned integer. value = HS.Cast <byte>(value); // Define the function. brighter[x, y, c] = value; // The equivalent one-liner to all of the above is: // // brighter(x, y, c) = Halide::cast<uint8_t>(min(input(x, y, c) * 1.5f, 255)); // // In the shorter version: // - I skipped the cast to float, because multiplying by 1.5f does // that automatically. // - I also used an integer constant as the second argument in the // call to min, because it gets cast to float to be compatible // with the first argument. // - I left the Halide:: off the call to min. It's unnecessary due // to Koenig lookup. // Remember, all we've done so far is build a representation of a // Halide program in memory. We haven't actually processed any // pixels yet. We haven't even compiled that Halide program yet. // So now we'll realize the Func. The size of the output image // should match the size of the input image. If we just wanted to // brighten a portion of the input image we could request a // smaller size. If we request a larger size Halide will throw an // error at runtime telling us we're trying to read out of bounds // on the input image. var output = brighter.Realize <byte>(input.Width, input.Height, input.Channels); // Save the output for inspection. It should look like a bright parrot. output.SaveImage("brighter.png"); // See figures/lesson_02_output.jpg for a small version of the output. Console.WriteLine("Success!"); return(0); }