引言
隨著科技的發展,遙感技術已經形成了一個從地面到空中乃至空間,從數據獲取、處理到解譯分析和應用,對全球進行探測和監測的多層次、多視角、多領域的觀測體系,成為獲取地球資源與環境信息的重要手段。隨著對如此龐大數據的處理速度的提升,遙感數字圖像處理技術也得以迅速發展。
任何一套遙感圖像處理軟件都不能完全滿足所有用戶的需求; 并且隨著數字圖像處理技術的發展,圖像處理軟件還需要不斷地添加新的功能,如果對軟件進行修改,然后重新編譯、發布,不僅費時費力,而且還可能出現版本兼容性方面的問題。
本文提出一種可擴展遙感圖像處理軟件框架的設計方法,可將執行程序\\( EXE\\) 和動態鏈接庫\\( dy-namic link library,DLL\\) 程序以擴展模塊的形式添加到軟件平臺中; 并動態添加菜單項,以約定方式對其調用,利用文件映射技術進行數據交換,以便科研人員能夠快速地將研究成果編寫成新的功能模塊,并方便地添加到遙感圖像處理軟件系統中。
1 系統框架設計
可擴展遙感圖像處理系統主要實現框架功能,構成一個遙感圖像處理軟件平臺。該系統中需要規定統一的遙感圖像格式及其訪問模式; 通過應用程序接口\\( application programming interface,API\\) 以平臺功能方式提供基礎圖像處理功能,定義對外開放的內部程序接口和外部程序調用接口; 其他功能均可由擴展模塊來實現。系統的整體結構如圖1 所示?!緢D1】
1. 1 數據接口
遙感圖像的格式多種多樣,為了方便圖像處理功能的開發,在遙感圖像處理軟件平臺中規定一種統一的圖像格式,并且提供格式轉換功能,在處理圖像之前將圖像轉換成統一格式。主程序將實現讀寫遙感圖像的功能\\( 比如獲取頭文件信息,獲取某波段、某區塊的圖像數據\\) ,擴展模塊時不需要了解遙感圖像的具體格式,只要通過接口直接調用主程序中的功能就能獲取到圖像信息。
1. 2 基礎平臺功能
主程序可以提供圖像文件訪問、圖像顯示等基礎功能,還可以包括以下功能: ①圖像信息查看,包括圖像直方圖查看和圖像波譜查看等; ②圖像增強功能,包括圖像拉伸、圖像平滑、圖像銳化等功能;③顯示外部圖像源與窗口控制,可以讓擴展模塊新建窗口或指定現有窗口并顯示擴展模塊組織的圖像; ④圖像裁剪、旋轉與縮放功能。
1. 3 API 的設計
軟件平臺應提供應用程序接口\\( API\\) ,主要包括遙感圖像處理的一些基礎功能,如上文提到的數據接口,基本的遙感圖像增強、圖像顯示等。將這些功能封裝到一個動態鏈接庫\\( DLL\\) 中,并開放 API供擴展模塊調用。專業人員可以軟件平臺為基礎,進行二次開發,不必在基礎功能上重復開發、浪費精力。平臺和擴展模塊之間、擴展模塊和擴展模塊之間都可以互相調用,完全開放、透明,使系統具有良好的可擴展性。
1. 4 擴展模塊與動態菜單
擴展模塊使用 C\\C ++ 進行開發,可以以 EXE,DLL\\( 或其他形式,只要定義相應 API 即可\\) 的形式實現。在圖像界面中通過動態菜單調用擴展模塊,EXE 模塊可以被直接調用,DLL 模塊可以開放程序接口或者定義一個導出變量\\( 包含接口信息\\) ??蒲腥藛T可以隨時將新功能添加到系統平臺中\\( 有新的研究成果時,可以依托平臺現有功能,僅針對新的研究成果快速地編寫程序,并方便地添加到系統平臺中\\) ,新的功能模塊可以實時地反映到系統平臺的動態菜單中。整個系統平臺的關鍵是擴展模塊的添加與調用方式。
2 菜單的動態添加與響應
在圖形界面中使用擴展模塊功能,主要是通過動態添加的菜單來調用的??梢园巡藛雾椇蛿U展模塊的對應關系保存到一個配置文件中,每次程序啟動時,根據這個配置文件來實現動態菜單初始化,這種方法需要開發人員自行開發一個編輯配置文件的工具; 還可以把所有擴展模塊存儲到同一個文件夾中,每次程序啟動時掃描該文件夾,為每一個擴展模塊動態添加一個菜單項。第一種方法需要額外開發一個工具,但是配置文件中可以保存多種信息\\( 比如菜單的上下級關系\\) ,這樣在添加菜單時可以更好地進行擴展模塊的分類和顯示; 而第二種方法雖然使用起來簡單方便,但是在菜單中所有的擴展模塊只能羅列在一起,不美觀也不方便使用。
2. 1 菜單的動態添加
動態添加菜單的操作可在應用程序類 CWinApp的 InitInstance 函數中進行。添加菜單項的操作主要使用 AppendMenu 函數,使用該函數時要根據設計的菜單級別獲取相應的父級菜單對象,設置該函數的第一個參數為 MF_POPUP 和 MF_STRING,可以分別添加下拉菜單和菜單項。在所有的菜單項添加完成之后,進行 DrawMenuBar 操作,這樣動態添加的菜單就可以顯示出來。添加2 級菜單的動態添加過程可以通過以下代碼實現:
CMenu * pTopMenu = AfxGetMainWnd\\( \\) - > GetMenu\\( \\) ; / / 獲取系統菜單指針
int nMenu = pTopMenu - > GetMenuItemCount\\( \\) ; / / 獲取現有菜單項個數
while \\( i < nMenuCount\\)
{
szTitle. Format\\( _T\\( " % s" \\) ,menuItem[i]. title\\) ;
if\\( menuItem[i]. type = = TOP_MENU\\)
{
CMenu newMenu;
newMenu. CreatePopupMenu\\( \\) ;
pTopMenu - > AppendMenuW\\( MF_POPUP,\\( UINT\\) newMenu. m_hMenu,szTitle\\) ;
newMenu. Detach\\( \\) ; / / 菜單項與局部變量分離
+ + nMenu; / / 遞增菜單數,以便讓子菜單獲取正確的父級菜單
}
else if \\( menuItem[i]. type = = SUB_MENU\\)
{
/ / 獲取當前子菜單的父級菜單
pParentMenu = pTopMenu - > GetSubMenu\\( nMenu - 1\\) ;
/ / 子菜單需要有相應的命令響應,需要一個全局唯一的 ID
pParentMenu - > AppendMenuW\\( MF_STRING,newMenuID,szTitle\\) ;+ + newMenuID;
}
+ + i;
}
DrawMenuBar\\( AfxGetMainWnd\\( \\) - > m_hWnd\\) 。
如果只進行添加菜單的操作,那么界面中的這些動態菜單項都是“灰色的”\\( 不可用狀態\\) ,而不像其他系統自帶菜單項那樣顯示為可用狀態;這是因為其他系統的程序對自帶的菜單項進行統一管理,而通過上述方法添加的菜單項需要自行管理。對 菜 單 狀 態 進 行 自 定 義 操 作 需 要 重 寫CView 類中的 OnCmdMsg函數,其中 nCode 參數表明本次命令的消息類型,當 nCode 值為 CN_UP-DATE_COMMAND_UI 時,表示更新用戶界面。開發人員可以根據具體需求有選擇性地設置各個菜單項的狀態。將所有動態添加的菜單“激活”的過程可以通過以下代碼實現:
BOOL CView: : OnCmdMsg\\( UINT nID,int nCode,void* pExtra,AFX_CMDHANDLERINFO* pHandlerInfo\\)
{
if \\( nCode = = CN_UPDATE_COMMAND_UI
&& nID > = IDM_DYNAMENU
&& nID < \\( IDM_DYNAMENU + cntNewMenu\\)
&& pHandlerInfo = = NULL\\)
{
CCmdUI* cmdUI = \\( CCmdUI* \\) pExtra;
cmdUI - > Enable\\( TRUE\\) ;
return TRUE;
}
return CView: : OnCmdMsg\\( nID,nCode,pExtra,pHandlerInfo\\) ;
}
2. 2 菜單的動態響應
通過相應設置,動態添加的菜單項即可正常顯示; 但是點擊菜單項之后是沒有反應的,因為還沒有設置每個菜單項對應的命令,對菜單命令進行自定義操作也需要重寫 CView 類的 OnCmdMsg 函數,當 nCode 參數為 0 時,表示對菜單進行響應。動態響應菜單命令需要維護一個全局變量,這個變量記錄了每個菜單項與相應的擴展模塊的對應關系。該變量的初始化可以在添加動態菜單的同時進行。假設擴展模塊都是 EXE 形式,設置菜單命令響應的過程可以通過以下代碼實現:
BOOL CView: : OnCmdMsg\\( UINT nID,int nCode,void* pExtra,AFX_CMDHANDLERINFO* pHandlerInfo\\)
{
if \\( nCode = = NULL
&& nID > = IDM_DYNAMENU
&& nID < \\( IDM_DYNAMENU + cntNewMenu\\)
&& pHandlerInfo = = NULL\\)
{
for\\( i = 0; i < cntNewMenu; + + i\\)
{
if\\( menuCmdList[i]. ID = = nID\\)
{
WinExec\\( menuCmdList[i]. path,SW_SHOW\\) ;
return TRUE;
}
}
}
return CView: : OnCmdMsg\\( nID,nCode,pExtra,pHandlerInfo\\) ;
}
3 擴展模塊的接口設計及數據交換
擴展模塊可以以 EXE 或 DLL 的形式\\( 當然也可以是其他形式\\) 實現。對 EXE 程序的調用很簡單,直接使用文件路徑和 WinExec,ShellExecute 或者 CreateProcess 等函數即可; 而調用 DLL 程序則略微復雜,除了文件路徑,還要知道程序接口,然后利用 LoadLibrary,GetProcAddress 和 FreeLibrary 函數調用。對其他形式擴展模塊的調用只要定義相應的調用方式即可。
主程序中的部分功能\\( 包括圖像文件訪問功能、圖像顯示功能、窗口控制等\\) ,需要開放給擴展模塊??梢园堰@些功能封裝到一個 DLL 中,并對擴展模塊開放程序接口。
3. 1 擴展模塊的接口設計
調用擴展模塊的接口稍顯復雜。上述內部程序的接口由于已經做好規定,容易調用; 而擴展模塊是隨時添加的,對其他程序來說其接口是未知的,這就需要制定相應的調用規則\\( 比如把所有擴展模塊的接口都命名為“exFunc\\( \\) ”\\) ,再根據擴展模塊的路徑進行調用; 或者在每個 DLL 中都構造一個結構統一的導出變量\\( 這個變量中包含了接口的信息\\) ,再根據這個變量中的接口信息對接口進行調用。
3. 2 數據交換
接口規則制定好之后,就要處理數據交換\\( 或稱“進程通信”\\) 的問題。
主程序和擴展模塊都是獨立的進程。在 Windows系統中,每個進程都有自己獨立的內存空間,該獨立的內存空間包含了所有的 EXE 模塊或 DLL 模塊的代碼和數據以及動態內存分配的空間。每個進程的內存空間只能被該進程訪問,其他進程則不能訪問。
如果要在進程間共享數據\\( 也就是創建一塊不同進程都能訪問的內存\\) ,可以使用內核對象\\( 因為內核對象由 Windows 系統內核所擁有,而不是由進程所擁有\\) ??紤]到需要交換的數據量可能會很大,可以使用文件映射方式\\( 當然也可以使用其他方式\\) 進行數據交換。在程序初始化時使用 Create-FileMapping 函數開辟文件映射空間,然后使用 Map-ViewOfFile 函數創建文件視圖,可以根據需求創建多個文件視圖。在主程序運行過程中,根據需要更新文件視圖的內容\\( 也就是更新共享數據\\) 、在主程序退出時,使用 UnmapViewOfFile 函數和 CloseHan-dle 函數,分別關閉文件視圖和文件映射,防止內存泄漏。在讀寫文件視圖時,可以使用互斥\\( Mutex\\)來控制對公共資源的訪問。
值得注意的是,每個進程的虛擬地址空間都是有限的。在 Windows 系統中,32 位進程的虛擬地址空間通常為 2 GB,64 位進程的虛擬地址空間為 8 TB。如果使用 32 位的程序,而遙感圖像文件又大于 2 GB,則進行文件映射時就不能將整個圖像文件裝入內存。解決這個問題可以有 2 種方法:①每次使用 MapViewOfFile 函數時,只把圖像的一部分映射到內存空間即可; 程序使用圖像的其他部分時,先使用 UnmapViewOfFile 函數解除上一個映射,然后再映射新的圖像,這種方法需要一個高效靈活的圖像分塊讀取算法; ②開發 64 位的程序。但考慮到目前 PC 機的內存容量一般不超過 16 GB,如果圖像過大,映射到內存空間就會導致大量使用虛擬內存; 而大量的圖像文件存儲在硬盤空間中,會導致程序運行速度變慢。
4 結論
1\\) 開發具有基本功能的軟件平臺,為可執行程序\\( EXE\\) 和動態鏈接庫\\( DLL\\) 等擴展模塊預留調用接口,在需要時開發擴展模塊對平臺進行擴充,可大大降低軟件開發的復雜度,避免重復勞動。
2\\) 統一遙感圖像格式,制定圖像讀寫方法,可降低圖像處理的復雜度。
3\\) 制定合理的應用程序接口\\( API\\) 及其調用規則,使軟件平臺具有良好的可擴展性,并使各個模塊間的獨立性也得到很大提高,有利于模塊的擴展與更新。
隨著遙感技術的發展,遙感圖像包含的信息量越來越大,處理海量遙感圖像\\( 快速格式轉換、海量圖像快速顯示等\\) 需要更好的算法,這方面還有待進一步的研究。