基于MFC 对话框的 PCL、VTK 、OPENCV岩体识别系统构建(2)

在上一篇文章当中,小编为大家展示了系统的界面,并附上了相关代码,这篇文章主要介绍小编的第一个功能模块-----文件模块。

但是在开始介绍文件模块时,需要对之前的界面设计做一个优化:1、我们需要界面在进行放大缩小变化时,里面的控件以及控件内的对话框也能够随着主界面的变化自动调整为合适的大小。2、美化MFC程序界面。具体解决方案如下:

一、布局自动调整

1、首先在XXXDlg.h头文件中添加public成员变量及函数

POINT old;
void ReSize();
2、之后响应主对话框窗口的OnSize方法

afx_msg void OnSize(UINT nType, int cx, int cy);
3、在OnInitDialog中添加代码来获取原始对话框的大小

CRect rect;
GetClientRect(&rect);
old.x = rect.right - rect.left;
old.y = rect.bottom - rect.top;

4、在WM_SIZE消息中添加代码

 // TODO: Add your message handler code here  
    if (nType==SIZE_RESTORED||nType==SIZE_MAXIMIZED)  
    {  
        ReSize();  
    }  
表示当最大化与还原窗口时调用ReSize方法

5、编写ReSize代码

    float fsp[2];  
    POINT Newp; //获取现在对话框的大小  
    CRect recta;      
    GetClientRect(&recta);     //取客户区大小    
    Newp.x=recta.right-recta.left;  
    Newp.y=recta.bottom-recta.top;  
    fsp[0]=(float)Newp.x/old.x;  
    fsp[1]=(float)Newp.y/old.y;  
    CRect Rect;  
    int woc;  
    CPoint OldTLPoint,TLPoint; //左上角  
    CPoint OldBRPoint,BRPoint; //右下角  
    HWND  hwndChild=::GetWindow(m_hWnd,GW_CHILD);  //列出所有控件    
    while(hwndChild)      
    {      
        woc=::GetDlgCtrlID(hwndChild);//取得ID  
        GetDlgItem(woc)->GetWindowRect(Rect);    
        ScreenToClient(Rect);    
        OldTLPoint = Rect.TopLeft();    
        TLPoint.x = long(OldTLPoint.x*fsp[0]);    
        TLPoint.y = long(OldTLPoint.y*fsp[1]);    
        OldBRPoint = Rect.BottomRight();    
        BRPoint.x = long(OldBRPoint.x *fsp[0]);    
        BRPoint.y = long(OldBRPoint.y *fsp[1]);    
        Rect.SetRect(TLPoint,BRPoint);    
        GetDlgItem(woc)->MoveWindow(Rect,TRUE);  
        hwndChild=::GetWindow(hwndChild, GW_HWNDNEXT);      
    }  
    old=Newp; 
这样,当主对话框发生变化的时候,里面的控件也会自动调整大小。

同样的,由于我们的主显示区域是控件里面添加了对话框,同时在对话框内部放置了2个控件,所以当我们对对话框生成类的时候同样需要添加上诉代码来完成子对话框内部控件的自动调整。

二、MFC界面美化

MFC制作的界面比较粗糙,相关的贴图设置也比较麻烦,这里小编使用开源的界面美化类库SkinMagic v2.4破解版,主要使用方法如下:

1、 解压完成之后,选择里面的一个*.smf皮肤文件,将其拷贝到工程文件的res目录下,然后在程序中加载该资源。我们在资源目录里新建一类资源"SKINMAGIC",然后添加皮肤文件到其中,设置资源ID,如下图:



2、在工程文件XXX.cpp文件中的AfxEnableControlContainer()之后添加如下代码:

	AfxEnableControlContainer();
    VERIFY(1 == InitSkinMagicLib(AfxGetInstanceHandle(), NULL, NULL, NULL)); 
	VERIFY(1 == LoadSkinFromResource(AfxGetInstanceHandle(),(LPCTSTR)ID,"SKINMAGIC"));
3、在XXXDlg.cpp文件中的OnInitDialog中添加如下代码:

	SetWindowSkin(m_hWnd,"Dialog");
	VERIFY(1 == SetDialogSkin("Dialog"));

4、最后在XXX.cpp文件中添加ExitInstance

	ExitSkinMagicLib();
	return CWinApp::ExitInstance();

这样,我们就可以使用类库中的皮肤了。完成之后的界面如下所示:



