C#调用C++系列二:传结构体
这一篇记录下C#调用C++的结构体的方式来使用OpenCV的数据格式,这里会有两种方式,第一种是C#传一个结构体和图像的路径给C++,然后C++将图像加载进来,再把传进来的结构体填满即可,第二种是C#加载好图像之后传给C++去使用OpenCV处理图像。
情形一:C#传结构体给C++填满
这一种跟系列一的方式是一样的,只不过我将很多参数封装为一个结构体罢了,调用起来也就是函数参数看起来变少了而已。这种方法也是要将OpenCV的数据结构进行拆解,不管是Mat还是IplImage,要构造一个图像都是数据指针、图像长宽、图像步长和图像通道数即可,所以我要在C++端构造一个结构体,包含这几个变量即可:
struct cppCVMat
{
uchar *m_pData;
int m_nWidth;
int m_nHeight;
int m_nStep;
int m_nChannels;
};
接下来C++的函数就是对这个结构体进行补充就好了:
int _stdcall load_cv_mat(char* filepath, cppCVMat &cppMat)
{
if (NULL == filepath)
{
return -1;
}
IplImage *ptrSrc = NULL;
if (1 == cppMat.m_nChannels)
{
ptrSrc = cvLoadImage(filepath, CV_LOAD_IMAGE_GRAYSCALE);
}
else
{
ptrSrc = cvLoadImage(filepath, CV_LOAD_IMAGE_COLOR);
}
if (NULL == ptrSrc)
{
return -1;
}
cppMat.m_pData = (uchar *)ptrSrc->imageData;
cppMat.m_nWidth = ptrSrc->width;
cppMat.m_nHeight = ptrSrc->height;
cppMat.m_nStep = ptrSrc->widthStep;
cppMat.m_nChannels = ptrSrc->nChannels;
return 1;
}
这里可以看到还是用的IplImage,不用Mat的重要原因就是Mat会自动回收内存的,所以这种方式下是不适合用Mat的。
C#端的调用也是先定义好一个相似的结构体:
struct cppMat
{
public IntPtr m_pData;
public int m_nWidth;
public int m_nHeight;
public int m_nStep;
public int m_nChannels;
}
可以看到,这里我用IntPtr来对应C++的uchar*,其他的参数基本不变,int还是对应int,然后就是C#端引入dll和调用函数:
[DllImport("Cs_use_Cpp_ch2.dll")]
static extern int load_cv_mat(string filepath, out cppMat cMat);
引入函数之后就直接调用就可以了,只不过我这里在使用的时候要先声明好结构体的m_nChannels的值,因为C++中用这个值来确定要加载单通道还是双通道,可以显示下我简陋的C#界面:

原谅我没有学C#,只能简单弄一个。
情形二:C#加载图像传结构体给C++做图像处理
这种情形是C#自己加载好图像了,不需要用C++去加载图像,主要是确保C#需要的图像由C#自己去开辟内存自己回收,C++需要的图像由C++自己去开辟内存自己回收。
这一部分为了测试我就去查了下C#加载本地图像的一个做法。在C#端我测试的图像数据结构是Bitmap,Bitmap通过Bitmap.FromFile()函数从本地加载图像进来,不过要注意这个函数返回的是一个Image的数据类型,所以应该在前面要加一个数据类型转换:
Bitmap img = (Bitmap)Bitmap.FromFile(filename, false);
加载了图像之后我们需要填充之前的数据结构,但是这个数据结构填充需要由图像的数据指针,在C#下应该叫裸数据吧,查到裸数据可以通过以下方式来获取图像的裸数据:
BitmapData bmpData = img.LockBits(new Rectangle(0, 0, img.Width, img.Height), ImageLockMode.ReadOnly, img.PixelFormat);
cppMat cMat = new cppMat()
{
m_pData = bmpData.Scan0,
m_nWidth = img.Width,
m_nHeight = img.Height,
m_nStep = bmpData.Stride,
m_nChannels = 3
};
int result = cppCVMat_to_mat(cMat);
然后C++端是这样写这个cppCVMat_to_mat()函数的:
int _stdcall cppCVMat_to_mat(cppCVMat cppMat)
{
cv::Mat src;
if (cppMat.m_nChannels == 1)
{
src = cv::Mat(cppMat.m_nHeight, cppMat.m_nWidth, CV_8UC1, cppMat.m_pData, cppMat.m_nStep);
}
else if (cppMat.m_nChannels == 3)
{
src = cv::Mat(cppMat.m_nHeight, cppMat.m_nWidth, CV_8UC3, cppMat.m_pData, cppMat.m_nStep);
}
if (!src.data || src.empty())
{
return -1;
}
cv::imshow("src", src);
cv::waitKey(0);
return 1;
}
然后显示的结果是:

