制作自己的MFC MDI OPENCV程序框架

PS:我记录下这个过程,既是一个学习过程,也想分享给大家。因为是尝试,所以可能中途可能有错误。我尽量记录下来。而且我制作中尽量做到每一步都可以运行,方便测试。简单地说,我想要用VC6.0和Open CV 1.0进行数字图像处理的学习,并且写出自己完整程序来。如果直接使用open cv,看起来不太直观、不专业。《在MFC中使用OpenCV》应该是最好的学习资料和例程,可是我既不熟悉VC,也不熟悉open cv,入门难度比较大,这里把我的学习和分析的过程、经验以及结果分享给大家,希望对大家有帮助。

一、环境

1、VC6.0

2、OPENCV 1.0

3、改编自《在MFC中使用OpenCV》,来源于:

http://wiki.opencv.org.cn/index.php/%E5%9C%A8MFC%E4%B8%AD%E4%BD%BF%E7%94%A8OpenCV

4、VC2010 OPENCV2.4.9环境下面建立过程基本一样,只有少许变化。(VC2013下面如果使用2010这个工程,首先它会自动升级,然后编译失败,需要下载一个

Multibyte MFC Library for Visual Studio 2013,连接为)

关于vc2013直接建立工程,因为工程是默认的unicode,所以如果不设置成多字节模式,会有一个比较严重的问题:opencv的很多对文件操作的函数不是unicode的,而LPCTSTR到const char *这样的转换非常麻烦(当然可以用WideCharToMultiByte这样的函数来完成),所以,如果使用vc2013的话,还是应该不使用unicode,其他部分和vc2010是一样的。   

注意:这个原始程序估计是因为修改了类名,但是消息映射的某些东东没有改,最后导致类向导中处理某些类时失败!本人已经修复了此错误。

附件中将提供:《在MFC中使用OpenCV》原始程序及文档、VC6.0 OPENCV1.0版本、VC2010 OPENCV2.4.9版本3样东东,预计将放在CSDN下载频道中。可是本博客不知为何会处于审核状态,等解禁后就上传。

二、参考资料

1、《在MFC中使用OpenCV.doc》,上述文章和程序的作品。这些东东最后都提供给大家,可能需要大家仔细研究。

2、《MFC多文档中opencv处理图像打开、保存》,来源于:

http://blog.csdn.net/abcjennifer/article/details/7313711

这个MM的BLOG不错啊,大家可以认真研究一下。

 

三、新建MFC .EXE工程MyCV(这个工程特意取了一个新的名字,和原程序不一样!),MDI,注意向导的最后一步我们的视图类CMyCVView的基类应该选择CScrollView,因为我们做图象处理不希望显示时进行缩放,需要原样显示,所以滚动条是必须的。

 

四、加入OpenCV及DirectShow相关的东东

1、给工程加入opencv的几个库

cxcore.lib cv.lib ml.lib cvaux.libhighgui.lib cvcam.lib

2、将CameraDS.h、CameraDS.cpp、对应头文件以及目录DirectShow复制到你的项目中

3、Additional include directories

Project->Settings->Settings for:(All configurations)->C/C++->Category(Preprocessor)

->Additional include directories设置为../DirectShow/Include

4、Additional library directories

Project->Settings->Settings for:(Allconfigurations)->Link->Category(Input)

->Additional library directories设置为 ../DirectShow/Lib

PS:2、3、4三点在CameraDSA.cpp中有说明,注意上面绿色部分和目录结构有关!

4、tools->option中的lib file把例程的lib文件夹前置到最顶头

    例程中包含了strmiid.lib含有这些外部符号,windows系统SDK也包含了strmiid.lib,需要优先使用directshow中的,否则编译过不了。参见:

http://blog.csdn.net/makenothing/article/details/8750703

5、将Processing.cpp和Processing.h复制到你的项目中

    本程序设计的思路是这样的:图形处理本身使用opencv,而显示结果则使用windows自己的方式。由于二者存储格式不同,所以需要进行一些转换。另一种实现MFC的方法是采用CvvImage类,我们这里不采用它。

