最近在忙于点云系统的构建,主要结合点云库PCL、可视化库VTK以及图像处理开源库OpenCV来做结合图像和点云数据协同的岩体分析系统。这里希望跟大家分享一下自己的整体构建流程,不足的地方希望大家能够帮忙指出以便改进。由于还在搭建过程中,所以文章的更新时间不一定,但是有关键性的进展一定会写出来讨论,谢谢大家。
整个系统构建的分析过程包括模块划分,模块之间的耦合,数据库构建,相关类的创建、继承等理论细节不在这里讨论。这篇文章先从系统的界面框架开始:
一、菜单栏
关于系统的菜单由于之前讨论完成,主要分为了相关数据加载、数据编辑、三维可视化、点云处理、图像处理等模块。
在建立完成一个基于Dialog的MFC应用程序之后在资源视图下,右键选择“添加资源”,在弹出的对话框中我们选择新建一个Meun,确定之后就会产生一个新的IDR_MENU1菜单资源,保持默认设置,ID号不变,我们编辑我们的菜单项,如下图所示:
菜单项前的图标等其他细节还未设置,之后会进行补充。
建立完成资源之后,我们需要加载这个资源。打开XXXDlg.h,创建一个成员变量
CMenu m_Menu;
接着在XXXDlg.cpp文件中找到XXXDlg::OnInitDialog(){},在//TODE下编写代码:
m_Menu.LoadMenu(IDR_MENU1); SetMent(&m_Menu);
这样我们的菜单资源就加载好了;
二、工具栏
有了菜单栏接下来是工具栏,这里只是简单设置了下工具栏,关于工具栏的详细操作可以参考这篇文章。
工具栏的创建可以进入资源窗口里进行创建,当然这里并没有使用这一方法。
同样在XXXDlg.h中创建ToolBar的成员变量和CImageList:
CToolBar m_toolbar; CImageList m_toolbarImg;
接着在XXXDlg.cpp下的OnInitDialog(){}中编写代码:
if(!m_toolbar.CreateEx(this, TBSTYLE_FLAT, WS_CHILD | WS_VISIBLE | CBRS_TOP | CBRS_GRIPPER | CBRS_TOOLTIPS | CBRS_FLYBY | CBRS_SIZE_DYNAMIC ) ) { TRACE0(_T("创建工具条失败\n")); }
由于我们需要为我们的工具栏上的按钮设置自定义图片,所以这里要利用CImageList:
/*指定按钮图标图片*/ m_toolbarImg.Create(16,16,ILC_COLOR16,3,3); m_toolbarImg.Add(AfxGetApp()->LoadIcon(IDI_ICON1)); m_toolbarImg.Add(AfxGetApp()->LoadIcon(IDI_ICON2)); m_toolbarImg.Add(AfxGetApp()->LoadIcon(IDI_ICON3)); m_toolbar.GetToolBarCtrl().SetImageList(&m_toolbarImg);
由于这里使用的图片大小为16*16的,所以需要在Crete方法里加以设置,Create方法最后两个参数可以随便设置。
接下来的三行代码是加载我们的自定义图标,在此之前我们需要将我们需要的图标文件拷贝到工程文件的res文件夹下,然后在资源视图下的Icon文件夹中导入我们想要的图标,这里我暂时先载入了3个图标:
修改ID分别为IDI_ICON1,IDI_ICON2,IDI_ICON3。然后利用CImageList的Add方法,载入三个图标。
最后一行代码是获取CToolBarCtrl变量并进行图标的设置,这里要提一句,SetImageList是默认的显示图片方式,而SetHotImageList是鼠标放上去之后的高亮显示,两者是不一样的。
接下来就可以具体为工具栏上添加button工具按钮了。
m_toolbar.SetButtons(NULL,3); m_toolbar.SetButtonInfo(0,IDI_ICON1,TBSTYLE_BUTTON,0); m_toolbar.SetButtonText(0,"功能1"); m_toolbar.SetButtonInfo(1,IDI_ICON2,TBSTYLE_CHECK,1); m_toolbar.SetButtonText(1,"功能2"); m_toolbar.SetButtonInfo(2,IDI_ICON3,TBSTYLE_BUTTON,2); m_toolbar.SetButtonText(2,"功能3"); m_toolbar.SetSizes(CSize(24,24),CSize(16,16)); RepositionBars(AFX_IDW_CONTROLBAR_FIRST,AFX_IDW_CONTROLBAR_LAST,0); m_ImageList.Detach();
我们暂时添加3个按钮,所以第一行为3,第二行的setButtonInfo表示开始添加工具,里面 有4个参数,第一个参数表示工具按钮的id号,0表示第一个;第二个参数是图标的ID号;第三个参数是工具的样式,这里为按钮所以设置为TBSTYLE_BUTTON,当然也可以设置为TBSTYLE_CHECK表示单选check控件;最后一个为CImageList里对应的图片id号,第一个从0开始。之后设置按钮的鼠标提示文本。
按这样的方式设置好三个按钮之后,我们需要设置按钮大小。利用SetSizes方法,第一个参数是按钮的大小,第二个参数是按钮里图标图片的大小,注意的是图标图片的大小于我们之前加载图片的时候需要 一致,同为16*16。
最后不要忘记最后两行RepositionBars和CImageList的Detach(),前者用来显示工具栏,后者释放图片资源。
三、内容树状图
我们在打开文件的时候,通常软件的左侧会显示出一个内容的树状图,这里要用的是CTreeControl。
我们打开工程的主对话框,在左侧添加一个tree Control,设置好大小与布局之后,修改ID号,通过鼠标右键为其关联成员变量,如下图:
完成之后可以在XXXDlg.h中找到该变量,之后我们可以对该控件进行初始化设置。
我们知道内容树状图的节点前一般可以有图标来美化,所以这里我们同样需要利用CImageList来保存我们节点的图标文件:
hIcon[0] = theApp.LoadIcon(IDI_ICON1); hIcon[1] = theApp.LoadIcon(IDI_ICON2); hIcon[2] = theApp.LoadIcon(IDI_ICON3); m_ImageList.Create(16,16,ILC_COLOR16,3,3); for(int i = 0 ;i < 3;i++) { m_ImageList.Add(hIcon[i]); } m_Tree.SetImageList(&m_ImageList,TVSIL_NORMAL);
之后我们建立树状节点:
/*建立第一个节点树*/ hRoot = m_Tree.InsertItem(_T("根节点1"),0,0); hCataItem = m_Tree.InsertItem(_T("节点1"),1,1,hRoot,TVI_LAST); m_Tree.SetItemData(hCataItem,1); hArtItem = m_Tree.InsertItem(_T("子节点1"),2,2,hCataItem,TVI_LAST); hArtItem = m_Tree.InsertItem(_T("子节点2"),2,2,hCataItem,TVI_LAST); *建立第二个节点树*/ hRoot = m_Tree.InsertItem(_T("根节点2"),0,0); hCataItem = m_Tree.InsertItem(_T("节点1"),1,1,hRoot,TVI_LAST); m_Tree.SetItemData(hCataItem,1); hArtItem = m_Tree.InsertItem(_T("子节点1"),2,2,hCataItem,TVI_LAST); hArtItem = m_Tree.InsertItem(_T("子节点2"),2,2,hCataItem,TVI_LAST);
依次建立根节点,节点和子节点,并设置节点对应的父节点以及子节点对应的节点。这样就算初始化了节点树状图了。当然了,在具体的系统当中,我们应该是针对具体数据打开之后再显示内容树状图,那时候节点对应的文本应该是根目录,路径名称以及文件名了,节点的级数也是动态变化的,如根据具体的文件路径来设置。
四、属性表单
由于系统涉及图片影像数据与点云数据,我们需要在系统界面上能够在加载数据的同时显示出有关数据的属性信息,所以在界面右侧设置了属性表单。
如何建立我们自己的属性表单呢?
第一、在资源视图中找到Dialog文件夹,鼠标右键添加资源,在弹出的资源对话框中选择展开Dialog,选择里面的IDD_PROPERTY_LARGE,选择新建。
第二、按住Ctrl依次拖动鼠标,创建2份拷贝,这时候有了3个资源,分别是IDD_PROPERTY_LARGE,IDD_PROPERTY_LARGE1,IDD_PROPERTY_LARGE2。(这里只是暂时创建了3份属性表,可以根据需要自己改变)
第三、在XXXDlg.h文件中添加CPropertyPage成员变量和CPropertySheet成员变量:
CPropertyPage m_page1; CPropertyPage m_page2; CPropertyPage m_page3; CPropertySheet m_pagesheet;
之后添加主对话框的OnCreate()和OnSize()事件。(之前想在OnInitDialog里设置,可是要么出错要么显示不出来,咋回事?)
在OnCreate中加入代码:
m_page1.Construct(IDD_PROPPAGE_LARGE); m_page2.Construct(IDD_PROPPAGE_LARGE1); m_page3.Construct(IDD_PROPPAGE_LARGE2); m_pagesheet.Construct("property pages",this); m_pagesheet.AddPage(&m_page1); m_pagesheet.AddPage(&m_page2); m_pagesheet.AddPage(&m_page3); m_pagesheet.Create(this,WS_CHILD|WS_VISIBLE);
第四、为了方便布局,我们先在主对话框中放置属性表单的位置添加一个static控件,设置好布局和Visial属性为false,这样我们就可以获取其Rect来布局我们的表单。
第五、在OnSize()中添加变量:
CRect rc; GetDlgItem(IDC_STATIC_PROPERTY)->GetWindowRect(&rc); m_pagesheet.SetWindowPos(&wndTop,rc.left-10,rc.top-25,rc.right,rc.bottom,NULL);
这里有个问题,就是我直接获取rc的left和top,为什么不能完全叠加到static控件上,总是会有偏移,所以这里我就自己设置了偏移量好吻合上去,可是这样好麻烦...求解答。
以上我们就设置了表单及布局,具体要在表单上添加什么内容,以后再设计。
第五、主工作区视图
主视图区是系统的主要部分,这里的主视图主要分为两个,由Tab控件用于切换,主要是点云的3D显示区域以及图像显示区域。
第一、我们在预留的主视图区添加一个Tab控件,右键为其关联成员变量m_Tab;
第二、由于我们需要为Tab控件里放2个显示方式,所以我们先在资源视图下的Dialog里添加两个对话框资源,修改其Bord属性为None、Style属性为Child,然后为3D显示的对话框修改ID为IDD_DIALOG_VIEW,图片显示的对话框为IDD_DIALOG_PICTURE。在VIEW对话框中添加了static控件来布局VTK窗口,在PIC对话框中添加Picture控件来显示图片影像,并分别对其生成类CPageView和CPagePic。
第三、在XXXDlg.h中添加成员变量
CPagePic m_pagepicture; CPageView m_pageviewer;
第四、在XXXDlg.cpp文件中的OnInitDialog里添加代码;
CRect r; m_tab.InsertItem(1,_T("可视化")); m_tab.InsertItem(2,_T("影像")); m_pageviewer.Create(IDD_DIALOG_View,&m_tab);//加载对话框并管理到tab控件 m_pagepicture.Create(IDD_DIALOG_PICTURE,&m_tab); m_tab.GetClientRect(&r); m_pageviewer.SetWindowPos(NULL,r.left+1,r.top+1,r.right-1,r.bottom-25,SWP_SHOWWINDOW);//设置显示位置 m_pagepicture.SetWindowPos(NULL,r.left+1,r.top+1,r.right-1,r.bottom-25,SWP_HIDEWINDOW);
第五、响应Tab控件的OnTcnSelchangeTab1()消息,双击就行。并在其中添加代码:
CRect rect; m_tab.GetClientRect(&rect); switch(m_tab.GetCurSel()) { case 0: m_pageviewer.SetWindowPos(NULL,rect.left+1,rect.top+1,rect.right-1,rect.bottom-25,SWP_SHOWWINDOW); m_pagepicture.SetWindowPos(NULL,rect.left+1,rect.top+1,rect.right-1,rect.bottom-25,SWP_HIDEWINDOW); break; case 1: m_pageviewer.SetWindowPos(NULL,rect.left+1,rect.top+1,rect.right-1,rect.bottom-25,SWP_HIDEWINDOW); m_pagepicture.SetWindowPos(NULL,rect.left+1,rect.top+1,rect.right-1,rect.bottom-25,SWP_SHOWWINDOW); break; } *pResult = 0;
第六、设置好了之后如果我们想设置VTK的显示该怎么办呢?我们可以调用VTK的vtkRenderWindow对象的SetParetId方法来设置,获取tab控件下的pageviewer对象,得到其内部的static控件的句柄。
renwin->SetParentId(m_pageviewer.GetDlgItem(IDC_STATIC_VTK)->m_hWnd); CRect rect; m_pageviewer.GetDlgItem(IDC_STATIC_VTK)->GetWindowRect(&rect); renwin->SetSize(rect.Width(),rect.Height()); iren->SetRenderWindow(renwin); iren->Initialize(); renwin->Render(); iren->Start();
第七、请包含所有需要的相关头文件。
全部的布局就这样设置好了,现在我们可以看一下效果了,我事先添加了一部分的PCL文件打开的代码,以txt文本的方式被VTK读取并显示。