C#调用openCV函数及其移植

来源:互联网 发布:mac如何删除系统软件 编辑:程序博客网 时间:2024/06/12 05:57

众所周知,OpenCV是一个图形图像处理方面的库,里边封装了许多有用的函数。网上也有许多很实用的教程。但是,大部分都是使用C++进行开发。这段时间一直在做一个项目,过程中踩了不少坑,谨以此文提醒自己,留下记录。

首先,上某度查找“C#调用OpenCv”得到的结果大部分是关于EmguCv的使用。不得不说,EmguCv也是一个很好的工具,我在编码过程中也有用到。但是今天不是讲这个。——如果读者想要在C#中使用类似OpenCv的函数,强烈建议先下载一个EmguCv,具体的下载、配置、使用教程不在此说明,请自行查找。如果在EmguCv中找不到想要用的函数,再继续看下去。

本人在编码过程中,使用的是EmguCv3.0版本。使用过其中的二值化、腐蚀、膨胀等方法。但是想要进行线段检测时,却找不到LSD(lineSegmentDetector)方法。在Emgucv的文档中进行搜索,也找不到这个方法。而OpenCv3.0中有一个LineSegmentDetector的类,其中提供了LSD方法。

这里说明一下,其实EmguCv提供了Hough检测的方法,但是我想要的是LSD,所以才要继续研究怎么去调用OpenCv,如果读者想要用的方法已经有了,完全不必再去费心思调用openCv。

查找了某度,实在无果,想到opencv是用C++进行编程的,因此便想利用C# C++混合编程来进行调用:先使用C++编写dll(动态链接库)文件,然后在C#中进行引用。

相关资料:点击打开链接

上述资料讲的是如何编写dll并在C#中调用;要调用openCv还需要进行配置。

具体配置方法也可以参考:点击打开链接

这个配置方法很容易搜到,只是要注意现在要编写的是dll。

然后在dll项目中定义好头文件和源文件:

这里我定义了一个存储结构的头文件myVec.h,一个声明函数的头文件cvRefClass.h。因为要考虑传递数据的类型,而OpenCv中大部分都是InputArray,OutputArray之类的,所以我定义的是一个vector<Vec4i>进行存储——因为我要调用的是LSD,结果其实是所有线段的两个端点的横纵坐标,所以采用这种形式。

然后在cvRefClass.cpp中进行方法的实现。

然后对工程生成解决方案。成功之后,可以在Debug目录下看到有dll文件生成。