模块中增加了4个函数,即CtreateMapInfo、imageType、imageClone与imageReplace。CtreateMapInfo函数用于建立工作位图workImg的位图信息m_lpBmi,其特点是可以为单通道位图设置两种调色板,即黑白灰阶调色板与VGA默认调色板。因为在OpenCV中二值图像显示为灰阶图像,imageType函数可从单通道位图中识别出二值位图。ImageClone与imageReplace函数使用OpenCV函数实现位图的复制,自动释放老的指针所指向的存储单元以防止内存泄漏,同时返回的m_dibFlag标志可以用于激发刷新工作位图workImg的位图信息m_lpBmi。imageReplace与ImageClone相似,但不建立新位图,只用输入位图替换输出位图。

 这四个函数及其他常规图像处理放在两个文件中:Processing.h和Processing.cpp,拷贝并加工程中。编译有17个错误,经检查,这两个文件没有包含opencv的头文件(顺便说一下,前面建立工程后就应该引用cxcore.lib cv.lib ml.lib cvaux.lib highgui.lib cvcam.lib这几个库文件,赶紧加上)。经查,是在stdafx.h中include的:

#include "MyCV.h"                          //  窗口管理

#include "cv.h"                             //  OpenCV 文件头
#include "highgui.h"
 
#include "CameraDS.h"                       //  DirectShow(基于OpenCV)
#include "CVDSCap.h"                        //  视频采集接口

#include "Processing.h"                     //  附加辅助函数

#endif 

//{{AFX_INSERT_LOCATION}}
// Microsoft Visual C++ will insert additional declarations immediately before the previous line.
  
// !defined(AFX_STDAFX_H__1836CF0C_3704_4215_8745_F068486288D6__INCLUDED_)

 

五、修改IDD_ABOUTBOX对话框,加上自己的版权信息。修改String Table中的IDR_MAINFRAME和AFX_IDS_APP_TITLE的Caption为:“OpenCV MFC MDI图像处理程序框架”,把应用程序的名称改为我们需要的。

 

六、修改CMainFrame::OnCreate,注释掉工具条(mainfrm.cpp文件中),因为我们需要对菜单等进行大动作,如果保留工具条的话太复杂了,有很多工作要做,只留菜单要简单得多。

int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
	if (CMDIFrameWnd::OnCreate(lpCreateStruct) == -1)
		return -1;

//去掉工具条
/*	
	if (!m_wndToolBar.CreateEx(this, TBSTYLE_FLAT, WS_CHILD | WS_VISIBLE | CBRS_TOP
		| CBRS_GRIPPER | CBRS_TOOLTIPS | CBRS_FLYBY | CBRS_SIZE_DYNAMIC) ||
		!m_wndToolBar.LoadToolBar(IDR_MAINFRAME))
	{
		TRACE0("Failed to create toolbar\n");
		return -1;      // fail to create
	}

	if (!m_wndStatusBar.Create(this) ||
		!m_wndStatusBar.SetIndicators(indicators,
		  sizeof(indicators)/sizeof(UINT)))
	{
		TRACE0("Failed to create status bar\n");
		return -1;      // fail to create
	}

	// TODO: Delete these three lines if you don't want the toolbar to
	//  be dockable
	m_wndToolBar.EnableDocking(CBRS_ALIGN_ANY);
	EnableDocking(CBRS_ALIGN_ANY);
	DockControlBar(&m_wndToolBar);
*/
	return 0;
}

七、修改菜单

1、主菜单IDR_MAINFRAME

先删除“查看”菜单

“文件”菜单中,删除“新建”、“打印设置”菜单(同时为了美观删除多余的separator),只留下“打开”、“最近文件”、“退出”三个菜单

2、子菜单IDR_MYCVTYPE(为了简单起见,我们认为所有的图象都是一种类型),所以只有这一个!

“编辑”、“查看”都删掉,只保留“文件”、“窗口”、“帮助”三个菜单

“窗口”:只保留“层叠”、“平铺”两个,其余删掉。(PS:我感觉可以全部保留,留待后面验证,到时不行再删掉。结论是:其他两个没有什么用,删掉)

“文件”:

(1)“新建”删掉

(2)“打开”ID_FILE_OPEN,改成“打开图象”

(3)新建一个“恢复图象”,ID_REFRESH

(4)在下面插入一个separator

(5)“关闭”ID_FILE_CLOSE,改成“关闭当前窗口”

(6)新建一个“保存当前位图”,ID_CONSERVATION_IMAGE

(7)删除“保存”、“另存为”、separator、“打印”、“打印预览”、“打印设置”

(8)“退出”前面插入一个“恢复原始图象”,ID_COLOR_IMAGE_REFRESH

(9)“退出”前面插入一个“当前画面存盘”,ID_FILE_SAVE_AS

(10)“退出”前面插入一个separator

(11)暂时到这,后面加入具体功能时再来加入其他菜单

