EmguCV编程总结学习C#图像处理编程有一段时间了,然后写写自己的已经积累的经验,网上有关于C#数字图像处理的资料很少,教程方面,专门为C#数字图像处理的到目前为止我就看到了三本,这三本的内容都是利用GDI+来做图像处理的。
GDI+为我们提供了图像处理所需要的一些类,还有一些函数。
在结合C#做界面的优势,因此做图像处理我个人感觉要比c++和opencv方便,opencv中有强大的算法,但是就是这个界面不友好。
在GDI+中只是提供了图像处理的类,还有少量的函数,如果真正要做处理,大量的函数还是的需要自己编写,我看到的这三本书上讲的基本上都是编写这些函数的内容,在GDI+编程中对图像处理通常有三种方式,第一种是利用纯C#的方式,理解简单,但是效率差。
第二种是内存法,理解稍微困难点,但是效率比纯C#的要高几百倍。
第三种就是在C#下利用指针了,当然利用指针就是效率最高的了,比内存法都要效率高,但是指针的理解比较困难,如果退指针理解还可以,就推荐用这种方法。
在前面的文章中我对比过在C#中用这三种方式灰度化图像的运行效率对比,还给出了具体时间。
我看到的这三本C#图像处理的书上,其中一本的算法是按照内存法来写的,其他两本上的算法都是按照纯C#方法写的,效率令人堪忧。
当然还有一些关于GDI+的书籍,这上面就介绍的内容就不单单是图像方面的处理了,还包括其他方面的内容,这是对学软件计算机的人看的,我们了解下就可以,再说,上面处理的方式都是纯C#的方式处理的,对我们图像处理来说效率实在不好,所以看看就好。
因此,C#图像处理的话,我推荐的是指针的方法,效率最好,速度快。
这是我们直接用C#来图像处理,当然大多数算法还得自己编写程序,反正我看到的教材上都是这样教的,但是实际中,我们写大量成熟的算法是很花时间的,估计写完这些都花个好几年时间了,因此,我们还得需要一些计算机视觉处理的库,调用其中的函数完成主要的处理功能。
有本名字叫《EmguCV Essentials》的书上就对现在常见的库做了对比,这本书上对比了有三种计算机视觉库,有opencv,EmguCV,这三种库。
首先从许可证的获取方面做了对比,其中opencv是开放的,emguCv有开放的也有商业版的,就没有前两种那开放了。
下图就是对比结果:然后从可以获得的学习资料方面,这本书上也给出了对比,结果如下:可以看出来opencv的学习资料最多,文献数量多,例子多,关注的人多。
而次之,EmguCV的资料最少了。
这本书上给出的解释是,由于EmguCV就是封装的opencv,操作差不多,所以相应的专门介绍的资料就少。
当然,由于是在C#下的opencv,肯定还有有点差别的。
然后从易用程度上,这本书也给出的对比,结果如下:其中opencv易用性最差,最好,EmguCV居中。
接下来从执行效率做了对比,下面是对比表:从中可以看出opencv最好,EmguCV和P/invoke差不多,P/invoke指的就是在C#中调用封装的opencv函数,其实还是EmguCV,至于传统的C#处理方法,这里没有具体给出到底是纯C#写的还是用指针法写的,不过看运行时间应该不是纯粹的C#法写的。
最后书中给出的最终的对比,如下图:然后综合比较结果如下图:最后书中综合比较得出EmguCV综合性能比较好:其实综合性能,易用性,界面的友好性,我个人也推荐EmguCV。
为了提升程序的运行性能,通常我们编程的时候采用混合编程的方法,如果抱着单一的方法,肯定会限制性能的发挥。
根据我自己的经验,然后我说说我自己对C#编程的理解,最后给出一个图形细化的例子。
在用C#编程时,我们是和EMGUCV结合起来做的,如果EMGUCV中有处理图像的函数,那么我们就调用这些函数完成该处理的算法,比如图像灰度化,边缘检测,SIFT匹配等等,这些在实际中已经够用了。
当然,在一些情况下,有些我们需要的算法,或者自己想到的,文献中提最新提到的算法,如果我们想用这些的话,EMGUCV中是没有的,那就需要我们自己编程了。
这时候我们可以在C#中利用GDI+做,也可以在利用EMGUCV提供的类来编程,那到底哪个好了?前面我对这些做过一个对比,就是对一幅图像进行灰度处理,结果如下:可见调用EmGUCV的函数时候效率最高,然后要自己写的话利用GDI+中的指针法效率最高,不过这里还没有包括完全情况,就是利用MIplImage 来处理,最后我发现了一个很奇怪的现象,我单独定义一个MIplImage时候速度很快,和指针法效率差不多,接下来我慢慢解释:在EmguCV中承载图像的类是:Image<Bgr,Byte> ,例如我们实例化一幅图像:Image<Bgr, Byte> Img = new Image<Bgr, Byte>(curBitmap);在EMGUCV官网中推荐的对图像数据访问的形式是如下:比较慢的形式:比如假如我们要自己写灰度化图像的算法,用这种慢的形式写出来就应该这样,如下图:当然,官网还推荐一种快点的方式:例如我们同样自己假如要写灰度化算法,利用这种方式的话应该这样,如下图:当然,为了方便,我们可以单独在定义一个数组,静Img.Data给他,然后按照数组的方式处理,如下图代码:当然这两种的效率我前面都对比过了,快的方式基本在灰度一幅图像中的速度是慢的方法的两到三倍,我在贴下结果吧,如下图:当然相比于用GDI+中的指针法,慢了十几倍到几十倍不止。
好,接下来我要提到利用MIplImage类型来处理图像了,这是在OPENCV 中常用的承载图像的,如果采用指针直接访问的形式的话,速度应该很快的,和在GDI+中利用指针访问速度应该差不多的,我在这里也是利用指针直接访问的,但是从对比结果中看出,结果仅仅比EMGUCV官网推荐的慢的访问速度快点,比快的访问速度都慢,不应该出现这种情况呀。
今天,我在做程序时,突然发现的是写程序的原因,不过就简单的改动,效率就上去的,至于原因我也不知道为啥。
在上面几种方法对比的时候,我利用MIplImage类的时候是这样写的代码,如下:结果效果不是很好,今天的时候,我这样写,结果效率马上提高了,和GDI+中利用指针法基本一样的,代码如下图:对比上面两端代码,有啥区别没,对了,就这一句:MIplImage Img1 = Img.MIplImage;这里我只是新定义了一个MIplImage类,下面处理的时候就利用Img1来做处理,而不像上面代码中还要从Img.MIplImage中在取,就像在EmguCV官网推荐的快速方式访问像素时,我们在定义一个三维的矩阵,将数据取出来,然后在去访问的一样,就这么一个小小的变动,速度就上去了,下图是我处理一幅图像的时间,当然和GDI+的21ms还差了10ms,不过差别不多,比上面的方式写的快了十几倍,这里真不知道是为啥,难道是我代码写的有问题?不过这样写确实效率上去了。
当然,MIplImage中的访问也可以用cvGet2D( )的方式访问,这和opencv中的方式一样的,但是效率就慢了很多,大概要比上面指针访问的速度慢十几倍吧,下面是用这种方式写的代码:好,此外,图像就是一个矩阵,比如对于一幅图像A,有时候我们希望能像matlab那样的访问元素,比如取第10行,第10列的时候可以这样写,A[10,10],当然,C#中GDI+中提供的GetPixel()和SetPixel()函数可以帮助让我们按照这样方式实现,不过效率太低下了,然后在EMGUCV中,官网上推荐的哪两种方式都可以帮助我们按照这种方式实现,虽然效率比起用GDI+中的GetPixel()和SetPixel()要快速上不少,但是还是不够快,这时候怎么办,我们可以自己写一个函数,实现从Bitmap承载类型到矩阵的转换和在转回来。
下面就是我写的两个转换的函数,注意,这两个都是转换灰度图像的。
如果要实现彩色图像转换成矩阵,这时候我们可以转换成一个三维的矩阵,代码如下:由于有些懒,就没有贴全,截图有点不全,如果看懂的话自己可以补上后面的部分的。
这时候我们就可以利用转换的矩阵来实现如同像matlab那样的操作了,当然,从上面的代码中可以看到,矩阵的转换都是在GDI+中按照指针的方式写的,速度很快的。
下面是写的二值话图像细化的算法,这才EMGUCV中也没有,我是在GDI+中写的,就是利用转换成矩阵的方式操作的,我只给出一部分代码,显示是按矩阵的方式做的,比较方便而已。
在学opencv的教材上,也有一种叫做利用c++外壳的方式访问数据,我看了貌似就是转换成这种数组的形式来访问的,如下图所示为教材上的叫法:当然,转换成如数组那样的矩阵形式,非常方便,好理解,速度也很快,比用C#中的提取像素的方式和EMGUCV官网上推荐的提取像素的方法要快好多,至少十几倍吧。
不过,这还不是最快的,最快的是什么,当然是用指针写的,不过指针写起来不太好理解,不要紧,接下来我们换种方式写就好理解了。
感觉和用A[i,j]这样提取像素的方式类似,编程的时候也好理解。
在opencv中我们访问IplImage类型的图像数据时候,教材上有一种叫做直接访问的方式,这就是我们前面写的程序中用的方式,下图是教材上提到的:这样访问,速度最快的,不过确实不好看,不好理解。
其实,我们换种写法就好理解了,还是指针,还和上面的访问一样的,不过就是换种写法而已,在opencv的教材上叫做用指针直接访问,其实这和直接访问都是一样的,都是用的地址,不过就是写法不一样而已。
在C#的教材上访问图像的方式如下图指针所示:其实我们改成用data[i*step+j]这种形式访问就好了,这不就像用矩阵形式访问的吗,不过矩阵形式是data[i,j],这里多了一个步宽,逗号改成的加号而已,如果是彩色图的话,在还有一个通道数,data[i*step+j*3]访问data(i,j)处的B分量像素,data[i*step+j*3+1]访问data(i,j)处G分量的像素,data[i*step+j*3+2]访问data(i,j)处R分量的像素。
这里的step在GDI+中其实就是Bitmapdata类的扫描宽度stride,所以把如上图的C#中的GDI+中利用指针写的代码,改成如下写而已,代码如下,截图也如下:public unsafe Bitmap ToGray(Bitmap srcImg){BitmapData imgData = srcImg.LockBits(newRectangle(0,0,srcImg.Width,srcImg.Height),ImageLockMode.ReadWrite,srcImg.PixelFormat);byte temp = 0;byte* data = (byte*)(imgData.Scan0);//int step=imgData.Stride;for (int i = 0; i < imgData.Height; i++){for (int j = 0; j < imgData.Width; j++){temp = (byte)(data[i * imgData.Stride + j * 3] * 0.114 + data[i * imgData.Stride + j * 3 + 1] * 0.578 + data[i * imgData.Stride + j * 3 + 2] * 0.299);data[i * imgData.Stride + j * 3] = data[i * imgData.Stride + j * 3 + 1] = data[i * imgData.Stride + j * 3 + 2] = temp;}}srcImg.UnlockBits(imgData);return (srcImg);}因此,我们改成这样写,和按照矩阵数组的操作理解就一样的,不用在将指针形式转换为我们容易理解的矩阵数组形式了,下面我把喜欢算法不用矩阵数组的形式写了,直接就按照上面理解的方式写,部分代码如下:好,我们对比一下转换成矩阵数组形式细化的算法运行时间和直接按照指针理解成矩阵数组的形式的细化算法的运行时间,比如我们细化一些网格子:首先采用转换成矩阵数组的形式处理:采用理解成矩阵数组形式处理:可见,采用直接指针处理的速度要比转换一下快100多毫秒,其实转换成矩阵的操作是用指针写的,话不了多少时间,转换成矩阵然后转换回来,对这幅图片加起来最多就30几毫秒,但是综合下来就慢了100多。