zoukankan      html  css  js  c++  java
  • 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);
            }
        }
    }

    漆黑的夜里有一种笑声笑断我坟墓的木板

    你可知道。

    这是一片埋葬老虎的土地

    正当水面上渡过一只火红的老虎

    你的笑声使河流漂浮

    的老虎

    断了两根骨头

    正当这条河流开始在存有笑声的黑夜里结冰

    断腿的老虎顺流而下

    来到我的

    窗前。

    一块埋葬老虎的木板

    被一种笑声笑断两截

    上善若水,为而不争。
  • 相关阅读:
    git 去除对某个文件的版本控制
    10:08 小记
    写读书笔记
    恢复已删除且已添加至暂存区的文件
    第七周
    第六周
    软件测试
    短信获取
    Android-8
    增删改查
  • 原文地址:https://www.cnblogs.com/Bearoom/p/11721767.html
Copyright © 2011-2022 走看看