Getting distorted images after sending them from C# to OpenCV in C++?
Asked Answered
R

1

4

I created a C DLL out of my C++ class which uses OpenCV for image manipulations and want to use this DLL in my C# application. Currently, this is how I have implemented it:

#ifdef CDLL2_EXPORTS
#define CDLL2_API __declspec(dllexport)
#else
#define CDLL2_API __declspec(dllimport)
#endif

#include "../classification.h" 
extern "C"
{
    CDLL2_API void Classify_image(unsigned char* img_pointer, unsigned int height, unsigned int width, char* out_result, int* length_of_out_result, int top_n_results = 2);
    //...
}

C# related code:

DLL Import section:

//Dll import 
[DllImport(@"CDll2.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
static extern void Classify_Image(IntPtr img, uint height, uint width, byte[] out_result, out int out_result_length, int top_n_results = 2);

The actual function sending the image to the DLL:

//...
//main code 
private string Classify(int top_n)
{
    byte[] res = new byte[200];
    int len;
    Bitmap img = new Bitmap(txtImagePath.Text);
    BitmapData bmpData = img.LockBits(new Rectangle(0, 0, img.Width, img.Height), 
                                      ImageLockMode.ReadWrite,  
                                      PixelFormat.Format24bppRgb);
    Classify_Image(bmpData.Scan0, (uint)bmpData.Height, (uint)bmpData.Width, res, out len, top_n);
    img.UnlockBits(bmpData); //Remember to unlock!!!
    //...
}

and the C++ code in the DLL :

CDLL2_API void Classify_Image(unsigned char* img_pointer, unsigned int height, unsigned int width,
                              char* out_result, int* length_of_out_result, int top_n_results)
    {
        auto classifier = reinterpret_cast<Classifier*>(GetHandle());

        cv::Mat img = cv::Mat(height, width, CV_8UC3, (void*)img_pointer, Mat::AUTO_STEP);

        std::vector<Prediction> result = classifier->Classify(img, top_n_results);

        //...
        *length_of_out_result = ss.str().length();
    }

This works perfectly with some images but it doesn't work with others, for example when I try to imshow the image in the Classify_Image, right after being created from the data sent by C# application, I am faced with images like this :

Problematic example:

enter image description here

Fine example:

enter image description here

Rostand answered 17/8, 2017 at 15:4 Comment(13)
it looks like the stride/pitch is incorrect e.g. the step arg will align the memory such that each row fits on some byte-alignment for performance reasons. This is why the next pixel is not aligned, you need to look at what the step/stride size is and this probably needs to be passed through so that when it accesses the subsequent row it uses the correct offset in memory, I can't suggest anything else as I code purely in C++ with openCV but this is your issue here wrt the image errorBerylberyle
I've found here something relevant: msdn.microsoft.com/en-us/library/… I don't code in c# but it looks like you should check this, it's likely it's not the same as your image width you'll find. It may be image width * num bytes per channel * num of channels + padding. The doc states the number will be 4-byte alignedBerylberyle
Your problematic image has a width of 1414, the image is 24-bit with 3 * 8-bit colour channels, if we calc the number of bytes: 1414 * 3 = 4242 bytes if we then divide by 4 4242/4=1060.5 you can see that we are left with 0.5 which means that the stride will be set to 4244 because 0.5 * 4 bytes = 2 bytes, please check if this is the caseBerylberyle
@EdChum: Thanks alot, using the bmpData.Stride did the trick and worked! By the way how can I identify the type of the image? im currently using CV_8UC3, how can I get the equivalent type here and send it as well? wouldnt this create an issue when I feed grayscale image or png images with 4 channels? by the way please post that as the answer.Rostand
You need to determine the bit depth and number of colour channels from the bitmap, normally they will be something that tells you the packing, such as yuv, rgb, greyscale for example you need to get number of bits in this case 24 and divide by the number of colour channels which would be three to get 8 bits per channel. I'm in the middle of cooking so can't post anything meaningful at the momentBerylberyle
Actually thinking about it why not send the file path to your dll and just open CV to open it? Opencv can sniff the bit depth and number of channels and sets this automatically, then you don't need to care about this and then do the colour conversion to greyscale as usualBerylberyle
@EdChum: Thanka again. apart from your first comment with which I will fiddle in a minute, about your suggestion on using the file path, I should say that, this was the first thing that we did, since the dll is actually being used in a server, we had to save each stream onto the disk and then give the image path, which works just fine, but the disk space was consumed very quickly. thats why we tried to use the image on the fly and avoid any unnecessary IO.Rostand
in that case why not just send the file as a memory buffer and use imdecode, this should work: #14727767 and docs.opencv.org/3.0-beta/modules/imgcodecs/doc/… also this has the advantage that you can can pass the flag for grayscale and it will just load and convert on the fly, I think this should work without faffing about with bitmap loadingBerylberyle
@EdChum: Thanks, I updated the question by the way. I faced some sudden issues which I didn't expect!Rostand
I'm not sure that incrementally updating the question to present a new problem is a good thing for SO, I'd close this issue and open a new question that references this question. Besides that, where is the code that calcs GetResizeHeight? also the docs for setResolution msdn.microsoft.com/en-us/library/… state you're supposed to pass the resolution in dots per inch, rather than pixel values, this could be your confusion. Additionally you should just use openCV to resize and just pass some scaled resolution size like half height/widthBerylberyle
@EdChum. Since you didnt post any asnwer for the first issue, I updated the question. Please post the solution for the first part which we already solved. and I'll Create a new question with needed informationRostand
Post another question (also try what I suggested above), generally I just resize by scaling the orig resolution, it should just workBerylberyle
@EdChum, Thank you very much, I post a new question and meanwhile do as you suggested.Rostand
B
2

Your initial issue is to do with what is called stride or pitch of image buffers. Basically for performance reasons pixel row values can be memory aligned, here we see that in your case it's causing the pixel rows to not align because the row size is not equal to the pixel row width.

The general case is:

resolution width * bit-depth (in bytes) * num of channels + padding

in your case the bitmap class state:

The stride is the width of a single row of pixels (a scan line), rounded up to a four-byte boundary

So if we look at the problematic image, it has a resolution of 1414 pixel width, this is a 8-bit RGB bitmap so if we do the maths:

1414 * 1 * 3 (we have RGB so 3 channels) = 4242 bytes

So now divide by 4-bytes:

4242 / 4 = 1060.5

So we are left with 0.5 * 4 bytes = 2 bytes padding

So the stride is in fact 4244 bytes.

So this needs to be passed through so that the stride is correct.

Looking at what you're doing, I'd pass the file as memory to your openCV dll, this should be able to call imdecode which will sniff the file type, additionally you can pass the flag cv::IMREAD_GRAYSCALE which will load the image and convert the grayscale on the fly.

Berylberyle answered 18/8, 2017 at 8:9 Comment(1)
Thanks a lot. here is the new question by the way, I tried to be thorough : #45753929Rostand

© 2022 - 2024 — McMap. All rights reserved.