生成dll文件之后,即可在C#中添加引用。

        public ArrayList LsdUsingOpenCv(string path)        {            unsafe            {                //ArrayList res = new ArrayList();                cvRefClass cvClass = new cvRefClass();                sbyte[] sbArray = (sbyte[])((Array)System.Text.Encoding.Default.GetBytes(path));                fixed (sbyte* psb = sbArray)    //涉及指针必须在unsafe使用                {                    cvClass.lsd(psb);   //在这里会对cvClass的数组进行赋值                }                int[] nums = new int[cvClass.arrayLen];                IntPtr datas = (IntPtr)cvClass.datas;                Marshal.Copy(datas, nums, 0, cvClass.arrayLen); //将datas的内容全部复制到nums                ArrayList numList = new ArrayList();                for (int i = 0; i < cvClass.arrayLen; i++)                {                    numList.Add(nums[i]);                }                ArrayList list = new ArrayList();                for (int i = 0; i < numList.Count / 4; i++)                {                    int x1 = (int)numList[i * 4];                    int y1 = (int)numList[i * 4 + 1];                    int x2 = (int)numList[i * 4 + 2];                    int y2 = (int)numList[i * 4 + 3];                    LineSegment2D line = new LineSegment2D(new Point(x1, y1), new Point(x2, y2));                    list.Add(line);                }                return list;            }        }

因为我知道数据的格式和含义,所以直接用一个arraylist去存。如果调用的是其他方法,需要根据实际情况去修改。

上述代码涉及了一些指针操作,和数据类型的传递问题有关,在此不做赘述,可以参考其他资料获得更完整的数据类型传递说明。

实现了上述方法之后,在C#中是可以进行调用的,传入图片的路径,即可产生相应的ArrayList。

至此,C#中调用openCv的问题算是告一段落。


但是!!!这仍然留着坑。并且在最近坑了我一次。

C#项目中引用了许多dll文件,比如我用了EmguCv,还有自己写的这个dll,在移植到其他主机上时可能会有问题:

首先,如果引用的时候使用的是绝对路径,那么移植到其他主机上,就会因为找不到dll而报错。


这个问题,网上也有许多解决方法。有人说引用了之后会复制dll到debug目录下,这没错,如果引入dll的属性中的“复制本地”(localCopy)选择true,确实会自动复制。但是仍然没用,因为我打包发给别人是整个项目发过去,也就是在debug中也把dll发过去了。网上还说了一些其他的方法,比如反射之类的,但是我没有尝试。这里说一下我的解决方法:

在app.config文件中,增加配置:

比如我的配置是

<?xml version="1.0"?><configuration>  <startup useLegacyV2RuntimeActivationPolicy="true">    <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5"/>  </startup>  <runtime>    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">      <dependentAssembly>        <assemblyIdentity name="CVRef"/>        <codeBase href="./CVRef.dll" mce_href="./CVRef.dll"/>      </dependentAssembly>      <dependentAssembly>        <assemblyIdentity name="Emgu.CV"/>        <codeBase href="./Emgu.CV.dll" mce_href="./Emgu.CV.dll"/>      </dependentAssembly>      <dependentAssembly>        <assemblyIdentity name="Emgu.CV.ML"/>        <codeBase href="./Emgu.CV.ML.dll" mce_href="./Emgu.CV.ML.dll"/>      </dependentAssembly>      <dependentAssembly>        <assemblyIdentity name="Emgu.CV.OpenCL"/>        <codeBase href="./Emgu.CV.OpenCL.dll" mce_href="./Emgu.CV.OpenCL.dll"/>      </dependentAssembly>      <dependentAssembly>        <assemblyIdentity name="Emgu.CV.UI"/>        <codeBase href="./Emgu.CV.UI.dll" mce_href="./Emgu.CV.UI.dll"/>      </dependentAssembly>      <dependentAssembly>        <assemblyIdentity name="Emgu.CV.Util"/>        <codeBase href="./Emgu.CV.Util.dll" mce_href="./Emgu.CV.Util.dll"/>      </dependentAssembly>      <dependentAssembly>        <assemblyIdentity name="ZedGraph"/>        <codeBase href="./ZedGraph.dll" mce_href="./ZedGraph.dll"/>      </dependentAssembly>    </assemblyBinding>  </runtime></configuration>

可以看到,在<dependentAssembly>中我使用了相对路径去配置,指向的是debug目录下的多个dll文件。

这个是我之前使用C#调用matlab的时候从网上查到的,但具体网址忘记了,因此没法放出来。若读者调用的是这些第三方工具提供的(matlab能够把.m文件的函数编译成dll)dll,使用这种方式,并对项目重新编译,应该就能移植成功了。即在其他主机上使用是不会再找不到dll库。


但是!!!还是个坑。大家读到这里应该知道,CVRef.dll是我自己编写的dll文件,也是我在自己机器上编译的,移植到其他人的电脑上时,发现其他dll都找得到,就是CVRef.dll找不到。


************** 异常文本 **************
System.IO.FileNotFoundException: 未能加载文件或程序集“CVRef.dll”或它的某一个依赖项。找不到指定的模块。
文件名:“CVRef.dll”


当时我也很纳闷,因为其他都找得到,没理由这个找不到。想了一段时间,注意到“或它的某一个依赖项”,顿时茅塞顿开。赶紧查查他依赖项是什么。


====>打开visual studio->工具->visual studio命令提示

输入dumpbin -dependents d:\CVRef.dll

读者如果不了解这个命令可以查一查,后边跟着的是dll文件的路径。回车之后,输出了几个dll:


openCv的dll是指编译时使用的那个版本的动态链接库文件,另外几个都是放在C盘的Windows下。因为我使用的是VS2012,所以是msvcp110d和msvcr110d。

然后我把这几个放到了debug文件中,在本机测试,发现没问题了(即使我把openCV和CVRef.dll项目都放到其他地方,也可以照常运行)。


但是!!!还是个坑。


我把整个项目重新生成解决方案之后发给其他人,到其他机器上,又提示:“不是win32应用程序。”

这个问题又是困扰了我一段时间。个中辛酸不表,说说如何解决:

将CVRef工程发到那台机器上,在那台机器上进行编译,并把编译生成的CVRef.dll替换掉原有的CVRef.dll(可能还要替换掉依赖)即可运行,C#项目无需在其他机器上重新编译。


事实上,我的那个小伙伴电脑上是只有VS2010,而我只有VS2012,他的VS打不开高版本的解决方案。

到某度上查了之后,用notepad++打开sln文件对其进行替换:

Microsoft Visual Studio Solution File, Format Version 12.00# Visual Studio 2012


替换为:
Microsoft Visual Studio Solution File, Format Version 11.00# Visual Studio 2010
即可用vs2010打开解决方案文件。

这时候还不能成功编译项目,会提示平台工具集错误。

但要编译的时候,还需要设置:项目->属性->配置属性->常规->平台工具集:选择v100(对于VS2010)

如果是VS2012,其平台工具集是v110.

但这样还不够,如果是直接编译,会提示error LNK2038: 检测到“_MSC_VER”的不匹配项: 值“1700”不匹配值“1600”

想了一下,是因为我在配置项目的时候,选择opencv下的x86->v11->lib文件夹中的dll,这个也是对应VS2012(版本号是11)。而同学的VS2010应该对应v10.


但是下载的opencv中没有提供v10,因此需要自己编译。


使用cmake工具编译:

参考资料:点击打开链接

之后参考v11中,看需要依赖哪些dll,重新指定依赖路径,并重新生成解决方案。


解决方案生成成功之后,将新的dll替换掉旧的dll,并查看其依赖,发现此时依赖的msvcr和msvcp后缀都变成了100,便将这两个也放入debug文件夹下。


运行->成功。

-------------------------2017.05.26更新-------------------------------

最近又遇到一点问题,因为是同一个项目的问题,就写在这了。

我使用一个C#写的exe去调用上面的exe,发现窗口打得开,但是一进行图像处理就报错误找不到组件。

后来查了一些资料,好像和运行时环境有关,不过没有深究,我改为开启一个进程去启动上边的exe,就没问题了。

private void startExe(string path, string name)        {            ProcessStartInfo psi = new ProcessStartInfo();            // 设置启动进程的初始目录            psi.WorkingDirectory = path;            // 设置启动进程的应用程序或文档名            psi.FileName = name;            // 设置启动进程的参数            psi.Arguments = "";            //启动由包含进程启动信息的进程资源            try            {                Process.Start(psi);            }            catch (System.ComponentModel.Win32Exception ex)            {                MessageBox.Show(ex.Message);                return;            }        }
这是启动一个新进程的方法

0 0