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

标签 c# c++ opencv dll

我正在开发一个项目,该项目需要一个 dll 文件供另一个用 c# 编写的程序使用(我不太熟悉 C++/C# 的用法)。对于完成我的工作的最后一步,我在将“多个”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);
}

由于它成功运行,我计划使用相同的语法并使用函数通过参数传递结果,以便我可以根据需要返回多个 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的部分,但是这段代码并没有要么工作...

// 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: Attempted to read or write protected memory)


然后我想出了另一种方法,传递一个带有函数参数的cv::Mat* vector ,代码如下:

//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);
    }
}

问题是,我预计返回的 vector 中应该有 2 项,但结果只有一项。

(当我循环遍历 IntPtr[ ] 时,它只得到 1 个项目,然后因“索引超出范围”之类的错误而停止)

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

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


由于上面的方法仍然可以获得 vector 的“第一个”项,我目前可以通过这种方式传递“多个”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. 用函数的参数传递多个cv::Mat*

  2. 将函数的参数传递给一个包含多个cv::Mat*的 vector

    (不是每个只有一个 cv::Mat* 的多个 vector )

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

(提前感谢您阅读我凌乱的问题描述!)

最佳答案

因为您已经返回了一个指向 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 也是一个引用,因此您可以将分配给 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();
        }            
    }
}

关于c# - 如何正确地将多个 cv::Mat 从 c++ dll 传递到 opencvsharp Mat c#?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/58271603/

相关文章:

android - Android Studio 中的 OpenCV - Haar Cascade - 如何检查是否检测到图像

c# - 解码 OAEP 填充时出错

c# - 单击 gridview 列标题整个 gridview 试图覆盖窗口

c# - ASP.NET MVC : No owin. 环境

c++ - 命名嵌套类时无法在 Lambda 中解析名称

c++ - 为什么匿名对象有时需要默认构造函数?

c++ - 在Linux中以最低延迟从多播套接字接收数据

qt - Windows 标题栏使用 QDockWidget 消失——使用非暴露窗口调用 qbackingstore::flush()

c# - Entity Framework 函数是否自动转义输入以防止注入(inject)?

c# - 即使解决方案中的示例正常工作,emgucv示例的精确副本也无法工作