如何正确地将多个 cv::Mat 从 c++ dll 传递到 opencvsharp Mat c#?

How to pass multiple cv::Mat from c++ dll to opencvsharp Mat c# properly?

我正在做一个项目,该项目需要一个 dll 文件供另一个用 c# 编写的程序使用(我不是很熟悉 C++/C# 的用法)。对于完成我的工作的最后一步,我在将 "multiple" cv::Mat 从 dll 传递到 C# 时遇到问题。

我在 Internet 上找到了一些关于 C# 使用 OpenCvSharp 从 dll 接收 cv::Mat 的示例,它在我的代码中运行良好(它是简化):

//original.hpp
extern "C" LIB_API cv::Mat* inference(unsigned char* img_pointer, long data_len);


//original.cpp
LIB_API cv::Mat* inference(unsigned char* img_pointer, long data_len)
{
    cv::Mat A;
    ..... // process that update A
    return new cv::Mat(A);
}

//original.cs
[DllImport(@"D:\Coco\Code\C_plus_plus\cpp_dll\x64\Release\cpp_dll.dll")]
private static extern IntPtr inference(byte[] img, long data_len);

static void Main()
{
    Intptr res = inference(X, Y);
    Mat A1 = new Mat(res);
    Cv2.ImShow("test1", A1);
    Cv2.WaitKey(2000);
}

既然成功了,我打算使用相同的语法并通过函数传递参数,这样我就可以return多个cv::Mat,但是这段代码不起作用...

//rv1.hpp
extern "C" LIB_API void inference(unsigned char* img_pointer, long data_len, cv::Mat* res);

//rv1.cpp
LIB_API void inference(unsigned char* img_pointer, long data_len, cv::Mat *res)
{
    cv::Mat A;
    ..... // process that update A
    res = new cv::Mat(A);
}

//rv1.cs
[DllImport(@"D:\Coco\Code\C_plus_plus\cpp_dll\x64\Release\cpp_dll.dll")]
private static extern void inference(byte[] img, long data_len, out IntPtr res);

static void Main()
{
    Intptr res;
    inference(X, Y, out res);
    Mat A1 = new Mat(res);
    Cv2.ImShow("test1", A1);
    Cv2.WaitKey(2000);
}

我以为是因为我给cv::Mat*赋值错误,所以没有得到正确的地址,于是修改了rv1.cpp,[=55=的部分]但是这段代码也不起作用...

// rv1_1.cpp
LIB_API void inference(unsigned char* img_pointer, long data_len, cv::Mat *res)
{
    cv::Mat A;
    ..... // process that update A
    res = &A;
}

