c#向c++(opencv)实现双向图像数据传递,以及内存空间申请与释放问题

2023-05-16

c#与c++实现图像数据的双向数据传输

  • 一、c#中基础数据类型对应的c++中的基础数据类型以及转换过程注意事项
  • 二、c#将image传递给向c++.dll中的图像处理函数(opencv实现)使用
  • 三、c++实现将opencv中的cv::Mat向c#中传递
    • 问题现象1
    • 针对问题1的代码改进
  • 四、关于c#与c++相互传递指针以及内存释放的问题
    • 4.1 传入.dll前,在c#中申请内存空间
    • 4.2 .dll内部申请内存空间

一、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++之间相互数据转换需要注意的点:

  1. 由于.NET在运行时会存在堆内存来回转移的情况,所以c#的存储在堆中的数据地址会不断的发生变换,如果将c#中数据的地址传递给c++,那么就不能保证传递的地址真正包含我们想传递的数据。所以在将c#数据传递给c++时,需要将c#中的数据进行内存固定操作。可以参考下图2,引用类型的数据是存储在堆中的,而地址是存储在栈中的,而堆中的数据的长度是可变的。所以必须指定一个固定大小的存储区域。
  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实现)使用

  1. 第一步:创建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;
}
  1. 第二步:创建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

  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;
}
  1. 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的代码改进

  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;
}
  1. 更改c#代码(直接去掉其中一行代码如下):
// int stride1 = (stride / (int)4) * 4 + 4; 
Bitmap temImg = new Bitmap(width, height, stride, format, data);
  1. 结果显示如下所示(该图像相较于原图多了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释放内存。
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

c#向c++(opencv)实现双向图像数据传递,以及内存空间申请与释放问题 的相关文章

随机推荐