首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >如何正确地将图像从C#发送到接受OpenCV Mat的DLL?

如何正确地将图像从C#发送到接受OpenCV Mat的DLL?
EN

Stack Overflow用户
提问于 2017-08-15 17:10:21
回答 1查看 2.9K关注 0票数 4

我有一个C++类,为此我创建了一个用于C#解决方案的C DLL。现在我需要向DLL发送图像,但我不知道正确的方法。这是C++函数签名:

代码语言:javascript
运行
复制
std::vector<Prediction> Classify(const cv::Mat& img, int N = 2);  

我就是这么做的。目前,我尝试在DLL中创建此包装器方法:

代码语言:javascript
运行
复制
#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);
    //...
}

dll中的代码:

代码语言:javascript
运行
复制
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_32FC3, (void*)img_pointer);

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

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

在我编写的C#代码中:

代码语言:javascript
运行
复制
[DllImport(@"CDll2.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
static extern void Classify_image(byte[] img, uint height, uint width, byte[] out_result, out int out_result_length, int top_n_results = 2);

private string Classify(Bitmap img, int top_n_results)
{
    byte[] result = new byte[200];
    int len;
    var img_byte = (byte[])(new ImageConverter()).ConvertTo(img, typeof(byte[]));

    Classify_image(img_byte, (uint)img.Height, (uint)img.Width,res, out len, top_n_results);

    return  ASCIIEncoding.ASCII.GetString(result);
}

但是,每当我试图运行代码时,我都会得到访问冲突错误:

“System.AccessViolationException”类型的未处理异常在使用dotNet.exe的分类中发生 其他信息:试图读取或写入受保护的内存。这通常表明其他内存已损坏。

异常错误显示:

{“试图读取或写入受保护的内存。这通常表示其他内存已损坏”}

对代码的深入研究表明,我在这个函数中得到了异常错误:

代码语言:javascript
运行
复制
void Classifier::Preprocess(const cv::Mat& img, std::vector<cv::Mat>* input_channels)
{
    /* Convert the input image to the input image format of the network. */
    cv::Mat sample;
    if (img.channels() == 3 && num_channels_ == 1)
        cv::cvtColor(img, sample, cv::COLOR_BGR2GRAY);
    else if (img.channels() == 4 && num_channels_ == 1)
        cv::cvtColor(img, sample, cv::COLOR_BGRA2GRAY);
    else if (img.channels() == 4 && num_channels_ == 3)
        cv::cvtColor(img, sample, cv::COLOR_BGRA2BGR);
    else if (img.channels() == 1 && num_channels_ == 3)
        cv::cvtColor(img, sample, cv::COLOR_GRAY2BGR);
    else
        sample = img;

    //resize image according to the input
    cv::Mat sample_resized;
    if (sample.size() != input_geometry_)
        cv::resize(sample, sample_resized, input_geometry_);
    else
        sample_resized = sample;

    cv::Mat sample_float;
    if (num_channels_ == 3)
        sample_resized.convertTo(sample_float, CV_32FC3);
    else
        sample_resized.convertTo(sample_float, CV_32FC1);

    cv::Mat sample_normalized;
    cv::subtract(sample_float, mean_, sample_normalized);

    /* This operation will write the separate BGR planes directly to the
    * input layer of the network because it is wrapped by the cv::Mat
    * objects in input_channels. */
    cv::split(sample_normalized, *input_channels);

    CHECK(reinterpret_cast<float*>(input_channels->at(0).data)
        == net_->input_blobs()[0]->cpu_data())
        << "Input channels are not wrapping the input layer of the network.";
}

当试图调整图像大小时,访问冲突就会发生,这意味着运行此代码段:

代码语言:javascript
运行
复制
//resize image according to the input
cv::Mat sample_resized;
if (sample.size() != input_geometry_)
    cv::resize(sample, sample_resized, input_geometry_);

进一步的调查和调试(here)使罪魁祸首清晰可见!

这种方法被证明是完全错误的,或者至少是错误的。使用此代码,C++侧的图像似乎已被正确初始化,通道数、高度和宽度似乎都很好。

但是,当您试图使用该图像时,无论是通过调整大小,还是使用imshow()显示它,它都会使应用程序崩溃,并给出访问冲突异常,这与调整大小和在问题中发布时发生的错误完全相同。

看看这个answer,我更改了负责将图像传递给dll的C#代码。新的守则如下:

代码语言:javascript
运行
复制
//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);

//...
//main code 

Bitmap img = new Bitmap(txtImagePath.Text);
BitmapData bmpData = img.LockBits(new Rectangle(0, 0, img.Width, img.Height),  ImageLockMode.ReadWrite,  PixelFormat.Format24bppRgb);
result = Classify_UsingImage(bmpData, 1);
img.UnlockBits(bmpData); //Remember to unlock!!!

以及DLL中的C++代码:

代码语言:javascript
运行
复制
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();
    }

这样做就纠正了我以前的所有违规行为。

虽然我现在可以轻松地将图像从C#发送到DLL,但是我的当前实现有一些问题。我不知道如何将OpenCV类型从C#发送到所需的函数,目前我使用的是硬编码的图像类型(如您所见),这就引出了一个问题,当输入图像是灰度的,甚至是带有4个通道的png时,我该怎么办?

EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2017-08-18 12:35:27

在尝试了许多不同的方法之后,我想对其他寻求同样事情的人来说,了解这一点是有益的。长话短说(see this question),我能找到的最好方法是this (正如@EdChum所说):

我会将文件作为内存传递给您的openCV dll,这应该能够调用imdecode来嗅探文件类型,另外您还可以传递标志。

并解释了here发送指向DLL的指针,并在那里使用imdecode解码图像。这解决了其他方法引入的许多问题。也能帮你省去很多头痛。

这是intrest的代码:

我在DLLC#中的函数应该是这样的:

代码语言:javascript
运行
复制
#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, long data_len, char* out_result, int* length_of_out_result, int top_n_results = 2);
//...
}

