VC图像编程实验


1/56 目 录 实验 1 Windows 编程模型及 Visual C++环境.....................................................2 实验 2 基本类库应用程序框架............................................................................9 实验 3 设备无关位图的访问...............................................................................16 实验 4 图像的灰度变换.......................................................................................29 实验 5 图像的增强...............................................................................................33 实验 6 图像的分割...............................................................................................42 实验 7 图像编码压缩...........................................................................................48 2/56 实验 1 Windows 编程模型及 Visual C++环境 (一)实验目的 1、熟悉 Visual C++开发环境和 Windows 编程模型。 2、学会使用 Visual C++开发环境编写简单的应用程序以及程序的调试方法。 (二)实验原理 1、Visual C++开发环境介绍 (1)工作空间窗口 z 类浏览方式 在 ClassView Tab 页内,树状栏内每个节点对应一个类,分别为 CAboutDlg、 CImageShowApp、CImageShowDoc、CImageShowView、CMainFrame 类以及 Globals 全局 目录。 z 资源浏览方式 在 ResourceView Tab 页内,可对每种资源进行编辑或修改,如添加菜单、添加工具栏、 添加对话框以及添加图标等等,如下图: 3/56 z 文件浏览方式 在左下方的 FileView Tab 页内,从中可看到 Source Files、Header Files、Resource Files 等,分别为程序实现文件(.cpp)、头文件(.h)和资源文件,一般情况下,每组相同文件名的 头文件和实现文件对应一个类;除了上述的这些文件外,还有创建的中间文件,具体如下: (2)向导栏 方便查找类、类中成员变量、类中成员函数以及编译、链接及调试的工具条按钮窗口。 (3)源代码窗口 代码编辑窗口。 (4)输出信息窗口 输出编译、链接等信息。 2、Windows 编程模型 (1)应用程序创建过程图 文件扩展名 说明 CPP 程序实现文件 H 头文件 RC 资源文件 APS 支持 ResourceView BSC 浏览器信息文件 CLW 支持 ClassWizard DEP 附属文件 DSP 工程文件 DSW 工作区文件 MAK 外部生成文件 NCB 支持 ClassView OPT 保留工作区配置 PLG 建立日志文件 4/56 (三)实验步骤 1、文件--新建 Visual C++ Resource.h Windows 头文件 源文件 MFC 头文件 编译器 OBJ 文件 资源脚本文 件(RC) 位图、图标和其他 资源 资源编译器 资源文件 (RES) Windows,运行库和 MFC 库 链接程序 可执行文件 (EXE) 5/56 2、选定工程类型—输入工程保存目录和工程名 (1)选择 Win32 Console Application 工程类型。 (2)选择好工程存放的目录位置,如 E:\。 (3)在工程名文本框中输入工程名,如 TestHello,系统将自动新建 TestHello 工程,并将该 工程的所有文件保存在 E:\TestHello 目录下。 (4)点击确定按钮。 3、”Hello World!” 程序 6/56 (1)选择 Console Application 类型:A “Hello,World!” Application。 (2)点击完成按钮。 4、生成 Hello World Application (1)在源代码窗口中系统自动输入了上述窗口中的实现代码: 7/56 #include "stdafx.h" int main(int argc, char* argv[]) //函数入口 { printf("Hello World!\n"); //在 Command 窗口打印 Hello World!字符串 return 0; } 5、编译、链接并运行,观察显示 6、按任意键,结束运行 7、在原程序的基础上添加变量保存键盘输入值,并在 main 函数中添加接受键盘输入值的 执行代码,代码如下: int i,j,sum; //添加的变量 int main(int argc, char* argv[]) { printf("Hello World!\n"); printf("i="); scanf("%d",&i); //接受键盘输入 printf("j="); scanf("%d",&j); //接受键盘输入 sum=i+j; printf("i+j=%d\n",sum); //打印加法结果 return 0; } 8、设置断点和单步调试:将光标设置在需要设置断点的代码行处,按 F9 快捷键,在该行 最前端可看到一个实心圈,表示断点设置有效,当程序运行到有效断点处,会停止在该处。 如图: 需对程序进行单步调试,可在上述状态下按 F10 快捷键,观察界面运行效果。要全速运行, 8/56 按 F5 快捷键,若要取消断点,可在刚才选中行上按 F9 即可删除断点 9、退出工程 (四)实验结果 (五)思考 1、编程实现加、减、乘、除等类似计算器的简单运算功能。 9/56 实验 2 基本类库应用程序框架 (一)实验目的 1、熟悉 Visual C++开发环境和 Windows 编程模型。 2、学会使用 Visual C++开发环境编写 MFC 应用程序并掌握绘图的方法。 (二)实验原理 1、应用程序框架与类库 C++流行的一个原因是它可以用类库扩充。类库是可在应用程序中使用的有关 C++类的 集合。例如,数学类库可以执行一般的数学运算,而通信类库可以支持通过串行链路的数据 传输。有时,我们要派生自己的类。 应用程序框架是类库的超集。一般的类库只是一种类的集合,用来嵌入在任何程序中, 但是,应用程序框架却定义了程序的结构。 2、MFC 单文档应用程序框架类介绍 在 ClassView Tab 页内,树状栏内除了 Globals 全局目录,每个节点对应一个类,分别为 CAboutDlg、CTestMFCApp、CTestMFCDoc、CTestMFCView、CMainFrame 类。每个类对应 FileView 页内的两个文件,分别为程序实现文件(.cpp)、头文件(.h) (1)CAboutDlg 类 关于对话框类,一般显示软件版本及其他一些软件信息,按下工具栏上的问号图标即可 弹出关于对话框。 (2)CTestMFCApp 类 应用程序类,该程序的可见入口就在该类中,其他的类都会由该类调用,程序可见入口 由 InitInstance 函数开始,如 BOOL CTestMFCApp::InitInstance(){},此函数相当于 c 程 10/56 序的 main()函数。 (3)CTestMFCDoc 类 程序文档类,利用该类对程序的数据进行管理,特别是各类文件数据的管理,如 BMP 文件就在该类中打开,并将图像数据保存在该类的某个成员变量中。 (4)CTestMFCView 类 程序视图类,利用该类对程序的显示视图(中间白色的一块区域)进行管理,如对 BMP 图片的显示就由该类管理,由类中的 OnDraw 函数执行。 (5)CMainFrame 类 程序框架类,利用该类对程序的框架进行管理,如标题栏、菜单、工具栏、状态栏等, 当然有时对这些框架的响应事件也可由视图类进行管理,放在框架类中只是为了结构上 清晰一些。 (6)Globals 目录 程序的全局变量和全局函数显示在该节点中。 3、MFC 单文档应用程序资源介绍 Visual C++内含资源编辑器,通过读取工程中的.rc 文件对工程中的资源进行编辑。 在很大程度上,.rc 文件中的内容决定了应用程序的“外观与感觉”,其中包括快捷键、 对话框、图标、菜单、字符串表、工具栏、版本描述。如下图所示: (三)实验步骤 1、运行 AppWizard 新建 MFC 单文档工程 11/56 (1)选择 MFC AppWizard 工程类型。 (2)选择好工程存放的目录位置,如 E:\。 (3)在工程名文本框中输入工程名,如 TestMFC,系统将自动新建 TestMFC 工程,并将该 工程的所有文件保存在 E:\ TestMFC 目录下。 (4)点击确定按钮。 2、选定单文档 Single Document 项目 12/56 (1)选择 S 单个文档。 (2)点击下一步。 3、前面四个屏幕都默认设置,都选择点击下一步,最后一个视图如图: (1)点击完成 4、工程创建完成 13/56 (1)向导自动生成了类 CAboutDlg、CMainFrame、CTestMFCApp、CTestMFCDoc、 CTestMFCView。 (2)在 Globals 节点中显示有全局变量 CTestMFCApp theApp,该变量表示的是该应用程序 类。 5、编译、链接(F7)及运行(CTRL+F5) 6、添加代码进行绘制窗口 14/56 (1)在类 CTestMFCView 的实现文件 TestMFCView.cpp 文件中找到绘图函数 OnDraw,在 该函数中添加如下代码: pDC->TextOut( 0,0,"Hello,world!" ); //打印文字”Hello,world!” pDC->SelectStockObject( GRAY_BRUSH ); //选择灰色刷子 pDC->Ellipse( CRect(0,20,100,120) ); //画直径为 100 的圆 7、编译、链接(F7)及运行(CTRL+F5),观察显示 8、添加类中成员变量和成员函数 8、结束运行,退出工程 (四)实验结果 1、绘图运行效果 15/56 (1)窗口中显示”Hello,world!”字符串,在其下方显示一半径为 50 的圆。 (五)思考 1、编程实现在单文档窗口中绘出 sine 曲线。 16/56 实验 3 设备无关位图的访问 (一)实验目的 1、熟悉 Visual C++开发环境和 Windows 编程模型。 2、掌握设备无关位图的数据格式。 3、学会使用 DIBAPI 函数访问设备无关位图。 4、结合实例学习如何在应用程序中添加对设备无关位图的访问操作。 (二)实验原理 BMP 文件是 DIB(Device-Independent Bitmaps,设备无关位图)中比较典型的一种,其 文件格式可分为两大部分:文件头部分和像素点阵部分。文件头包含了 DIB 的结构数 据,进一步分为三段:BITMAPFILEHEADER、BITMAPINFOHEADER 以及调色板, 具体见教材 27 页。在此之后便是图像的像素点阵序列,根据位深度的不同有着不同的 定义。24 位位深度图像用三个字节存放一个完整的像素;16 位位深度的图像采用“565” 的组织方式,用两个字节存放一个像素。后续所有程序均以 24 位位深度 BMP 图像作 为处理对象。 1、 显示彩色图像或者灰度图像 在菜单中或者工具栏中按下“打开”文件按钮,即弹出一个文件选择对话框,选择 一个 BMP 图片文件,点击“确定”按钮后即可显示图片,彩色和灰度都可。 2、 将彩色图像转换成灰度图像 在菜单中点击“编辑”按钮,按下“彩色—灰度”按钮,即执行将彩色转换成灰度 的操作同时进行显示。 (三)实验步骤 1、 图像显示代码说明 (1)利用工程向导建立好初始应用工程 ImageShow 后,会见到上面提到的 5 个程 序类以及一个 Globals 目录,第一步要将 DIBAPI 函数库包含到工程中来。 DIBAPI 函数库包含两个文件 DIBAPI.h 以及 DIBAPI.cpp。在文件浏览方式下 ImageShow Files 中 Source Files 的节点上点击右键,按下 Add Files to Folder 按钮,如图: 17/56 弹出实现文件选择对话框,选择 DIBAPI.cpp 文件,点击确定,这样就将该实 现文件添加进了工程,同样在 Header Files 的节点上点击右键,按下 Add Files to Folder 按钮,弹出头文件选择对话框,选择 DIBAPI.h 文件,点击确定,这 样就将该头文件添加进了工程。于是在类浏览方式下可在 Globals 节点下看到 包含在上述两个文件中的 DIBAPI 函数,如图: (2)接下来做的是要打开一个 BMP 文件,并将文件中读取到的内容保存到 HDIB (该类型在 DIBAPI 函数库中定义)对象中由 CImageShowDoc 类管理起来, 首先在定义 CImageShowDoc 类的头文件 ImageShowDoc.h 中包含 DIBAPI.h 头文件,然后定义 HDIB 对象变量,HDIB m_hDIB,定义该变量用来保存打 开的图像;HDIB m_hProcDIB; 定义该变量用来保存处理后的图像,定义保 存打开文件路径的变量 CString m_filePathName ,将其添加在 HDIB m_hProcDIB 之后,如图: 18/56 在使用 HDIB 对象 m_hDIB 和 m_hProcDIB 之前,须对其在 ImageShowDoc.cpp 文件 CImageShowDoc 类的构造函数中进行变量初始化,并在析构函数中释放 m_hDIB 和 m_hProcDIB 中的资源,如图: 接着利用菜单“查看”下的“建立类向导”按钮或按快捷键 CTRL+W,弹出 一个类向导对话框,如下所示: 19/56 Class Name 选择 CImageShowDoc,Object Ids 选择 CImageShowDoc,Messages 选择并双击 OnOpenDocument 项,在下方 Member functions 中会出现 V OnOpenDocument 函数,最后点击确定或点击“Edit Code”按钮直接转到要编 辑的 OnOpenDocument 函数中。 (3)在 CImageShowDoc 中出现 OnOpenDocument 函数,然后在该函数中可以编 写自己的实现代码: if (!CDocument::OnOpenDocument(lpszPathName)) //弹出打开对话框,选 择所要打开的文件 return FALSE; if( m_hDIB!=NULL ) //判断存放图像文件的DIB对象是否 为空,若非空,便将其删除,重新填充 { GlobalFree( m_hDIB ); //释放 DIB m_hDIB = NULL; } CFile file; if( file.Open( lpszPathName,CFile::modeRead,NULL ) ) // 根据选择的文 件按照只读方式将其打开 { m_hDIB = ReadDIBFile( file ); //调用 DIBAPI 函数 ReadDIBFile 将打开的文件读入 内存,由 HDIB 对象 m_hDIB 来 保存 m_filePathName=lpszPathName; //保存打开文件路径 file.Close(); //关闭文件 } UpdateAllViews( NULL ); //更新视图 (4)将读入的图像文件显示在视图中,视图类 CImageShowView 中用来显示 HDIB 对象的执行函数为 OnDraw 函数,参数 CDC 类为设备上下文类,相当于显卡 内存区域,如图: 20/56 然后在该函数中编写显示代码如下: CImageShowDoc* pDoc = GetDocument(); //获得程序文档类对象 ASSERT_VALID(pDoc); HDIB hDIB = pDoc->m_hDIB; //获得文档类中保存的 HDIB 对象 if( hDIB!=NULL ) //判断该对象是否为空,若为空,程 序将出错,所以该对象不能为空 { LPSTR lpDIB = (LPSTR)GlobalLock( hDIB );//将 HDIB 内存区域锁定 LONG lWidth = DIBWidth( lpDIB ); //获取该图像的宽度 LONG lHeight = DIBHeight( lpDIB ); //获取该图像的高度 LPSTR lpDIBBits = FindDIBBits(lpDIB); //获取图像点阵地址 WORD wBitCount = DIBBitCount(lpDIB); //获取图像位深度 CRect rectSrc,rectDst; //定义图像的区域大小以 及将要绘制在设备上的区域大小 rectSrc.top=rectSrc.left=0; rectSrc.right = lWidth; rectSrc.bottom = lHeight; //对这些区域进行赋值 rectDst = rectSrc; //目标区域与源区域一般大小 PaintDIB( pDC->m_hDC,&rectDst,hDIB,rectSrc,NULL );//绘制 HDIB GlobalUnlock( hDIB ); //对 HDIB 内存解锁 } (5)编译、链接(F7)及运行(CTRL+F5),打开一 BMP 图像文件 lena.bmp,观 察显示 2、 彩色转换成灰度 (1)添加保存处理后图像的 HDIB 对象变量,如在 CImageShowDoc 类的头文件 ImageShowDoc.h 中加入 HDIB m_hProcDIB,见上图; 21/56 (2)在资源浏览方式下,选择 Menu 节点,点击 IDR_MAINFRAME,增加操作按 钮,见下图,如在菜单“编辑”中添加“彩色—灰度”按钮。 (3)对该按钮进行编辑,如图: ID 设为 ID_RGBTOGRAY,标题设为“彩色—灰度”。 (4)添加该按钮的消息映射即事件响应,使得按下该按钮就能执行一函数,如: OnRgbToGray(),于是启动类向导对话框,如下设置: 22/56 在 Object Ids 中选择 ID_RGBTOGRAY,Messages 中选择 COMMAND 并双击, 即弹出函数命名对话框,输入 OnRgbToGray,确定后便出现 Member functions 中显示的样子,按确定或点击“Edit Code”按钮,便可在程序视图类中找到 OnRgbToGray 函数或直接转到 OnRgbToGray 函数中,如下所示: void CImageShowView::OnRgbToGray() { // 在这里加代码把彩色图转换成灰度图 LONG i,j; // 指向 DIB 的指针 LPSTR lpDIB; LPSTR lpNewDIB; // 指向 DIB 象素指针 LPSTR lpDIBBits; LPSTR lpNewDIBBits; DWORD dwWidth,dwHeight; LONG lLineBytes; // 锁定 DIB CImageShowDoc* pDoc = GetDocument(); ASSERT_VALID(pDoc); HDIB hDIB = pDoc->m_hDIB; if( hDIB != NULL ) { lpDIB = (LPSTR) ::GlobalLock((HGLOBAL) hDIB); dwWidth = DIBWidth( lpDIB ); dwHeight = DIBHeight( lpDIB ); lLineBytes = WIDTHBYTES(dwWidth*24); HDIB hNewDIB = NULL; hNewDIB = (HDIB) CopyHandle( hDIB ); // 锁定内存 lpNewDIB = (char * )::GlobalLock((HGLOBAL) hNewDIB); 23/56 // 找到 DIB 图像象素起始位置 lpDIBBits = ::FindDIBBits(lpDIB); // 判断是否是 24-bpp 位图 if (DIBBitCount(lpNewDIB) != 24) { // 提示用户 MessageBox("请先将其转换为 24 位色位图,再进行处理!", "系统提 示" , MB_ICONINFORMATION | MB_OK); // 解除锁定 ::GlobalUnlock((HGLOBAL) hNewDIB); // 返回 return; } // 找到 DIB 图像象素起始位置 lpNewDIBBits = ::FindDIBBits(lpNewDIB); // 对各像素进行灰度转换 for (i = 0; i < dwHeight; i ++) { for (j = 0; j < lLineBytes; j ++) { // 获取各颜色分量 unsigned char B = *((unsigned char *)lpDIBBits + lLineBytes * i + j); j++; unsigned char G = *((unsigned char *)lpDIBBits + lLineBytes * i + j); j++; unsigned char R = *((unsigned char *)lpDIBBits + lLineBytes * i + j); // 计算灰度值 unsigned char Y = (unsigned char)(0.3 * R + 0.59 * G + 0.11 * B); // 回写灰度值 *((unsigned char *)lpNewDIBBits + lLineBytes * i + j - 2) = Y; *((unsigned char *)lpNewDIBBits + lLineBytes * i + j - 1) = Y; *((unsigned char *)lpNewDIBBits + lLineBytes * i + j ) = Y; } } GlobalUnlock( hDIB ); GlobalUnlock( hNewDIB ); if( pDoc->m_hProcDIB!=NULL ) { GlobalFree( pDoc->m_hProcDIB ); } pDoc->m_hProcDIB = hNewDIB; pDoc->UpdateAllViews( NULL ); 24/56 } } (5)将灰度化处理后的图像显示在源图像旁,在 ImageShowView.cpp 中的 OnDraw 中添加如下代码: HDIB hProcDIB = pDoc->m_hProcDIB; if( hProcDIB!=NULL ) { LPSTR lpDIB = (LPSTR)GlobalLock( hProcDIB ); LONG lWidth = DIBWidth( lpDIB ); LONG lHeight = DIBHeight( lpDIB ); CRect rectSrc,rectDst; rectSrc.top=rectSrc.left=0; rectSrc.right = lWidth; rectSrc.bottom = lHeight; rectDst = rectSrc; rectDst.left=rectDst.left+lWidth; //关键代码 rectDst.right = rectDst.right+lWidth; PaintDIB( pDC->m_hDC,&rectDst,hProcDIB,rectSrc,NULL ); GlobalUnlock( hDIB ); } (6)编译、链接(F7)及运行(CTRL+F5),打开一 BMP 图像文件 lena.bmp,运 行彩色转灰度操作,观察显示 3、 进行处理后的图像保存 (1)利用菜单“查看”下的“建立类向导”按钮,弹出一个类向导对话框,如下 所示: 25/56 Class Name 选择 CImageShowDoc,Object Ids 选择 CImageShowDoc,Messages 选 择并双击 OnSaveDocument 项,在下方 Member functions 中会出现 V OnSaveDocument 函数,最后点击确定或点击“Edit Code”按钮。 在 CImageShowDoc 中出现 OnSaveDocument 函数,然后在该函数中可以编写自己 的保存文件的实现代码: BOOL CImageShowDoc::OnSaveDocument(LPCTSTR lpszPathName) { BOOL bSuccess = FALSE; if( m_hProcDIB!=NULL ) { CFile file; if( file.Open( lpszPathName,CFile::modeCreate|CFile::modeWrite,NULL ) ) //根据选择的文件按照创建和可写方式将其打开 { bSuccess = SaveDIB( m_hProcDIB,file ); file.Flush(); file.Close(); } } return bSuccess; } (2)编译、链接(F7)及运行(CTRL+F5),完成上述的彩色转灰度操作之后, 保存处理后的图像。 4、 获取图像灰度直方图 (1)增加操作按钮,如在菜单“查看”中添加“源图像灰度直方图”按钮,对该 按钮进行编辑,如图: ID 设为 ID_VIEW_INTENSITY,标题设为“源图像灰度直方图”, (2)添加该按钮的消息映射即事件响应,使得按下该按钮就能执行一函数,如: OnViewIntensity(),于是需启动类向导对话框,如下设置: 26/56 void CImageShowView::OnViewIntensity() { CImageShowDoc* pDoc = GetDocument(); HDIB hDIB = pDoc->m_hProcDIB; if( hDIB!=NULL ) { // 循环变量 LONG i; LONG j; // 灰度计数 int nNs_Y[256]; float fPs_Y[256]; // 变量初始化 memset(nNs_Y,0,sizeof(nNs_Y)); // 指向 DIB 的指针 LPSTR lpDIB; // 指向 DIB 象素指针 LPSTR lpDIBBits; // 锁定 DIB lpDIB = (LPSTR)GlobalLock((HGLOBAL) hDIB); // 找到 DIB 图像象素起始位置 lpDIBBits = FindDIBBits(lpDIB); // 判断是否是 24-bpp 位图 if (DIBBitCount(lpDIB) != 24) { // 提示用户 27/56 MessageBox("请先将其转换为 24 位色位图,再进行处理!", "系统提示" , MB_ICONINFORMATION | MB_OK); // 解除锁定 ::GlobalUnlock((HGLOBAL) hDIB); // 返回 return; } // DIB 的宽度 LONG lWidth = DIBWidth(lpDIB); // DIB 的高度 LONG lHeight = DIBHeight(lpDIB); // 计算图像每行的字节数 LONG lLineBytes = WIDTHBYTES(lWidth * 24); // 对各像素进行灰度转换 for (i = 0; i < lHeight; i ++) { for (j = 0; j < lLineBytes; j ++) { unsigned char B = *((unsigned char *)lpDIBBits + lLineBytes * i + j); j++; unsigned char G = *((unsigned char *)lpDIBBits + lLineBytes * i + j); j++; unsigned char R = *((unsigned char *)lpDIBBits + lLineBytes * i + j); // 计算灰度值 unsigned char Y = (unsigned char)(0.3 * R + 0.59 * G + 0.11 * B); // 灰度统计计数 nNs_Y[Y]++; } } // 计算灰度分布密度 for(i=0;i<256;i++) fPs_Y[i] = nNs_Y[i] / (lHeight * lWidth * 1.0f); // 解除锁定 ::GlobalUnlock((HGLOBAL) hDIB); } } (3)编译、链接(F7)及运行(CTRL+F5)。 (四)实验结果 1、打开 lena 图像文件 lena.bmp,显示如图: 28/56 2、执行彩色转换成灰度操作,显示如图: (五)思考 1、在 DIBAPI 函数库中添加自己定义的图像处理函数,如彩色—灰度函数。 2、将获取的图像灰度直方图进行可视化。 29/56 实验 4 图像的灰度变换 (一)实验目的 1、熟悉 Visual C++开发环境和 Windows 编程模型。 2、掌握设备无关位图的数据格式。 3、学会使用 DIBAPI 函数访问设备无关位图。 4、结合实例学习如何在应用程序中添加图像处理算法。 5、了解图像灰度变换的算法和用途。 (二)实验原理 图像的灰度变换处理是图像处理技术中非常基本、直接的空域图像处理方法,它主要针 对独立的像素点进行变换处理,在处理时通过改变原始图像数据所占据的灰度范围而使其在 视觉上能得到好的改观。 在处理方式上,灰度变换可分为线性灰度变换和非线性灰度变换两大类,前者处理相对 简单,主要考虑输出灰度级与输入灰度级之间的线性关系,如: D0=f(DI)=aDI+b 若 a=-1,b=255,则上式变成 D0=f(DI)=255-DI,可知该变换为反色处理。 1、将彩色图像或灰度图像进行反色处理 在菜单中或者工具栏中按下“打开”文件按钮,即弹出一个文件选择对话框,选择一 个 BMP 图片文件,确定后即可显示图片,彩色和灰度都可。在菜单中点击“编辑”按钮, 按下“灰度变换”按钮,即执行图像反色处理的操作同时进行显示。 (三)实验步骤 1、操作与实验 3 相似,只是其中部分关键代码不同,这里的关键代码如下: // 对各像素进行灰度转换 for (i = 0; i < dwHeight; i ++) { for (j = 0; j < lLineBytes; j ++) { // 对像素各颜色分量进行反色处理 unsigned char B = *((unsigned char *)lpDIBBits + lLineBytes * i + j); *((unsigned char *)lpNewDIBBits + lLineBytes * i + j) = 255 - B; j++; unsigned char G = *((unsigned char *)lpDIBBits + lLineBytes * i + j); *((unsigned char *)lpNewDIBBits + lLineBytes * i + j) = 255 - G; 30/56 j++; unsigned char R = *((unsigned char *)lpDIBBits + lLineBytes * i + j); *((unsigned char *)lpNewDIBBits + lLineBytes * i + j) = 255 - R; } } 2、该实验中增加操作按钮,在菜单“编辑”中添加“灰度变换”按钮,对该按钮进行编 辑,ID 设为 ID_GRAYTRAN,标题设为“灰度变换”;利用向导在 CImageShowView 类中 添加响应函数—灰度变换函数 OnGrayTran,具体如下: void CImageShowView::OnGrayTran() { // 在这里加代码把彩色图转换成灰度图 LONG i,j; // 指向 DIB 的指针 LPSTR lpDIB; LPSTR lpNewDIB; // 指向 DIB 象素指针 LPSTR lpDIBBits; LPSTR lpNewDIBBits; DWORD dwWidth,dwHeight; LONG lLineBytes; // 锁定 DIB CImageShowDoc* pDoc = GetDocument(); ASSERT_VALID(pDoc); HDIB hDIB = pDoc->m_hDIB; if( hDIB != NULL ) { lpDIB = (LPSTR) ::GlobalLock((HGLOBAL) hDIB); dwWidth = DIBWidth( lpDIB ); dwHeight = DIBHeight( lpDIB ); lLineBytes = WIDTHBYTES(dwWidth*24); HDIB hNewDIB = NULL; hNewDIB = (HDIB) CopyHandle( hDIB ); // 锁定内存 lpNewDIB = (char * )::GlobalLock((HGLOBAL) hNewDIB); // 找到 DIB 图像象素起始位置 lpDIBBits = ::FindDIBBits(lpDIB); // 判断是否是 24-bpp 位图 if (DIBBitCount(lpNewDIB) != 24) { // 提示用户 MessageBox("请先将其转换为 24 位色位图,再进行处理!", "系统提 示" , MB_ICONINFORMATION | MB_OK); // 解除锁定 ::GlobalUnlock((HGLOBAL) hNewDIB); 31/56 // 返回 return; } // 找到 DIB 图像象素起始位置 lpNewDIBBits = ::FindDIBBits(lpNewDIB); // 对各像素进行灰度变换—反色处理 for (i = 0; i < dwHeight; i ++) { for (j = 0; j < lLineBytes; j ++) { // 对像素各颜色分量进行反色处理 unsigned char B = *((unsigned char *)lpDIBBits + lLineBytes * i + j); *((unsigned char *)lpNewDIBBits + lLineBytes * i + j) = 255 - B; j++; unsigned char G = *((unsigned char *)lpDIBBits + lLineBytes * i + j); *((unsigned char *)lpNewDIBBits + lLineBytes * i + j) = 255 - G; j++; unsigned char R = *((unsigned char *)lpDIBBits + lLineBytes * i + j); *((unsigned char *)lpNewDIBBits + lLineBytes * i + j) = 255 - R; } } GlobalUnlock( hDIB ); GlobalUnlock( hNewDIB ); if( pDoc->m_hProcDIB!=NULL ) { GlobalFree( pDoc->m_hProcDIB ); } pDoc->m_hProcDIB = hNewDIB; pDoc->UpdateAllViews( NULL ); } } 3、编译、链接(F7)及运行(CTRL+F5),打开一 BMP 图像 lena.bmp 或 lenag.bmp, 运行灰度变换操作,观察显示 (四)实验结果 1、打开彩色 lena 图像文件 lena.bmp 并对其灰度变换—反色处理,显示如图: 32/56 2、打开灰度 lena 图像文件 lenag.bmp 并对其灰度变换—反色处理,显示如图: (五)思考 1、在 DIBAPI 函数库中添加自己定义的图像处理函数,如灰度变换—反色处理函数。 33/56 实验 5 图像的增强 (一)实验目的 1、熟悉 Visual C++开发环境和 Windows 编程模型。 2、掌握设备无关位图的数据格式。 3、学会使用 DIBAPI 函数访问设备无关位图。 4、结合实例学习如何在应用程序中添加图像处理算法。 5、了解图像增强的算法和用途。 (二)实验原理 图像的增强可分图像的空域滤波与频域滤波,空域滤波方法是在图像空间域中借助模板进 行邻域操作完成的。根据处理效果的不同,可分为平滑滤波和锐化滤波两种。其采用的手法 是一致的,即都是采用小区域模板卷积的方法来进行。这种模板卷积操作是图像处理中非常 基本的一种处理方法,在具体计算时,首先将模板中心和图像中待处理的某像素点重合,并 将模板各元素值与模板下各自的对应像素值相乘,最后将模板输出响应(上步计算出的模板 各元素乘积之和)作为当前模板中心所处像素的灰度值。 1、将彩色图像或灰度图像进行图像增强—平滑处理 在菜单中或者工具栏中按下“打开”文件按钮,即弹出一个文件选择对话框,选择一 个 BMP 图片文件,确定后即可显示图片,彩色和灰度都可。在菜单中点击“编辑”按钮, 按下“平滑变换”按钮,即执行图像增强—平滑变换的操作同时进行显示。 2、将彩色图像或灰度图像进行图像增强—平滑处理 在菜单中点击“编辑”按钮,按下“锐化变换”按钮,即执行图像增强—锐化变换的 操作同时进行显示。 (三)实验步骤 1、在 ImageShowView.h 文件中定义图像增强滤波模板数据结构体 kernel,见书 P129 页 typedef struct kernel { int Element[3][3]; //模板元素 int Divisor; //除以的系数 int Dimention; //模板维数 }KERNEL, *LPKERNEL; 如图: 34/56 图像增强中所做的平滑或者锐化变换主要的区别就在于所选取的滤波模板不同,如 果采用的是平滑滤波模板,则做的是平滑变换,采用的是锐化滤波模板,做的是锐化变 换。举例,平滑滤波模板: KERNEL Line_Filter[1]= { { 1, 1, 1, 1, 1, 1, 1, 1, 1 }, 9, 3 } 锐化滤波模板: KERNEL Line_Filter[1]= { { -1, -1, -1, -1, 8, -1, -1, -1, -1 }, 9, 3 } 2、建立模板滤波函数 StencilFilter()完成对图像的模板滤波处理,在类浏览方式下可在 CImageShowView 节点上点击鼠标右键,在弹出菜单中点击 Add Member Function 按钮, 如图: 35/56 随即弹出一添加函数对话框,如图设置: HDIB hDIB 为待滤波的图像,HDIB hProcDIB 为处理后的图像,LPKERNEL lpKernel 为选择的模版的指针。点击“OK”按钮,即可在 ImageShowView.cpp 文件中出现 void CImageShowView:: StencilFilter(HDIB hDIB, HDIB hProcDIB,LPKERNEL lpKernel),在该 函数中添加如下代码: void CImageShowView::StencilFilter(HDIB hDIB, HDIB hProcDIB,LPKERNEL lpKernel) { // 循环变量 LONG i; LONG j; LONG k; LONG l; // 临时变量 LONG R, G, B; 36/56 // 指向 DIB 的指针 LPSTR lpDIB,lpProcDIB; // 指向 DIB 象素指针 LPSTR lpDIBBits,lpProcDIBBits; // 锁定 DIB lpDIB = (LPSTR) ::GlobalLock((HGLOBAL) hDIB); lpProcDIB = (LPSTR) ::GlobalLock((HGLOBAL) hProcDIB); // 找到 DIB 图像象素起始位置 lpDIBBits = FindDIBBits(lpDIB); lpProcDIBBits = FindDIBBits(lpProcDIB); // 判断是否是 24-bpp 位图 if (DIBBitCount(lpDIB) != 24) { // 提示用户 MessageBox("请先将其转换为 24 位色位图,再进行处理!", "系统提示" , MB_ICONINFORMATION | MB_OK); // 解除锁定 ::GlobalUnlock((HGLOBAL) hDIB); // 返回 return; } // DIB 的宽度 LONG lWidth = DIBWidth(lpDIB); // DIB 的高度 LONG lHeight = DIBHeight(lpDIB); // 计算图像每行的字节数 LONG lLineBytes = WIDTHBYTES(lWidth * 24); // 申请并分配中间缓存 HLOCAL hLocal = LocalAlloc(GHND, lLineBytes * lHeight); if (hLocal == NULL) return; LPBYTE m_temp = (LPBYTE)LocalLock(hLocal); // 复制图像数据到中间缓存 for (i = 0; i < lHeight; i ++) { for (j = 0; j < lLineBytes; j ++) { *(m_temp + lLineBytes * i + j) = *((unsigned char *)lpDIBBits + lLineBytes * i + j); 37/56 j++; *(m_temp + lLineBytes * i + j) = *((unsigned char *)lpDIBBits + lLineBytes * i + j); j++; *(m_temp + lLineBytes * i + j) = *((unsigned char *)lpDIBBits + lLineBytes * i + j); } } // 模版滤波 for (i = 0; i < lWidth; i++) //被处理像素在 i 列 { for (j = 0; j < lHeight; j++) //被处理像素在 j 行 { // 计数清零 R = G = B = 0; // 进行小区域模版滤波 for (k = i - (int)(lpKernel->Dimention / 2); k < i + (int)(lpKernel->Dimention / 2) + 1; k++) { for(l = j - (int)(lpKernel->Dimention / 2); l < j + (int)(lpKernel->Dimention / 2) + 1; l++) { // 防止内存溢出 if (k >= 0 && l >= 0 && k < lWidth && l < lHeight) { unsigned char TR = *((unsigned char *)lpDIBBits + l * lLineBytes + k * 3); R += lpKernel->Element[k - i + (int)(lpKernel->Dimention / 2)][l - j + (int)(lpKernel->Dimention / 2)] * TR; unsigned char TG = *((unsigned char *)lpDIBBits + l * lLineBytes + k * 3 + 1); G += lpKernel->Element[k - i + (int)(lpKernel->Dimention / 2)][l - j + (int)(lpKernel->Dimention / 2)] * TG; unsigned char TB = *((unsigned char *)lpDIBBits + l * lLineBytes + k * 3 + 2); B += lpKernel->Element[k - i + (int)(lpKernel->Dimention / 2)][l - j + (int)(lpKernel->Dimention / 2)] * TB; } } } // 进行模版平均 R /= lpKernel->Divisor; G /= lpKernel->Divisor; B /= lpKernel->Divisor; // 存储计算结果到中间缓存 *(m_temp + j * lLineBytes + i * 3) = R; *(m_temp + j * lLineBytes + i * 3 + 1) = G; *(m_temp + j * lLineBytes + i * 3 + 2) = B; } 38/56 } // 将转换后的中间缓存数据回存到 DIB for (i = 0; i < lHeight; i ++) { for (j = 0; j < lLineBytes; j ++) { *((unsigned char *)lpProcDIBBits + lLineBytes * i + j) = *(m_temp + lLineBytes * i + j); j++; *((unsigned char *)lpProcDIBBits + lLineBytes * i + j) = *(m_temp + lLineBytes * i + j); j++; *((unsigned char *)lpProcDIBBits + lLineBytes * i + j) = *(m_temp + lLineBytes * i + j); } } // 解除锁定 ::GlobalUnlock((HGLOBAL) hDIB); ::GlobalUnlock((HGLOBAL) hProcDIB); LocalUnlock(hLocal); LocalFree(hLocal); } 3、在 ImageShowView.cpp 文件中定义好所有要用到的滤波器模板: KERNEL Line_Filter[4]= { //平滑滤波模板 1 { { 1, 1, 1, 1, 1, 1, 1, 1, 1 }, 9, 3 }, //平滑滤波模板 2 { { 1, 1, 1, 1, 2, 1, 1, 1, 1 }, 10, 3 }, //平滑滤波模板 3 39/56 { { 1, 2, 1, 2, 4, 2, 1, 2, 1 }, 16, 3 }, //锐化滤波模板 1 { { -1, -1, -1, -1, 8, -1, -1, -1, -1 }, 9, 3 } }; 如图: 5、 图像增强-平滑变换 增加操作按钮,如在菜单“编辑”中添加“平滑变换”按钮,ID 设为 ID_SMOOTHING, 标题设为“平滑变换”;利用向导添加该按钮的消息映射即事件响应,使得按下该按钮就能 执行一函数,如:OnSmoothing (),可在程序视图类中找到 OnSmoothing 函数,如下所示: void CImageShowView::OnSmoothing() { CImageShowDoc* pDoc = GetDocument(); 40/56 if( pDoc->m_hDIB!=NULL ) { if( pDoc->m_hProcDIB!=NULL ) GlobalFree( pDoc->m_hProcDIB ); pDoc->m_hProcDIB = (HDIB)CopyHandle( pDoc->m_hDIB ); StencilFilter(pDoc->m_hDIB,pDoc->m_hProcDIB,&Line_Filter[0]); //采用平 滑滤波模板 1 进行平滑变换 pDoc->UpdateAllViews( NULL ); } } 6、 图像增强-锐化变换 增加操作按钮,如在菜单“编辑”中添加“锐化变换”按钮,ID 设为 ID_SHARPENING, 标题设为“锐化变换”;添加该按钮的消息映射即事件响应,使得按下该按钮就能执行一函 数,如:OnSharpening (),可在程序视图类中找到 OnSharpening 函数,如下所示: void CImageShowView::OnSharpening() { CImageShowDoc* pDoc = GetDocument(); if( pDoc->m_hDIB!=NULL ) { if( pDoc->m_hProcDIB!=NULL ) GlobalFree( pDoc->m_hProcDIB ); pDoc->m_hProcDIB = (HDIB)CopyHandle( pDoc->m_hDIB ); StencilFilter(pDoc->m_hDIB,pDoc->m_hProcDIB,&Line_Filter[3]); //采用锐化滤 波模板 4 进行锐化变换 pDoc->UpdateAllViews( NULL ); } } 7、 编译、链接(F7)及运行(CTRL+F5),打开一 BMP 图像 lenag.bmp,运行平滑变换和 锐化变换操作,观察显示 (四)实验结果 1、打开灰度 lena 图像文件 lenag.bmp 并对其图像增强—平滑变换,显示如图: 41/56 2、打开灰度 lena 图像文件 lenag.bmp 并对其图像增强—锐化变换,显示如图: (五)思考 1、参照教材中的中值滤波原理实现中值滤波操作。 42/56 实验 6 图像的分割 (一)实验目的 1、熟悉 Visual C++开发环境和 Windows 编程模型。 2、掌握设备无关位图的数据格式。 3、学会使用 DIBAPI 函数访问设备无关位图。 4、结合实例学习如何在应用程序中添加图像处理算法。 5、了解 Hough 变换进行直线或圆检测的算法和用途。 (二)实验原理 图像分割(image segmentation)是数字图像处理领域一类非常重要的图像分析技术, 在对图像的研究和应用中,根据不同领域的不同需要,在某一领域往往仅对原始图像中的某 些部分(目标)感兴趣。这些目标区域一般来说都具备其自身特定的一些诸如灰度、纹理等 性质,图像分割就主要根据图像在各个区域的不同特性,而对其进行边界或区域上的分割, 并从中提取出所关心的目标。 图像分割算法有 4 大类:并行边界分割算法、串行边界分割算法、并行区域分割算法和 串行区域分割算法。哈夫(Hough)变换方法是利用图像的全局特性而对目标轮廓进行直接 检测的方法。 哈夫(Hough)变换的核心思想是点-线的对偶性(duality),通过变换将图像从图像空 间转到参数空间。 采取的基本策略是根据图像在图像空间内的点在所对应的参数空间里, 计算出符合对偶特性的参数点的所有可能的轨迹,并通过累加参数点的数量来做出最终的决 策。因此,哈夫变换不仅能检测直线等一阶曲线目标,对于圆、椭圆等高阶曲线甚至是所有 满足解析式 f(x,c)=0 的各类曲线目标,同样可以进行检测。 本算法实现的是圆的检测,最终目标是在不同形状的几何图像中检测出圆,并显示在其 与原来对应的位置上。圆周在图像空间的解析表达式如下: (x-a)2+(y-b)2=r2 转换到参数坐标,将增加到三维 a,b,r,其参数轨迹为一个圆锥的部分表面。当圆周 的半径 r 给定为一个常数时,参数轨迹则变为一个以(a,b)为圆心半径为 r 的圆周。 如图是一张含有直线以及圆等多种几何形状的混合图像,对其进行处理时,先对图像的 所有点进行枚举。如果当前点灰度级为 0 就以该点为圆心,用已知半径画圆,在与图像同样 43/56 大小的二维累加缓存中为该圆弧所经过的象素点的灰度值加 1,在对图像所有象素枚举过一 遍后,根据几何上的有关知识,圆周上的点再作为圆心并以原半径为半径作圆后,所画的圆 周必定经过圆心,因此灰度累加统计的结果必定只在圆心一点上产生一个统计最大值。 确定了圆心坐标,半径又是已知,由此马上可以得到圆周检测结果。 1、 显示哈夫测试图像 在菜单中或者工具栏中按下“打开”文件按钮,即弹出一个文件选择对话框,选择哈夫 测试图像文件,点击“确定”按钮后即可显示该图像。 将彩色图像转换成灰度图像 2、在菜单中点击“编辑”按钮,按下“哈夫检测”按钮,即执行哈夫检测圆的操作同时将 检测结果进行显示。 (三)实验步骤 1、增加操作按钮,如在菜单“编辑”中添加“哈夫检测”按钮,ID 设为 ID_Hough,标题 设为“哈夫检测”;利用向导添加该按钮的消息映射即事件响应,使得按下该按钮就能执行 一函数,如:OnHough (),可在程序视图类中找到 OnHough 函数,具体如下: void CImageShowView::OnHough() { LONG i; LONG j; LONG k; LPSTR lpDIB; LPSTR lpDIBBits; LPSTR lpNewDIB; LPSTR lpNewDIBBits; LONG lWidth,lHeight,lLineBytes; CImageShowDoc* pDoc = GetDocument(); ASSERT_VALID(pDoc); HDIB hDIB = pDoc->m_hDIB; if( hDIB != NULL ) { lpDIB = (LPSTR) ::GlobalLock((HGLOBAL) hDIB); HDIB hNewDIB = NULL; hNewDIB = (HDIB) CopyHandle( hDIB ); // 锁定内存 lpNewDIB = (char * )::GlobalLock((HGLOBAL) hNewDIB); // 判断是否是 24-bpp 位图 if (DIBBitCount(lpNewDIB) != 24) { // 提示用户 MessageBox("请先将其转换为 24 位色位图,再进行处理!", "系统提示" , MB_ICONINFORMATION | MB_OK); // 解除锁定 ::GlobalUnlock((HGLOBAL) hNewDIB); // 返回 return; 44/56 } // 找到 DIB 图像象素起始位置 lpDIBBits = ::FindDIBBits(lpDIB); lpNewDIBBits = ::FindDIBBits(lpNewDIB); //DIB 的宽度 lWidth=DIBWidth (lpNewDIB); //DIB 的高度 lHeight=DIBHeight (lpNewDIB); //计算图像每行的字节数 lLineBytes=WIDTHBYTES (lWidth*24); //不能用 char,也不能用::strcpy() unsigned char*m_temp; m_temp=new unsigned char [lLineBytes*lHeight]; //中间变量 int cx, cy1, cy2; memset (m_temp, 0, sizeof(m_temp)); //以离散点为圆心画圆 for (i=0; i=0&&cx=0&&cy1=0&&cx=0&&cy2=0&&cx=0&&cy1=0&&cx=0&&cy2m_hProcDIB!=NULL ) { GlobalFree( pDoc->m_hProcDIB ); } pDoc->m_hProcDIB = hNewDIB; pDoc->UpdateAllViews( NULL ); } } 2、编译、链接(F7)及运行(CTRL+F5),打开一 BMP 图像 hough.bmp,运行哈夫检测 操作,观察显示 (四)实验结果 1、打开灰度图像文件 hough.bmp,显示如图: 2、对上述图像进行哈夫检测,显示如图: 47/56 (五)思考 1、编写程序将 hough.bmp 图像文件中的直线检测并显示。 48/56 实验 7 图像编码压缩 (一)实验目的 1、熟悉 Visual C++开发环境和 Windows 编程模型。 2、掌握设备无关位图的数据格式。 3、学会使用 DIBAPI 函数访问设备无关位图。 4、结合实例学习如何在应用程序中添加图像处理算法。 5、了解霍夫曼编码的算法和用途。 (二)实验原理 霍夫曼编码最早于 1952 年由霍夫曼提出,是一种比较经典的信息无损的属于熵编码机 制的统计编码机制的统计编码方法。这种编码算法只用来消除编码冗余,在具体编码时通过 一棵二叉树来完成对信源符号的变长编码,即以较少的比特数来表达出现概率较大的灰度 级,对于出现概率低的灰度级则用较长的比特数来表示,从而使像素平均比特数达到最小, 达到对图像进行编码压缩的目的。霍夫曼编码可以采取静态和动态两种方式对数字图像进行 编码,静态霍夫曼编码用二叉树在编码前根据可能的字符出现概率表预先建立,动态霍夫曼 编码则是在编码的过程中根据对图像字符实际出现的概率来动态建立编码树。根据仙农第一 定理,霍夫曼编码对于固定的 n 值是可以达到最优的。 霍夫曼编码的具体步骤归纳如下: z 概率统计,得到 n 个不同概率的信息符号。 z 将 n 个信源信息符号的 n 个概率,按概率大小排序。 z 将 n 个概率中,最后两个小概率相加,这时概率个数减为 n-1 个。 z 将 n-1 个概率,按大小重新排列。 z 重复步骤 3),将新排列后的最后两个小概率再相加,相加和与其余概率再排序。 z 如此反复重复 n-2 次,得到只剩两个概率的序列。 z 以二进制码元(0,1)赋值,构成霍夫曼码字,编码结束。 霍夫曼码字长度和信息符号出现的概率大小次序正好相反,即大概率信息符号分配码字长度 短,小概率信息符号分配码字长度长。 假定某图像共有 6 个灰度级别 S1,S2,S3,S4,S5 和 S6,在图像中出现的概率依次为 0.03,0.07,0.05,0.40,0.25 和 0.20。虽然总的原则是对于出现概率大的灰度级用较短 49/56 的比特来表示,出现概率相对小得灰度级可用较长的比特来表示,但也要遵循一定的规则, 不能随意编码,否则将造成解码上的混乱,比如如果对出现概率最大的 S4 用 01 表示,出现 概率较小的 S1 用 0101 表示,那么在解码时如果出现 010101 的编码,则将无法断定到底应 将序列解码为 S4S4S4,S4S1 还是 S1S4。因此在编码时必须要确保任意两个码字之间的前几 位有所不同,这也是编码序列能正确解码的必要条件。 霍夫曼编码首先根据灰度级的出现概率对各灰度级进行一次排序。然后将排序过的序列 中概率最小的两个元素相加,作为一个新元素取代原序列中概率最小的那两个元素,在形成 的新序列中继续对其进行排序。如果此时序列中的元素个数小于 2,那么就继续重复上一步 操作,直至最终使序列元素只剩一个值为 1 的元素。之所以要进行这样的迭代排序合并,其 目的只有一个:从数量上对信源符号进行缩减。 在经过迭代排序合并操作得到最终值为 1 的序列元素后,可以将该元素作为根节点,按 照前述过程的逆过程构造出一个二值树。 该二值树共有 5 个交汇节点,每一个交汇节点下的两个子节点均是序列在上一次排序后 概率最小的两个元素。在建立二叉树从根节点开始向下延伸时,按照次序将大值放在左节点, 小值放于右节点,并分别为左、右节点标上 0、1 标识符。最后,把从根节点到最末端叶子 节点沿途经历的所有 0、1 符号串接起来,便得到了对各叶子节点的编码。 一旦获取了霍夫曼编码,编码或解码就可以通过简单的查表的方式来对图像进行编码压 缩或解压缩处理了。 根据以上对霍夫曼编码算法的分析,可将数字图像的霍夫曼编码实现过程分为 4 个部 分:对图像灰度分布概率密度进行统计、对统计结果进行迭代排序合并、建立编码二叉树以 及对 DIB 位图编码。 1、在菜单中或者工具栏中按下“打开”文件按钮,即弹出一个文件选择对话框,选择一图 像文件,点击“确定”按钮后即可显示该图像。 2、将彩色图像转换成灰度图像并对其进行哈夫曼编码 在菜单中点击“编辑”按钮,按下“哈夫曼编码”按钮,即执行哈夫曼编码操作同时将 编码结果以文件的形式保存下来。 (三)实验步骤 1、增加操作按钮,如在菜单“编辑”中添加“哈夫曼编码”按钮,ID 设为 ID_Huffman, 标题设为“哈夫曼编码”;利用向导添加该按钮的消息映射即事件响应,使得按下该按钮就 能执行一函数,如:OnHuffman (),可在程序视图类中找到 OnHuffman 函数,具体如下: void CImageShowView::OnHuffman() { CImageShowDoc* pDoc = GetDocument(); 50/56 HDIB hDIB = pDoc->m_hDIB; if( hDIB!=NULL ) { // 在这里加代码把彩色图转换成灰度图 LONG i,j,k; //灰度统计数 int nNs[256]; //灰度概率分布 float fPs[256]; //映射关系 int iMap[256]; //霍夫曼编码 CString m_strCode [256]; //变量初始化 memset (nNs, 0, sizeof (nNs)); // 指向 DIB 的指针 LPSTR lpDIB; // 指向 DIB 象素指针 LPSTR lpDIBBits; // 锁定 DIB lpDIB = (LPSTR) ::GlobalLock((HGLOBAL) hDIB); // 找到 DIB 图像象素起始位置 lpDIBBits = ::FindDIBBits(lpDIB); // 找到 DIB 图像象素起始位置 lpDIBBits = FindDIBBits(lpDIB); // 判断是否是 24-bpp 位图 if (DIBBitCount(lpDIB) != 24) { // 提示用户 MessageBox("请先将其转换为 24 位色位图,再进行处理!", "系统提示" , MB_ICONINFORMATION | MB_OK); // 解除锁定 ::GlobalUnlock((HGLOBAL) hDIB); } ////////////////////////////////////////////////////////// // 计算灰度概率分布 // DIB 的宽度 LONG lWidth = DIBWidth(lpDIB); // DIB 的高度 LONG lHeight = DIBHeight(lpDIB); // 计算图像每行的字节数 LONG lLineBytes = WIDTHBYTES(lWidth * 24); // 对各像素进行灰度分布统计 for (i = 0; i < lHeight; i ++) 51/56 { for (j = 0; j < lLineBytes; j ++) { // 对各像素进行灰度统计 unsigned char V = *((unsigned char *)lpDIBBits + lLineBytes * i + j); nNs[V]++; } } // 计算灰度分布密度 for(i = 0; i < 256; i++) fPs[i] = nNs[i] / (lHeight * lLineBytes * 1.0f); // 初始化 for (i = 0; i < 256; i ++) iMap[i] = i; ////////////////////////////////////////////////////////// // 用冒泡法对 fPs[]进行排序 for (j = 0; j < 256 - 1; j ++) { for (i = 0; i < 256 - j - 1; i ++) { if (fPs[i] > fPs[i + 1]) { // 互换 float fTemp = fPs[i]; fPs[i] = fPs[i + 1]; fPs[i + 1] = fTemp; // 更新映射关系 for (k = 0; k < 256; k ++) { // 判断是否是 fPs[i]的子节点 if (iMap[k] == i) { // 映射到节点 i+1 iMap[k] = i + 1; } else if (iMap[k] == i + 1) { // 映射到节点 i iMap[k] = i; } } } } } 52/56 ////////////////////////////////////////////////////////// // 计算哈夫曼编码表 for (i = 0; i < 256 - 1; i ++) { // 寻找第一个不为 0 的概率灰度级 if (fPs[i] > 0) break; } // 开始编码 for (i = i; i < 256 - 1; i ++) { // 更新 m_strCode for (k = 0; k < 256; k ++) { // 判断是否是 fPs[i]的子节点并编码字符串 ///////////////////////////////////////////////////////////////////////////////////////// if (iMap[k] == i) m_strCode[k] = "0" + m_strCode[k]; else if (iMap[k] == i + 1) m_strCode[k] = "1" + m_strCode[k]; //////////////////////////////////////////////////////////////////////////////////////// } // 概率最小的两个概率相加,结果保存到 fPs[i + 1] fPs[i + 1] += fPs[i]; // 改变映射关系 for (k = 0; k < 256; k ++) { // 判断是否是 fPs[i]的子节点 if (iMap[k] == i) { // 映射到节点 i+1 iMap[k] = i + 1; } } // 重新排序 for (j = i + 1; j < 256 - 1; j ++) { if (fPs[j] > fPs[j + 1]) { // 互换 float fTemp = fPs[j]; fPs[j] = fPs[j + 1]; fPs[j + 1] = fTemp; 53/56 // 更新映射关系 for (k = 0; k < 256; k ++) { // 判断是否是 fPs[i]的子节点 if (iMap[k] == j) { // 映射到节点 j+1 iMap[k] = j + 1; } else if (iMap[k] == j + 1) { // 映射到节点 j iMap[k] = j; } } } else { // 退出循环 break; } } } ////////////////////////////// //对 DIB 进行编码压缩 //位掩码 unsigned char Mask[8]={128, 64, 32, 16, 8, 4, 2, 1}; CString strTemp=""; //打开文件 CFile file; pDoc->m_filePathName=pDoc->m_filePathName+"1"; file.Open( pDoc->m_filePathName,CFile::modeCreate|CFile::modeWrite,NULL ); // 对各像素进行编码 for (i = 0; i < lHeight; i ++) { for (j = 0; j < lLineBytes; j ++) { unsigned char V = *((unsigned char *)lpDIBBits + lLineBytes * i + j); strTemp += m_strCode[V]; int len = strTemp.GetLength(); int loop = 0; // 保存编码后的数据 do{ unsigned char T = 0; 54/56 for (k = 0; k < min(8, strTemp.GetLength()); k++) { if (strTemp.Mid(k, 1) == "1") T |= Mask[k]; } file.Write(&T, 1); loop++; len -= 8; if (strTemp.GetLength() < 8) break; else strTemp = strTemp.Right(strTemp.GetLength() - 8); }while (len >= 8); } } //关闭文件 file.Close(); // 解除锁定 ::GlobalUnlock((HGLOBAL) hDIB); } } 2、编译、链接(F7)及运行(CTRL+F5),打开一 BMP 图像 lena.bmp,运行哈夫曼编码 操作,观察生成的哈夫曼编码文件 lena.bmp1。 (四)实验结果 1、打开一图像文件 lena.bmp,显示如图: 2、对上述图像进行哈夫曼编码,在当前目录下生成哈夫曼编码文件 lena.bmp1,如图: 55/56 (五)思考 1、参照教材编写程序将 lena.bmp 图像文件进行游程编码并输出编码文件。 56/56 参考书 教 材:《数字图像处理学-Visual C++实现》 郎锐 编写 北京希望电子出版社 2003.1 参考书:1.《图像工程:图像处理和分析》 章毓晋 编著 清华大学出版社 2.《MATLAB 应用图像处理》 徐飞 等编著 西安电子科技大学出版社 3.《数字图像模式识别技术及工程实践》 求是科技 等编著 人民邮电出版社 4.《数字图像获取处理及实践应用》 杨枝灵 等编著 人民邮电出版社
还剩55页未读

继续阅读

下载pdf到电脑,查找使用更方便

pdf的实际排版效果,会与网站的显示效果略有不同!!

需要 8 金币 [ 分享pdf获得金币 ] 0 人已下载

下载pdf

pdf贡献者

nb44

贡献于2012-10-31

下载需要 8 金币 [金币充值 ]
亲,您也可以通过 分享原创pdf 来获得金币奖励!
下载pdf