(错误是System.AccessViolationException:试图读取或写入受保护的内存


然后我想到了另一种方法,将cv::Mat*的向量与函数的参数一起传递,代码如下:

//rv2.hpp
extern "C" LIB_API void inference(unsigned char* img_pointer, long data_len, cv::Mat ***data_1, long* len);

// rv2.cpp
LIB_API void inference(unsigned char* img_pointer, long data_len, cv::Mat ***data_1, long* len)
{
    std::vector<cv::Mat*> vec_mat;
    cv::Mat A;
    cv::Mat B;
    ..... // process that update A, B
    vec_mat.push_back(new cv::Mat(A));
    vec_mat.push_back(new cv::Mat(B));

    *len = vec_mat.size();
    auto size = (*len) * sizeof(cv::Mat*);
    *data_1 = static_cast<cv::Mat**>(CoTaskMemAlloc(size));
    memcpy(*data_1, vec_mat.data(), size);
}

//rv2.cs
[DllImport(@"D:\Coco\Code\C_plus_plus\cpp_dll\x64\Release\cpp_dll.dll")]
private static extern void inference(byte[] img, long data_len, out IntPtr[] data, out int len);

static void Main()
{
    IntPtr[] sss1;
    int itemsCount;
    inference(image_byte_array, ms.Length, out sss1, out itemsCount);
    for (int i = 0; i < itemsCount; i++) // index out of range (the length of ss1 is "1")
    {
       Mat A3 = new Mat(sss1[i]);
       Cv2.ImShow("test3", A3);
       Cv2.WaitKey(2000);
    }
}

问题是,我预计 returned 向量应该有 2 个项目,但结果只有一个项目。

(当我循环遍历 IntPtr[ ] 时,它只得到 1 个项目,然后停止并出现类似 "index out of range" 的错误)

我知道我的代码中一定有一些语法错误,但我不知道它们在哪里以及如何更正它们...

(这似乎是一些 非常基本的语法问题与指针的使用...


由于上面的方法还是可以得到vector的"first"项,我目前可以这样传"multiple" cv::Mat*:

(像这样写代码真的很愚蠢也不合适...)

//rv3.hpp
extern "C" LIB_API void inference(unsigned char* img_pointer, long data_len, cv::Mat*** data_1, cv::Mat ***data_2);

// rv3.cpp
LIB_API void inference(unsigned char* img_pointer, long data_len, cv::Mat ***data_1, cv::Mat ***data_2)
{
    std::vector<cv::Mat*> vec_mat1;
    std::vector<cv::Mat*> vec_mat2;

    cv::Mat A;
    cv::Mat B;
    ..... // process that update A, B
    vec_mat1.push_back(new cv::Mat(A));
    vec_mat2.push_back(new cv::Mat(B));

    auto size = (*len) * sizeof(cv::Mat*);
    *data_1 = static_cast<cv::Mat**>(CoTaskMemAlloc(size));
    *data_2 = static_cast<cv::Mat**>(CoTaskMemAlloc(size));
    memcpy(*data_1, vec_mat1.data(), size);
    memcpy(*data_2, vec_mat2.data(), size);

}

//rv3.cs
[DllImport(@"D:\Coco\Code\C_plus_plus\cpp_dll\x64\Release\cpp_dll.dll")]
private static extern void inference(byte[] img, long data_len, out IntPtr[] data_1, out IntPtr[] data_2);

static void Main()
{
    IntPtr[] sss1, sss2;
    int itemsCount;
    inference(image_byte_array, ms.Length, out sss1, out sss2);

    Mat A3 = new Mat(sss1[0]);
    Cv2.ImShow("test3", A3);
    Cv2.WaitKey(2000);

    Mat A4 = new Mat(sss2[0]);
    Cv2.ImShow("test4", A4);
    Cv2.WaitKey(2000);
}

正如我上面所说,我不认为这是将多个 cv::Mat* 从 dll 传递到 C# 的正确方法。

在我看来应该是这样的:

  1. 用函数的参数

  2. 传递多个cv::Mat*
  3. 多个cv::Mat*的向量传进去,函数的参数是

    (不是只有一个cv::Mat*的多个向量)

但我真的不知道如何正确修改代码,所以非常感谢任何建议或帮助...

(提前感谢您看完我乱七八糟的问题描述!)

因为你已经 return 一个指向 cv::Mat 的指针,你也可以使它成为一个动态数组。

LIB_API void inference(unsigned char* img_pointer, long data_len, cv::Mat*& res, int& img_count, int& mat_type_size)
{
    img_count = 10;
    mat_type_size = sizeof(cv::Mat);
    res = new cv::Mat[img_count]

    for (int i = 0; i < img_count; i++)
    {
        // process each mat 
        cv::Mat& A = res[i];
    }
}

注意

  • cv::Mat*& res 现在是对指针的引用。仅传递指针是行不通的,因为您只是将指针重新分配给新地址。
  • int& img_count 也是一个参考,因此您可以 return 您分配回 C# 的实际图像数量。
  • int& mat_type_size 只是说一个 cv::Mat 对象有多少字节。这是正确递增 C# IntPtr 以指向数组中的下一个图像所必需的。

在您的 C# 代码中,您应该能够像这样导入它(我对编组的了解有限):

[DllImport(@"D:\Coco\Code\C_plus_plus\cpp_dll\x64\Release\cpp_dll.dll")]
private static extern void inference(byte[] img, long data_len, out IntPtr images, ref int img_count, out int mat_type_size);

并像这样使用它:

static void Main()
{   
    int imgCount = 5;
    inference(new byte[10], 10, out var imgPtrs, ref imgCount, out var matTypeSize);

    List<Mat> images = new List<Mat>();
    for (int i = 0; i < imgCount; i++)
            images.Add(new Mat(IntPtr.Add(imgPtrs, i * matTypeSize)));
    // ...
}

我已经测试了代码并且它有效。这就是我的使用方式:

例子

C++

// hpp
extern "C" __declspec(dllexport) void inference(unsigned char* img_pointer, long data_len, cv::Mat * &res, int& img_count, int& mat_type_size);

// cpp
void inference(unsigned char* img_pointer, long data_len, cv::Mat*& res, int& img_count, int& mat_type_size)
{
    mat_type_size = sizeof(cv::Mat);
    res = new cv::Mat[img_count];

    for (int i = 0; i < img_count; i++)
    {
        // process each mat 
        cv::Mat& A = res[i];
        A.create(100, 100, CV_8UC1);
        cv::circle(A, {50, 50}, 10 * i, 255, -1);
    }
}

C#

static class Program
{
    [DllImport(@"Cpp.dll")]
    private static extern void inference(byte[] img, long data_len, out IntPtr images, ref int img_count, out int mat_type_size);


    static void Main(string[] args)
    {            
        int imgCount = 5;
        inference(new byte[10], 10, out var imgPtrs, ref imgCount, out var matTypeSize);

        List<Mat> images = new List<Mat>();
        for (int i = 0; i < imgCount; i++)
                images.Add(new Mat(IntPtr.Add(imgPtrs, i * matTypeSize)));

        foreach (var img in images)
        {
            Cv2.ImShow("Test", img);
            Cv2.WaitKey();
        }            
    }
}

您可以只使用一个函数在两种语言之间传递 Mat 对象。

C++ 函数

//for conversion from c++ to cs
//this variable must be delete after using
std::vector<uchar>* vec = new std::vector<uchar>;
void convertMat2CS(cv::Mat income_mat, uchar** ptr, int* length)
{
    cv::imencode(".png", income_mat, *vec);
    *ptr = &vec[0][0];
    *length = static_cast<int>(vec->size());
}

C# 端


[DllImport(dll)]
public static extern void convertMat2CS(out IntPtr ptr, out int len);

void Main(){
    convertMat2CS(out IntPtr ptr, out int length);
    byte[] pngImageBytes = new byte[length];    
    Marshal.Copy(ptr, pngImageBytes, 0, length);
    Mat mat = new Mat();
    CvInvoke.Imdecode(pngImageBytes, LoadImageType.AnyColor, mat);
    CvInvoke.Imshow("Test_" + showCount, mat);
    CvInvoke.WaitKey(1);

}