http://blog.linjian.org/?cat=227
posted @ 2008-10-08 09:40 wrh 阅读(645) | 评论 (2)编辑 收藏
     摘要: 1.        如何获取系统日期 CTime tm = CTime :: GetCurrentTime(); CString strTime = tm.Format(_TEXT(“%Y-%M-%d %H:%M:%S)); MessageBox(strTime);   2.  ...  阅读全文
posted @ 2008-09-22 15:10 wrh 阅读(3683) | 评论 (0)编辑 收藏
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下编译通过。
posted @ 2008-09-21 16:19 wrh 阅读(237) | 评论 (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);
  }


然后根据错误的原因查找是哪条语句执行了相关操作,传入的参数是否正确等,就可以修正错误了。

posted @ 2008-09-11 09:59 wrh 阅读(520) | 评论 (0)编辑 收藏
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来获得.

posted @ 2008-09-11 09:44 wrh 阅读(203) | 评论 (0)编辑 收藏

内存映射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" 前缀的特殊含义就被忽略了

posted @ 2008-09-11 09:39 wrh 阅读(1559) | 评论 (0)编辑 收藏
摘 要 该文介绍了用直方图均衡化对灰度图像进行灰度映射,从而达到使图像增强的目的。利用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) ,

  VC++6.0在灰度数字图像增强处理中的应用(图一)
   (k=0,1,2,……,L-1)

  上述求和区间为0到k,根据该方程可以由源图像的各像素灰度值直接得到直方图均衡化后各像素的灰度值。在实际处理变换时,一般先对原始图像的灰度情况进行统计分析,并计算出原始直方图分布,然后根据计算出的累计直方图分布求出fk到gk的灰度映射关系。在重复上述步骤得到源图像所有灰度级到目标图像灰度级的映射关系后,按照这个映射关系对源图像各点像素进行灰度转换,即可完成对源图的直方图均衡化。
posted @ 2008-09-10 19:33 wrh 阅读(456) | 评论 (0)编辑 收藏
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环境下顺利编译通过,运行正常。
posted @ 2008-09-10 19:30 wrh 阅读(1042) | 评论 (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);
……

  四、程序的实例检测

  下面就通过一个实例--拼合一幅古代国画残片来对程序的拼合效果进行检测。其中图一到图三是拼合前的三幅古代国画残片,图四是经过本程序处理后存盘得到的经过无缝合成的图片。经过检测,拼合效果还是相当不错的,在碎片图像的衔接处根本没有接缝的存在:

碎片图像无缝拼合技术的VC++实现(图一)
图一

碎片图像无缝拼合技术的VC++实现(图二)
图二

 
碎片图像无缝拼合技术的VC++实现(图三)
图三

碎片图像无缝拼合技术的VC++实现(图四)
图四
 

  小结

  本程序通过一个实例讲述了处理图片无缝拼合的一种实用方法,在测绘、勘察、文博等行业均有较大的应用潜力。在理解了程序的设计思路和编程思想的前提下,结合具体的实际需求,通过对本文具体代码的改动可以设计出更适合本单位实际情况的类似软件。另外,本文所讲述的截取并保存屏幕技术在类似程序的编制上也可以提供一定的参考。本程序在Windows 2000 Professional下,由Microsoft Visual C++ 6.0编译通过。
posted @ 2008-09-10 19:27 wrh 阅读(212) | 评论 (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)。经过以上的分析,图像的退化和恢复过程(模型)大致可用下图来表示:

VC++实现对退化图像的恢复(图一)

  一种简便的恢复方法是在选取恢复转移函数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)*VC++实现对退化图像的恢复(图二) f(x,y)exp[-j2πvy/N]] (v=0,1,……,N-1)
F(u,v)=(1/N)* VC++实现对退化图像的恢复(图三)F(x,v)exp[-j2πux/N] (u,v=0,1,……,N-1)

  类似也可以得出二维离散傅立叶变换逆变换用一维变换计算的表达式:

F(x,v)=VC++实现对退化图像的恢复(图四) F(u,v)exp[j2πux/N] (x,y=0,1,……,N-1)
f(x,y)=(1/N)*VC++实现对退化图像的恢复(图五) 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为处理对象应用以上恢复处理算法,效果如下图所示。其中间图像为未经过改进的简单算法,在胳膊和脸部存在较明显的振铃现象,而采取了改进措施的图像则没有任何振铃现象出现,图像得到了较好的恢复。

VC++实现对退化图像的恢复(图六)
维纳滤波处理

  维纳(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图像应用维纳滤波处理,可得到如右图所示的恢复效果图。由于维纳滤波在进行恢复时对噪声进行了处理,因此其恢复效果要比逆滤波要好,尤其是退化图像的噪声干扰较强时效果更为明显。

VC++实现对退化图像的恢复(图七)

  小结

  本文对比较常用的两种图像恢复算法逆滤波和维纳滤波的实现过程作了较为详细的讲述,通过对图像质量较低的退化图像应用上述算法可以使图像质量得到一定程度的改善,在视觉上可以得到较好的改观。类似的图像恢复算法还有有约束最小平方恢复算法等多种,应视具体情况灵活选择合适的算法以获取最佳的恢复效果。本文所述程序在Windows 98下,由Microsoft Visual C++ 6.0编译通过。
posted @ 2008-09-10 19:24 wrh 阅读(411) | 评论 (0)编辑 收藏
仅列出标题
共25页: First 16 17 18 19 20 21 22 23 24 Last 

导航

<2024年5月>
2829301234
567891011
12131415161718
19202122232425
2627282930311
2345678

统计

常用链接

留言簿(19)

随笔档案

文章档案

收藏夹

搜索

最新评论

阅读排行榜

评论排行榜