c++image-processinghalide

How to calculate chroma image in Halide?


I am currently evaluating if Halide is a good choice for my programmes. As a short Hello Halide example, I wanted to convert an rgb image to hsl space. However, when trying, I got stuck at the first stage.

In order to convert to hsl, I would need to calculate the chroma image first. This is the difference between the maximum and minimum channel value for a given pixel. I tried to find something similar in the documentation, but I was not able to. How would I do this in Halide?

In C++ it would be something like:

for (auto y = 0; y < image.height(); ++y)
    for (auto x = 0; x < image.width(); ++x)
    {
        auto maximum = 0;
        auto minimum = 255;
        for (auto c = 0; c < image.channels(); ++c)
        {
            if (image(x, y, c) < minimum)
                minimum = image(x, y, c);
            if (image(x, y, c) > maximum)
                maximum = image(x, y, c);
        }
        chroma(x, y) = maximum - minimum;
    }

Solution

  • Here's an example Halide program for converting from RGB to HSL:

    #include <Halide.h>
    #include <halide_image_io.h>
    using namespace Halide;
    using namespace Halide::Tools;
    
    int main(int argc, char *argv[]) {
        Buffer<float> input = load_and_convert_image(argv[1]);
    
        Func max_channels, min_channels, chroma, result_hsl;
        Var x, y, c;
    
        Expr R = input(x, y, 0);
        Expr G = input(x, y, 1);
        Expr B = input(x, y, 2);
    
        max_channels(x, y) = max(R, G, B);
        min_channels(x, y) = min(R, G, B);
        chroma(x, y) = max_channels(x, y) - min_channels(x, y);
    
        Expr V = max_channels(x, y);
        Expr C = chroma(x, y);
    
        Expr H = select(C == 0, 0,
                        R == V, 60 * (0 + (G - B) / C),
                        G == V, 60 * (2 + (B - R) / C),
                        60 * (4 + (R - G) / C)) /
                 360;
        Expr L = V - C / 2;
        Expr S = (V - L) / min(L, 1 - L);
    
        result_hsl(x, y, c) = select(
                c == 0, H,
                c == 1, S,
                L);
    
        result_hsl
                .bound(c, 0, 3)
                .reorder(c, x, y)
                .unroll(c, 3);
    
        Buffer<float> result = result_hsl.realize({input.width(), input.height(), 3});
    
        convert_and_save_image(result, argv[2]);
    }
    

    and here's the CMakeLists.txt I used to build this:

    cmake_minimum_required(VERSION 3.20)
    project(rgb_to_hsl)
    
    find_package(Halide REQUIRED)
    
    add_executable(rgb_to_hsl main.cpp)
    target_link_libraries(rgb_to_hsl PRIVATE Halide::Halide Halide::ImageIO)
    

    There might be bugs with the actual HSL conversion, here, but it should still demonstrate the big ideas. Note one counterintuitive thing here: we bound() and unroll() the channel dimension (after reorder()-ing it innermost) to avoid any loop / branching overhead for that math.