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这个工程,首先它会自动升级,然后编译失败,需要下载一个
关于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; }
这个也算是对原始程序的一个小改进吧。