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;
}
这个也算是对原始程序的一个小改进吧。
代码下载 点击打开链接