shimat / opencvsharp

OpenCV wrapper for .NET
Apache License 2.0
5.37k stars 1.15k forks source link

[Question] How to use InputOutputArray as InputArray? #1568

Open chrisheil opened 1 year ago

chrisheil commented 1 year ago

Summary of your issue

I am trying to define a function which accepts an InputOutputArray to perform various tasks with Cv2 method calls. When I try to pass this into the parameter for InputArray I am getting a compiler error that it cannot convert from InputOutputArray to InputArray.

From my research this is the intended use for this class in C++ (here is one source discussing this usage) and the OpenCV documentation shows the inheritance being InputArray -> OutputArray -> InputOutputArray. Looking at the source code in this library it looks like OutputArray does not inherit from InputArray and this might be the cause of the issue? If so, how can I work around this?

Example code:

This is a simplified example of how I want to use this parameter:

public static void DoStuff(InputOutputArray image)
{
    Cv2.Resize(image, image, new Size(500, 500));
    Cv2.CvtColor(image, image, ColorConversionCodes.BGR2GRAY);
}

Alternatively, if this is not currently possible, I would like to do something like this instead.

public static void DoStuff(InputArray src, OutputArray dst)
{
    Cv2.Resize(src, dst, new Size(500, 500));
    Cv2.CvtColor(dst, dst, ColorConversionCodes.BGR2GRAY);
}

The issue I run into here is the CvtColor call now has a compiler error since dst is an OutputArray and cannot be passed as an InputArray. Is there accomplish this without allocating memory to a new Mat?

BalashovK commented 1 year ago

I do not see how CvtColor in your example can run in place. Input is 24bpp, output is 8bpp. Same with Resize. You will need to allocate data array for pixels twice in DoStuff. Even if you somehow trick OpenCV to re-use Mat, it will not save you much, because pixel data array will need to be re-allocated anyways.

I would write it like:

public static Mat DoStuff(Mat src) { Mat resized = new Mat() Cv2.Resize(src, resized, new Size(500, 500)); Mat gray = new Mat(); Cv2.CvtColor(resized , gray , ColorConversionCodes.BGR2GRAY); resized.Release(); return gray; }

or do using (Mat resized = new Mat()) { ... }

chrisheil commented 1 year ago

@BalashovK Thank you for the response. I am still learning OpenCV and maybe miss some nuances in it's usage, so my exact example might not be the best. The original code I am working with (written in python) is passing the same Mat instance into both the InputArray and OutputArray parameters which is what I am trying to understand.

I see in articles like this one with C++ examples like the below snippet which also recommend this approach to reduce allocating memory for a copy of the original image

// Revision 3
void preprocess(InputArray input, OutputArray output) {
  Mat gray;
  cvtColor(input, gray, COLOR_BGR2GRAY);
  blur(gray, output, Size(3, 3));
}

And they give the following explanation as follows:

Now instead of returning a new Mat object, whatever we pass into output will be written to. Why would we want to do this? Well, when you’re writing computer vision code, you need to be careful about how your image data is being passed around. After all, the images you’re passing around are uncompressed; a 4000x4000 image in BGR888 format would take around 48 megabytes of space in RAM. That’s not a lot, but it adds up fast, particularly if you’re parallelizing or running on a mobile platform. If we gave that image to Revision 2 of the preprocess() function, we would be allocating two copies of the image; one temporary (gray) and one that we keep and return ( output). With Revision 3, we’ve moved the decision to allocate the output from inside of the function to wherever it gets called. If you want a copy, call preprocess(someMat, someOtherMat). If you want to overwrite the original Mat, just call preprocess(someMat, someMat), using the same object for the input and the output. If you’ve wondered why so many of OpenCV’s functions write outputs to an OutputArray parameter instead of returning a Mat, this is why!

(Revision 2 in the article is taking a similar approach to your recommendation, allocating a new Mat object.

And then finally, they go on to recommend using InputOutputArray when you know you always want to overwrite the existing Mat with the final result

Now let’s say we always want preprocess() to operate in-place. We can make use of OpenCV’s InputOutputArray to completely avoid allocating new images in preprocess() and make the function a little easier to read

// Revision 4
void preprocess(InputOutputArray input) {
  cvtColor(input, input, COLOR_BGR2GRAY);
  blur(input, input, Size(3, 3));
}

So this is the heart of the original question: is it possible to create a function with an InputOutputArray which can be passed as an InputArray like is possible in C++? And to your point, is the example form the article a valid one, or is there only specific scenarios where this would make sense?