三、文件模块功能实现

本系统的文件打开功能主要有:打开(点云/图片),保存(点云/图片),另存为(点云/图片),关闭(显示区),退出系统
在正式编写代码前,我们需要进行相关类的设计工作,例如可以定义哪些抽象类,然后什么情况下需要进行派生,抽象类中定义哪些虚函数,数据的输入输出是否可以统一在一个类中进行等等。这里小编的设计肯定有所不足,还望大家给予指出。
在小编的设计中,文件的输入输出主要通过自定义类FileIO文件来实现,里面定义了如何进行读取和写入,如何保存,如何另存为等相关操作。
针对点云数据和图像数据,分别建立了对应的自定义类,里面封装了两者的相关操作。
对于可视化,自定义了一个VTK的基类,具体的显示类则派生自该类。
相关结构如下:
PCL: MyPCL类--->派生自自定义的PCLAbstract抽象类
OpenCV:MyCVImage类----->派生自自定义的CVAbstract抽象类
文件读写:自定义FileIO类
封装数据结构:PCLPoints---->派生自MyPoint抽象类
VTK显示:VTKPCL类----->派生自自定义VTKAbstract抽象类

一、点云数据文件打开

首先在PCLPoints中定义的点云数据结构如下:
派生自基类的部分:
	//成员变量
	double m_x;
	double m_y;
	double m_z;

派生类自定义的部分:
	//成员变量;
	int m_id;
	int m_pid;

因此,CFileIO::openPCLData如下:
/************************************************************************/
/* 打开txt的点云数据                                                     */
/************************************************************************/
CVTKPCL* CFileIO::openPCLData()
{
	m_defaultFilePath = m_filePath;//记录下这个地址;
	ifstream inFile;
	inFile.open(m_filePath.c_str());
	if(!inFile.is_open())
	{
		AfxMessageBox("错误:文件打开失败",MB_OK);
		return NULL;
	}

	int i = 0;
	char t = 'a';
	while(!inFile.eof())
	{
		CPCLPoints pt;
		inFile>>pt.m_id>>t>>pt.m_pid>>t>>pt.m_x>>t>>pt.m_y>>t>>pt.m_z;
		pArray.push_back(pt);
	}
	i = pArray.size();
	CString s;
	s.Format("共载入%d个点,请问需要进行可视化显示吗?",pArray.size());
	if(IDYES == AfxMessageBox(s,MB_YESNOCANCEL))
	{
		vtkpclObject.showPCLPoints(pArray);	
	}
	inFile.close();
	return &vtkpclObject;
}
这里首先在FileIO类中定义了用于保存点云文件路径的m_filePath以及m_defaultFilePath,并且定义了存储点云数据的vector:
	vector<CPCLPoints>pArray;
定义了将点云数据pArray用于vtk显示化的vtkpclObject对象,并对其所在类CVTKPCL定义了showPCLPoints方法:
/************************************************************************/
/* 显示点云数据
   param[in]:CPCLPoints的对象数组
   主要保存VTK相关renwin等信息  */
/************************************************************************/
void CVTKPCL::showPCLPoints(vector<CPCLPoints>& array)
{
	int iPts = array.size();
	this->m_renWin->AddRenderer(this->m_render);
	this->m_iren->SetRenderWindow(this->m_renWin);

	m_vtkpoints->SetNumberOfPoints(iPts);
	for(int i = 0;i < iPts ;i++)
	{
		m_vtkpoints->InsertPoint(array.at(i).m_id,array.at(i).m_x,array.at(i).m_y,array.at(i).m_z);
	}
	m_polyvertex->GetPointIds()->SetNumberOfIds(iPts);
	for(int i = 0;i < iPts ;i++)
	{
		m_polyvertex->GetPointIds()->SetId(i,i);
	}
	m_unstructgrid->SetPoints(m_vtkpoints);
	m_unstructgrid->InsertNextCell(m_polyvertex->GetCellType(),m_polyvertex->GetPointIds());
	m_datasetmapper->SetInput(m_unstructgrid);
	m_actor->SetMapper(m_datasetmapper);
	m_actor->GetProperty()->SetRepresentationToPoints();
	m_actor->GetProperty()->SetColor(0,0,0);
	m_render->AddActor(m_actor);
	m_render->SetBackground(1,1,1);
}
这样就完成了读取数据以及vtk可视化的内部工作,最后只是将显示结果放置到显示区域上即可。所以在XXXDlg.cpp文件中响应显示消息:
/************************************************************************/
/* 菜单功能之-------打开点云数据                                         */
/************************************************************************/
void CMyPCLProjectDlg::OnOpenPcldata()
{
	// TODO: 在此添加命令处理程序代码
	string filepath ="";
	CFileDialog dlg(TRUE,NULL,NULL,OFN_HIDEREADONLY|OFN_OVERWRITEPROMPT,(LPCTSTR)_TEXT("文本文件(*.txt)|*.txt|All Files(*.*)|*.*||"),NULL);
	dlg.m_ofn.lpstrTitle="打开点云txt文件";
	if(IDOK == dlg.DoModal())
	{
		filepath = dlg.GetPathName();
		CVTKPCL* object;
		//CFileIO myFile(m_openDataTag,filepath);
		m_myFile = new CFileIO(m_openDataTag,filepath);
		object = m_myFile->openPCLData();
		//
		vtkRenderWindow* renwin = object->GetThisRenWin();
		vtkRenderWindowInteractor * iren = object->GetThisRenderInteractor();
		renWinConfig(renwin,iren);
	}
}

