我有一个C++类,为此我创建了一个用于C#解决方案的C DLL。现在我需要向DLL发送图像,但我不知道正确的方法。这是C++函数签名:
std::vector<Prediction> Classify(const cv::Mat& img, int N = 2);
我就是这么做的。目前,我尝试在DLL中创建此包装器方法:
#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中的代码:
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#
代码中:
[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的分类中发生 其他信息:试图读取或写入受保护的内存。这通常表明其他内存已损坏。
异常错误显示:
{“试图读取或写入受保护的内存。这通常表示其他内存已损坏”}
对代码的深入研究表明,我在这个函数中得到了异常错误:
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.";
}
当试图调整图像大小时,访问冲突就会发生,这意味着运行此代码段:
//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#代码。新的守则如下:
//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++代码:
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时,我该怎么办?
发布于 2017-08-18 12:35:27
在尝试了许多不同的方法之后,我想对其他寻求同样事情的人来说,了解这一点是有益的。长话短说(see this question),我能找到的最好方法是this (正如@EdChum所说):
我会将文件作为内存传递给您的openCV dll,这应该能够调用imdecode来嗅探文件类型,另外您还可以传递标志。
并解释了here发送指向DLL
的指针,并在那里使用imdecode
解码图像。这解决了其他方法引入的许多问题。也能帮你省去很多头痛。
这是intrest的代码:
我在DLL
和C#
中的函数应该是这样的:
#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);
//...
}
实际方法:
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导入:
[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的实际方法:
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);
}
https://stackoverflow.com/questions/45697930
复制相似问题