0

A recurrent problem in OpenCV is how to apply single-channel functions to multi-channel images (e.g. color depth reduction with cv::LUT). The general case is simple enough:

  1. Split image across channels;
  2. Apply function to separate channels;
  3. Assemble result image from channel outputs.

But it's sort of silly that I have to code this same algorithm over and over, when the only thing that changes is the function applied to the channels (and the odd extra parameters).

Does OpenCV provide a generic implementation of the algorithm above – some mechanism for applying single-channel functions to each channel of multi-channel images?

If not, how do you suggest solving this problem in C++? A macro could do the trick, but it would be a somewhat complicated macro, large and ugly. I'd prefer a more elegant solution if available.

Community
  • 1
  • 1
xperroni
  • 2,606
  • 1
  • 23
  • 29

1 Answers1

2

FP for the rescue. ;)

You don't need a marco. std::function provides all we need for this:

#include <opencv2/opencv.hpp>
#include <functional>
#include <iterator>

// Apply a given function to every channel of an image.
cv::Mat ApplyToChannels(const cv::Mat& img,
    const std::function<cv::Mat(const cv::Mat& src)>& f)
{
    // Split image.
    std::vector<cv::Mat> channelsIn;
    cv::split(img, channelsIn);

    // Apply function to channels.
    std::vector<cv::Mat> channelsOut;
    std::transform(begin(channelsIn), end(channelsIn),
        std::back_inserter(channelsOut), [&f](const cv::Mat& channel)
    {
        return f(channel);
    });

    // Merge image.
    cv::Mat result;
    cv::merge(channelsOut, result);
    return result;
}

cv::Mat Identity(const cv::Mat& src)
{
    return src;
}

cv::Mat Sobel(const cv::Mat& src)
{
    cv::Mat result;
    cv::Sobel(src, result, src.depth(), 1, 1);
    return result;
}

int main()
{
    // Lamdas also work.
    auto Blur = [](const cv::Mat& src) -> cv::Mat
    {
        cv::Mat result;
        cv::blur(src, result, cv::Size(15, 15));
        return result;
    };

    // Create test image and draw something on it.
    cv::Mat image(120, 160, CV_8UC3, cv::Scalar(0, 0, 0));
    cv::line(image, cv::Point(32, 32), cv::Point(120, 80),
        cv::Scalar(56, 123, 234), 28);

    // Apply two different operations.
    auto image2 = ApplyToChannels(image, Sobel);
    auto image3 = ApplyToChannels(image, Blur);

    // Save results.
    cv::imwrite("1.png", image);
    cv::imwrite("2.png", image2);
    cv::imwrite("3.png", image3);
}

If you want to keep your functions more general, you can use std::bind etc. to set for example the sobel parameters.

enter image description here enter image description here enter image description here

Tobias Hermann
  • 9,936
  • 6
  • 61
  • 134