额,这个是C#的原因啦,所以要在C#的代码里填充好结构体之后或者说用完img.LockBits()之后就补上
img.UnlockBits(bmpData);
不然就会报错。然后再次运行发现根本没有显示,因为上面C#加载进来之后是四通道的图像,但是C++里面只有1通道和3通道,所以这里有两种做法,要么C++的Mat改用四通道(CV_8UC4),要么C#转三通道,因为考虑到用OpenCV的时候是三通道和单通道最常见,所以我倾向于将C#的四通道改为三通道,这个的话可以用以下方法来转:
Bitmap bmp32 = (Bitmap)Bitmap.FromFile(filename, false);
Bitmap bmp24 = new Bitmap(bmp32.Width, bmp32.Height, PixelFormat.Format24bppRgb);
Graphics g = Graphics.FromImage(bmp24);
g.DrawImage(bmp32, new Rectangle(0, 0, bmp32.Width, bmp32.Height));
这样就获取了三通道的图像,然后显示结果如下:

我一开始还觉得要做通道调换的,还以为C#端是RGB的顺序,C++端OpenCV的默认顺序是BGR,但是结果好像都不需要转了。完整的代码如下:
C++端头文件:
#pragma once
#include "opencv.hpp"
struct cppCVMat
{
uchar *m_pData;
int m_nWidth;
int m_nHeight;
int m_nStep;
int m_nChannels;
};
extern "C" __declspec(dllexport) int _stdcall load_cv_mat(char* filepath, cppCVMat &cppMat);
extern "C" __declspec(dllexport) int _stdcall cppCVMat_to_mat(cppCVMat cppMat);
C++端源文件:
#include "Cs_use_Cpp_ch2.h"
int _stdcall load_cv_mat(char* filepath, cppCVMat &cppMat)
{
if (NULL == filepath)
{
return -1;
}
IplImage *ptrSrc = NULL;
if (1 == cppMat.m_nChannels)
{
ptrSrc = cvLoadImage(filepath, CV_LOAD_IMAGE_GRAYSCALE);
}
else
{
ptrSrc = cvLoadImage(filepath, CV_LOAD_IMAGE_COLOR);
}
if (NULL == ptrSrc)
{
return -1;
}
cppMat.m_pData = (uchar *)ptrSrc->imageData;
cppMat.m_nWidth = ptrSrc->width;
cppMat.m_nHeight = ptrSrc->height;
cppMat.m_nStep = ptrSrc->widthStep;
cppMat.m_nChannels = ptrSrc->nChannels;
return 1;
}
int _stdcall cppCVMat_to_mat(cppCVMat cppMat)
{
cv::Mat src;
if (cppMat.m_nChannels == 1)
{
src = cv::Mat(cppMat.m_nHeight, cppMat.m_nWidth, CV_8UC1, cppMat.m_pData, cppMat.m_nStep);
}
else if (cppMat.m_nChannels == 3)
{
src = cv::Mat(cppMat.m_nHeight, cppMat.m_nWidth, CV_8UC3, cppMat.m_pData, cppMat.m_nStep);
}
if (!src.data || src.empty())
{
return -1;
}
//cv::cvtColor(src, src, cv::COLOR_RGB2BGR);
cv::imshow("src", src);
cv::waitKey(0);
return 1;
}
C#端:
public partial class Form1 : Form
{
public object BitmapFactory { get; private set; }
struct cppMat
{
public IntPtr m_pData;
public int m_nWidth;
public int m_nHeight;
public int m_nStep;
public int m_nChannels;
}
[DllImport("Cs_use_Cpp_ch2.dll")]
static extern int load_cv_mat(string filepath, out cppMat cMat);
[DllImport("Cs_use_Cpp_ch2.dll")]
static extern int cppCVMat_to_mat(cppMat cMat);
public Form1()
{
InitializeComponent();
}
private void load_struct_Click(object sender, EventArgs e)
{
OpenFileDialog dialog = new OpenFileDialog();
if (dialog.ShowDialog() == DialogResult.OK)
{
//获取文件路径
string filename = dialog.FileName;
cppMat cMat = new cppMat()
{
m_nChannels = 3
};
//调用函数
int result = load_cv_mat(filename, out cMat);
Bitmap img = new Bitmap(cMat.m_nWidth, cMat.m_nHeight, cMat.m_nStep, System.Drawing.Imaging.PixelFormat.Format24bppRgb, cMat.m_pData);
pictureBox1.Image = img;
}
}
private void bitmap_2_mat_Click(object sender, EventArgs e)
{
OpenFileDialog dialog = new OpenFileDialog();
if (dialog.ShowDialog() == DialogResult.OK)
{
//获取文件路径
string filename = dialog.FileName;
Bitmap bmp32=(Bitmap)Bitmap.FromFile(filename, false);
Bitmap bmp24 = new Bitmap(bmp32.Width, bmp32.Height, PixelFormat.Format24bppRgb);
Graphics g = Graphics.FromImage(bmp24);
g.DrawImage(bmp32, new Rectangle(0, 0, bmp32.Width, bmp32.Height));
BitmapData bmpData = bmp24.LockBits(new Rectangle(0, 0,bmp24.Width, bmp24.Height), ImageLockMode.ReadOnly, bmp24.PixelFormat);
bmp24.UnlockBits(bmpData);
pictureBox1.Image = bmp24;
cppMat cMat = new cppMat()
{
m_pData = bmpData.Scan0,
m_nWidth = bmp24.Width,
m_nHeight = bmp24.Height,
m_nStep = bmpData.Stride,
m_nChannels = 3
};
int result = cppCVMat_to_mat(cMat);
}
}
}
漆黑的夜里有一种笑声笑断我坟墓的木板
你可知道。
这是一片埋葬老虎的土地
正当水面上渡过一只火红的老虎
你的笑声使河流漂浮
的老虎
断了两根骨头
正当这条河流开始在存有笑声的黑夜里结冰
断腿的老虎顺流而下
来到我的
窗前。
一块埋葬老虎的木板
被一种笑声笑断两截
C#调用C++系列二:传结构体的更多相关文章
- C语言程序设计(十二) 结构体和共用体
第十二章 结构体和共用体 当需要表示复杂对象时,仅使用几个基本数据类型显然是不够的 根本的解决方法是允许用户自定义数据类型 构造数据类型(复合数据类型)允许用户根据实际需要利用已有的基本数据类型来构造 ...
- python调用c/c++时传递结构体参数
背景:使用python调用linux的动态库SO文件,并调用里边的c函数,向里边传递结构体参数.直接上代码 //test1.c # include <stdio.h> # include ...
- C# 调用C/C++动态链接库,结构体中的char*类型
用C#掉用C++的dll直接import就可以之前有不同的类型对应,当要传递结构体的时候就有点麻烦了,这里有一个结构体里边有char*类型,这个类型在C#中调用没法声明,传string是不行的默认st ...
- C和指针 第十二章 结构体 整体赋值 error: expected expression
定义结构体后整体赋值时发生错误 typedef struct NODE { struct NODE *fwd; struct NODE *bwd; int value; } Node; //声明变量 ...
- ctypes 操作 python 与 c++ dll 互传结构体指针
CMakeLists.txt # project(工程名) project(blog-3123958139-1) # add_library(链接库名称 SHARED 链接库代码) add_libra ...
- Qt socket中怎么传结构体?
直接发送和接收结构体,例如:struct A {...};struct A objectA; 发送的时候: tcpSocket->write((char *)&objectA, size ...
- STM32L0系列EEPROM中结构体的读取
在STM32L0中操作EEPROM本来参考了上篇操作FLASH的方法,多多少少都有些问题.我觉得可能是结构体在转换成其他变量的时候出了问题. 比如下面这段代码,在Windows上可以正常运行(使用g+ ...
- jvm系列二内存结构
二.内存结构 整体架构 1.程序计数器 作用 用于保存JVM中下一条所要执行的指令的地址 特点 线程私有 CPU会为每个线程分配时间片,当当前线程的时间片使用完以后,CPU就会去执行另一个线程中的代码 ...
- C和指针 第十二章 结构体 习题
12.3 重新编写12.7,使用头和尾指针分别以一个单独的指针传递给函数,而不是作为一个节点的一部分 #include <stdio.h> #include <stdlib.h> ...
随机推荐
- 解题报告:luogu P2678 跳石头
题目链接:P2678 跳石头 很简单的二分查找,可悲的是我并不会. 不过题解贴心的写得很清楚(学会了套路) 二分一次判断一次,复杂度是\(O(nlogl)\),可以通过此题. \(Code:\) #i ...
- C++中数据类型表示范围
32位机器环境下结果如下: Type Size 数值范围 无值型void 0 byte 无值域 布尔型bool 1 byte true false 有符号短整型short [int] /signe ...
- YOKOGAWA ProSafe-RS 通道测试 CENTUMVP
20180927 我并没有调试这个项目 仅仅是听同事讲解了 横河ProSafe-RS通道测试 然后做了笔记 软件安装并不在本记录中 ProSafe-RS版本 CENTUMVP版本 ProSafe-RS ...
- c# 外挂操作(内存操作)(内存读写取窗口句柄移动窗口取模块地址)工具类
来源于网上 参考 https://www.cnblogs.com/fuhua/p/5877781.html 等众多文章 详情取看我第二个例子封装功能较多 https://www.cnblogs.co ...
- 创建一个TCP代理
代理技术 代理一般被我们用于"穿墙",具体来说可以看这里,或者查wiki.理解代理背后的原理之后,便很容易知道,代理的作用不仅仅只是"穿墙".例如我们可以把经过 ...
- netty权威指南学习笔记七——编解码技术之GoogleProtobuf
首先我们来看一下protobuf的优点: 谷歌长期使用成熟度高: 跨语言支持多种语言如:C++,java,Python: 编码后消息更小,更利于存储传输: 编解码性能高: 支持不同协议版本的兼容性: ...
- 基于云开发开发 Web 应用(三):云开发相关数据调用
介绍 在完成了 UI 界面的实现后,接下来可以开始进行和云开发相关的数据对接.完成数据对接后,应用基础就打好了,接下来的就是发布上线以及一些小的 feature 的加入. 配置 在进行相关的配置调用的 ...
- python中添加requests资源包
1.进入资源网址下载:https://www.lfd.uci.edu/~gohlke/pythonlibs/ 2.按下CTRL+F进行页面查找“requests” 3.点击requests-2.22. ...
- java核心-JVM-gc面试题
1.写一个memory leak的例子 public class MemonyLeak { //1.memoryLeak内存泄漏 /* 这类错误报错具体显示:java.lang.OutOfMemory ...
- 16 协程和www
1.一个应用程序一般对应一个进程,一个进程一般有一个主线程,还有若干个辅助线程,线程之间是平行运行的,在线程里面可以开启协程,让程序在特定的时间内运行.2协程和线程的区别是:协程避免了无意义的调度,由 ...