这一篇记录下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++系列二:传结构体的更多相关文章

  1. C语言程序设计(十二) 结构体和共用体

    第十二章 结构体和共用体 当需要表示复杂对象时,仅使用几个基本数据类型显然是不够的 根本的解决方法是允许用户自定义数据类型 构造数据类型(复合数据类型)允许用户根据实际需要利用已有的基本数据类型来构造 ...

  2. python调用c/c++时传递结构体参数

    背景:使用python调用linux的动态库SO文件,并调用里边的c函数,向里边传递结构体参数.直接上代码 //test1.c # include <stdio.h> # include ...

  3. C# 调用C/C++动态链接库,结构体中的char*类型

    用C#掉用C++的dll直接import就可以之前有不同的类型对应,当要传递结构体的时候就有点麻烦了,这里有一个结构体里边有char*类型,这个类型在C#中调用没法声明,传string是不行的默认st ...

  4. C和指针 第十二章 结构体 整体赋值 error: expected expression

    定义结构体后整体赋值时发生错误 typedef struct NODE { struct NODE *fwd; struct NODE *bwd; int value; } Node; //声明变量 ...

  5. ctypes 操作 python 与 c++ dll 互传结构体指针

    CMakeLists.txt # project(工程名) project(blog-3123958139-1) # add_library(链接库名称 SHARED 链接库代码) add_libra ...

  6. Qt socket中怎么传结构体?

    直接发送和接收结构体,例如:struct A {...};struct A objectA; 发送的时候: tcpSocket->write((char *)&objectA, size ...

  7. STM32L0系列EEPROM中结构体的读取

    在STM32L0中操作EEPROM本来参考了上篇操作FLASH的方法,多多少少都有些问题.我觉得可能是结构体在转换成其他变量的时候出了问题. 比如下面这段代码,在Windows上可以正常运行(使用g+ ...

  8. jvm系列二内存结构

    二.内存结构 整体架构 1.程序计数器 作用 用于保存JVM中下一条所要执行的指令的地址 特点 线程私有 CPU会为每个线程分配时间片,当当前线程的时间片使用完以后,CPU就会去执行另一个线程中的代码 ...

  9. C和指针 第十二章 结构体 习题

    12.3 重新编写12.7,使用头和尾指针分别以一个单独的指针传递给函数,而不是作为一个节点的一部分 #include <stdio.h> #include <stdlib.h> ...

随机推荐

  1. Zabbix——自动监控

    zabbix简介 zabbix是一个基于WEB界面的提供分布式系统监视以及网络监视功能的企业级的开源解决方案. zabbix能监视各种网络参数,保证服务器系统的安全运营:并提供灵活的通知机制以让系统管 ...

  2. GoJS实例2

    复制如下内容保存到空白的.html文件中,用浏览器打开即可查看效果 <!DOCTYPE html> <html> <head> <meta name=&quo ...

  3. 数据可视化-gojs插件使用技巧总结

    随着云计算时代的到来,由于Web技术的快速革新以及为了提供高质量的用户体验,数据可视化成为了前端技术发展的一大方向.为了解决这个问题,现如今涌现了很多优秀的第三方的javascript图形库,比如hi ...

  4. mysql导入导出无权限

    error:The MySQL server is running with the --secure-file-priv option so it cannot execute this state ...

  5. Java If ... Else

    章节 Java 基础 Java 简介 Java 环境搭建 Java 基本语法 Java 注释 Java 变量 Java 数据类型 Java 字符串 Java 类型转换 Java 运算符 Java 字符 ...

  6. 51nod 1276:岛屿的数量 很好玩的题目

    1276 岛屿的数量 题目来源: Codility 基准时间限制:1 秒 空间限制:131072 KB 分值: 20 难度:3级算法题  收藏  取消关注 有N个岛连在一起形成了一个大的岛屿,如果海平 ...

  7. Easy_vb

    拿到之后运行一下 之后使用ida打开先关键字搜索一下,结果就出来了

  8. R 数据框的操作

    1.插入一列 根据自带数据集beaver 进行操作,比如插入一列id. > colnames(beaver1) [1] "day" "time" &quo ...

  9. 通过GlobalAddAtom,GlobalGetAtomName方式发送字符串

    unit Unit1; interface uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms ...

  10. javaweb利用ajax使登录窗口不跳转页面实现对账号密码的判断

    和上一篇判断用户名是否被占用不跳转页面类似!利用ajax实现跳转,要导入jquery文件库!具体代码我会贴出来,注释在里面!!可以观摩一手!(代码我也留下链接,如果失效,评论补发,代码可能导入也无法使 ...