实际方法:

代码语言:javascript
运行
复制
CDLL2_API void Classify_Image(unsigned char* img_pointer, long data_len,
                              char* out_result, int* length_of_out_result, int top_n_results)
{
    auto classifier = reinterpret_cast<Classifier*>(GetHandle());
    vector<unsigned char> inputImageBytes(img_pointer, img_pointer + data_len);
    cv::Mat img = imdecode(inputImageBytes, CV_LOAD_IMAGE_COLOR);

    cv::imshow("img just recieved from c#", img);

    std::vector<Prediction> result = classifier->Classify(img, top_n_results);
    //...
    *length_of_out_result = ss.str().length();
}

下面是C# Dll导入:

代码语言:javascript
运行
复制
[DllImport(@"CDll2.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
static extern void Classify_Image(byte[] img, long data_len, byte[] out_result, out int out_result_length, int top_n_results = 2);

这是将图像发送回DLL的实际方法:

代码语言:javascript
运行
复制
private string Classify_UsingImage(Bitmap image, int top_n_results)
{
    byte[] result = new byte[200];
    int len;
    Bitmap img;

    if (chkResizeImageCShap.Checked)
        img = ResizeImage(image, int.Parse(txtWidth.Text), (int.Parse(txtHeight.Text)));
    else
        img = image;

    ImageFormat fmt = new ImageFormat(image.RawFormat.Guid);

    var imageCodecInfo = ImageCodecInfo.GetImageEncoders().FirstOrDefault(codec => codec.FormatID == image.RawFormat.Guid);
    //this is for situations, where the image is not read from disk, and is stored in the memort(e.g. image comes from a camera or snapshot)
    if (imageCodecInfo == null)
    {
        fmt = ImageFormat.Jpeg;
    }

    using (MemoryStream ms = new MemoryStream())
    {
        img.Save(ms,fmt);
        byte[] image_byte_array = ms.ToArray();
        Classify_Image(image_byte_array, ms.Length, result, out len, top_n_results);
    }

    return ASCIIEncoding.ASCII.GetString(result);
}
票数 3
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/45697930

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档