摘要: 1. 如何獲取系統日期
CTime tm = CTime :: GetCurrentTime();
CString strTime = tm.Format(_TEXT(“%Y-%M-%d %H:%M:%S));
MessageBox(strTime);
2.  ...
閱讀全文
Visual C++以其可視化的編程風格成為目前Windows程序設計與開發的主流開發工具。而對話框在Visual C++編程中使用的尤其多。諸如模式對話框、無模式對話框、基于對話框的應用程序等。絕大部分的VC++的書籍中都花費大量的篇幅與筆墨來講解對話框,這充分證明了對話框在Windows應用程序中的作用。
很多人可能都用過Bitware軟件,不知大家還記不記得其界面對話框就可以伸展自如。按下一個按鈕,對話框就向水平方向或垂直方向擴展。再按一下按鈕,對話框又回復到原來的大小。其實這并不是一個很復雜的問題,下面我們就來講解如何制作伸展自如的對話框。
1 打開VisualC++工作臺,新建工程設為aaa。
2 創建基于對話框的應用程序如下所示:
其余選擇皆為缺省即可。
3 在對話框資源中增加控件資源,如下圖所示:
其中,最靠右邊的一排控件和最靠近下面的兩排控件將在對話框伸展或收縮時顯示出來或被遮蓋。并且為了示例方便,我們有意將他們的值對應起來。并且我們需要通過ClassWizard給每個控件分別關聯成員變量,如下所示:
參考DoDataExchange()函數我們就可以知道每個控件所關聯的變量了,如下所示:
DDX_Text(pDX, IDC_HEIGHT, m_wHeight);
DDX_Text(pDX, IDC_STREAM_ID, m_wStreamID);
DDX_Text(pDX, IDC_WIDTH, m_wWidth);
DDX_Text(pDX, IDC_SEQUENCE_ORDER, m_wSequenceOrder);
DDX_Text(pDX, IDC_MAX_RATE, m_dwMaxRate);
DDX_Text(pDX, IDC_MIN_RATE, m_dwMinRate);
DDX_Text(pDX, IDC_HEIGHT2, m_wHeight2);
DDX_Text(pDX, IDC_MAX_RATE2, m_dwMaxRate2);
DDX_Text(pDX, IDC_MIN_RATE2, m_dwMinRate2);
DDX_Text(pDX, IDC_SEQUENCE_ORDER2, m_wSequenceOrder2);
DDX_Text(pDX, IDC_STREAM_ID2, m_wStreamID2);
DDX_Text(pDX, IDC_WIDTH2, m_wWidth2);
DDX_Check(pDX, IDC_HORIZONTAL, m_bHorizontal);
DDX_Check(pDX, IDC_VERTICAL, m_bVertical);
實際上,我們也可以不用ClassWizard而直接將上面的一段代碼copy到DoDataExchange()函數的
//{{AFX_DATA_MAP(CAaaDlg)
......
//}}AFX_DATA_MAP
之間,(注意一定要在“//{{AFX_DATA_MAP(CAaaDlg)”與“//}}AFX_DATA_MAP”之間)。
同時在aaaDlg.h文件中,在
//{{AFX_DATA(CAaaDlg)
enum { IDD = IDD_AAA_DIALOG };
......
//}}AFX_DATA
之間增加如下變量定義即可:
(注意一定要在“//{{AFX_DATA(CAaaDlg)”與“//}}AFX_DATA”之間)
UINT m_wHeight;
UINT m_wStreamID;
UINT m_wWidth;
UINT m_wSequenceOrder;
DWORD m_dwMaxRate;
DWORD m_dwMinRate;
UINT m_wHeight2;
DWORD m_dwMaxRate2;
DWORD m_dwMinRate2;
UINT m_wSequenceOrder2;
UINT m_wStreamID2;
UINT m_wWidth2;
BOOL m_bHorizontal;
BOOL m_bVertical;
5 在完成上面的步驟后,我們就可以定義幾個新的變量用來保存窗口伸展狀態時的信息以及收縮狀態時的信息。如下:
WORD m_wOrigrinWidth; //原始狀態下的窗口寬度
WORD m_wReducedWidth; //收縮狀態下的窗口寬度
WORD m_wOrigrinHeight; //原始狀態下的窗口高度
WORD m_wReducedHeight; //收縮狀態下的窗口高度
WORD m_screenWidth; //屏幕寬度
WORD m_screenHeight; //屏幕高度
在完成以上所有的步驟后,就可以對窗口的伸展與收縮進行隨心所欲的控制了,首先我們來侃侃具體的代碼,下面再進行具體的解釋。代碼為:
CenterWindow(NULL);
m_screenWidth = GetSystemMetrics(SM_CXSCREEN);
m_screenHeight = GetSystemMetrics(SM_CYSCREEN);
WINDOWPLACEMENT* lpwndpl=new WINDOWPLACEMENT;
GetWindowPlacement(lpwndpl);
m_wOrigrinWidth = lpwndpl->rcNormalPosition.right;
m_wOrigrinWidth -= lpwndpl->rcNormalPosition.left;
m_wOrigrinHeight = lpwndpl->rcNormalPosition.bottom;
m_wOrigrinHeight -= lpwndpl->rcNormalPosition.top;
LPRECT lpRect1,lpRect2;
lpRect1=new RECT;
lpRect2=new RECT;
GetDlgItem(IDC_PROGRESS_BAR)->GetWindowRect(lpRect1);
GetDlgItem(IDC_STREAM_ID)->GetWindowRect(lpRect2);
lpwndpl->rcNormalPosition.right=(lpRect1->right+lpRect2->left)/2;
m_wReducedWidth = lpwndpl->rcNormalPosition.right;
m_wReducedWidth -= lpwndpl->rcNormalPosition.left;
GetDlgItem(IDC_PROGRESS_BAR)->GetWindowRect(lpRect1);
GetDlgItem(IDC_SEQUENCE_ORDER2)->GetWindowRect(lpRect2);
lpwndpl->rcNormalPosition.bottom=(lpRect1->bottom+lpRect2->top)/2;
m_wReducedHeight = lpwndpl->rcNormalPosition.bottom;
m_wReducedHeight -= lpwndpl->rcNormalPosition.top;
delete lpRect1;
delete lpRect2;
if(m_bHorizontal == TRUE)
{
lpwndpl->rcNormalPosition.right = lpwndpl->rcNormalPosition.left;
lpwndpl->rcNormalPosition.right += m_wOrigrinWidth;
lpwndpl->rcNormalPosition.bottom = lpwndpl->rcNormalPosition.top;
lpwndpl->rcNormalPosition.bottom += m_wReducedHeight;
}
else
{
lpwndpl->rcNormalPosition.right = lpwndpl->rcNormalPosition.left;
lpwndpl->rcNormalPosition.right += m_wReducedWidth;
lpwndpl->rcNormalPosition.bottom = lpwndpl->rcNormalPosition.top;
lpwndpl->rcNormalPosition.bottom += m_wReducedHeight;
}
if(m_bVertical == TRUE)
{
lpwndpl->rcNormalPosition.right = lpwndpl->rcNormalPosition.left;
lpwndpl->rcNormalPosition.right += m_wReducedWidth;
lpwndpl->rcNormalPosition.bottom = lpwndpl->rcNormalPosition.top;
lpwndpl->rcNormalPosition.bottom += m_wOrigrinHeight;
}
else
{
lpwndpl->rcNormalPosition.right = lpwndpl->rcNormalPosition.left;
lpwndpl->rcNormalPosition.right += m_wReducedWidth;
lpwndpl->rcNormalPosition.bottom = lpwndpl->rcNormalPosition.top;
lpwndpl->rcNormalPosition.bottom += m_wReducedHeight;
}
SetWindowPlacement(lpwndpl);
上面這段代碼首先將窗口置于屏幕中間,這可以通過函數CenterWindow(GetDesktopWindow()) 來實現,函數 CenterWindow()的用法為:
void CenterWindow( CWnd* pAlternateOwner = NULL );
其中參數pAlternateOwner指向所想居中的窗口的指針。
然后利用函數GetSystemMetrics( int nIndex )得到系統當前設置如屏幕分辨率等。
nIndexs= SM_CXSCREEN 時函數返回屏幕的寬度;返回值單位為像素點。
nIndexs= SM_CYSCREEN 時函數返回屏幕的高度;返回值單位為像素點。
函數BOOL GetWindowPlacement( WINDOWPLACEMENT* lpwndpl ) 是最重要的。他的參數為一個指向結構變量WINDOWPLACEMENT的指針(lpwndpl);其中WINDOWPLACEMENT結構變量數據結構具體為:
typedef struct tagWINDOWPLACEMENT { /* wndpl */
UINT length;
UINT flags;
UINT showCmd;
POINT ptMinPosition;
POINT ptMaxPosition;
RECT rcNormalPosition;
} WINDOWPLACEMENT;
他包含了窗口在屏幕上的定位信息。其中成員變量的含義為:
length:指結構變量的長度,單位字節。
flags: 標志值,控制窗口最小化或窗口還原的方法,可以取如下值:
WPF_SETMINPOSITION:指定窗口最小化時的x位置和y位置。
WPF_RESTORETOMAXIMIZED:指定窗口以最大化方式還原,盡管可能窗口并不是在最大化時最小化的。不改變窗口的缺省還原方式。
showCmd:指定窗口的當前顯示狀態。可以取值:
SW_HIDE:隱藏窗口并激活另一窗口。
SW_MINIMIZE:最小化指定窗口并激活系統窗口列表中最頂層窗口。
SW_RESTORE:激活并顯示窗口,如果窗口處于最小化或最大化狀態,則窗口還原到原始大小和位置。
SW_SHOW:以窗口的當前大小和位置激活并顯示窗口。
SW_SHOWMAXIMIZED:以最大化方式激活并顯示窗口。
SW_SHOWMINIMIZED:以圖標方式激活并顯示窗口。
SW_SHOWMINNOACTIVE:以圖標方式窗口。 但不改變窗口的活動狀態。
SW_SHOWNA:以窗口的當前狀態顯示窗口。
SW_SHOWNOACTIVATE:以窗口最近一次的大小和位置顯示窗口。 但不改變窗口的活 動狀態。
SW_SHOWNORMAL:激活并顯示窗口。如果窗口被最大化或最小化,則窗口還原到原始大小和位置。
ptMinPosition:指定窗口最小化時的左傷角坐標。
ptMaxPosition:指定窗口最大化時的左傷角坐標。
rcNormalPosition:指定窗口在還原時的坐標。
通過靈活使用函數GetWindowPlacement()就可以得到窗口的配置信息。
看到這,可能有些讀者已經想到了GetWindowPlacement()函數的姐妹函數SetWindowPlacement(),不用多說,其用法如下:
BOOL SetWindowPlacement( WINDOWPLACEMENT* lpwndpl );
顯然,通過函數SetWindowPlacement(),再加以簡單的計算,我們就可以來設置窗口的位置、大小以及狀態等,從而可以自如地控制窗口顯示與否以及窗口的大小、位置等。這里我們就不再多解釋了。
6 利用ClassWizard對控件IDC_HORIZONTAL和IDC_VERTICAL增加消息映射BB_CLICKED,
并分別在消息映射函數中增加如下代碼如下:
void CAaaDlg::OnHorizontal()
{
// TODO: Add your control notification handler code here
m_bHorizontal = !m_bHorizontal;
UpdateData(FALSE);
WINDOWPLACEMENT* lpwndpl=new WINDOWPLACEMENT;
GetWindowPlacement(lpwndpl);
if(m_bHorizontal == TRUE)
{
lpwndpl->rcNormalPosition.right = lpwndpl->rcNormalPosition.left;
lpwndpl->rcNormalPosition.right += m_wOrigrinWidth;
/*
lpwndpl->rcNormalPosition.bottom = lpwndpl->rcNormalPosition.top;
lpwndpl->rcNormalPosition.bottom += m_wReducedHeight;
*/
}
else
{
lpwndpl->rcNormalPosition.right = lpwndpl->rcNormalPosition.left;
lpwndpl->rcNormalPosition.right += m_wReducedWidth;
/*
lpwndpl->rcNormalPosition.bottom = lpwndpl->rcNormalPosition.top;
lpwndpl->rcNormalPosition.bottom += m_wReducedHeight;
*/
}
SetWindowPlacement(lpwndpl);
delete lpwndpl;
}
void CAaaDlg::OnVertical()
{
// TODO: Add your control notification handler code here
m_bVertical = !m_bVertical;
UpdateData(FALSE);
WINDOWPLACEMENT* lpwndpl=new WINDOWPLACEMENT;
GetWindowPlacement(lpwndpl);
if(m_bVertical == TRUE)
{
lpwndpl->rcNormalPosition.bottom = lpwndpl->rcNormalPosition.top;
lpwndpl->rcNormalPosition.bottom += m_wOrigrinHeight;
}
else
{
lpwndpl->rcNormalPosition.bottom = lpwndpl->rcNormalPosition.top;
lpwndpl->rcNormalPosition.bottom += m_wReducedHeight;
}
SetWindowPlacement(lpwndpl);
delete lpwndpl;
}
7 最后利用ClassWizard對控件IDC_BEGIN_SIMULATE增加消息映射BB_CLICKED。在這里我們模擬了一個100次循環的隨機數顯示程序。具體大媽如下:
void CAaaDlg::OnBeginSimulate()
{
// TODO: Add your control notification handler code here
srand((unsigned)time(NULL));
char temp[10];
SetDlgItemText(IDC_STATIC11,"Now Beginning ...");
for(int i=0;i<m_maxRange;i++)
{
m_pProgressCtrl->SetPos(i);
m_wSequenceOrder = m_wSequenceOrder2 = i;
m_wStreamID = m_wStreamID2 = rand();
m_wHeight = m_wHeight2 = rand();
m_wWidth = m_wWidth2 = rand();
m_dwMaxRate = m_dwMaxRate2 = rand();
m_dwMinRate = m_dwMinRate2 = rand();
switch(i%4)
{
case 0:
sprintf(temp,"歡 迎 使 用");
break;
case 1:
sprintf(temp,"迎 使 用 歡");
break;
case 2:
sprintf(temp,"使 用 歡 迎");
break;
case 3:
sprintf(temp,"用 歡 迎 使");
break;
}
SetDlgItemText(IDC_WELCOME,temp);
UpdateData(FALSE);
UpdateWindow();
Sleep(50);
}
SetDlgItemText(IDC_WELCOME,"歡 迎 使 用");
SetDlgItemText(IDC_STATIC11,"Now Finnished ...");
}
8 完成以上所有的步驟之后,我們就可以編譯程序并運行。運行結果如下:
(a) (b)
(a): 程序啟動時對話框狀態
(b): 點擊Horizontal框后對話框狀態。
(c) (d)
(c): 點擊Vertical框后對話框狀態。
(d): 點擊BeginSimulating按鈕后系統模擬運行對話框狀態。
在本程序中,我們還用到了一些其它的技巧如修改窗口標題,進程狀態條的顯示、動態字符串顯示以及不通過ClassWizard而直接通過在.cpp和.h文件中增加代碼的方法來關聯控件與成員變量和消息映射等,這些都是一些很實用的技巧,讀者可以參考上面的代碼以及源程序細細體會,這里我們就不多說了。
程序源工程文件見aaa.zip。在VisualC++6.0下編譯通過。
在編程過程中,當程序出現錯誤,卻又不知道錯誤的原因時,可以使用
GetLastError函數,它可以幫助你快速找到出錯的原因和語句。
可以直接使用GetLastError函數得到錯誤代碼,然后查找MSDN找到代碼對應的錯誤原因,也可使用下面函數直接把錯誤原因顯示出來:
void ShowErrMsg()

...{
TCHAR szBuf[80];
LPVOID lpMsgBuf;
DWORD dw = GetLastError();

FormatMessage(
FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_FROM_SYSTEM,
NULL,
dw,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
(LPTSTR) &lpMsgBuf,
0, NULL );

MessageBox(NULL, lpMsgBuf, "系統錯誤", MB_OK|MB_ICONSTOP);

LocalFree(lpMsgBuf);
}


然后根據錯誤的原因查找是哪條語句執行了相關操作,傳入的參數是否正確等,就可以修正錯誤了。
CreateFile函數祥解2006-9-18 18:48:00
CreateFile
The CreateFile function creates or opens the following objects and returns a handle that can be used to access
the object:
files
pipes
mailslots
communications resources
disk devices(Windows NT only)
consoles
directories(open only)
CreateFile函數創建或打開下列對象,并返回一個可以用來訪問這些對象的句柄。
文件
pipes
郵槽
通信資源
磁盤驅動器(僅適用于windowsNT)
控制臺
文件夾(僅用于打開)
HANDLE CreateFile(
LPCTSTR lpFileName, //指向文件名的指針
DWORD dwDesiredAccess, //訪問模式(寫/讀)
DWORD dwShareMode, //共享模式
LPSECURITY_ATTRIBUTES lpSecurityAttributes, //指向安全屬性的指針
DWORD dwCreationDisposition, //如何創建
DWORD dwFlagsAndAttributes, //文件屬性
HANDLE hTemplateFile //用于復制文件句柄
);
Parametes
參數列表
lpFileName
Pointer to a null-terminated string that specifies the name of the object(file, pipe, mailslot,
communications resource, disk device, console, or directory) to create or open.
指向一個空結尾字符串。該參數指定了用于創建或打開句柄的對象。
if *lpFileName is a path, there is a default string size limit of MAX_PATH characters, This limit is
related to how the CreateFile function parses paths.
如果lpFileName的對象是一個路徑,則有一個最大字符數的限制。不能超過常量(MAX_PATH).這個限制指示了
CreateFile函數如何解析路徑.
dwDesiredAccess
Specifies the type of access to the object. An application can obtain read access, write access,
read-write access, or device query access, This parameter can be any combination of the following
values
指定對象的訪問方式,程序可以獲得讀訪問權,寫訪問權,讀寫訪問權或者是詢問設備("device query") 訪問權.
這個參數可以是下列值的任意組合
Value(值) Meaning(含義)
0 Specifies device query access to the object. An application can query device
attributes without accessing the device.
指定詢問訪問權.程序可以在不直接訪問設備的情況下查詢設備的屬性.
GENERIC_READ Specifies read access to the object, Data can be read from the file and the
file pointer can be moved. Combine with GENERIC_WRITE for read-write access.
指定讀訪問權.可以從文件中讀取數據,并且移動文件指針.可以和GENERIC_WRITE組合
成為"讀寫訪問權".
GENERIC_WRITE specifies write access to the object. Data can be written to the file and the
file pointer can be moved. Combine with GENERIC_READ fro read-write access
指定寫訪問權.可以從文件中寫入數據,并且移動文件指針.可以和GENERIC_READ組合
成為"讀寫訪問權".
dwShareMode
Set of bit flags that specifies how the object can be shared, If dwShareMode is 0, the object cannot
be shared. Subsequent open operations on the object will fail, until the handle is closed.
設置位標志指明對象如休共享.如果參數是0, 對象不能夠共享. 后續的打開對象的操作將會失敗,直到該對象的句
柄關閉.
To share the object, use a combination of one or more of the following values:
使用一個或多個下列值的組合來共享一個對象.
Value(值) Meaning(含義)
FILE_SHARE_DELETE WindowsNT: Subsequent open operations on the object will succeed only if
delete access is requested.
WINDOWS NT:后續的僅僅請求刪除訪問權的打開操作將會成功.
FILE_SHARE_READ Subsequent open operations on the object will successd only if read access
is requested.
后續的僅僅請求讀訪問權的打開操作將會成功.
FILE_SHARE_WRITE Subsequent open operations on the object will succeed only if write access
is requested.
后續的僅僅請求寫訪問權的打開操作將會成功.
lpSecurityAttributes
pointer to a SECURITY_ATTRIBUTES structure that determines whether the returned handle can be
inherited by child processes, if lpSecurityAttributes is NULL, the handle cannot be inherited.
指向一個 SECURITY_ATTRIBUTES 結構的指針用于確定如何在子進程中繼承這個句柄.如果這個參數是NULL,
則該句柄不可繼承.
dwCreationDisposition
Specifies which action to take on files that exist, and which action to take when files do not exist.
For more information about this parameter, see the remarks section. This parameter must be one of the
following values
指定當文件存在或者不存在時如何動作。關于這個參數更多的信息,參考批注部分。這個參數必須是一個或多個
下列值。
VALUE(值) Neaning(含義)
CREATE_NEW Creates a new file. The function fails if the specified file already exists
創建一個新文件. 如果該文件已經存在函數則會失敗.
CREATE_ALWAYS Creates a new file. If the file exsts, the function overwrites the file and
clears the existing attributes.
創建一個新文件.如果該文件已經存在,函數將覆蓋已存在的文件并清除已存在的文件屬性
OPEN_EXISTING Opens the file. The function fails if the file does not exist.
See the Remarks section for a discussion of why you should use the
OPEN_EXISTING flag if you are using the CreateFile function for devices,
including the console.
打開一個文件,如果文件不存在函數將會失敗.
如查你使用CreateFile函數為設備裝載控制臺.請查看批注中的"為什么使用
OPEN_EXISTING標志"的部分.
OPEN_ALWAYS Opens the file, if it exsts. If the file does not exist, the function creates
the file as if dwCreationDisposition were CREATE_NEW.
如果文件存在,打開文件. 如果文件不存在,并且參數中有CREATE_NEW標志,則創建文件.
TRUNCATE_EXISTING Opens the file. Once opened, the file is truncated so that its size is zero
bytes The calling process must open the file with at least GENERIC_WRITE access.
The function fails if the file does not exist.
打開一個文件,每次打開,文件將被截至0字節.調用進程必須用GENERIC_WRITE訪問模式打
開文件.如果文件不存在則函數就會失敗.
dwFlagsAndatributes
Specifies the file attributes and flags for the file.
為文件指定屬性和標志位
Any combination of the following attributes is acceptable for the dwFlagsAndAttributes parameter,
except all other file attributes override FILE_ATTRIBUTE_NORMAL.
該參數可以接收下列屬性的任意組合.除非其它所有的文件屬性忽略FILE_ATTRIBUTE_NORMAL.
Attribute(屬性) Meaning(標志)
FILE_ATTRIBUTE_ARCHIVE The ifle should be archived. Application use this attribute to mark
files for backup or removal.
文件將被存檔,程序使用此屬性來標志文件去備份或移除
FILE_ATTRIBUTE_HIDDEN The file is hidden. It is not to be included in an ordinary directory
listing.
文件被隱藏,它不會在一般文件夾列表中被裝載.
FILE_ATTRIBUTE_NORMAL The file has no other attributes set. This attribute is valid only if
used alone
文件沒有被設置任何屬性.
FILE_ATTRIBUTE_OFFLINE The data of the file is not immediately available. Indicates that the
file data has been physically moved to offline storage.
文件的數據沒有被立即用到。指出正在脫機使用該文件。
FILE_ATTRIBUTE_READONLY The file is read only.Applications can read the file but cannot write
to it or delete it
這個文件只可讀取.程序可以讀文件,但不可以在上面寫入內容,也不可刪除.
FILE_ATTRIBUTE_SYSTEM The file is part of or is used exclusively by the operation system.
文件是系統的一部分,或是系統專用的.
FILE_ATTRIBUTE_TEMPORARY The file is being used for temporary storage. File systems attempt
to keep all of the data in memory for quicker access rather than
flushing the data back to mass storage. A temporary file should be
deleted by the application as soon as it is no longer needed.
文件被使用后,文件系統將努力為(文件的)所有數據的迅迅訪問保持一塊
內存。臨時文件應當在程序不用時及時刪除。
Any combination of the following flags is acceptable for the dwFlagsAndAttributes parameter.
dwFlagAndAttributes可以接受下列標志的任意組合。
FLAG(標志) Meaning(含義)
FILE_FLAG_WRITE_THROUGH Instructs the system to write through any intermediate cache and go
directly to disk. The system can still cache write operations, but
cannot lazily flush them.
指示系統通過快速緩存直接寫入磁盤,
FILE_FLAG_OVERLAPPED Instructs the system to initialize the object, so that operations that
take a significant amount of time to process return ERROR_IO_PENDING.
When the operation is finished, the specified event is set to the
signaled state.
指示系統初始化對象, 此操作將對進程設置一個引用計數并返回ERROR_IO_PENDING.
處理完成后, 指定對象將被設置為信號狀態.
When you specify FILE_FLAG_OVERLAPPED, the file read and write functions
must specify an OVERLAPPED structure. That is, when FILE_FLAG_OVERLAPPED
is specified, an application must perform overlapped parameter(pointing
to an OVERLAPPED structure)to the file read and write functions.
This flag also enables more than one operation to be performed
simultaneously with the handle(a simultaneous read and write operation,
for example).
當你指定FILE_FLAG_OVERLAPPED時,讀寫文件的函數必須指定一個OVERLAPPED結構.
并且. 當FILE_FLAG_OVERLAPPED被指定, 程序必須執行重疊參數(指向OVERLAPPED
結構)去進行文件的讀寫.
這個標志也可以有超過一個操作去執行.
FILE_FLAG_NO_BUFFERING Instructs the system to open the file with no intermediate buffering or
caching.When combined with FILE_FLAG_OVERLAPPED, the flag gives maximum
asynchronous performance, because the I/O does not rely on the synchronous
operations of the memory manager. However, some I/O operations will take
longer, because data is not being held in the cache.
指示系統不使用快速緩沖區或緩存,當和FILE_FLAG_OVERLAPPED組合,該標志給出最
大的異步操作量, 因為I/O不依賴內存管理器的異步操作.然而,一些I/O操作將會運行
得長一些,因為數據沒有控制在緩存中.
An application must meet certain requirements when working with files
opened with FILE_FLAG_NO_BUFFERING:
當使用FILE_FLAG_NO_BUFFERING打開文件進行工作時,程序必須達到下列要求:
File access must begin at byte offsets within the file that are
integer multiples of the volume's sector size.
文件的存取開頭的字節偏移量必須是扇區尺寸的整倍數.
File access must be for numbers of bytes that are integer
multiples of the volume's sector size. For example, if the sector
size is 512 bytes, an application can request reads and writes of
512, 1024, or 2048 bytes, but not of 335, 981, or 7171bytes.
文件存取的字節數必須是扇區尺寸的整倍數.例如,如果扇區尺寸是512字節
程序就可以讀或者寫512,1024或者2048字節,但不能夠是335,981或者7171
字節.
buffer addresses for read and write operations must be sector
aligned(aligned on addresses in memory that are integer multiples
of the volume's sector size).
進行讀和寫操作的地址必須在扇區的對齊位置,在內存中對齊的地址是扇區
尺寸的整倍數.
One way to align buffers on integer multiples of the volume sector size is
to use VirtualAlloc to allocate the buffers, It allocates memory that is
aligned on addresses that are integer multiples of the operating system's
memory page size. Because both memory page and volume sector sizes are
powers of 2, this memory is also aligned on addresses that are integer
multiples of a volume's sector size.
一個將緩沖區與扇區尺寸對齊的途徑是使用VirtualAlloc函數. 它分配與操作系統
內存頁大小的整倍數對齊的內存地址.因為內存頁尺寸和扇區尺寸--2都是它們的冪.
這塊內存在地址中同樣與扇區尺寸大小的整倍數對齊.
An application can determine a volume's sector size by calling the
GetDiskFreeSpace function
程序可以通過調用GetDiskFreeSpace來確定扇區的尺寸.
FILE_FLAG_RANDOM_ACCESS
Indicates that the file is accessed randomly. The system can use this as
a hint to optimize file caching.
指定文件是隨機訪問,這個標志可以使系統優化文件的緩沖.
FILE_FLAG_SEQUENTIAL_SCAN
Indicates that the file is to be accessed sequentially from beginning to
end. The system can use this as a hint to optimize file caching. If an
application moves the file pointer for random access, optimum caching may
not occur; however, correct operation is still guaranteed.
指定文件將從頭到尾連續地訪問.這個標志可以提示系統優化文件緩沖. 如果程序在
隨機訪問文件中移動文件指針,優化可能不會發生;然而,正確的操作仍然可以得到保
證
Specifying this flag can increase performance for applications that read
large files using sequential access, performance gains can be even more
noticeable for applications that read large files mostly sequentially,
but occasionally skip over small ranges of bytes.
指定這個標志可以提高程序以順序訪問模式讀取大文件的性能, 性能的提高在許多
程序讀取一些大的順序文件時是異常明顯的.但是可能會有小范圍的字節遺漏.
FILE_FLAG_DELETE_ON_CLOSE Indicates that the operating system is to delete the file immediately
after all of its handles have been closed, not just the handle for which
you specified FILE_FLAG_DELETE_ON_CLOSE.
指示系統在文件所有打開的句柄關閉后立即刪除文件.不只有你可以指定FILE_FLAG_DELETE_ON_CLOSE
Subsequent open requests for the file will fail, unless FILE_SHARE_DELETE
is used.
如果沒有使用FILE_SHARE_DELETE,后續的打開文件的請求將會失敗.
FILE_FLAG_BACKUP_SEMANTICS WINDOWS NT:Indicates that the file is being opened or created for a backup
or restore operation.The system ensures that the calling process overrides
file security checks, provided it has the necessary privileges. The
relevant privileges are SE_BACKUP_NAME and SE_RESTORE_NAME.
WINDOWS NT:指示系統為文件的打開或創建執行一個備份或恢復操作. 系統保證調
用進程忽略文件的安全選項,倘若它必須有一個特權.則相關的特權則是SE_BACKUP_NAME
和SE_RESTORE_NAME.
You can also set this flag to obtain a handle to a directory. A directory
handle can be passed to some Win32 functions in place of a file handle.
你也可以使用這個標志獲得一個文件夾的句柄,一個文件夾句柄能夠象一個文件句柄
一樣傳給某些Win32函數。
FILE_FLAG_POSIX_SEMANTICS Indicates that the file is to be accessed according to POSIX rules. This
includes allowing multiple files with names, differing only in case, for file
systems that support such naming. Use care when using this option because
files created with this flag may not be accessible by applications written
for MS-DOS or 16-bit Windows.
指明文件符合POSIX標準.這是在MS-DOS與16位Windows下的標準.
FILE_FLAG_OPEN_REPARSE_POINT Specifying this flag inhibits the reparse behavior of NTFS reparse points.
When the file is opened, a file handle is returned, whether the filter that
controls the reparse point is operational or not. This flag cannot be used
with the CREATE_ALWAYS flag.
指定這個標志制約NTFS分區指針.該標志不能夠和CREAT_ALWAYS一起使用.
FILE_FLAG_OPEN_NO_RECALL Indicates that the file data is requested,but it should continue to reside in
remote storage. It should not be transported back to local storage. This flag
is intended for use by remote storage systems or the Hierarchical Storage
Management system.
指明需要文件數據,但是將繼續從遠程存儲器中接收.它不會將數據存放在本地存儲器中.
這個標志由遠程存儲系統或等級存儲管理器系統使用.
hTemplateFile
Specifies a handle with GENERIC_READ access to a template file. The template file supplies file attributes and
extended attributes for the file being created.
為GENERIC_READ訪問的模式指定一個句柄到模板文件.模板文件在文件開始創建后提供文件屬性和擴展屬性.
Return Values
返回值
If the function succeeds, the return value is an open handle to the specified file. If the specified file exists before
the function call and dwCreation is CREATE_ALWAYS or OPEN_ALWAYS, a call to GetLastError returns ERROR_ALREADY_EXISTS
(even though the function has succeeded). If the file does not exist before the call, GetLastError returns zero.
如果函數成功,返回一個打開的指定文件的句柄.如果指定文件在函數調用前已經存在并且dwCreation參數是CREATE_ALWAYS 或者
OPEN_ALWAYS,調用GetLastError就會返回ERROR_ALREADY_EXISTS(表示函數成功). 如果函數文件在調用前不存在則會返回0.
If the function fails, the return value is INVALID_HANDLE_VALUE.To get extended error information, call GetLastError.
如果函數失敗,返會值會是INVALID_HANDLE_VALUE. 更多的錯誤信息可以調用GetLastError來獲得.
內存映射API函數CreateFileMapping創建一個有名的共享內存:
HANDLE CreateFileMapping(
HANDLE hFile, // 映射文件的句柄,
//設為0xFFFFFFFF以創建一個進程間共享的對象
LPSECURITY_ATTRIBUTES lpFileMappingAttributes, // 安全屬性
DWORD flProtect, // 保護方式
DWORD dwMaximumSizeHigh, //對象的大小
DWORD dwMaximumSizeLow,
LPCTSTR lpName // 必須為映射文件命名
);
與虛擬內存類似,保護方式可以是PAGE_READONLY或是PAGE_READWRITE。如果多進程都對同一共享內存進行寫訪問,則必須保持相互間同步。映射文件還可以指定PAGE_WRITECOPY標志,可以保證其原始數據不會遭到破壞,同時允許其他進程在必要時自由的操作數據的拷貝。
在創建文件映射對象后使用可以調用MapViewOfFile函數映射到本進程的地址空間內。
下面說明創建一個名為MySharedMem的長度為4096字節的有名映射文件:
HANDLE hMySharedMapFile=CreateFileMapping((HANDLE)0xFFFFFFFF),
NULL,PAGE_READWRITE,0,0x1000,"MySharedMem");
并映射緩存區視圖:
LPSTR pszMySharedMapView=(LPSTR)MapViewOfFile(hMySharedMapFile,
FILE_MAP_READ|FILE_MAP_WRITE,0,0,0);
其他進程訪問共享對象,需要獲得對象名并調用OpenFileMapping函數。
HANDLE hMySharedMapFile=OpenFileMapping(FILE_MAP_WRITE,
FALSE,"MySharedMem");
一旦其他進程獲得映射對象的句柄,可以象創建進程那樣調用MapViewOfFile函數來映射對象視圖。用戶可以使用該對象視圖來進行數據讀寫操作,以達到數據通訊的目的。
當用戶進程結束使用共享內存后,調用UnmapViewOfFile函數以取消其地址空間內的視圖:
if (!UnmapViewOfFile(pszMySharedMapView))
{
AfxMessageBox("could not unmap view of file");
}
//=================================================================================
//
CreateFileMapping的MSDN翻譯和使用心得//=================================================================================
測試創建和打開文件映射的時候老是得到"句柄無效"的錯誤, 仔細看了MSDN以后才發覺是函數認識不透, 這里把相關的解釋翻譯出來
HANDLE CreateFileMapping(
HANDLE hFile, //物理文件句柄
LPSECURITY_ATTRIBUTES lpAttributes, //安全設置
DWORD flProtect, //保護設置
DWORD dwMaximumSizeHigh, //高位文件大小
DWORD dwMaximumSizeLow, //低位文件大小
LPCTSTR lpName //共享內存名稱
);
1) 物理文件句柄
任何可以獲得的物理文件句柄, 如果你需要創建一個物理文件無關的內存映射也無妨, 將它設置成為 0xFFFFFFFF(INVALID_HANDLE_VALUE)就可以了.
如果需要和物理文件關聯, 要確保你的物理文件創建的時候的訪問模式和"保護設置"匹配, 比如: 物理文件只讀, 內存映射需要讀寫就會發生錯誤. 推薦你的物理文件使用獨占方式創建.
如果使用 INVALID_HANDLE_VALUE, 也需要設置需要申請的內存空間的大小, 無論物理文件句柄參數是否有效, 這樣 CreateFileMapping 就可以創建一個和物理文件大小無關的內存空間給你, 甚至超過實際文件大小, 如果你的物理文件有效, 而大小參數為0, 則返回給你的是一個和物理文件大小一樣的內存空間地址范圍. 返回給你的文件映射地址空間是可以通過復制, 集成或者命名得到, 初始內容為0.
2) 保護設置
就是安全設置, 不過一般設置NULL就可以了, 使用默認的安全配置. 在win2k下如果需要進行限制, 這是針對那些將內存文件映射共享給整個網絡上面的應用進程使用是, 可以考慮進行限制.
3) 高位文件大小
弟兄們, 我想目前我們的機器都是32位的東東, 不可能得到超過32位進程所能尋址的私有32位地址空間, 一般還是設置0吧, 我沒有也不想嘗試將它設置超過0的情況.
4) 低位文件大小
這個還是可以進行設置的, 不過為了讓其他共享用戶知道你申請的文件映射的相關信息, 我使用的時候是在獲得的地址空間頭部添加一個結構化描述信息, 記錄內存映射的大小, 名稱等, 這樣實際申請的空間就比輸入的增加了一個頭信息結構大小了, 我認為這樣類似BSTR的方式應該是比較合理的.
5) 共享內存名稱
這個就是我今天測試的時候碰壁的禍根, 因為為了對于內存進行互斥訪問, 我設置了一個互斥句柄, 而名稱我選擇和命名共享內存同名, 之下就是因為他們使用共同的namespace導致了錯誤, 呵呵.
7) 調用CreateFileMapping的時候GetLastError的對應錯誤
ERROR_FILE_INVALID 如果企圖創建一個零長度的文件映射, 應有此報
ERROR_INVALID_HANDLE 如果發現你的命名內存空間和現有的內存映射, 互斥量, 信號量, 臨界區同名就麻煩了
ERROR_ALREADY_EXISTS 表示內存空間命名已經存在
8) 相關服務或者平臺的命名保留
Terminal Services:
命名可以包含 "Global" 或者 "Local" 前綴在全局或者會話名空間初級文件映射. 其他部分可以包含任何除了()以外的字符, 可以參考 Kernel Object Name Spaces.
Windows 2000 or later:
如果 Terminal Services 沒有運行 "Global" 和 "Local" 前綴的特殊含義就被忽略了
摘 要 該文介紹了用直方圖均衡化對灰度圖像進行灰度映射,從而達到使圖像增強的目的。利用VC++6.0執行效率高,可繼承、封裝、移植等成熟的軟件技術,對直方圖均衡化算法進行實現。
實驗表明,該程序可以快速、準確地對灰度圖像進行灰度變換,達到了使圖像對比度增強,改善圖像質量的預期目的。
關鍵字 Visual C++;圖像增強;直方圖均衡;DIB 文件
由于噪聲、光照等外界環境或設備本身的原因,通常我們所獲取的原始數字圖像質量不是很高,因此在對圖像進行邊緣檢測、圖像分割等操作之前,一般都需要對原始數字圖像進行增強處理。圖像增強主要有兩方面應用,一方面是改善圖像的視覺效果,另一方面也能提高邊緣檢測或圖像分割的質量,突出圖像的特征,便于計算機更有效地對圖像進行識別和分析。 圖像增強是
圖像處理最關鍵的研究問題之一,圖像增強按作用域可分為兩類,即空域處理和頻域處理。空域處理是直接對圖像進行處理,而頻域處理則是在圖像的某個變化域內,對圖像的變換系數進行運算,然后通過逆變換獲得圖像增強效果。本文主要對空域增強法中的直方圖均衡進行分析并用VC ++ 6.0進行算法實現。當前圖像處理在算法實現中主要應用Matlab 仿真工具,但Matlab運行效率較低,且可移植性和實用性均不太理想。與
Java和C#等其他高級語言相比,VC++在程序運行效率、內存使用的可控性和編程的靈活性上均具有較大的優勢,因此本文采用VC ++ 6.0 集成開發環境,以達到算法快速有效地執行,同時增強了算法的可移植性。
灰度圖像直方圖均衡化的描述 1、灰度圖像直方圖處理方法
圖像的直方圖是圖像處理中一種十分重要且實用的工具,它概括了一副圖像的灰度級內容。從數學上來說圖像直方圖是圖像各灰度值統計特性與圖像灰度值的函數,它統計一幅圖像中各個灰度級出現的次數或概率。實際上,灰度圖像直方圖是一個離散函數:
pf(fk)=nk/n k=0,1,…,L-1
其中fk為圖像f(x,y)的第k級灰度,nk是圖像f(x,y)中具有灰度值fk的象素個數,n是圖像象素總數,L是圖像的灰度級數。因為pf(fk)給出了對各個fk出現概率的一個統計,所以直方圖提供了圖像的灰度值分布情況。在灰度直方圖坐標系中,橫坐標表示圖像中各個像素點的灰度級,縱坐標為各個灰度級上圖像各個像素點出現的次數或概率。在對灰度數字圖像的增強處理方法中,灰度均衡化和灰度規定化應用較為廣泛,但后者需要根據具體的圖像人為規定好適當的期望直方圖才能得到滿意的效果,如果期望直方圖規定不當則處理效果會很差,因此后者的通用性不好。而前者在處理時只需要將當前的灰度分布重新均衡地分布于整個灰度區間即可,雖然對于某一幅特定的圖象處理效果可能不及灰度規定化,但通用性卻要好的多,對任意圖象均可獲得相當不錯的處理效果。
2、灰度圖像直方圖均衡化算法分析
直方圖均衡化的基本思想是把原始圖的直方圖變換為均勻分布的形式,這樣就增加了象素灰度值的動態范圍從而可達到增強圖像整體對比度的效果。設原始圖像在(x,y)處的灰度為f,而改變后的圖像為g,則對圖像增強的方法可表述為將在(x,y)處的灰度f映射為g。在灰度直方圖均衡化處理中對圖像的映射函數可定義為:g = EQ (f),這個映射函數EQ(f)必須滿足兩個條件(其中L為圖像的灰度級數):
(1)EQ(f)在0≤f≤L-1范圍內是一個單值單增函數。這是為了保證增強處理沒有打亂原始圖像的灰度排列次序,原圖各灰度級在變換后仍保持從黑到白(或從白到黑)的排列。
(2)對于0≤f≤L-1有0≤g≤L-1,這個條件保證了變換前后灰度值動態范圍的一致性。
累計分布函數(cumulative distribution function,CDF)即可以滿足上述兩個條件,并且通過該函數可以完成將原圖像f的分布轉換成g的均勻分布。此時的直方圖均衡化映射函數為:
gk = EQ(fk) = (ni/n) = pf(fi) ,
(k=0,1,2,……,L-1)
上述求和區間為0到k,根據該方程可以由源圖像的各像素灰度值直接得到直方圖均衡化后各像素的灰度值。在實際處理變換時,一般先對原始圖像的灰度情況進行統計分析,并計算出原始直方圖分布,然后根據計算出的累計直方圖分布求出fk到gk的灰度映射關系。在重復上述步驟得到源圖像所有灰度級到目標圖像灰度級的映射關系后,按照這個映射關系對源圖像各點像素進行灰度轉換,即可完成對源圖的直方圖均衡化。
VC++ 6.0下自制
媒體播放器
可視動畫控件ActiveMovie是
Microsoft公司開發的ActiveX控件,從開始的1.0版、1.2版到現在的2.0版,功能上已經有了很大的改進。由于該控件內嵌了Microsoft MPEG音頻解碼器和Microsoft MPEG視頻解碼器,所以能夠很好地支持音頻文件和視頻文件,用其播放的VCD效果就很好。 另外,播放時若用鼠標右鍵單擊畫面,可以直接對畫面的播放、暫停、停止等進行控制,讀者還可以自行在“屬性”欄中對影片播放進行控制設置,用起來非常方便。
在Microsoft公司去年推出的VC++6.0中已經包含了ActiveMovie控件的2.0版,筆者 在VC++6.0下利用這個控件自制了一個簡易的媒體播放器,除了滿屏功能外,還可以對音量進行控制。下面把具體做法介紹給讀者。
一 建立工程
利用 VC++6.0的AppWizard生成一個基于對話框的工程Player,去掉對話框上的確定和取消按鈕,并加入ActiveMovie控件(通常情況下ActiveMovie控件并不出現在控件面板中,可在菜單中依次選擇“project—Add To Project— >Components And Controls”,在出現的“Components And Controls Gallery”對話框中打開“Registered Active Controls”文件夾,選中“ActiveMovie Control Object”選項,按“Insert”后關閉該對話框,ActiveMovie控件便出現在控件面板中),調整好控件在對話框中的位置。為了能夠控制控件的操作,應為對話框設計一個菜單,菜單的項目可以定為文件、屏幕控制和音量控制。
二 添加代碼
首先利用ClassWizard為ActiveMovie控件聲明一個變量m_ActiveMovie。然后為菜單文件添加兩個菜單項打開文件和退出,并分別添加函數OnOpen()和OnExit(),代碼如下:
void Cplayer::OnOpen()
{ // TODO: Add your command handler code here char szFilter[] =
" Video File (*.dat)∣ *.dat∣Wave File (*.wav)∣*.wav∣AVI File (*.avi)∣ (*.avi)∣Movie File
(*.mov)∣(*.mov)∣ Media File (*.mmm)∣(*.mmm)∣Mid File(*.mid;*.rmi)∣ (*.mid;*.rmi)∣MPEG File
(*.mpeg)∣(*.mpeg)∣ All File (*.*)∣*.* ";
//用于設置FileDialog的文件類型
CFileDialog FileDlg( TRUE, NULL, NULL, OFN_HIDEREADONLY, szFilter );
if( FileDlg.DoModal() == IDOK ) { CString PathName = FileDlg.GetPathName();
PathName.MakeUpper();
m_ActiveMovie.SetFileName(PathName);
}
}
OnOpen()函數的作用是顯示“打開”對話框,通過該對話框選擇要執行的文件。
利用Visual C++實現AVI文件的圖像截取
劉 濤 yesky
AVI文件就是我們所說的
多媒體文件,所謂的AVI圖像就是視頻圖像,該文件是一個RIFF說明文件,它用于獲取、編輯、演示音頻、視頻序列。一般的AVI文件包含音頻流和視頻流,有的特殊的AVI還包含一個控制路徑或MIDI路徑作為附加的數據流。
現在播放AVI文件的軟件很多,但大多無法從AVI視頻文件中讀取一幀圖像并生成BMP格式的文件。筆者在使用AVI文件開發項目過程中對AVI文件的操作積累了一些經驗,對于如何實現從AVI視頻流中獲取任意幀的圖像數據并存儲成BMP文件,其中最關鍵的是要從AVI文件中獲取具體某一幀的圖像數據,為此我利用Windows提供的API函數實現了自定義的CAvi類,用于操作AVI文件。
在使用API函數操作AVI文件時,一定要注意用AVIFileInit()來初始化AVI庫,程序結束時用AVIFileExit()釋放AVI庫,否則API函數無法使用。現以操作包含真彩色圖像的AVI文件為例,給出Cavi類的部分函數的具體實現,其中CaviCreate()函數用于讀取AVI文件信息并初始化Cavi類的成員,例如根據AVI文件信息定義每幀圖像的寬、高、每幀圖像的信息頭結構等等;函數AviRead(int mFrame)用于從AVI文件中讀取第mFrame幀。實現代碼顯示如下:
//Cavi類頭文件定義;
class CAvi file://AVI類,處理AVI文件
{
public:
int cy;//圖象高
int cx;//圖象寬
file://long m_maxFrame;
BYTE *pData;//寸儲圖象數據
BITMAPINFO *m_pBMI;//位圖文件信息頭
PAVISTREAM pavi;//AVI流
PAVIFILE pfile;//AVI文件指針
AVIFILEINFO * pfi; file://AVI信息
BOOL AviRead(int mFrame);//讀AVI文件的第mFrame幀
CAvi();//標準構造函數
CAviCreate(CString &string);//用文件名初始化AVI類的成員
virtual ~CAvi();
};
//Cavi類文件實現部分;
CAvi::CAvi()
{ AVIFileInit();//初始化AVI庫
cx=0;//定義圖象寬、高、等成員
cy=0;
m_pBMI=NULL;
pData=NULL;
file://m_maxFrame=0;
pfi=NULL;
}
CAvi::~CAvi()//析構、釋放指針
{
// AVIFileClose(pfile);
AVIFileExit();
if(pData!=NULL)
delete pData;
pData=NULL;
if(m_pBMI!=NULL)
delete m_pBMI;
m_pBMI=NULL;
if(pfi!=NULL)
delete pfi;
pfi=NULL;
}
CAvi::CAviCreate(CString &string)//讀文件初始化該類
{
HRESULT hr;
pfi=new AVIFILEINFO;
hr = AVIFileOpen(&pfile, // returned file pointer
string, // file name
OF_READ, // mode to open file with
NULL);
hr= AVIFileInfo(pfile, file://獲取AVI信息,放入pfi中
pfi,
sizeof(AVIFILEINFO)
);
cx=pfi->dwWidth;//圖象寬、高
cy=pfi->dwHeight;
hr=AVIFileGetStream(//將AVI變成視頻流
pfile,
&pavi,
streamtypeVIDEO,
0//LONG lParam
);
m_pBMI=new BITMAPINFO;//定義BMP信息頭
m_pBMI->bmiHeader.biBitCount=24;
m_pBMI->bmiHeader.biClrImportant=0;
m_pBMI->bmiHeader.biClrUsed=0;
m_pBMI->bmiHeader.biCompression=BI_RGB;
m_pBMI->bmiHeader.biHeight=cy;
m_pBMI->bmiHeader.biWidth=cx;
m_pBMI->bmiHeader.biPlanes=1;
m_pBMI->bmiHeader.biSize=sizeof(BITMAPINFOHEADER);
m_pBMI->bmiHeader.biXPelsPerMeter=0;
m_pBMI->bmiHeader.biYPelsPerMeter=0;
m_pBMI->bmiHeader.biSizeImage=cx*cy*3;
pData=(BYTE*)new char[cx*cy*3];//根據AVI中BMP圖象的信息定義緩沖區
}
BOOL CAvi::AviRead(int mFrame)//將AVI文件的M幀數據讀入PData緩沖區
{
HRESULT hr;
hr= AVIStreamRead( pavi,
mFrame,
1,
pData,
cx*cy*3,
NULL,
NULL
);
if(hr==0)
return TRUE;
else
return FALSE;
}
上述Cavi類實現部分所涉及到的API函數可以參考微軟提供的MSDN。Cavi類中的pData緩沖區存放AVI文件中的具體某一幀圖像數據,同時Cavi類的m_pBMI為BMP圖像文件信息結構,這時可以根據圖像的大小定義BMP圖像文件頭結構,關于BMP文件的存儲,由于篇幅的原因,我不在多講了,有興趣的讀者可以參見筆者的拙作"Visual C++6.0開發灰度位圖處理"(天極網軟件欄目2001.9.10發表),該文里面講述了如何存取BMP文件。以上程序在
Windows2000、Visual C++6.0環境下順利編譯通過,運行正常。
摘要:本文講述了在
Microsoft Visual C++ 6.0下多幅碎片圖像無縫拼合技術的實現原理和過程,并給出了部分關鍵代碼以供參考。
關鍵字:Microsoft Visual C++ 6.0、圖像、無縫拼合、位圖文件
一、 引言
在測繪、文博等行業經常會遇到這樣一種情況:觀測對象比較大,為保證分辨率又不能將其全部照下,只能進行局部照相,事后再將這些局部照相的重合部分去掉,拼合成一幅完整的圖像。以前多采用手工拼合,誤差較大,往往不能很好的實現無縫拼合,即使有少量的專業設備,成本也普遍較高。其實只需將照片通過掃描儀將其錄入到計算機中,通過程序處理,完全能很好的實現多幅圖像的無縫拼合,滿足實際需要,而且對于文博行業中常會遇到的破碎的、不規則對象如古舊字畫殘片等也能很好的進行無縫拼合。本文就對針對該程序的實現原理及過程做了簡要的介紹。 二、 程序設計原理 首先我們從實際出發,我們是通過進行局部照相的手段來保存整體的全部信息,而要保證這些局部照片所含的信息之和能包括整體的全部信息就必然的使每兩幅鄰近的圖片有一部分交疊的部分,這樣才能保證在將整體對象劃分為若干局部照片而后再拼合成整體圖像的過程中不遺漏任何信息,即該劃分、拼合的整個過程是無損的。既然如此,我們只需能保證讓兩相鄰圖片的重疊部分能完全重合,那么我們也就能夠肯定在此狀態下的這兩幅圖像實現了無縫拼合。所以,問題就轉換為使相鄰圖片的重疊部分能完全重合,而判斷兩相同的圖像片段是否完全重疊可以用光柵掩碼來進行直觀的判斷,比如我們可以采用"異或"的掩碼,當相同位置上的兩幅圖片的像素相同時就為0即黑色,所以可以對兩圖片進行移動,只要重疊部分全黑,則表明此時兩圖像的重疊部分已準確的重合了,而此時也實現了圖像的無縫拼合。此后只需再采用"或"的光柵掩碼將合并后的圖像顯示出來,再通過拷屏等手段將其存盤即可。在實現拼合的全過程中主要涉及到圖像的拖放、圖像文件的讀取及顯示、光柵掩碼、拷屏以及內存位圖的保存等多種技術。接下來就對這些技術的具體應用進行介紹。
三、 程序的具體實現 在進行拼合之前,首先要將從掃描儀錄入的圖像從文件讀取到內存中,并顯示出來。由于在拼合時采取的光柵操作掩碼是"異或",所以為保持圖像的原始面貌,可以在消息WM_ERASEBKGND 的響應函數中用PatBlt函數將整個客戶區的初始背景設定為黑色:
…… pDC->PatBlt(0,0,rect.Width(),rect.Height(), BLACKNESS); return TRUE; |
讀取位圖文件可以用LoadImage函數來實現,m_sPath1指定了文件的路徑,LR_LOADFROMFILE屬性指定從文件中讀取位圖,返回值為該位圖的句柄:
…… HBITMAP hbitmap; hbitmap=(HBITMAP)LoadImage(AfxGetInstanceHandle(), m_sPath1, IMAGE_BITMAP,0,0, LR_LOADFROMFILE|LR_CREATEDIBSECTION); |
之后我們就可以創建一個和當前設備環境兼容的內存設備環境hMemDC1,并將剛才讀取到內存的位圖放置到該設備環境中:
hMemDC1=::CreateCompatibleDC(NULL); SelectObject(hMemDC1,hbitmap); ::DeleteObject(hbitmap); //釋放掉用過的位圖句柄 Invalidate(); |
至于位圖的顯示,由于需要頻繁的拖動和其他處理,將其放置于OnDraw函數中較為合理,需要更新顯示時只需顯式地用Invalidate()函數刷新即可。OnDraw()中的顯示位圖部分最好用BitBlt函數來完成,該函數負責把hMemDC1中的位圖放置到pDC頁面中以完成內存頁面的置換,其處理速度還是比較快的:
…… ::BitBlt(pDC->m_hDC,m_nX1,m_nY1, m_nWidth1,m_nHeight1, hMemDC1,0,0,m_dwRop); …… |
函數中的m_dwRop變量對光柵操作碼進行設置,初始為SRCINVERT即光柵異或操作,當拼合成功需要顯示合并后的效果時再將其設定為SRCPAINT光柵或操作。
我們可以通過對鼠標消息響應函數的編程來實現在客戶區內的位圖拖放,按照
Windows系統的習慣,首先在鼠標左鍵的響應函數中通過PtInRect()函數判斷鼠標在左鍵按下時是否是落在位圖上,如果是就可以在鼠標左鍵彈起之前將圖片隨鼠標拖動了,顯然這部分應在WM_MOUSEMOVE消息的響應函數內編寫代碼:
…… if(m_bCanMove1==true) //在移動之前鼠標左鍵是在圖片上點擊的 { int dx=m_nOldX1-m_nX1; //計算鼠標距離圖片原點的距離 int dy=m_nOldY1-m_nY1; m_nX1=point.x-dx; //計算新的圖片原點的坐標(客戶區坐標) m_nY1=point.y-dy; Invalidate(); //更新視圖 } m_nOldX1=point.x; //保存上一次的鼠標位置 m_nOldY1=point.y; …… |
到此為止,可以運行程序對多幅碎片圖像進行拼合了,用鼠標拖動一幅圖像在另一幅圖像邊緣移動,由于采用了"異或"的光柵掩碼,兩幅圖片交疊的地方顏色會發生改變,但只有完全重合時才會全黑,表明此時的拼合是無縫的,將掩碼換為"或"即可將拼合后的圖像顯示出來。但此時只是保留在內存中,還要經過進一步的處理,才能將合并后的圖像存盤保留。
首先要對合并后的圖像所在的矩形框的位置、大小進行判斷,可以用下面的類似代碼來完成(本例同時最多能有4幅圖像進行拼合):
…… int temp1,temp2,x0,y0,x1,y1; temp1=m_nX1<m_nX2?m_nX1:m_nX2; if(m_sPath3!="")//如果有3幅圖片參與拼合 { if(m_sPath4!="")//如果有4幅圖片參與拼合 temp2=m_nX3<m_nX4?m_nX3:m_nX4; else temp2=m_nX3; x0=temp1<temp2?temp1:temp2; } else x0=temp1; …… temp1=m_nX1+m_nWidth1>m_nX2+m_nWidth2?m_nX1+m_nWidth1:m_nX2+m_nWidth2; if(m_sPath3!="") { if(m_sPath4!="") temp2=m_nX3+m_nWidth3>m_nX4+m_nWidth4?m_nX3+m_nWidth3:m_nX4+m_nWidth4; else temp2=m_nX3+m_nWidth3; x1=temp1>temp2?temp1:temp2; } else x1=temp1; |
可以用類似的代碼計算出y0和y1。在進行屏幕截圖之前必須將由x0,y0,x1,y1構成的矩形由客戶坐標轉換成屏幕坐標,可以用ClientToScreen()函數來實現。下面是將屏幕指定區域以位圖形式拷貝到內存中去的函數的主要實現代碼:
HBITMAP CImageView::CopyScreenToBitmap(LPRECT lpRect) { …… // 確保選定區域不為空矩形 if(IsRectEmpty(lpRect)) return NULL; //為屏幕創建設備描述表 hScrDC = CreateDC("DISPLAY", NULL, NULL, NULL); //為屏幕設備描述表創建兼容的內存設備描述表 hMemDC = CreateCompatibleDC(hScrDC); …… // 創建一個與屏幕設備描述表兼容的位圖 hBitmap = CreateCompatibleBitmap(hScrDC, lpRect->Width(),lpRect->Height()); // 把新位圖選到內存設備描述表中 hOldBitmap = (HBITMAP)SelectObject(hMemDC, hBitmap); // 把屏幕設備描述表拷貝到內存設備描述表中 BitBlt(hMemDC, 0, 0, lpRect->Width(),lpRect->Height, hScrDC, lpRect->left lpRect->top, SRCCOPY); //得到屏幕位圖的句柄 hBitmap =(HBITMAP)SelectObject(hMemDC, hOldBitmap); //清除 DeleteDC(hScrDC); DeleteDC(hMemDC); …… // 返回位圖句柄 return hBitmap; } |
當把拼合后的區域拷貝到內存,并獲取到該內存位圖的句柄后可以將其通過剪貼板傳送到其他圖形處理軟件中進行進一布的處理,也可以按照位圖的格式直接將其保存成文件,為方便計,本例采用了后者。其實現過程主要是根據剛才獲取到的內存位圖句柄按格式填充BMP文件的信息頭以及像素陣列,下面就結合實現的關鍵代碼進行介紹:
首先獲取
設備描述表句柄,并用函數GetDeviceCaps()獲取到當前顯示分辨率下每個像素所占字節數,并據此計算出調色板的大小:
…… hDC = CreateDC("DISPLAY",NULL,NULL,NULL); iBits = GetDeviceCaps(hDC, BITSPIXEL) * GetDeviceCaps(hDC, PLANES); DeleteDC(hDC); if (iBits <= 1) wBitCount = 1; else if (iBits<= 4) wBitCount = 4; else if (iBits<= 8) wBitCount = 8; else if (iBits <= 24) wBitCount = 24; //計算調色板大小 …… |
然后就可以設置位圖信息頭結構了,其中bi 是BITMAPINFOHEADER 結構的實例對象:
…… if (wBitCount <= 8) dwPaletteSize = (1<<wBitCount) *sizeof(RGBQUAD); //設置位圖信息頭結構 GetObject(hBitmap, sizeof(BITMAP), (LPSTR)&Bitmap); bi.biSize = sizeof(BITMAPINFOHEADER); bi.biWidth = Bitmap.bmWidth; bi.biHeight = Bitmap.bmHeight; bi.biPlanes = 1; bi.biBitCount = wBitCount; bi.biCompression = BI_RGB; bi.biSizeImage = 0; bi.biXPelsPerMeter = 0; bi.biYPelsPerMeter = 0; bi.biClrUsed = 0; bi.biClrImportant = 0; |
用GlobalAlloc()函數根據計算的結果為位圖內容分配內存,并返回分配得到的內存句柄hDib,并用GetStockObject()來設置缺省狀態下的調色板:
…… dwBmBitsSize = ((Bitmap.bmWidth*wBitCount+31)/32)*4*Bitmap.bmHeight; hDib = GlobalAlloc(GHND,dwBmBitsSize+dwPaletteSize+sizeof(BITMAPINFOHEADER)); lpbi = (LPBITMAPINFOHEADER)GlobalLock(hDib); *lpbi = bi; // 處理調色板 hPal = GetStockObject(DEFAULT_PALETTE); if (hPal) { hDC = ::GetDC(NULL); hOldPal =SelectPalette(hDC, (HPALETTE)hPal, FALSE); RealizePalette(hDC); } // 獲取該調色板下新的像素值 GetDIBits(hDC, hBitmap, 0, (UINT) Bitmap.bmHeight, (LPSTR)lpbi + sizeof(BITMAPINFOHEADER)+dwPaletteSize, (BITMAPINFO*)lpbi, DIB_RGB_COLORS); //恢復調色板 if (hOldPal) { SelectPalette(hDC,(HPALETTE)hOldPal, TRUE); RealizePalette(hDC); ::ReleaseDC(NULL,hDC); } …… |
最后的工作就是創建位圖文件了,需要把設置好的位圖文件頭和像素點陣信息依次保存到文件中,其中bmfHdr 是BITMAPFILEHEADER位圖文件頭結構的實例對象,需要按照BMP位圖的存盤格式對其進行設置:
…… fh = CreateFile(lpFileName, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN,NULL); // 設置位圖文件頭 bmfHdr.bfType = 0x4D42; // "BM" dwDIBSize = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + dwPaletteSize + dwBmBitsSize; bmfHdr.bfSize = dwDIBSize; bmfHdr.bfReserved1 = 0; bmfHdr.bfReserved2 = 0; bmfHdr.bfOffBits = (DWORD)sizeof(BITMAPFILEHEADER) + (DWORD)sizeof(BITMAPINFOHEADER)+ dwPaletteSize; //寫入位圖文件頭 WriteFile(fh, (LPSTR)&bmfHdr, sizeof(BITMAPFILEHEADER), &dwWritten, NULL); // 寫入位圖文件其余內容 WriteFile(fh, (LPSTR)lpbi, dwDIBSize,&dwWritten, NULL); …… |
四、程序的實例檢測 下面就通過一個實例--拼合一幅古代國畫殘片來對程序的拼合效果進行檢測。其中圖一到圖三是拼合前的三幅古代國畫殘片,圖四是經過本程序處理后存盤得到的經過無縫合成的圖片。經過檢測,拼合效果還是相當不錯的,在碎片圖像的銜接處根本沒有接縫的存在:
小結:
本程序通過一個實例講述了處理圖片無縫拼合的一種實用方法,在測繪、勘察、文博等行業均有較大的應用潛力。在理解了程序的設計思路和編程思想的前提下,結合具體的實際需求,通過對本文具體代碼的改動可以設計出更適合本單位實際情況的類似軟件。另外,本文所講述的截取并保存屏幕技術在類似程序的編制上也可以提供一定的參考。本程序在Windows 2000 Professional下,由
Microsoft Visual C++ 6.0編譯通過。
摘要:本文以
VC++ 6.0為編程工具,講述了采取逆濾波和維納濾波兩種圖像恢復算法對退化圖像的恢復實現過程。
引言
圖像恢復技術是
圖像處理領域一類重要的處理技術,與圖像增強等其他基本圖像處理技術類似,該技術也是以獲取視覺質量得到某種程度改善為目的的,所不同的是圖像恢復過程需要根據指定的圖像退化模型來完成,根據這個退化模型對在某種情況下退化或惡化了的退化圖像進行恢復,以獲取到原始的、未經過退化的原始圖像。換句話說,圖像恢復的處理過程實際是對退化圖像品質的提升,并通過圖像品質的提升來達到圖像在視覺上的改善。本文以VC++作為開發工具,講述了對退化圖像進行逆濾波和維納濾波處理算法。
逆濾波處理 對圖像進行恢復處理通常需要根據一定的圖像退化模型來進行,一個簡單的通用圖像退化模型可將圖像的退化過程模型化為一個作用在原始圖像f(x,y)上的退化系統H,作用結果與一個加性噪聲n(x,y)的聯合作用導致產生出了退化圖像g(x,y),表現為數學形式為g(x,y)=H[f(x,y)]+n(x,y)。根據上述退化系統H可以從給定的退化圖像g(x,y)得到原始圖像f(x,y)的一個近似結果。逆濾波處理就是其中一種無約束恢復的圖像恢復技術,其恢復過程的數學形式可表示為F (u,v)=G(u,v)/H(u,v) (u,v=0,1,…,M-1),其中F(u,v)和G(u,v)分別為圖像f(x,y)和g(x,y)的頻域變換,H(u,v)可看作是一個濾波函數。由于圖像在退化過程中存在噪聲的干擾,因此通常情況下的濾波器往往不是正好的1/H(u,v),而是關于u和v的某個非線形的恢復轉移函數M(u,v)。經過以上的分析,圖像的退化和恢復過程(模型)大致可用下圖來表示:
一種簡便的恢復方法是在選取恢復轉移函數M(u,v) 時,如果u2+v2≤w2,則取值1/H(u,v),否則為1。這樣處理雖然簡單,但是恢復后的圖像往往存在較明顯的振鈴現象,通常為了消除振鈴現象,以H(u,v)的值作為判據,如不大于d(0
由于恢復過程需要在頻域進行,因此需要通過二維傅立葉變換將圖像由空域變換到頻域。二維的傅立葉變換較一維傅立葉變換要復雜的多,一般采取連續2次運用一維離散快速傅立葉變換的方法來實現,即先沿f(x,y)的每一個x對y求變換再乘以N得到F(x,v),完成第一步變換。然后再將得到的F(x,v)沿f(x,v)的每一個v對x求變換即可得到f(x,y)的最終變換F(u,v),這兩步的數學表達式如下:
F(x,v)=N*[(1/N)* f(x,y)exp[-j2πvy/N]] (v=0,1,……,N-1) F(u,v)=(1/N)* F(x,v)exp[-j2πux/N] (u,v=0,1,……,N-1) |
類似也可以得出二維離散傅立葉變換逆變換用一維變換計算的表達式:
F(x,v)= F(u,v)exp[j2πux/N] (x,y=0,1,……,N-1) f(x,y)=(1/N)* F(x,v)exp[j2πvy/N]] (y=0,1,……,N-1) |
在分布進行一維傅立葉變換時,多采用"蝴蝶圖"的快速算法(詳見信號處理方面資料),其核心算法如下:
int N=(int)pow(2,M); file://N:序列長度(2的整數次冪) ReverseOrder(A,N); file://對空間序列進行倒序 for(int i=1;i<=M;i++){ int b=(int)pow(2,(i-1)); for(int j=0;j<=(b-1);j++) { float p=(float)(pow(2,(M-i))*j*2.0*PI/(float)N); for(int k=j;k<=(N-1);){ float tr=(float)(A[k+b].Re*cos(p)+A[k+b].Im*sin(p)); file://計算復數運算A*U float ti=(float)(A[k+b].Im*cos(p)-A[k+b].Re*sin(p)); A[k+b].Re=A[k].Re-tr; file://復數運算A-tr A[k+b].Im=A[k].Im-ti; A[k].Re+=tr; file://復數運算A+tr A[k].Im+=ti; k+=b*2; } } } |
傅立葉逆變換的同傅立葉變換比較相似,只是在計算exp[j2πvy/N]時同正變換有符號的區別,以及在計算完成后逆變換需要將值除以N,因此不難寫出一維傅立葉逆變換的實現代碼。在進行二維傅立葉變換將圖像由空域變換到頻域之前,首先需要通過補0的手段將點數非2的整數次冪的非正方型
網格采樣構造為一個長寬均為2的整數次冪的正方型網格:
int WM=(int)(log(W)/log(2)+1.0f); file://計算圖像寬應為2的多少次冪 int HM=(int)(log(H)/log(2)+1.0f); file://計算圖像高應為2的多少次冪 WM=HM=max(WM,HM); file://取二者大值 int WN=(int)pow(2,WM); file://構造網格寬度 int HN=(int)pow(2,HM); file://構造網格高度 for{int i=0;i;for(int j=0;j if(i U[i*WN*3+j].Re=D[i*W*3+j]; file://D為圖像序列 U[i*WN*3+j].Im=0.0f; }else file://缺位補0 U[i*WN*3+j].Re=U[i*WN*3+j].Im=0.0f; } } |
預處理完畢后,可對構造網格的每一列分別進行一維快速傅立葉變換,并將結果存放在原位置,結果乘以N,完成第一步的轉換,求得F(x,v):
for(i=0;i for(int j=0;j UH[j].Re=U[j*WN*3+i].Re; UH[j].Im=U[j*WN*3+i].Im; } DFT_FFT(UH,HM); file://對UH進行快速離散傅立葉變換 for(j=0;j U[j*WN*3+i].Re=HN*UH[j].Re; file://N=HN U[j*WN*3+i].Im=HN*UH[j].Im; } } |
隨即對構造網格的每一行進行傅立葉變換,得到最終的變換結果F(u,v):
for(i=0;i for(int k=0;k<3;k++){ file://對24位位圖的R、G、B三分量均各自進行變換 for(int j=0;j UW[j].Re=U[i*WN*3+j*3+k].Re; UW[j].Im=U[i*WN*3+j*3+k].Im; } DFT_FFT(UW,WM); file://對UW序列進行快速離散傅立葉變換 for(j=0;j U[i*WN*3+j*3+k].Re=UW[j].Re; U[i*WN*3+j*3+k].Im=UW[j].Im; } } } |
至于二維傅立葉逆變換則基本上是上述過程的逆過程,在此就不再贅述。根據逆濾波圖像恢復的設計方案,先通過前面的二維傅立葉變換將退化圖像g(x,y)從空域變換到頻域得到G(u,v),然后在頻域經過恢復轉移函數M(u,v)的恢復處理并經過二維傅立葉逆變換將結果由頻域轉換回空域,就可得到經過恢復處理的近似原始圖像:
…… dsp.DFT_2D_FFT(m_cpBuffer+54,m_nWidth,m_nHeight,U); file://進行二維傅立葉變換 for(int i=0;i for(int j=0;j int k=(int)(j/3); D1=(float)sqrt(i*i+k*k); H=1.0f/(1+(D1/D0)*(D1/D0)); file://H(u,v)=1/(1+(u2+v2)/D02)) if(H>0.45f){ file://閥值 d取0.45 U[i*3*WN+j].Re/=H; file://在頻域與M(u,v)相乘 U[i*3*WN+j].Im/=H; }else{ U[i*3*WN+j].Re*=0.6f; file://如未超過閥值則M(u,v)取常數k=0.6 U[i*3*WN+j].Im*=0.6f; } } } dsp.DFT_2D_IFFT(m_cpBuffer+54,m_nWidth,m_nHeight,U); file://進行傅立葉逆變換 |
這里的逆濾波處理算法采用的是經過改進的恢復轉移函數M(u,v),因此恢復后的圖像不會出現振鈴現象。以標準檢測圖像Lina為處理對象應用以上恢復處理算法,效果如下圖所示。其中間圖像為未經過改進的簡單算法,在胳膊和臉部存在較明顯的振鈴現象,而采取了改進措施的圖像則沒有任何振鈴現象出現,圖像得到了較好的恢復。
維納濾波處理
維納(Wiener)濾波是對退化圖像進行恢復處理的另一種常用算法,是一種有約束的恢復處理方法,其采用的維納濾波器是一種最小均方誤差濾波器,其數學形式比較復雜:
F(u,v)=[(1/H(u,v))*(|H(u,v)|2)/(|H(u,v)|2+s*[Sn(u,v)/Sf(u,v)])]*G(u,v) |
當s為1時,上式就是普通的維納濾波;如果s為變量,則為參數維納濾波,如果沒有噪聲干擾,即Sn(u,v)=0時,上式實際就是前面的逆濾波。從其數學形式可以看出:維納濾波比逆濾波在對噪聲的處理方面要強一些。以上只是理論上的數學形式,在進行實際處理時,往往不知道噪聲函數Sn(u,v)和Sf(u,v)的分布情況,因此在實際應用時多用下式進行近似處理:
F(u,v)=[(1/H(u,v))* (|H(u,v)|2)/(|H(u,v)|2+K)]*G(u,v) |
其中K是一個預先設定的常數。由此可以寫出維納濾波的實現代碼:
…… float K=0.05f; file://預先設定常數K dsp.DFT_2D_FFT(m_cpBuffer+54,m_nWidth,m_nHeight,U); file://轉換到頻域 for(int i=0;i for(int j=0;j int k=(int)(j/3); D1=(float)sqrt(i*i+k*k); float H=1.0f/(1+(D1/D0)*(D1/D0));//H(u,v)= 1/(1+(u2+v2)/D02)) U[i*3*WN+j].Re=(U[i*3*WN+j].Re*H)/(H*H+K); file://維納濾波 U[i*3*WN+j].Im=(U[i*3*WN+j].Im*H)/(H*H+K); } } dsp.DFT_2D_IFFT(m_cpBuffer+54,m_nWidth,m_nHeight,U);//返回到空域 |
對經過退化的Lina圖像應用維納濾波處理,可得到如右圖所示的恢復效果圖。由于維納濾波在進行恢復時對噪聲進行了處理,因此其恢復效果要比逆濾波要好,尤其是退化圖像的噪聲干擾較強時效果更為明顯。
小結 本文對比較常用的兩種圖像恢復算法逆濾波和維納濾波的實現過程作了較為詳細的講述,通過對圖像質量較低的退化圖像應用上述算法可以使圖像質量得到一定程度的改善,在視覺上可以得到較好的改觀。類似的圖像恢復算法還有有約束最小平方恢復算法等多種,應視具體情況靈活選擇合適的算法以獲取最佳的恢復效果。本文所述程序在
Windows 98下,由
Microsoft Visual C++ 6.0編譯通過。