二、图片数据打开

我们在CFileIO中添加打开影像函数:
	void openImageData();
声明保存图片地址的string对象:
	string m_imgFilePath;
并且声明了CMyCVImage类的对象,用该对象来具体实现图片的相关操作:
	CMyCVImage* myImage;
对象的初始化以及释放可以在构造函数和析构函数中进行。

/************************************************************************/
/* 通过OPENCV打开图像数据                                                */
/************************************************************************/
void CFileIO::openImageData()
{
	/*opencv显示图片*/
	CFileDialog dlg(TRUE,_T("*.bmp"),NULL,OFN_FILEMUSTEXIST|OFN_PATHMUSTEXIST|OFN_HIDEREADONLY,_T("image files(*.bmp; *.jpg; *.tif; *.jpeg)|*.bmp; *.jpg; *.tif; *.jpeg|All Files (*.*)|*.*||"), NULL);
	dlg.m_ofn.lpstrTitle = _T("打开影像数据");
	if(IDOK == dlg.DoModal())
	{
		//获取图片路径
		m_imgFilePath = dlg.GetPathName();
		myImage = new CMyCVImage(m_imgFilePath);
		if(!myImage->LoadCVImage())
		{
			AfxMessageBox("图片文件路径不正确",MB_OK);
			return;
		}
	}
}
在CMyCVImage类中,通过自定义构造函数,获取参数后初始化到该类的成员变量,从而记录文件打开的地址
CMyCVImage::CMyCVImage(std::string path):m_ImageFilePath(path){}
该类目前的成员函数如下:
	//自定义成员函数
	void setImgPath(const string& path);
	void binaryThesholdImg();
	void contrastImg();
	void resetsrcImg();
    string getImgPath();
	bool LoadCVImage();
	bool SaveCVImage();
	bool SaveAsCVImage();
	cv::Mat getThisImage();
其中有用于设置修改文件路径的setImgPath函数,也有获取当前图片的getThisImage()函数等。LoadCVImage就是根据当前的文件路径加载图片:
/************************************************************************/
/* 加载图片                                                             */
/************************************************************************/
bool CMyCVImage::LoadCVImage()
{
	m_Img = cv::imread(m_ImageFilePath);
	m_curImg = m_Img;
	if(!m_Img.data)
	{
		AfxMessageBox("图片加载失败,请检查",MB_OK);
	}
	cv::namedWindow(pstrWindowsSrcTitle);
	cv::imshow(pstrWindowsSrcTitle,m_Img);
	cv::waitKey(0);
	return true;
}
这样我们就完成了图片的加载,最后只要在XXXDlg.cpp中响应消息即可。

三、图片数据与点云数据的保存

这里的保存指的是根据当前的文件路径,直接保存覆盖原始文件。对于点云文件,是保存为txt,图像文件则可以为jpg,img,bmp等多种格式。
同样在CFileIO当中添加保存文件的相关函数:
	void savePCLData();
	void saveImgData();
