基于OpenCV编写图像处理项目,除了算法以外,比较重要一个问题就是界面设计问题。对于c++语系的程序员来说,一般来说有QT/MFC两种考虑。QT的确功能强大,特别是QML编写android界面很有一套( ),在树莓派上进行设计也很方便( );但是使用QT的一个现实问题就是和现有平台的结合,比如客户需要将结果导出到excel中,使用QT就比较别扭(当然不是说不可以)。所以现在我一般这样来做:对于Android和PI,或者需要在Linux上运行的项目,使用QT编写界面,调用Opencv函数;对于需要在windows上运行的项目,使用MFC编写界面,直接就可以引用OpenCV。
有人会吐槽MFC使用起来非常麻烦,这点我非常同意。但MFC经过这么多年的发展,今日仍有活力,并且短时间内不会消失。因为相比较其他一些所见即所得的语言和环境来说(QT/Csharp),mfc的消息映射机制和坐标体系等,的确有它的优势,对于图像处理程序来说尤其如此;加以积累,能够快速做出很多专业的东西;近期出现的ribbon界面也为mfc加分不少( )
选择了MFC这个方向,思考图像处理程序问题,一般来说分为“处理图像”和"处理视频"两类:对于图像处理来说,我提供的GOPaint框架( )能够提供一个基本的静态图像处理框架;而GOMFCTemplate2( )则适合用来处理视频。这两种都 分别成功运用于多种视频处理项目中。
但是这里我想更进一步:希望能够用Csharp编写界面,因为它更好用;但是又不想引入EmguCV类似的库,因为里面很多东西不是我需要的。那么最直接的方法就是使用Csharp调用基于Opencv编写的类库文件(Dll)的,我取名叫做
经过比较长时间的探索研究,目前的GOCW已经可以直接以函数的形式在内存中传递bitmap和Mat对象,达到了函数级别的应用。因为这里涉及到托管代码编写,也就是CLR程序编写,所以有比较复杂的地方;为了展现GOCW的优良特性,我编写实现GOGPY项目,也就是一个"Csharp编写界面,OpenCV实现算法的实时视频处理程序”,相关细节都包含其中。之所以叫“GPY”,是采集硬件这块,我采用了成像质量较好的高拍仪设备(GaoPaiYi)。
这里简单将最核心内容进行讲解。GOCW的核心问题,无非就是基于CLR之上的两个方向的数据流转换。核心函数为
Bitmap ^ GOClrClass : :testMethod(cli : :array < unsigned char > ^ pCBuf1) { pin_ptr <System : :Byte > p1 = &pCBuf1[ 0]; unsigned char * pby1 = p1; cv : :Mat img_data1(pCBuf1 - >Length, 1,CV_8U,pby1); cv : :Mat img_object = cv : :imdecode(img_data1,IMREAD_UNCHANGED); //获得数据到img_object中去 //处理过程/// cvtColor(img_object,img_object, 40); / Bitmap ^ bb = MatToBitmap(img_object); if ( !img_object.data) return nullptr; std : :vector <uchar > buf; cv : :imencode( ".jpg", img_object, buf); return bb; }
以及
System : :Drawing : :Bitmap ^ MatToBitmap( const cv : :Mat & img) { if (img.type() != CV_8UC3) { throw gcnew NotSupportedException( "Only images of type CV_8UC3 are supported for conversion to Bitmap"); } //create the bitmap and get the pointer to the data PixelFormat fmt(PixelFormat : :Format24bppRgb); Bitmap ^bmpimg = gcnew Bitmap(img.cols, img.rows, fmt); BitmapData ^data = bmpimg - >LockBits(System : :Drawing : :Rectangle( 0, 0, img.cols, img.rows), ImageLockMode : :WriteOnly, fmt); //byte *dstData = reinterpret_cast<byte*>(data->Scan0.ToPointer()); Byte *dstData = reinterpret_cast <Byte * >(data - >Scan0.ToPointer()); unsigned char *srcData = img.data; for ( int row = 0; row < data - >Height; ++row) { memcpy( reinterpret_cast < void * >( &dstData[row *data - >Stride]), reinterpret_cast < void * >( &srcData[row *img.step]), img.cols *img.channels()); } bmpimg - >UnlockBits(data); return bmpimg; }
而在chsarp中,直接
Bitmap b = new Bitmap(cam.Width, cam.Height, cam.Stride, PixelFormat.Format24bppRgb, m_ip); // If the image is upsidedown b.RotateFlip(RotateFlipType.RotateNoneFlipY); srcImage = b; if (picPreview.Image != null) picPreview.Image.Dispose(); //调用clr+opencv图像处理模块 MemoryStream ms = new MemoryStream(); b.Save(ms, System.Drawing.Imaging.ImageFormat.Jpeg); byte[] bytes = ms.GetBuffer(); Bitmap bitmap = client.testMethod(bytes);
就可以调用,并且获得结果。
以下内容为2017年更新的内容,适当参考:
一、CLR编写的DLL部分
1、按照正常方法引入Opencv;
2、提供接口函数,进行图像处理(这里只是实现了cvtColor,实际过程中可以用自己编写的复杂函数)
String ^ Class1 : :Method(cli : :array < unsigned char > ^ pCBuf1) { pin_ptr <System : :Byte > p1 = &pCBuf1[ 0]; unsigned char * pby1 = p1; cv : :Mat img_data1(pCBuf1 - >Length, 1,CV_8U,pby1); cv : :Mat img_object = cv : :imdecode(img_data1,IMREAD_UNCHANGED); //处理过程/ cvtColor(img_object,img_object, 40); / if ( !img_object.data) return nullptr; //获得目录,保存文件 cv : :imwrite( "c:/Method.jpg",img_object); return "c:/Method.jpg"; } String ^ Class1 : :Method2(cli : :array < unsigned char > ^ pCBuf1) { pin_ptr <System : :Byte > p1 = &pCBuf1[ 0]; unsigned char * pby1 = p1; cv : :Mat img_data1(pCBuf1 - >Length, 1,CV_8U,pby1); cv : :Mat img_object = cv : :imdecode(img_data1,IMREAD_UNCHANGED); //处理过程/// cvtColor(img_object,img_object, 6); / if ( !img_object.data) return nullptr; //获得目录,保存文件 cv : :imwrite( "c:/Method2.jpg",img_object); return "c:/Method2.jpg"; }
二、Winform调用接口部分(TIP:不仅可以用Winform调用,asp.net/webservice都是可以调用的)
1、直接引用clr dll
2、编写helper文件(应该也可以叫做 warpper),通过外部IO的方法获取clr dll的文件
class GOCsharpHelper { Class1 client = new Class1(); string strResult1 = null; string strResult2 = null; //输入参数是string或bitmap public Bitmap ImageProcess(string ImagePath){ Image ImageTemp = Bitmap.FromFile(ImagePath); return ImageProcess(ImageTemp); } //输出结果是bitmap public Bitmap ImageProcess(Image image) { MemoryStream ms = new MemoryStream(); image.Save(ms, System.Drawing.Imaging.ImageFormat.Jpeg); byte[] bytes = ms.GetBuffer(); strResult1 = client.Method(bytes); Image ImageResult = Bitmap.FromFile(strResult1); return (Bitmap)ImageResult; } public Bitmap ImageProcess2(string ImagePath) { Image ImageTemp = Bitmap.FromFile(ImagePath); return ImageProcess2(ImageTemp); } //输出结果是bitmap public Bitmap ImageProcess2(Image image) { MemoryStream ms = new MemoryStream(); image.Save(ms, System.Drawing.Imaging.ImageFormat.Jpeg); byte[] bytes = ms.GetBuffer(); strResult2 = client.Method2(bytes); Image ImageResult = Bitmap.FromFile(strResult2); return (Bitmap)ImageResult; } public void Clear() { if (File.Exists(strResult1)) File.Delete(strResult1); if (File.Exists(strResult2)) File.Delete(strResult2); } }
3、使用例子(注意控件的dispose):
private void button2_Click(object sender, EventArgs e) { if (pictureBox1.Image != null) pictureBox1.Image.Dispose(); if (pictureBox2.Image != null) pictureBox2.Image.Dispose(); Image image1 = gocsharphelper.ImageProcess( " E:/sandbox/logo.jpg"); pictureBox1.Image = image1; Image image2 = gocsharphelper.ImageProcess2( "E:/sandbox/lena.jpg"); pictureBox2.Image = image2; }
三、解释说明
使用外部I/O不仅仅是权宜之计,实际上Opencv的Decode使用的就是外部I/O。就目前研究的水平来说,这是最稳定的。
目前搭建成功的框架已经能够完成“csharp调用opencv的”目标,并且在调试、参数传递方面都很强。
如果是处理静态图片,已经够用。
四、杀手程序
GOImageResearch:
使用这种方法编写的图像处理预分析程序。