Warm tip: This article is reproduced from serverfault.com, please click

Problem with getting OpenCV image data to C#

发布于 2020-12-02 19:45:49

So I'm fairly new to C++, and I tried to use it to read images using the OpenCV library. My idea was to put the C++ code in a DLL and then get the decoded image by calling the DLL from a C# script (I need the image in C#).

I looked arround a bit on what would be the best way to send the bytes to C#, and saw that most people were using a char* to store bytes. That could then be returned by a C++ function and stored as a C# string like so:

char* GetSomeBytes()
{
    // Got this method of allocating memory from the internet, sadly can't remember from were
    // (I get memory errors if I pass my_bytes directly)

    size_t stSize = strlen(my_bytes) + sizeof(char);
    char* pszReturn = NULL;

    pszReturn = (char*)::CoTaskMemAlloc(stSize);
    strcpy_s(pszReturn, stSize, my_bytes);
    return pszReturn;
}

and then in C#:

[DllImport(path_to_my_dll, EntryPoint = "GetSomeBytes", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
static extern string GetSomeBytes();

static void Main(string[] args)
{
    string raw_bytes = GetSomeBytes();

    byte[] bytes = Encoding.ASCII.GetBytes(raw);
}

And this even worked, but now I had to get the raw data from the Mat to a char*. I was quite sure I had to use the Mat.data field, but that contained a unsinged char*. There were several ways in which I tried this conversion:

  • Using a simple cast (like (char*)my_mat.data)
  • Using reinterpret_cast (like reinterpret_cast<char*>(my_mat.data)), which some people said would be better for C++
  • Using memcpy like:
int size = my_mat.total() * my_mat.elemSize();
char* bytes = new char[size];
memcpy(bytes, my_mat.data, size);

So here's my problem: When I used my_mat.total() * my_mat.elemSize() on a full-hd image, it returned 6220804, what makes sense to me (because 1920 * 1080 = 2073600, so the image has 2.073.600 pixels, and 2073600 * 3 = 6220804, the image has 3 color channels, and so it takes a total of 6.220.804 bytes to store it).

However, after the conversion to a char*, with every one of the three methods above, strlen(the_converted_bytes) was something completely different with every image I tried, ranging from about 2.000 all the way up to about 11.000.000. bytes.Length in C# returned the same value though, so I don't think the error is in the C++-char* to C#-bytes process. I tried different charsets in both the DllImport and the Encoding.GetBytes anyway, but that didn't seem to help.

So I think there is something wrong with my understanding of char and unsigned char or with pointers. Or both. Anyway, it really seems to me that it should be possible to convert the data of a C++ Mat to a C# byte[]. Isn't it possible to convert between a unsinged char* and a char* as easily as I thought, or have I overlooked something completely different (maybe there's something wrong with my use of my_mat.data)?

I would apreciate any help

Questioner
Wanja
Viewed
0
Wanja 2020-12-04 01:45:12

Ok, nevermind, turns out it actually makes more sense to just return the unsigned char* from the C++ function and then convert it in C#. If anyone else has problems with this, here's the thread I found this answer on (Thanks to Louis.fr).

Here are the methods I ended up using:

In C++:

unsigned char* GetUnsignedBytes(char* image, int* bytes_count)
{
    Mat img = imread(image);

    int size = img.total() * img.elemSize();
    unsigned char* raw_bytes = new unsigned char[size];

    memcpy(raw_bytes, img.data, size * sizeof(std::byte));
    
    *bytes_count = size;
    return raw_bytes;
}

and in C# (using System and using System.Runtime.InteropServices):

[DllImport(path_to_my_dll)]
public static extern IntPtr GetUnsignedBytes(string image, out int bytes_count);

static void Main(string[] args)
{
    IntPtr ptr = GetUnsignedBytes(some_image, out int bytes_count);

    byte[] bytes = new byte[bytes_count];
    Marshal.Copy(ptr, bytes, 0, bytes_count);
}

Glad it was this simple after all. Louis.fr also wrote that you could just pass a pointer, but as far as I understand using these pointers in C# would require unsave code (compiling with /unsave or something like that). So I think I'm going to stick with this method, as long as I don't stumble into further problems with it in the future.