/************************************************************************/
/* 通过CPCLDATA对象保存数据                                              */
/************************************************************************/
void CFileIO::savePCLData()
{
	if(pArray.size()<=0)
	{
		AfxMessageBox("没有可供保存的数据",MB_OK);
		return;
	}
	vector<CPCLPoints>& array = pArray;//初始化引用对象array,让其指向已经保存的数据pArray;
	ofstream ouFile(m_defaultFilePath.c_str());
	for(vector<CPCLPoints>::iterator it = pArray.begin();it!= pArray.end();++it)
	{
		CPCLPoints pt = (*it);
		ouFile<<pt;
	}
}
这里我们通过全局函数的方式重载了操作符<<:
//重载操作符<<
ostream& operator<<(ostream& os,const CPCLPoints& rhs)
{
	os<<rhs.m_id<<" "<<rhs.m_x<<" "<<rhs.m_y<<" "<<rhs.m_z<<std::endl;
	return os;
}

之后添加保存图片的代码:
/************************************************************************/
/* 保存图片                                                              */
/************************************************************************/
void CFileIO::saveImgData()
{
	if(!myImage)
	{
		return;
	}
	if(myImage->SaveCVImage())
	{
		AfxMessageBox("保存成功",MB_OK);
		return;
	}

}
为了获取之前打开图片时的MyCVImage对象,我们添加了getCVImg()方法来获得活动Img对象的指针:
CMyCVImage* CFileIO::getCVImg()
{
	return this->myImage;
}
之后需要在CMyCVImage中添加SaveCVImage的代码:
/************************************************************************/
/* 保存图片                                                             */
/************************************************************************/
bool CMyCVImage::SaveCVImage()
{
	if(!m_curImg.data)
	{
		AfxMessageBox("图像未创建",MB_OK);
		return false;
	}
	if(g_dstImage.data)
		g_dstImage.copyTo(m_curImg);
	cv::imwrite(m_ImageFilePath.c_str(),m_curImg);
	return true;
}
这样就完成了图片的保存

四、图片/点云数据的另存为

另存为能够自定义设置保存的路径已经文件名,与保存比较类似,不同之处在于打开文件对话框的相关设置:
以点云另存为为例:
/************************************************************************/
/* 另存为打开的PCL数据                                                   */
/************************************************************************/
void CFileIO::saveAsPCLData()
{
	if(pArray.size() <= 0)
	{
		AfxMessageBox("没有可供保存的数据",MB_OK);
		return;
	}
	vector<CPCLPoints>& array = pArray;
	CFileDialog dlg(FALSE,"txt","新建文本文件",OFN_HIDEREADONLY|OFN_OVERWRITEPROMPT,(LPCTSTR)_TEXT("文本文件(*.txt)|*.txt|All Files(*.*)|*.*||"),NULL);
	if(IDOK == dlg.DoModal())
	{
		ofstream ouFile(dlg.GetPathName());
		for(vector<CPCLPoints>::iterator it = pArray.begin();it!= pArray.end();++it)
		{
			CPCLPoints pt = (*it);
			ouFile<<pt;
		}
	

CFileDialog的初始化代码里第一个参数是FALSE表示保存,第二个参数表示保存的类型,第三个参数表示默认文件名。

五、系统退出

系统退出的时候需要注意在各个自定义类的析构函数里释放内存,如指针变量,new分配的数组等,从而避免对象被释放的时候对象所使用的资源没有及时释放而导致内存泄露。
/************************************************************************/
/* 菜单功能之-------退出系统                                             */
/************************************************************************/
void CMyPCLProjectDlg::OnMainClose()
{
	// TODO: 在此添加命令处理程序代码
	this->DestroyWindow();
	if(m_myImgFile)
		delete m_myImgFile;
}

响应对话框右上角的关闭按钮时,可以响应WM_CLOSE消息以及WM_DESTROY消息:
void CMyPCLProjectDlg::OnClose()
{
	if(IDYES == MessageBox(_T("确定退出程序吗?"),"Message",MB_YESNO|MB_ICONQUESTION))
	{
		//在此销毁定义的成员变量
		this->DestroyWindow();
		if(m_myImgFile)
		{	
			delete m_myImgFile;
		}
	}	
	CDialog::OnClose();
}

void CMyPCLProjectDlg::OnDestroy()
{	// TODO: 在此处添加消息处理程序代码
    CDialog::OnDestroy();
}

以上就是第一个功能模块的相关构建,写的不够精细还请包涵,如有疑问请大家提出,如果不合理的地方也请大家批评。













  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值