封装opencv的函数,形成自己的dll文件,调试(debug+release)正确,运行(ctrl+f5)内存溢出

jsxyhelu 2014-09-08 08:33:54
加精
问题描述:
封装opencv的函数,形成自己的dll文件,GOImage.dll.并且以函数的形式开放接口。在该函数中,主要是对读取的一系列图片数据进行特征识别、对准、融合操作。最终此dll以非托管的形式被 csharp程序调用,输入待拼接的图片地址,并在指定的位置生成结果图片。
在vs环境下进行调试(F5),无论debug还是 release模式,都可以正常运行,没有问题。然而单独运行程序的过程中(ctrl +F5),则会发生内存不断增大的现象。为了方便说明问题,我重新生成一个简单的函数,该函数的唯一作用就是读取图片到vector组中。
DllExport int imageprocesstest( char *cur_dir,char *imagepath_res, int isstraighten)
{
char DllBuff[50];
vector<Mat> inputmat;
Mat src;
for (int i = 1;i<=12;i++) //执行D全?部?文?件t的?遍历
{
if (i<10)
{
sprintf(DllBuff, "%s\\image00000%d.jpg" ,cur_dir,i);
}
else
{
sprintf(DllBuff, "%s\\image0000%d.jpg" ,cur_dir,i);
}
src= cv::imread(DllBuff,CV_LOAD_IMAGE_COLOR);
if (!src.data)
{
inputmat.clear();
return 1;
}
inputmat.push_back(src); //将?读取?的?结果?压1入?inutmat
}
// vector<Mat> (inputmat).swap(inputmat);
return 0;

}
及时对于此函数,也会存在上面一样的情况,同时在vs中以F5调试的时候,不会出现内存溢出(每隔一段时间vs会自动清除内存),而以ctrl+F5的模式则会出现溢出的情况
为了解决这个问题,我做了很多摸索。比较靠谱的是两条线路,一条是从csharp调用这个方面来考虑,为什么在F5的模式下能够成功调用,至少说明有一种模式能够解决这个问题(我认为有可能是和appdomin相关的,具体的需要继续去查一查F5和ctrl +F5的区别)我探索过的方法包括动态绑定、销毁;dymanticmethod;生成clr类;已经实现idisable接口等;另一条思路是从dll文件那个部分考虑的,即如何以在dll中就将对象销毁掉,其中比较有突破性的是vector的swap方法。
但是以上的方法都不能很好地解决现有问题。到目前为止,唯一看到的正确执行的就是vs的F5。

或者哪位知道f5的时候发生了什么,ctrl+f5的时候有什么不同,也请告诉我。感谢。
再或者,如果采用DynamicMethod+Delegate的方法是否可以解决,有无高手指教,ganx
...全文
3937 22 打赏 收藏 转发到动态 举报
写回复
用AI写文章
22 条回复
切换为时间正序
请发表友善的回复…
发表回复
bl349221762 2015-09-25
  • 打赏
  • 举报
回复
楼主碉堡了 小白学习了
sam-fat-chang 2014-11-20
  • 打赏
  • 举报
回复
引用 1 楼 sp1234 的回复:
创建对象却不在离开函数时时销毁它。你从哪个学校学习的 c 语言呢?
没有必要吹毛求疵吧
柏林 2014-09-23
  • 打赏
  • 举报
回复
跟着学习了,好资源顶
u014327958 2014-09-22
  • 打赏
  • 举报
回复
高大上,楼主威武!
dargoner 2014-09-18
  • 打赏
  • 举报
回复
楼主写的很详细
phzyj 2014-09-18
  • 打赏
  • 举报
回复
创建对象,使用完毕后,离开使用一定要销毁,否则会内存泄露
CodeC 2014-09-17
  • 打赏
  • 举报
回复
梦里跟着谭老师学习的c语言。
莫_问 2014-09-15
  • 打赏
  • 举报
回复
引用 1 楼 sp1234 的回复:
创建对象却不在离开函数时时销毁它。你从哪个学校学习的 c 语言呢?
梦里跟着谭老师学习的c语言。
sbigwolf 2014-09-15
  • 打赏
  • 举报
回复
SP4哥,你太武断了。看别人自己做的,分享了。你上来就一通屁。非你等高人所为。
末世苍雪 2014-09-15
  • 打赏
  • 举报
回复
引用 10 楼 u012910273 的回复:
[quote=引用 6 楼 nettman 的回复:]
好东西[/quote] 弄错了
末世苍雪 2014-09-15
  • 打赏
  • 举报