八、“软件锁”

   本演示程序中有许多功能需要打开OpenCV设置的窗口,如果不关闭这些窗口就执行其他功能会造成程序故障。为了避免产生这种情况,特在使用这种窗口的程序中加设“软件锁”,即在相应功能的执行程序中设置参数m_ImageType的值为-3。而在需要锁住功能的使能函数OnUpdateXXX()中用条件(m_ImageType!=-3)来限制。因此,当程序执行过程中菜单无法使用时,必定是有所开的窗口忘了关闭,请先关闭这些窗口,然后再运行所需功能或退出演示程序。

    具体是在MyCVView.h中CMyCVView类的定义中进行修改:

#ifdef _DEBUG
	virtual void AssertValid() const;
	virtual void Dump(CDumpContext& dc) const;
#endif

protected:
	IplImage* saveImg;
	IplImage* workImg;
 
	LPBITMAPINFO m_lpBmi;
 
	int     m_CaptFlag;
	int     m_dibFlag;
	int     m_SaveFlag;
 	int     m_ImageType;
 
// Generated message map functions

相应的构造函数也要初始化它们

CFile fCapture;
CFileException eCapture;
char pbuf[20];
int  captSetFlag=0;

CMyCVView::CMyCVView()
{
	// TODO: add construction code here
	saveImg    = NULL;
	workImg    = NULL;
  
	m_lpBmi    = 0;

	m_CaptFlag = 0;
	m_dibFlag  = 0;
 	m_ImageType= 0;
  
	CSize sizeTotal;
	sizeTotal.cx = sizeTotal.cy = 100;
	SetScrollSizes(MM_TEXT, sizeTotal);

	if(fCapture.Open( "CaptSetup.txt", CFile::modeRead, &eCapture ) )
	{
		fCapture.Read( pbuf, 20 );          //  取出分辨率设置数据
 		sscanf(pbuf,"%d  %d",&frameSetW,&frameSetH);
		fCapture.Close();
	}

}


CMyCVDoc类的定义中:

