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);
            }
        }
    }

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

    你可知道。

    这是一片埋葬老虎的土地

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

    你的笑声使河流漂浮

    的老虎

    断了两根骨头

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

    断腿的老虎顺流而下

    来到我的

    窗前。

    一块埋葬老虎的木板

    被一种笑声笑断两截

    上善若水,为而不争。
  • 相关阅读:
    Swift3 重写一个带占位符的textView
    Swift3 使用系统UIAlertView方法做吐司效果
    Swift3 页面顶部实现拉伸效果代码
    Swift3 倒计时按钮扩展
    iOS 获取当前对象所在的VC
    SpringBoot在IDEA下使用JPA
    hibernate 异常a different object with the same identifier value was already associated with the session
    SpringCloud IDEA 教学 番外篇 后台运行Eureka服务注册中心
    SpringCloud IDEA 教学 (五) 断路器控制台(HystrixDashboard)
    SpringCloud IDEA 教学 (四) 断路器(Hystrix)
  • 原文地址:https://www.cnblogs.com/Bearoom/p/11721767.html
Copyright © 2011-2022 走看看