回复
引用 9 楼 llyyqq123 的回复:
楼主写的很详细
测试
末世苍雪 2014-09-15
  • 打赏
  • 举报
回复
引用 6 楼 nettman 的回复:
好东西
馒头仔 2014-09-15
  • 打赏
  • 举报
回复
楼主写的很详细
jsxyhelu 2014-09-14
  • 打赏
  • 举报
回复
这个是真的,最后我发现不是内存控制的问题,而是pvinvoke调用的问题。 我总结了一下 使用csharp 编写winform程序,不仅速度快,而且容易界面美化并找到其他类库的支持;而使用 opencv编写图形图像处理程序,是目前比较流行,而且高效的一种方法。如果需要将两者结合,需要解决的问题就是使用 csharp调用vc 下编写的库文件。两个难点,一个是平台调用的内存控制问题,一个是参数传递问题。关注点在解决实际问题 在现实中,我发现问题比较大的是两点,一点是内存控制问题,一个是平台调用问题。 一、内存控制:(1-6种方法是我验证后失败的方法,关注问题解决者可直接看第7点) 1、验证 opencv下的dll 程序是否能够对内存起到很好的控制。方法是将图片路径写在 dll文件中,使用csharp调用 dll文件,以此来验证是否是函数内部的溢出。 DllExport void imageprocesstest1() { vector<Mat> inputmat; vector<cv::Point2f> points1; vector<cv::Point2f> points2; vector<Point> locmat1;vector<Point> locmat2; char cur_dir[50]; char strimg [50]; for (int iimg = 1;iimg<=23;iimg++) { inputmat.clear(); points1.clear(); points2.clear(); locmat2.clear(); locmat1.clear(); sprintf(cur_dir, "D:\\t\\SteelNo%d\\Camera01" ,iimg); Mat tmp; //读取数据 for (int i =1;i<=10;i++)//执行全部文件的遍历 { if (i<10) { sprintf(strimg, "%s\\image00000%d.jpg",cur_dir,i); } else { sprintf(strimg, "%s\\image0000%d.jpg",cur_dir,i); } cv::Mat src= cv::imread(strimg,0); if (!src.data) return; inputmat.push_back(src); } InputAdjust(inputmat); MulitMatch(inputmat,locmat1,locmat2,5); Mat ma = MulitAlign(inputmat,locmat1,locmat2); if (ma.rows == 2 || ma.rows ==3) { return ; } else { Mat mb; mb =MulitBlend(inputmat,ma,locmat1,locmat2); if (mb.rows == 1) { return; } else { sprintf(strimg, "%s\\3.jpg",cur_dir); imwrite(strimg,mb); } } } return; } 在这个函数中,我对d盘 t文件目录下的23个文件夹进行遍历,调用 InputAdjust,MulitMatch ,MulitBlend这 3个主要函数进行拼接操作。在运行状态下 (ctrl+F5),不溢出。 2、采用动态加载 dll文件的方式进行函数调用。(具体见 Zealic.Windows) 就是采用loaddll 的方式动态地将 dll调入程序中,并且采用freedll的方式动态地将 dll文件注销掉。这个方法在运行状态下 (ctrl+F5),溢出,没有效果。 3、采用轻量化代码生成的方法 (dynamicmethod)进行dll 文件的调用。 DynamicLibrary golib = new DynamicLibrary("GOImage.dll" ); NativeMethodBuilder goBuilder = new NativeMethodBuilder(); goBuilder.ParameterLength = 3; goBuilder.SetParameterInfo(0, typeof(string )); goBuilder.SetParameterInfo(1, typeof(string )); goBuilder.SetParameterInfo(2, typeof(int )); NativeMethod method = goBuilder.MakeMethod(golib, "imageprocess"); method.Invoke(strtmp, strImagePath_Res, 1); 同样溢出。以上方法在F5模式下都能够运行正常。 4、这个时候,我开始想是不是我的 dll那个地方的资源没有释放掉。所以我特地生成了一个简单的函数。 DllExport void imageprocesstest( char *cur_dir,char *imagepath_res) { char DllBuff[50]; vector<Mat> inputmat; Mat src; for (int i = 1;i<=10;i++)//执行全部文件的遍历 { if (i<10) { sprintf(DllBuff, "%s\\image00000%d.jpg",cur_dir,i); } else { sprintf(DllBuff, "%s\\image0000%d.jpg",cur_dir,i); } src= cv::imread(DllBuff,CV_LOAD_IMAGE_COLOR); if (!src.data) { inputmat.clear(); return ; } for (int j=0;j<10;j++) //采用这种方式 { inputmat.push_back(src); //将读取的结果压入inutmat } } vector<Mat> (inputmat).swap(inputmat); return ; } 它的调用采用 [DllImport( "GOImage.dll", EntryPoint = "imageprocesstest", CharSet = CharSet.Ansi, CallingConvention = CallingConvention .Cdecl)] public static extern int imageprocesstest(string ImagePath, string ImagePath_Res); 这个函数不执行任何函数,唯一的作用就是讲图片读进去,然后清空。其中 vector<Mat> (inputmat).swap(inputmat); 叫做vector 的swap方法,是 effiective c++上面推荐的一种确实能够清空 vector的方法。而mat对象时 opencv的内建对象,自己带注销代码,而且其注销无法被显示调用。 这个时候,依然是ctrl+F5会溢出。 我认为,现在的情况是,在重复调用一个 dll文件的情况下(而且这个 dll文件需要若干秒的执行时间),会发生内存无法情况的情况。因为非托管方式下,垃圾回收器无法管理非托管代码内存。但是我已经显示地将内存注销了,只是不能被垃圾回收器回收。 5、所以我强制进行垃圾回收 Gc.colect(); 依然没有效果。所以这个方向的问题我卡在了这里。 6、这个时候,我想到了以前通过 com调用matlab 函数的时候也出现过这样的问题,但是当时是个小项目,就采用了一种方法对付了一下。现在也想看一下这种方法是否可行。 我们要达到的效果,就是输入一个目录,下面是以某种方式保存的图片,然后 进行拼接,得到对应的拼接图片。是否可以采用这种形式 Csharp调用图像处理的 dll文件,同时向param.log文件中以固定格式写入需要处理的图片的参数。 Dll文件收到后,按照param.log中指定的参数进行处理,同时将结果写入 result.log,并且在完成后,返回一个值, csharp在收到这个值后,到指定的地方读取 param.log,并显示出来。 如果采用这种方法,就只需要调用几次 dll文件,那么内存溢出的问题就不会影响到系统的稳定。但是这个问题仍然没有被解决。 7、最终,在研究appdomin后,我发现,可以讲pinvoke 的调用写在program.cs下面。 using System; using System.Collections.Generic; using System.Linq; using System.Windows.Forms; using System.Runtime.InteropServices; using System.Text; namespace WinFormTester { static class Program { /// <summary> /// 应|用?程序的?主入?口点?。 /// </summary> [ STAThread] static void Main() { Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); Application.Run(new Form1()); } [ DllImport("F:\\04.研D究?项?目?\\PInvoke\\stringbuilder\\Debug\\stringbuilder.dll" , EntryPoint = "stringbuilder", CharSet = CharSet.Auto, CallingConvention = CallingConvention .Cdecl)] public static extern Boolean ParamIsString(string str, ref StringBuilder ip); } } 这种方法下再进行调用,解决内存问题。其原有我认为是。。 二、参数传递问题。传递int等系统默认变量很简单方便,回写的方法,最后能够传递image变量 1、int 写入,如果要写出,加* 2、算是解决吧 bool值 DllExport int stringbuilder( char *str,int ** str_out) public static extern Boolean ParamIsString(string str, ref int ip); bool值的解决方法,装换为int类型后强制转换,0为假,不是0为真; private void button1_Click(object sender, EventArgs e) { int k = Convert .ToInt32(textBox1.Text); Boolean b = Program .ParamIsString(textBox1.Text,k); textBox2.Text = b.ToString(); } 就返回值这一块,其实就是一个类型转换,不仅是可行的,而且可以灵活运用起来 3、string 写入,char*在里面,没有问题 To summarize, System.String is preferable when working with immutable strings, especially for input (In) arguments. On the other hand, System.Text.StringBuilder is preferable with changeable strings especially output (Out) arguments. 我相信,marsh和stringbuilder的方法一定可以解决这个问题,但是目前我还没有研究清楚。还是那句老话“一鸟在手胜过千鸟在林”我这里有一种可用的解决方法。 #include "stdafx.h" #define DllExport extern "C"__declspec (dllexport) DllExport bool stringbuilder( char *str,char * str_out) { //这a里?的?256应|该?是?一?个?内部?定义?的?值了? char CharBuf[256]; // memcpy(CharBuf,str,sizeof(char)*256); // memcpy(str_out,CharBuf,sizeof(char)*256); strcpy(CharBuf,str); strcat(CharBuf, "ddd"); strcpy(str_out,CharBuf); return 1; } 这里采用了stringcpy,直接的内存操作,需要在外部结合起来进行边界检查 //传?出?string 的?方?法。研D究?意a义?大于实现?意a义?。 byte[] srcBytes = System.Text.Encoding .ASCII.GetBytes(textBox1.Text); byte[] resBytes = new byte[256]; bool b = Program .ParamIsString(ref srcBytes[0], ref resBytes[0]); string res = (System.Text.Encoding .ASCII.GetString(resBytes, 0, resBytes.Length)).TrimEnd(); textBox2.Text = res; 以及 [DllImport( "F:\\04.研D究?项?目?\\PInvoke\\stringbuilder\\Debug\\stringbuilder.dll" , EntryPoint = "stringbuilder", CharSet = CharSet.Auto, CallingConvention = CallingConvention .Cdecl)] public static extern bool ParamIsString(ref byte src, ref byte res); 有很多需要注意的点 .没有办法传递中文,但是这个方法具有通用性,体现了这种的问题如何来解决。变长等 4、结构体,关键是内外要用一样的数据结构,然后能够修改,这样就很强。 OK,这样可以解决问题: DllExport bool structbuilder(ImageInfo & imginfo, int width,int height,int type) { imginfo.width = width; imginfo.height = height; imginfo.type = type; return 1; } [ DllImport("F:\\04.研D究?项?目?\\PInvoke\\stringbuilder\\Debug\\stringbuilder.dll" , EntryPoint = "structbuilder", CharSet = CharSet.Auto, CallingConvention = CallingConvention .Cdecl)] public static extern bool ParamIsStruct(ref ImageInfo src, int width, int height, int type); [StructLayout( LayoutKind.Sequential)] public struct ImageInfo { public int width; public int height; public int type; }; 并且,非常重要的一点是,这里可以对这些类进行重新的封装,这样能够把csharp的优点和c++原生代码的优点结合起来。结果就可以变得比较稳定。这是一个新的总结性的问题。 下面是传递图像。如果int是第一类,代表的是系统原生的类型,struct是一类,代表你能够研究清楚内存放置的结构,而string又是一类,代表的是内存的直接操作。好的opencv的图像,要用上面的所有知识
jsxyhelu 2014-09-11
  • 打赏
  • 举报
回复
其实这个问题是两个原因, 一个是opencv的mat对象时自动释放的; 二个是我也编写了释放的代码段,在调用完成后就进行释放,但是没有效果。 我的主要问题是 在非托管的情况下,是否存在内存无法管理的情况。特别是我在函数体中要生成很大的变量的情况下。 感谢。后一点时间我重新贴一段代码,不好意思。
cancerser 2014-09-11
  • 打赏
  • 举报
回复
f5正常ctrf5不正常…… 这个真的啊?我有点不太相信 .net在连续操作的时候,当你的机器有充足的内存的时候是有可能连续增长的,特别是在server系列的服务器上 只要你程序跑不崩溃就是正常,当然这取决于你的数据量。 你需要做一些测试,当程序内存连续增长,你需要把操作停下来,最小化窗体或等一会,或者两者都整,上个大号回来再看内存是否恢复正常了,如果正常了 这说明垃圾回收的时机有问题或者在执行时没机会回收。 你可以在大资源使用完毕时手动启动垃圾回收,例如图片,大数据啥的使用完事自己gc。 如果是,调用非托管,连续执行可以在调用结束后sleep个0-10的把时间片交出来试试。
threenewbee 2014-09-09
  • 打赏
  • 举报
回复
如果你在C#调用C++的程序,并且C++程序不能自己释放内存,你需要编写一个释放内存的函数,然后在C#中用Dispose方法封装它。另外你可以用BoundCheck一类的工具调试下内存泄漏。
  • 打赏
  • 举报
回复
创建对象却不在离开函数时时销毁它。你从哪个学校学习的 c 语言呢?

110,565

社区成员

发帖
与我相关
我的任务
社区描述
.NET技术 C#
社区管理员
  • C#
  • Web++
  • by_封爱
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告

让您成为最强悍的C#开发者

试试用AI创作助手写篇文章吧