class CMyCVDoc : public CDocument
{
protected: // create from serialization only
	CMyCVDoc();
	DECLARE_DYNCREATE(CMyCVDoc)

// Attributes
public:
	IplImage*	pImg;
	int			m_Display;

相应的构造函数

CMyCVDoc::CMyCVDoc()
{
	// TODO: add one-time construction code here
	pImg=NULL;
 	m_Display=-1;

}

好了,现在可以打开图象文件了,但是没有绘制图象,这说明还有一些工作没有做。

 

 

九、现在需要打开图像

用类向导给文档生成打开的函数(CMyCVDoc::OnOpenDocument)


BOOL CMyCVDoc::OnOpenDocument(LPCTSTR lpszPathName) 
{
	if (!CDocument::OnOpenDocument(lpszPathName))
		return FALSE;
	
	// TODO: Add your specialized creation code here
	BOOL bool;

    Load(&pImg,lpszPathName);
	if (pImg!=NULL) bool=true;
	else bool=false;
	return(bool);
}

然后这里需要成员函数Load,干脆同时加入Save函数

BOOL CMyCVDoc::Load(IplImage** pp,LPCTSTR csFileName)
{
	IplImage* pImg=NULL;

	pImg = cvLoadImage(csFileName,-1);      //  读图像文件(DSCV)
	if (!pImg) return(false);
 	cvFlip(pImg);                           //  与 DIB 像素结构一致
 	if (*pp) {
		cvReleaseImage(pp);
	}
	(*pp)=pImg;
   	m_Display=0;
	return(true);
}


BOOL CMyCVDoc::Save(LPCTSTR csFileName,IplImage* pImg)
{
	int   bl;

 	cvFlip(pImg);                           //  恢复原 OpenCV 位图结构
  	bl=cvSaveImage(csFileName,pImg);        //  图像存盘
 	return(bl);
}

类中声明如下:

class CMyCVDoc : public CDocument
{
protected: // create from serialization only
	CMyCVDoc();
	DECLARE_DYNCREATE(CMyCVDoc)
	BOOL Load(IplImage** pImg,LPCTSTR pszFilename);
	BOOL Save(LPCTSTR pszFilename,IplImage* pImg);

// Attributes
public:
	IplImage*	pImg;
	int			m_Display;

还有这个需要通过类向导给打开菜单加上

void CMyCVView::OnUpdateFileOpen(CCmdUI* pCmdUI) 
{
	// TODO: Add your command update UI handler code here
    pCmdUI->Enable(m_ImageType!=-3);
	
}

十、CMyCVView类的OnDraw函数

/////////////////////////////////////////////////////////////////////////////
// CMyCVView drawing

void CMyCVView::OnDraw(CDC* pDC)
{
	CMyCVDoc* pDoc = GetDocument();
	ASSERT_VALID(pDoc);
	// TODO: add draw code for native data here

	if (pDoc->pImg!=NULL)	{	            //  有磁盘输入图像
		if (pDoc->m_Display==0) {           //  尚未显示
 			imageClone(pDoc->pImg,&saveImg);         //  复制到备份位图
			m_dibFlag=imageClone(saveImg,&workImg);  //  复制到工作位图

			m_ImageType=imageType(workImg);
			m_SaveFlag=m_ImageType;
			pDoc->m_Display=1;
		}
	}
 
	if (m_dibFlag) {                        //  DIB 结构改变
 		if (m_lpBmi)
			free(m_lpBmi);
		m_lpBmi=CtreateMapInfo(workImg,m_dibFlag);
		m_dibFlag=0;

		CSize  sizeTotal;
 		sizeTotal = CSize(workImg->width,workImg->height);
		SetScrollSizes(MM_TEXT,sizeTotal);  //  设置滚动条
	}


	
//下面这一段如果没有加入摄像头相关的会编译不过的,所以这里还是把它加进来	

	char *pBits;
	if (m_CaptFlag==1) pBits=m_Frame->imageData;
	else if (workImg)  pBits=workImg->imageData;

	if (workImg) {                          //  刷新窗口画面
		StretchDIBits(pDC->m_hDC,
			    0,0,workImg->width,workImg->height,
				0,0,workImg->width,workImg->height,
				pBits,m_lpBmi,DIB_RGB_COLORS,SRCCOPY);
	}

}

好了,现在可以打开并显示图象了

 

十一、当前画面存盘

利用类向导,对菜单“当前画面存盘”(ID_FILE_SAVE_AS)生成处理程序


void CMyCVView::OnFileSaveAs() 
{
	// TODO: Add your command handler code here
    CString csBMP="BMP Files(*.BMP)|*.BMP|";
	CString csJPG="JPEG Files(*.JPG)|*.JPG|";
	CString csTIF="TIF Files(*.TIF)|*.TIF|";
	CString csPNG="PNG Files(*.PNG)|*.PNG|";
	CString csDIB="DIB Files(*.DIB)|*.DIB|";
	CString csPBM="PBM Files(*.PBM)|*.PBM|";
	CString csPGM="PGM Files(*.PGM)|*.PGM|";
	CString csPPM="PPM Files(*.PPM)|*.PPM|";
	CString csSR ="SR  Files(*.SR) |*.SR|";
	CString csRAS="RAS Files(*.RAS)|*.RAS||";

    CString csFilter=csBMP+csJPG+csTIF+csPNG+csDIB
					 +csPBM+csPGM+csPPM+csSR+csRAS;

	CString name[]={"","bmp","jpg","tif","png","dib",
		               "pbm","pgm","ppm","sr", "ras",""};

	CString strFileName;
	CString strExtension;
 
	CFileDialog FileDlg(false,NULL,NULL,OFN_HIDEREADONLY,csFilter);
	                                        //  文件存盘对话框
	if (FileDlg.DoModal()==IDOK ) {         //  选择了文件名
 		strFileName = FileDlg.m_ofn.lpstrFile;
		if (FileDlg.m_ofn.nFileExtension == 0) {  //  无文件后缀
			strExtension = name[FileDlg.m_ofn.nFilterIndex];
			strFileName = strFileName + '.' + strExtension;
			                                //  加文件后缀
		}

		CMyCVDoc* pDoc = GetDocument();
		ASSERT_VALID(pDoc);

		pDoc->Save(strFileName,workImg);   //  当前画面存盘
	}
	
}


以及

void CMyCVView::OnUpdateFileSaveAs(CCmdUI* pCmdUI) 
{
	// TODO: Add your command update UI handler code here
    pCmdUI->Enable((m_CaptFlag!=1)&&(m_ImageType!=-3));
}

十二、恢复图象

利用类向导,对菜单“恢复图象”(ID_REFRESH)生成处理程序

void CMyCVView::OnRefresh() 
{
	// TODO: Add your command handler code here
	m_dibFlag=imageClone(saveImg,&workImg);
	m_ImageType=m_SaveFlag;
  	Invalidate();
}

以及

void CMyCVView::OnUpdateRefresh(CCmdUI* pCmdUI) 
{
	// TODO: Add your command update UI handler code here
    pCmdUI->Enable((m_CaptFlag!=1)&&(m_ImageType!=-3));
}

十三、保存当前位图

利用类向导,对菜单“保存当前位图”(ID_CONSERVATION_IMAGE)生成处理程序

void CMyCVView::OnConservationImage() 
{
	// TODO: Add your command handler code here
 	imageClone(workImg,&saveImg);
	m_SaveFlag=m_ImageType;
}


以及

void CMyCVView::OnUpdateConservationImage(CCmdUI* pCmdUI) 
{
	// TODO: Add your command update UI handler code here
   pCmdUI->Enable((m_CaptFlag!=1)&&(m_ImageType!=-3));
}

十四、恢复原始图像

利用类向导,对菜单“恢复原始图像”(ID_COLOR_IMAGE_REFRESH)生成处理程序

void CMyCVView::OnColorImageRefresh() 
{
	// TODO: Add your command handler code here
	CMyCVDoc* pDoc = GetDocument();
	ASSERT_VALID(pDoc);
	pDoc->m_Display=0;
	Invalidate();
	
}

以及

void CMyCVView::OnUpdateColorImageRefresh(CCmdUI* pCmdUI) 
{
	// TODO: Add your command update UI handler code here
    pCmdUI->Enable((m_CaptFlag!=1)&&(m_ImageType!=-3));
}


到此为止,框架基本完成,下面就是要加入一个真正的图象处理来验证我们的成果了。

 

十五、菜单建立

先建立菜单“点处理”

再新建菜单项“彩色变灰阶”,ID_COLOR_TO_GRAY

 

十六、建立彩色变灰阶的代码

利用类向导,对菜单“彩色变灰阶”(ID_COLOR_TO_GRAY)生成处理程序

void CMyCVView::OnColorToGray()             //  图像彩色变灰阶 
{
	// TODO: Add your command handler code here
	IplImage* pImage;
	IplImage* pImg8u = NULL;
 
	pImage = workImg;                       //  待处理图像换名

    //  建立辅助位图
 	pImg8u = cvCreateImage(cvGetSize(pImage),IPL_DEPTH_8U,1);

 	cvCvtColor(pImage,pImg8u,CV_BGR2GRAY);  //  彩色变灰阶

	m_dibFlag=imageReplace(pImg8u,&workImg);  //  输出处理结果
 
 	imageClone(workImg,&saveImg);           //  保存当前位图
     
	m_SaveFlag=m_ImageType=1;               //  设置灰阶位图标志
	Invalidate();

}

以及

void CMyCVView::OnUpdateColorToGray(CCmdUI* pCmdUI) 
{
	// TODO: Add your command update UI handler code here
    pCmdUI->Enable((m_CaptFlag==0)&&(m_ImageType>1));
	
}

现在,我们可以打开一个图象,点“彩色变灰阶”可以看到确实灰阶成功了,到此程序基本完成。

 

十七、让 MDI 程序 在刚启动时 不打开新文档 

在*App::InitInstance()中的(我们的程序是BOOLCMyCVApp::InitInstance())

CCommandLineInfo   cmdInfo;
ParseCommandLine(cmdInfo); 

后面加一句

cmdInfo.m_nShellCommand=CCommandLineInfo::FileNothing;

这个算是对原始程序的一个小改进吧。

 

十八、程序打开时就立刻最大化

因为我们做图象,自然是全屏显示的好,不希望经常去拉滚动条

BOOL CMyCVApp::InitInstance()进行如下修改:

	//把下面这句注释了,参数改成SW_SHOWMAXIMIZED
	//以实现启动时自动最大化
	//pMainFrame->ShowWindow(m_nCmdShow);
	m_pMainWnd->ShowWindow(SW_SHOWMAXIMIZED);   
	pMainFrame->UpdateWindow();

子窗口默认最大化显示:

BOOL CChildFrame::PreCreateWindow(CREATESTRUCT& cs)
{
	// TODO: Modify the Window class or styles here by modifying
	//  the CREATESTRUCT cs
	cs.style=WS_CHILD|WS_VISIBLE|WS_OVERLAPPEDWINDOW|WS_MAXIMIZE|FWS_ADDTOTITLE;

	if( !CMDIChildWnd::PreCreateWindow(cs) )
		return FALSE;

	return TRUE;
}


这个也算是对原始程序的一个小改进吧。


代码下载 点击打开链接

你可能感兴趣的:(框架,学习,mfc,opencv,MDI)