c#向c++(opencv)实现双向图像数据传递,以及内存空间申请与释放问题
本文转自:https://blog.csdn.net/xiaochuideai/article/details/128920338
c#与c++实现图像数据的双向数据传输
- 一、c#中基础数据类型对应的c++中的基础数据类型以及转换过程注意事项
- 二、c#将image传递给向c++.dll中的图像处理函数(opencv实现)使用
- 三、c++实现将opencv中的cv::Mat向c#中传递
- 四、关于c#与c++相互传递指针以及内存释放的问题
一、c#中基础数据类型对应的c++中的基础数据类型以及转换过程注意事项
c++与c#之间对应的数据关系: https://blog.csdn.net/qq_44544908/article/details/128784250
c++转换到c#的辅助工具(推荐,可以自动按照输入的c++代码函数或结构体转换为c#代码段): https://codeload.github.com/jaredpar/pinvoke-interop-assistant/zip/refs/heads/master
数据转换的基本依据:
一个数据有两个特征:数据类型和起始位置,读取数据一般是定位起始位置,然后按照类型长度去读取对应内存。c#与c++之间相互数据转换需要注意的点:
- 由于.NET在运行时会存在堆内存来回转移的情况,所以c#的存储在堆中的数据地址会不断的发生变换,如果将c#中数据的地址传递给c++,那么就不能保证传递的地址真正包含我们想传递的数据。所以在将c#数据传递给c++时,需要将c#中的数据进行内存固定操作。可以参考下图2,引用类型的数据是存储在堆中的,而地址是存储在栈中的,而堆中的数据的长度是可变的。所以必须指定一个固定大小的存储区域。
- 指针在32为OS上是一个32位的整数,在64位机OS上是一个64位整数。所以可以利用整形来接收指针。
重点:
由以上可得:将c#中的引用类型数据传递给c++时,必须要将c#中的引用类型数据进行固化处理。
参考链接: https://www.cnblogs.com/warensoft/archive/2011/12/09/Warenosoft3D.html
c#中创建指定长度内存空间的方法:
方法:使用Marshal.AllocHGlobal(length)方法创建固定长度的指针。
二、c#将image传递给向c++.dll中的图像处理函数(opencv实现)使用
- 第一步:创建c++的.dll工程并导入opencv相关库,并生成.dll文件
// 添加Test.h
#pragma once
extern "C" __declspec(dllexport) void GetImageFromCSharp(unsigned char* data, int width, int height, int channels); // 接收c#传递的数据
// 添加Test.cpp
#include "pch.h"
#include "Test.h"
#include "opencv2/core.hpp"
#include "opencv2/imgproc.hpp"
#include <opencv2/opencv.hpp>
using namespace cv;
void GetImageFromCSharp(unsigned char * data, int width, int height, int channels)
{
Mat frame = Mat(height, width, CV_MAKETYPE(CV_8U,channels), data);
// 其他图像处理程序
namedWindow("beautiful", WINDOW_NORMAL);
imshow("beautiful", frame);
waitKey(0);
return;
}
2.第二步:创建C#应用程序,调用.dll
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Drawing.Imaging;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace WindowsFormsApp1
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
[DllImport(@"D:\AFile_LIMAOHUA\OpencvProject\NEW_CSHARP\WindowsFormsApp1\WindowsFormsApp1\bin\Debug\Dll1.dll")]
public extern static void GetImageFromCSharp(IntPtr data, int width, int height, int channels);
private void button1_Click(object sender, EventArgs e)
{
// 读取图片
OpenFileDialog Dialog = new OpenFileDialog();
if (Dialog.ShowDialog() == DialogResult.OK)
{
string filepath = Dialog.FileName;
Image img = Image.FromFile(filepath);
Bitmap bitmap = new Bitmap(img);
// 系统内存中锁定现有的位图
BitmapData data = bitmap.LockBits(new Rectangle(0, 0, bitmap.Width, bitmap.Height), ImageLockMode.ReadWrite, bitmap.PixelFormat);
pictureBox1.Image = img;
int channel = 4;
if (bitmap.PixelFormat == PixelFormat.Format8bppIndexed)
{
channel = 1;
}
else if (bitmap.PixelFormat == PixelFormat.Format24bppRgb)
{
channel = 3;
}
else if (bitmap.PixelFormat == PixelFormat.Format32bppArgb)
{
channel = 4;
}
GetImageFromCSharp(data.Scan0, img.Width, img.Height, channel);
bitmap.UnlockBits(data);
}
return;
}
}
}
注意:C# System.DllNotFoundException 解决之路无法加载DLL“xxxx”:找不到指定的模块(异常来自HRESULT:0X8007007E),一般是因为缺少相关的依赖项。这一点opencv的安装可能有问题。
三、c++实现将opencv中的cv::Mat向c#中传递
问题现象1
- 创建.dll工程,并添加如下代码段
// C++头文件代码
#pragma once
extern "C" __declspec(dllexport) unsigned char* GetImageFromCSharp1(int& width, int& height, int& channels, int& stride); // 实现将Mat图像传递给c#调用
extern "C" __declspec(dllexport) void releasRoom(); // 内存空间释放函数
extern "C" __declspec(dllexport) unsigned char* GetBuffer(); // 用于查看.dll中申请的内存空间是否被完全释放
// c++.cpp文件
#include "pch.h"
#include "Test.h"
#include "opencv2/core.hpp"
#include "opencv2/imgproc.hpp"
#include <opencv2/opencv.hpp>
using namespace cv;
unsigned char* imageBuffer; // 创建一个全局变量,用于存储c++创建的Mat数据地址
void GetImageFromCSharp(unsigned char * data, int width, int height, int channels, int stride)
{
Mat frame = Mat(height, width, CV_MAKETYPE(CV_8U,channels), data, static_cast<size_t>(stride));
namedWindow("beautiful", WINDOW_NORMAL);
imshow("beautiful", frame);
waitKey(6000);
return;
}
unsigned char* GetImageFromCSharp1(int & width, int & height, int & channels, int& stride)
{
Mat frame = imread("C:/Users/hp/Desktop/tupian1/103919966.jpg"); // 创建Mat图像,并进行一系列图像处理
width = frame.cols;
height = frame.rows;
channels = frame.channels();
stride = static_cast<int>(frame.step);
// malloc返回类型为void*需要类型转换
imageBuffer = static_cast<unsigned char*>(malloc(width*height*channels*sizeof(unsigned char)));
if(imageBuffer != NULL) // 用于判断内存空间是否申请成功
{
for (int i = 0; i < width*height*channels; i++)
{
imageBuffer[i] = (unsigned char)frame.data[i];
}
}
return imageBuffer;
}
void releasRoom() // 释放malloc加载的内存空间
{
if (imageBuffer == NULL) return;
free(imageBuffer);
imageBuffer = NULL;
}
unsigned char * GetBuffer() // 用于查看申请的内存空间是否被释放
{
return imageBuffer;
}
- c#中调用.dll代码
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Runtime.Serialization.Formatters.Binary;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace WindowsFormsApp1
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
// 导入.dll中的函数
[DllImport(@"D:\AFile_LIMAOHUA\OpencvProject\NEW_CSHARP\WindowsFormsApp1\WindowsFormsApp1\bin\Debug\Dll1.dll")]
public static extern IntPtr GetImageFromCSharp1(ref int width, ref int height, ref int channels, ref int stride);
[DllImport(@"D:\AFile_LIMAOHUA\OpencvProject\NEW_CSHARP\WindowsFormsApp1\WindowsFormsApp1\bin\Debug\Dll1.dll")]
public static extern void releaseRoom();
[DllImport(@"D:\AFile_LIMAOHUA\OpencvProject\NEW_CSHARP\WindowsFormsApp1\WindowsFormsApp1\bin\Debug\Dll1.dll")]
public static extern IntPtr GetBuffer();
private void button2_Click(object sender, EventArgs e) // 从c++获取图像数据
{
// 读取图片
IntPtr data = IntPtr.Zero; // 初始化指针为空
int width = 0, height=0, stride=0, channels=0;
data = GetImageFromCSharp1(ref width, ref height, ref channels, ref stride);
if (data != null)
{
PixelFormat format = new PixelFormat();
switch (channels)
{
case 1:
format = PixelFormat.Format8bppIndexed;
break;
case 3:
format = PixelFormat.Format24bppRgb;
break;
case 4:
format = PixelFormat.Format32bppArgb;
break;
}
int stride1 = (stride / (int)4) * 4 + 4;
Bitmap temImg = new Bitmap(width, height, stride1, format, data);
// 这里进行数据的深度拷贝,防止.dll中对存储图像数据的空间进行释放后,c#中的data指针指向数据为空
Bitmap dstBitmap = null;
using (MemoryStream mStream = new MemoryStream())
{
BinaryFormatter bf = new BinaryFormatter();
bf.Serialize(mStream, temImg);
mStream.Seek(0, SeekOrigin.Begin);
dstBitmap = (Bitmap)bf.Deserialize(mStream);
mStream.Close();
}
IntPtr F = GetBuffer(); // 查看c++空间下存储图像数据地址不为空
releaseRoom(); // 释放c++中申请的空间
IntPtr D = GetBuffer(); // 查看c++空间下存储图像数据地址为空
Image img = Image.FromHbitmap(dstBitmap.GetHbitmap())
pictureBox1.Image = img;
}
else
{
DialogResult a = MessageBox.Show("没有数据");
}
return;
}
}
}
注意1: 但是上面的代码会出现图像变形问题,这主要是因为输入的图像的widthStep不能被4整除所导致的。上述代码只有当图像的widthStep能被4整除时才能够正常运行。
注意2: 在.dll中申请的内存空间,最好也是在.dll中进行释放。
针对问题1的代码改进
- 将c++代码更改为:
unsigned char* GetImageFromCSharp1(int & width, int & height, int & channels, int& stride)
{
Mat frame = imread("C:/Users/hp/Desktop/67.JPG");
// 将图像转变为每行的字节数能够被4整处的大小
Mat NewImage = cv::Mat::zeros(frame.rows, (frame.cols + 3) / 4 * 4, frame.type());
frame.copyTo(NewImage(Rect(0,0, frame.cols, frame.rows)));
width = NewImage.cols;
height = NewImage.rows;
channels = NewImage.channels();
stride = static_cast<int>(NewImage.step);
// malloc返回类型为void*需要类型转换,这里可以更换为其他形式,主要是为了测试c#中调用.dll的内存释放功能
imageBuffer = static_cast<unsigned char*>(malloc(width*height*channels * sizeof(unsigned char)));
for (int i = 0; i < width*height*channels; i++)
{
imageBuffer[i] = (unsigned char)NewImage.data[i];
}
return imageBuffer;
}
void releaseRoom()
{
if (imageBuffer == NULL) return;
free(imageBuffer);
imageBuffer = NULL;
}
unsigned char * GetBuffer()
{
return imageBuffer;
}
- 更改c#代码(直接去掉其中一行代码如下):
// int stride1 = (stride / (int)4) * 4 + 4;
Bitmap temImg = new Bitmap(width, height, stride, format, data);
- 结果显示如下所示(该图像相较于原图多了2个像素列):
四、关于c#与c++相互传递指针以及内存释放的问题
下面内容引用参考链接: https://www.cnblogs.com/moon3/p/14509714.html
配带代码解释链接: https://www.cnblogs.com/deathmr/p/6055561.html
4.1 传入.dll前,在c#中申请内存空间
// c# 下申请内存空间
IntPtr SrcImage = Marshal.AllocHGlobal(length); // 必须指定大小
// c#释放内存
Marshal.FreeHGlobal(SrcImage);
4.2 .dll内部申请内存空间
情况一:
// c++的.dll中申请内存空间一般使用(new,delete/delete[])/(malloc,free)
// 那么在c++.dll中申请的空间,就需要dll中提供内存空间释放接口。
情况二:
// 在COM中:在c++.dll中使用CoTaskMemAlloc分配内存,可以在c#中使用CoTaskMemFree释放内存。
更多推荐
所有评论(0)