【1】算法思路
算法运行的思路由图所示
思路1 (1)c#中读入一张本地图片的路径;(2)将该路径传递给dll里面的函数,OpenCV根据路径打开图像,完成图像处理;
(3)结果保存到C#中分配的内存中。(适用打开本地图片,进行处理)
思路2 (1)c#获取图像数据的在内存中的头部指针(图像可能为相机拍摄的图像存放在内存中,也可能为本地加载的图像保存到内存中)。(2)将指针传递给dll函数,调用OpenCV完成mat的重建,然后用OpenCV完成图像处理。(3)结果保存到C#中分配的内存中。(适用于在内存中的图像)
关于dll库的创建可以参考前面两篇文章,再次不详细阐述了。下面进入主题
【2】代码分享
DLL库代码:
.h文件
#pragma once
#define DLL_API extern "C" _declspec(dllexport)
#include<opencv.hpp>
#include<opencv2/highgui.hpp>
#include "opencv2/imgproc/imgproc_c.h"//IplImage头文件
#include <opencv2/core.hpp>
#include<iostream>
#include <opencv2/imgproc/imgproc.hpp>
#include "opencv2/highgui/highgui.hpp"
using namespace std;
using namespace cv;
//非托管代码实现
//*****************************************需要导出的DLL函数**********************************************************//
//以下两个函数均为c#调用c++里面的dll库完成图像处理的函数,不同点就是读入的是数据还是图像的路径。
/*
传入图像路径---------》》》opencv读取图像------------》》》图像处理----------》》》返回图像数据到内存中
GetFileToProcess函数简介:读入一个图片的路径,一块内存的首地址,返回图像的大小;
函数功能:在c#中调用此函数,输入图像的路径,在c++中完成图像处理,在c#中得到处理完成的结果;
@filename 文件的路径名,通常为"C:\\Users\\Administrator\\Pictures\\opencvpicture\\arrow2.jpg"形式;
@data 开辟内存的首地址,需要提前分配,内存的大小要大于图像的大小;
@size 图像的大小,长和宽;
*/
DLL_API void GetFileToProcess(char * filename, uchar *data, size_t&size);
/*
传入图像的数据到内存中---------》》》OpenCV重建mat图像-----------》》》图像处理----------》》》返回图像数据到内存中
GetDateToProcess函数简介:读入图像数据的指针,读入图像的长、宽、步长信息,读入一个空白的内存用于存放处理的数据
函数功能:从c#中读入一段图像内存和图像的大小信息,然后重建mat图像,完成图像处理,将图像返回值空白内存中;
@bimage:c#中图像的数据的头部指针
@nH:图像的高度
@nw:图像的宽度
@stride:图像的扫描步长-------------(图像在内存里是按行存储的。扫描行宽度就是存储一行像素,用了多少字节的内存)
@data:c#申请的内存,存放处理完成的图像数据
@size: c#处理完成的图像的图像大小
*/
DLL_API void GetDateToProcess(char * bimage, int nH, int nW, int stride, uchar *data, size_t &size);
//*****************************************内部进行图像处理的函数**********************************************************//
//以下函数为自定义的图像处理函数,为全局函数
//自定义函数
void GrayImage(Mat & image, Mat & grayimage);
.cpp文件
#include"unmanage.h"
#include<opencv.hpp>
#include<opencv2/highgui.hpp>
#include "opencv2/imgproc/imgproc_c.h"//IplImage头文件
#include <opencv2/core.hpp>
#include<iostream>
#include <opencv2/imgproc/imgproc.hpp>
#include "opencv2/highgui/highgui.hpp"
#include<algorithm>
#include<vector>
#include<typeinfo>
using namespace std;
using namespace cv;
/********************************************自定义图像处理函数********************************************************/
//自定义图像处理函数
void GrayImage(Mat & image, Mat & grayimage)
{
cvtColor(image, grayimage, COLOR_BGR2GRAY);
}
/******************************************************************************************************************/
DLL_API void _stdcall GetFileToProcess(char * filename ,uchar *data, size_t&size)
{
//[1]读入图像
std::vector<uchar> buf;
//Mat src = cv::imread("C:\\Users\\Administrator\\Pictures\\opencvpicture\\beads.jpg"); //读入一个Mat
Mat src =imread(filename);
//[2]图像处理
Mat grayimage;
GrayImage(src, grayimage);
//[3]处理结果数据保存到内存中
imencode(".bmp", grayimage, buf); //将Mat以bmp格式存入内存中,转换为uchar数组
size = buf.size();
for (uchar &var : buf)
{
*data = var;
data++;
}
}
DLL_API void _stdcall GetDateToProcess(char * pImg, int nH, int nW, int stride, uchar *data, size_t &size)
{
//[1]由内存数据重建mat图像
Mat image = Mat(nH, nW, CV_8UC3, pImg, stride).clone();
//[2]图像处理
Mat grayimage, thresholdimage;
GrayImage(image, grayimage);
threshold(grayimage,thresholdimage,50,255,1);
//[3]处理结果数据保存到内存中
vector<uchar> buf;
imencode(".bmp", thresholdimage, buf); //将Mat以bmp格式存入内存中,转换为uchar数组
size = buf.size();
for (uchar &var : buf) //将buf拷贝到C#的输出byte[] 内存中
{
*data = var;
data++;
}
}
.def文件
LIBRARY unmanaged
EXPORTS GetFileToProcess
EXPORTS GetDateToProcess
C#中的代码:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace ShowPicture
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private string pathname = string.Empty; //定义路径名变量
//打开图像
private void button1_Click(object sender, EventArgs e)
{
OpenFileDialog file = new OpenFileDialog();
file.InitialDirectory = ".";
file.Filter = "所有文件(*.*)|*.*";
file.ShowDialog();
if (file.FileName != string.Empty)
{
try
{
pathname = file.FileName; //获得文件的绝对路径
this.pictureBox1.Load(pathname);
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
}
//保存图像
private void button2_Click(object sender, EventArgs e)
{
SaveFileDialog save = new SaveFileDialog();
save.ShowDialog();
if (save.FileName != string.Empty)
{
pictureBox1.Image.Save(save.FileName);
}
}
//
//路径----》bitmap----->>mat----->>bmp----->>返回
/*GetDateToProcess函数简介:读入图像数据的指针,读入图像的长、宽、步长信息,读入一个空白的内存用于存放处理的数据
函数功能:从c#中读入一段图像内存和图像的大小信息,然后重建mat图像,完成图像处理,将图像返回值空白内存中;
@bimage:c#中图像的数据的头部指针
@nH:图像的高度
@nw:图像的宽度
@stride:图像的扫描步长-------------(图像在内存里是按行存储的。扫描行宽度就是存储一行像素,用了多少字节的内存)
@data:c#申请的内存,存放处理完成的图像数据
@size: c#处理完成的图像的图像大小
*/
[DllImport("unmanaged.dll")]
private extern static void GetDateToProcess(byte[] buffer, int imageHeight, int imageWeidth, int imageStride, ref byte ptrData, out ulong size);
private void button3_Click(object sender, EventArgs e)
{
Bitmap bmp = (Bitmap)Bitmap.FromFile(@"C:\\Users\\Administrator\\Pictures\\opencvpicture\\beads.jpg");
System.Drawing.Rectangle rect = new System.Drawing.Rectangle(0, 0, bmp.Width, bmp.Height);
System.Drawing.Imaging.BitmapData bmpData = bmp.LockBits(rect, System.Drawing.Imaging.ImageLockMode.ReadWrite, bmp.PixelFormat);
//Get the address of the first line.
IntPtr ptr = bmpData.Scan0;
// Declare an array to hold the bytes of the bitmap.
int bytesLength = Math.Abs(bmpData.Stride) * bmp.Height;
int imageWeidth = bmp.Width;
int imageHeight = bmp.Height;
//图像的Stride
int imageStride = bmpData.Stride;
byte[] buffer = new byte[bytesLength];
// Copy the RGB values into the array.
Marshal.Copy(ptr, buffer, 0, bytesLength);
bmp.UnlockBits(bmpData);
byte[] ptrData = new byte[2048 * 2048 * 3]; //尽可能大的byte[]
ulong size = new ulong();
GetDateToProcess(buffer, imageHeight, imageWeidth, imageStride, ref ptrData[0], out size);
pictureBox1.Image = Image.FromStream(new MemoryStream(ptrData, 0, (int)size));
}
//打开一个路径调用dll完成图像处理,传输图像路径到dll库完成图像处理,之后再返回数据。
//路径----》mat----->>bmp----->>返回
/*
@filename: 图片的路径名;------------在c#中类型为string ,在c++dll中对应的是char*
@data: 返回图片存放的数组,dll在使用时调用C#申明的这段内存,然后在c++将数据存放到此数组中;----------在c#中类型为byte ,在c++dll中对应的是 uchar*
@size: 图像的宽高信息。-------- 在c#中类型为ulong c++中为size_t类型
*/
[DllImport("unmanaged.dll")]
private extern static void GetFileToProcess(string filename, ref byte data, out ulong size);
private string filename = string.Empty;
private void button4_Click(object sender, EventArgs e)
{
OpenFileDialog file = new OpenFileDialog();
file.InitialDirectory = ".";
file.Filter = "所有文件(*.*)|*.*";
file.ShowDialog();
if (file.FileName != string.Empty)
{
try
{
filename = file.FileName; //获得文件的绝对路径
//string filename = "C:\\Users\\Administrator\\Pictures\\opencvpicture\\arrow2.jpg";//自定义打开文件路径
byte[] ptrData = new byte[2048 * 2048 * 3]; //尽可能大的byte[],一般大于显示的最大图片内存即可
ulong size = new ulong();
GetFileToProcess(filename, ref ptrData[0], out size); //调用dll库中的函数,将C++的内存数据转入C#的内存中
pictureBox1.Image = Image.FromStream(new MemoryStream(ptrData, 0, (int)size));//将byte[]转化为MemoryStream再传递给image
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
}
}
}
c#界面展示
(1)导入数据结果显示 (2)导入路径结果显示
【3】易犯错误
个人心得:前前后后做了两天终于完成了c++非托管dll库在c#中的调用,将路上的坑整理如下
(1)调用dll库上,c++里面调试没有问题,c#打开显示内存受损或者受保护。可能原因,
a) c#里面的数据和c++里面的数据格式不对应。格式参考:c# 调用 C++ dll 传入传出 字符串
b) private extern static void GetFileToProcess(string filename, ref byte data, out ulong size);检查导入外部函数时是否添加了static,没有statice返回的值很容易被覆盖掉。这里的返回值为void可以不添加。
c) 注意c#环境配置和c++里面的环境配置是否一致,对应debug和x64;
d)项目的目标平台改为和配置环境一样
(2)dll制造注意点
提示main函数不存在可能是如下原因,解决方法如下所示。
(1)将目标文件扩展名和配置类型改为dll形式,将环境配置改为x64和debug