资源预览内容
第1页 / 共27页
第2页 / 共27页
第3页 / 共27页
第4页 / 共27页
第5页 / 共27页
第6页 / 共27页
第7页 / 共27页
第8页 / 共27页
第9页 / 共27页
第10页 / 共27页
亲,该文档总共27页,到这儿已超出免费预览范围,如果喜欢就下载吧!
资源描述
VC 数字图像处理编程讲座之一 1 / 27 整理自天极网的 VC 数字图像处理编程讲座 前 言 数字图像处理技术与理论是计算机应用的一个重要领域, 许多工程应用都涉及到图像处理, 一直有一个强烈的愿望, 想系统的写一个关于数字图像处理的讲座, 由于工作学习很忙,时至今日才得以实现。 “图”是物体透射光或反射光的分布, “像”是人的视觉系统对图的接收在大脑中形成的印象或认识。图像是两者的结合。人类获取外界信息是靠听觉、视觉、触觉、嗅觉、味觉等,但绝大部分(约 80%左右)来自视觉所接收的图像信息。图像处理就是对图像信息进行加工处理,以满足人的视觉心理和实际应用的需要。简单的说,依靠计算机对图像进行各种目的的处理我们就称之为数字图像处理。 早期的数字图像处理的目的是以人为对象, 为了满足人的视觉效果而改善图像的质量, 处理过程中输入的是质量差的图像, 输出的是质量好的图像,常用的图像处理方法有图像增强、复原等。随着计算机技术的发展,有一类图像处理是以机器为对象,处理的目的是使机器能够自动识别目标,这称之为图像的识别,因为这其中要牵涉到一些复杂的模式识别的理论, 所以我们后续的讲座只讨论其中最基本的内容。 由于在许多实际应用的编程中往往都要涉及到数字图像处理, 涉及到其中的一些算法, 这也是许多编程爱好者感兴趣的一个内容, 我们这个讲座就是讨论如何利用微软的 Visual C+开发工具来实现一些常用的数字图像处理算法,论述了图像处理的理论,同时给出了 VC 实现的源代码。本讲座主要的内容分为基础篇、中级篇和高级篇,具体包含的主要内容有: 1 图像文件的格式; 2 图像编程的基础-操作调色板; 3 图像数据的读取、存储和显示、如何获取图像的尺寸等; 4 利用图像来美化界面; 5 图像的基本操作:图像移动、图像旋转、图像镜像、图像的缩放、图像的剪切板操作; 6 图像显示的各种特技效果; 7 图像的基本处理:图像的二值化、图像的亮度和对比度的调整、图像的边缘增强、如何得到图像的直方图、图像直方图的修正、图像的平滑、图像的锐化等、图像的伪彩色、彩色图像转换为黑白图像、物体边缘的搜索等等; 8 二值图像的处理:腐蚀、膨胀、细化、距离变换等; 9 图像分析:直线、圆、特定物体的识别; 10JEPG、GIF、PCX 等格式文件相关操作; 11图像文件格式的转换; 12图像的常用变换:付利叶变换、DCT 变换、沃尔什变换等; 13AVI 视频流的操作; 图像处理技术博大精深, 不仅需要有很强的数学功底, 还需要熟练掌握一门计算机语言,在当前流行的语言中,我个人觉的 Visual C+这个开发平台是图像开发人员的首选工具。本讲座只是起到抛砖引玉的作用,希望和广大读者共同交流。 第一节 图像的文件格式 要利用计算机对数字化图像进行处理, 首先要对图像的文件格式要有清楚的认识, 因为我们前面说过,自然界的图像以模拟信号的形式存在,在用计算机进行处理以前,首先要数字化,比如摄像头(CCD)摄取的信号在送往计算机处理前,一般情况下要经过数模转换,这个任务常常由图像采集卡完成, 它的输出一般为裸图的形式; 如果用户想要生成目标图像文件,必须根据文件的格式做相应的处理。随着科技的发展,数码像机、数码摄像机已经进VC 数字图像处理编程讲座之一 2 / 27 入寻常百姓家, 我们可以利用这些设备作为图像处理系统的输入设备来为后续的图像处理提供信息源。无论是什么设备,它总是提供按一定的图像文件格式来提供信息,比较常用的有BMP 格式、JPEG 格式、GIF 格式等等,所以我们在进行图像处理以前,首先要对图像的格式要有清晰的认识,只有在此基础上才可以进行进一步的开发处理。 在讲述图像文件格式前,先对图像作一个简单的分类。除了最简单的图像外,所有的图像都有颜色, 而单色图像则是带有颜色的图像中比较简单的格式, 它一般由黑色区域和白色区域组成,可以用一个比特表示一个像素,“1”表示黑色,“0”表示白色,当然也可以倒过来表示,这种图像称之为二值图像。我们也可以用 8 个比特(一个字节)表示一个像素,相当于把黑和白等分为 256 个级别,“0”表示为黑,“255”表示为白,该字节的数值表示相应像素值的灰度值或亮度值,数值越接近“0”,对应像素点越黑,相反,则对应像素点越白,此种图像我们一般称之为灰度图像。 单色图像和灰度图像又统称为黑白图像, 与之对应存在着彩色图像,这种图像要复杂一些,表示图像时,常用的图像彩色模式有 RGB 模式、CMYK 模式和 HIS 模式,一般情况下我们只使用 RGB 模式,R 对应红色,G 对应绿色,B 对应蓝色,它们统称为三基色,这三种色彩的不同搭配,就可以搭配成各种现实中的色彩,此时彩色图像的每一个像素都需要 3 个样本组成的一组数据表示, 其中每个样本用于表示该像素的一个基本颜色。 对于现存的所有的图像文件格式,我们在这里主要介绍 BMP 图像文件格式,并且文件里的图像数据是未压缩的, 因为图像的数字化处理主要是对图像中的各个像素进行相应的处理,而未压缩的 BMP 图像中的像素数值正好与实际要处理的数字图像相对应,这种格式的文件最合适我们对之进行数字化处理。 请读者记住, 压缩过的图像是无法直接进行数字化处理的,如 JPEG、GIF 等格式的文件,此时首先要对图像文件解压缩,这就要涉及到一些比较复杂的压缩算法。后续章节中我们将针对特殊的文件格式如何转换为 BMP 格式的文件问题作专门的论述, 经过转换, 我们就可以利用得到的未压缩的 BMP 文件格式进行后续处理。对于 JPEG、GIF 等格式,由于涉及到压缩算法,这要求读者掌握一定的信息论方面的知识,如果展开的话,可以写一本书,限于篇幅原因,我们只作一般性的讲解,有兴趣的朋友可以参考相关书籍资料。 一、BMP 文件结构 1. BMP 文件组成 BMP 文件由文件头、位图信息头、颜色信息和图形数据四部分组成。文件头主要包含文件的大小、文件类型、图像数据偏离文件头的长度等信息;位图信息头包含图象的尺寸信息、图像用几个比特数值来表示一个像素、图像是否压缩、图像所用的颜色数等信息。颜色信息包含图像所用到的颜色表, 显示图像时需用到这个颜色表来生成调色板, 但如果图像为真彩色,既图像的每个像素用 24 个比特来表示,文件中就没有这一块信息,也就不需要操作调色板。文件中的数据块表示图像的相应的像素值,需要注意的是:图像的像素值在文件中的存放顺序为从左到右,从下到上,也就是说,在 BMP 文件中首先存放的是图像的最后一行像素,最后才存储图像的第一行像素,但对与同一行的像素,则是按照先左边后右边的的顺序存储的;另外一个需要读者朋友关注的细节是:文件存储图像的每一行像素值时,如果存储该行像素值所占的字节数为 4 的倍数,则正常存储,否则,需要在后端补 0,凑足 4的倍数。 2. BMP 文件头 BMP 文件头数据结构含有 BMP 文件的类型、 文件大小和位图起始位置等信息。 其结构定义如下: typedef struct tagBITMAP VC 数字图像处理编程讲座之一 3 / 27 WORD bfType; / 位图文件的类型,必须为“BM” DWORD bfSize; / 位图文件的大小,以字节为单位 WORD bfReserved1; / 位图文件保留字,必须为 0 WORD bfReserved2; / 位图文件保留字,必须为 0 DWORD bfOffBits; / 位图数据的起始位置,以相对于位图文件头的偏移量表示,以字节为单位 BITMAP;该结构占据 14 个字节。 3. 位图信息头 BMP 位图信息头数据用于说明位图的尺寸等信息。其结构如下: typedef struct tagBITMAPINFOHEADER DWORD biSize; / 本结构所占用字节数 LONG biWidth; / 位图的宽度,以像素为单位 LONG biHeight; / 位图的高度,以像素为单位 WORD biPlanes; / 目标设备的平面数不清,必须为 1 WORD biBitCount/ 每个像素所需的位数, 必须是 1(双色), 4(16 色), 8(256 色)或 24(真彩色)之一 DWORD biCompression; / 位图压缩类型,必须是 0(不压缩),1(BI_RLE8 压缩类型)或2(BI_RLE4 压缩类型)之一 DWORD biSizeImage; / 位图的大小,以字节为单位 LONG biXPelsPerMeter; / 位图水平分辨率,每米像素数 LONG biYPelsPerMeter; / 位图垂直分辨率,每米像素数 DWORD biClrUsed;/ 位图实际使用的颜色表中的颜色数 DWORD biClrImportant;/ 位图显示过程中重要的颜色数 BITMAPINFOHEADER;该结构占据 40 个字节。 注意:对于 BMP 文件格式,在处理单色图像和真彩色图像的时候,无论图象数据多么庞大,都不对图象数据进行任何压缩处理,一般情况下,如果位图采用压缩格式,那么 16色图像采用 RLE4 压缩算法,256 色图像采用 RLE8 压缩算法。 4. 颜色表 颜色表用于说明位图中的颜色,它有若干个表项,每一个表项是一个 RGBQUAD 类型的结构,定义一种颜色。RGBQUAD 结构的定义如下: typedef struct tagRGBQUAD BYTErgbBlue;/ 蓝色的亮度(值范围为 0-255) BYTErgbGreen; / 绿色的亮度(值范围为 0-255) BYTErgbRed; / 红色的亮度(值范围为 0-255) BYTErgbReserved;/ 保留,必须为 0 RGBQUAD; 颜色表中 RGBQUAD 结构数据的个数由 BITMAPINFOHEADER 中的 biBitCount 项来确定,当 biBitCount=1,4,8 时,分别有 2,16,256 个颜色表项,当 biBitCount=24 时,图像为真彩色,图像中每个像素的颜色用三个字节表示,分别对应 R、G、B 值,图像文件没有颜色表项。位图信息头和颜色表组成位图信息,BITMAPINFO 结构定义如下: typedef struct tagBITMAPINFO BITMAPINFOHEADER bmiHeader; / 位图信息头 RGBQUAD bmiColors1; / 颜色表 VC 数字图像处理编程讲座之一 4 / 27 BITMAPINFO; 注意:RGBQUAD 数据结构中,增加了一个保留字段 rgbReserved,它不代表任何颜色,必须取固定的值为“0”,同时,RGBQUAD 结构中定义的颜色值中,红色、绿色和蓝色的排列顺序与一般真彩色图像文件的颜色数据排列顺序恰好相反, 既: 若某个位图中的一个像素点的颜色的描述为“00,00,ff,00”,则表示该点为红色,而不是蓝色。 5. 位图数据 位图数据记录了位图的每一个像素值或该对应像素的颜色表的索引值, 图像记录顺序是在扫描行内是从左到右,扫描行之间是从下到上。这种格式我们又称为 Bottom_Up 位图,当然与之相对的还有 Up_Down 形式的位图,它的记录顺序是从上到下的,对于这种形式的位图,也不存在压缩形式。位图的一个像素值所占的字节数:当 biBitCount=1(每个像素所需要的位数) 时, 8 个像素占 1 个字节; 当 biBitCount=4 时, 2 个像素占 1 个字节; 当 biBitCount=8时,1 个像素占 1 个字节;当 biBitCount=24 时,1 个像素占 3 个字节,此时图像为真彩色图像。当图像不是为真彩色时,图像文件中包含颜色表,位图的数据表示对应像素点在颜色表中相应的索引值,当为真彩色时,每一个像素用三个字节表示图像相应像素点彩色值,每个字节分别对应 R、G、B 分量的值,这时候图像文件中没有颜色表。上面我已经讲过了,Windows 规定图像文件中一个扫描行所占的字节数必须是 4 的倍数(即以字为单位),不足的以 0 填充,图像文件中一个扫描行所占的字节数计算方法: DataSizePerLine= (biWidth* biBitCount+31)/8;/ 一个扫描行所占的字节数 ? 位图数据的大小按下式计算(不压缩情况下): DataSize= DataSizePerLine* biHeight。 上述是 BMP 文件格式的说明,搞清楚了以上的结构,就可以正确的操作图像文件,对它进行读或写操作了。 三、JEPG 图像文件 JEPG 简称为联合摄影专家小组, 作为一种技术, 主要用于数字化图像的标准编码, JPEG主要采用有损的压缩编码方式,它比 GIF、BMP 图像文件要复杂的多,这不是短短的几页篇幅可以将清楚的,万幸的是,我们可以通过一些别的方法将该格式转化为 BMP 格式。读者需要知道的是在对 JEPG 文件格式编码时, 通常需要分为以下四步: 颜色转化、 DCT 变换、量化、编码。 以上介绍了一些常用的图像文件,对比较复杂的格式,如 GIF 和 JEPG,仅仅作了极其浮浅的介绍,后文我们会和它们作进一步的接触。实际应用中,还有许多图像格式,文章中都没有提到,读者如果需要做进一步的研究,还需要参考一些关于图像格式方面的资料。 BMP 图像的基本操作 上一讲我们主要介绍了图像的格式,其中重点说明了 BMP 文件的存储格式,同时对JEPG 和 GIF 等常用格式作了简单的介绍。本节主要讲述如何操作 BMP 文件,如对其读、写和显示等。 在实现数字图象处理的过程中, 主要是通过对图像中的每一个像素点运用各种图像处理算法来达到预期的效果,所以进行图像处理的第一步,也是我们最关心的问题,是如何得到图像中每一个像素点的亮度值; 为了观察和验证处理的图像效果, 另一个需要解决的问题是如何将处理前后的图像正确的显示出来。我们这章内容就是解决这些问题。 随着科技的发展,图像处理技术已经渗透到人类生活的各个领域并得到越来越多的应用, 但是突出的一个矛盾是图像的格式也是越来越多, 目前图像处理所涉及的主要的图像格式就有很多种,如 TIF、JEMP、BMP 等等,一般情况下,为了处理简单方便,进行数字图像处理所采用的都是 BMP 格式的图像文件(有时也称为 DIB 格式的图像文件) ,并且这种格式的文件是没有压缩的。 我们通过操作这种格式的文件, 可以获取正确显示图像所需的调VC 数字图像处理编程讲座之一 5 / 27 色板信息,图像的尺寸信息,图像中各个像素点的亮度信息等等,有了这些数据,开发人员就可以对图像施加各种处理算法, 进行相应的处理。 如果特殊情况下需要处理其它某种格式的图像,如 GIF、JEMP 等格式的图象文件,可以首先将该格式转换为 BMP 格式,然后再进行相应的处理。这一点需要读者清楚。 BMP 格式的图像文件又可以分为许多种类,如真彩色位图、256 色位图,采用 RLE(游程编码)压缩格式的 BMP 位图等等。 由于在实际的工程应用和图像算法效果验证中经常要处理的是 256 级并且是没有压缩的 BMP 灰度图像,例如通过黑白采集卡采集得到的图像就是这种格式,所以我们在整个讲座中范例所处理的文件格式都是 BMP 灰度图像。如果读者对这种格式的位图能够作到熟练的操作, 那么对于其余形式的 BMP 位图的操作也不会很困难。 BMP 灰度图像作为 Windows 环境下主要的图像格式之一,以其格式简单,适应性强而倍受欢迎。正如我们在上一讲中介绍过的那样,这种文件格式就是每一个像素用 8bit 表示,显示出来的图像是黑白效果,最黑的像素的灰度(也叫作亮度)值为“0”,最白的像素的灰度值为“255”,整个图像各个像素的灰度值随机的分布在“0”到“255”的区间中,越黑的像素,其灰度值越接近于“0”,越白(既越亮)的像素,其灰度值越接近于“255”;与此对应的是在该文件类型中的颜色表项的各个 RGB 分量值是相等的,并且颜色表项的数目是 256 个。 在进行图像处理时, 操作图像中的像素值就要得到图像阵列; 经过处理后的图像的像素值需要存储起来;显示图像时要正确实现调色板、得到位图的尺寸信息等。结合这些问题,下面我们针对性的给出了操作灰度 BMP 图像时的部分函数实现代码及注释。 一、 BMP 位图操作 首先我们回顾一下上讲中的重要信息:BMP 位图包括位图文件头结构 BITMAP、位图信息头结构 BITMAPINFOHEADER、位图颜色表 RGBQUAD 和位图像素数据四部分。处理位图时要根据文件的这些结构得到位图文件大小、位图的宽、高、实现调色板、得到位图像素值等等。这里要注意的一点是在 BMP 位图中,位图的每行像素值要填充到一个四字节边界,即位图每行所占的存储长度为四字节的倍数,不足时将多余位用 0 填充。 有了上述知识,可以开始编写图像处理的程序了,关于在 VC 的开发平台上如何开发程序的问题这里不再赘述,笔者假定读者都具有一定的 VC 开发经验。在开发该图像处理程序的过程中,笔者没有采用面向对象的方法,虽然面向对象的方法可以将数据封装起来,保护类中的数据不受外界的干扰, 提高数据的安全性, 但是这种安全性是以降低程序的执行效率为代价的,为此,我们充分利用了程序的文档视图结构,在程序中直接使用了一些 API 函数来操作图像。在微软的 MSDN 中有一个名为 Diblook(查阅)的例子,该例子演示了如何操作 Dib 位图,有兴趣的读者可以参考一下,相信一定会有所收获。 启动 Visual C+,生成一个名为 Dib 的多文档程序,将 CDibView 类的基类设为CscrollView 类,这样作的目的是为了在显示位图时支持滚动条,另外在处理图像应用程序的文档类(CDibDoc.h)中声明如下宏及公有变量: #define WIDTHBYTES(bits) (bits) + 31) / 32 * 4)/计算图像每行象素所占的字节数目; HANDLE m_hDIB;/存放位图数据的句柄; CPalette* m_palDIB;/指向调色板 Cpalette 类的指针; CSize m_sizeDoc;/初始化视图的尺寸,该尺寸为位图的尺寸; 最后将程序的字符串表中的字符串资源 IDR_DibTYPE 修改为:“nDibnDibnDib Files(*.bmp;*.dib)n.bmpnDib.DocumentnDib Document”。这样作的目的是为了在程序文件对话框中可以选择 BMP 或 DIB 格式的位图文件。 1、 读取灰度 BMP 位图 可以根据 BMP 位图文件的结构,操作 BMP 位图文件并读入图像数据,为此我们充分利用了 VC 的文档视图结构,重载了文挡类的 OnOpenDocument()函数,这样用户就可以VC 数字图像处理编程讲座之一 6 / 27 在自动生成程序的打开文件对话框中选择所要打开的位图文件, 然后程序将自动调用该函数执行读取数据的操作。该函数的实现代码如下所示: BOOL CDibDoc:OnOpenDocument(LPCTSTR lpszPathName) LOGPALETTE *pPal;/定义逻辑调色板指针; pPal=new LOGPALETTE;/初始化该指针; C; C fe; if (!(lpszPathName, C | C, &fe) /以“读”的方式打开文件; AfxMessageBox(图像文件打不开!); return FALSE; DeleteContents();/删除文挡; BeginWaitCursor(); BITMAP bmfHeader;/定义位图文件头结构; LPBITMAPINFO lpbmi; DWORD dwBitsSize; HANDLE hDIB; LPSTR pDIB;/指向位图数据的指针; BITMAPINFOHEADER *bmhdr;/指向位图信息头结构的指针 dwBitsSize = ();/得到文件长度 if (LPSTR)&bmfHeader, sizeof(bmfHeader) !=sizeof(bmfHeader) return FALSE;/读取位图文件的文件头结构信息; if (bmfHeader.bfType != 0x4d42) /检查该文件是否为 BMP 格式的文件; return FALSE; hDIB=(HANDLE) :GlobalAlloc(GMEM_MOVEABLE | GMEM_ZEROINIT, dwBitsSize); /为读取图像文件数据申请缓冲区 if (hDIB = 0) return FALSE; pDIB = (LPSTR) :GlobalLock(HGLOBAL)hDIB); /得到申请的缓冲区的指针; if (pDIB, dwBitsSize - sizeof(BITMAP) != dwBitsSize - sizeof(BITMAP) ) :GlobalUnlock(HGLOBAL)hDIB); hDIB=NULL; return FALSE; /此时 pDIB 数据块中读取的数据包括位图头信息、位图颜色表、图像像素的灰度值; bmhdr=(BITMAPINFOHEADER*)pDIB;/为指向位图信息头结构的指针赋值; :GlobalUnlock(HGLOBAL)hDIB); if (*bmhdr).biBitCount!=8)/验证是否为 8bit 位图 VC 数字图像处理编程讲座之一 7 / 27 AfxMessageBox(该文件不是灰度位图格式!); return FALSE; m_hDIB=hDIB;/将内部变量数据赋于全局变量; /下面是记录位图的尺寸; m_sizeDoc.x=bmhdr-biWidth; m_sizeDoc.y=bmhdr-biHeight; /下面是根据颜色表生成调色板; m_palDIB=new Cpalette; pPal-palVersion=0x300;/填充逻辑颜色表 pPal-palNumEntries=256; lpbmi=(LPBITMAPINFO)bmhdr; for(int i=0;ipalPalentryi.peRed=lpbmi-bmiColorsi.rgbRed; pPal-palPalentryi.peGreen=lpbmi-bmiColorsi.rgbGreen; pPal-palPalentryi.peBlue= lpbmi-bmiColorsi.rgbBlue; pPal-palPalentryi.peFlags=0; m_palDIB-CreatePalette(pPal); /根据读入的数据得到位图的宽、高、颜色表; if(pPal) delete pPal; EndWaitCursor(); SetPathName(lpszPathName);/设置存储路径 SetModifiedFlag(FALSE); / 设置文件修改标志为 FALSE return TRUE; 上面的方法是通过 CFile 类对象的操作来读取位图文件的,它需要分析位图中的文件头信息,从而确定需要读取的图像长度。这种方法相对来说有些繁琐,其实还可以以一种相对简单的方法读取位图数据,首先在程序的资源中定义 DIB 类型资源,然后添加位图到该类型中, 将图像数据以资源的形式读取出来, 这时候就可以根据所获取的数据中的位图信息结构来获取、显示图像数据了。下面的函数实现了以资源形式装载图像文件数据,该函数的实现代码如下所示: / HANDLE LoadDIB(UINT uIDS, LPCSTR lpszDibType) LPCSTR lpszDibRes =MAKEINTRESOURCE(uIDS);/根据资源标志符确定资源的名字; HINSTANCE hInst=AfxGetInstanceHandle();/得到应用程序的句柄; HRSRC hRes=: :FindResource(hInst,lpszDibRes, lpszDibType);/获取资源的句柄,这里lpszDibType 为资源的名字“DIB”; If(hRes=NULL) return NULL VC 数字图像处理编程讲座之一 8 / 27 HGLOBAL hData=: :LoadResource(hInst, hRes);/转载资源数据并返回该句柄; return hData; 2、 灰度位图数据的存储 为了将图像处理后所得到的像素值保存起来, 我们重载了文档类的 OnSaveDocument ()函数, 这样用户在点击 Save 或 SaveAs 子菜单后程序自动调用该函数, 实现图像数据的存储。该函数的具体实现如下: / BOOL CDibDoc:OnSaveDocument(LPCTSTR lpszPathName) C; C fe; BITMAP bmfHdr; / 位图文件头结构; LPBITMAPINFOHEADER lpBI;/指向位图头信息结构的指针; DWORD dwDIBSize;; if (!(lpszPathName, C |C | C, &fe) AfxMessageBox(文件打不开); return FALSE; /以读写的方式打开文件; BOOL bSuccess = FALSE; BeginWaitCursor(); lpBI = (LPBITMAPINFOHEADER) :GlobalLock(HGLOBAL) m_hDIB); if (lpBI = NULL) return FALSE; dwDIBSize = *(LPDWORD)lpBI + 256*sizeof(RGBQUAD); /图像的文件信息所占用的字节数; DWORD dwBmBitsSize;/BMP 文件中位图的像素所占的字节数 dwBmBitsSize=WIDTHBYTES(lpBI-biWidth)*(DWORD)lpBI-biBitCount) *lpBI-biHeight;/ 存储时位图所有像素所占的总字节数 dwDIBSize += dwBmBitsSize; /BMP 文件除文件信息结构外的所有数据占用的总字节数; lpBI-biSizeImage = dwBmBitsSize; / 位图所有像素所占的总字节数 /以下五句为文件头结构填充值 bmfHdr.bfType =0x4d42; / 文件为BMP类型 bmfHdr.bfSize = dwDIBSize + sizeof(BITMAP);/文件总长度 bmfHdr.bfReserved1 = 0; bmfHdr.bfReserved2 = 0; bmfHdr.bfOffBits = (DWORD)sizeof(BITMAP) + lpBI-biSize + 256*sizeof(RGBQUAD); /位图数据距离文件头的偏移量; (LPSTR)&bmfHdr, sizeof(BITMAP);/向文件中写文件头信息; (lpBI, dwDIBSize); /将位图信息(信息头结构、颜色表、像素数据)写入文件; :GlobalUnlock(HGLOBAL) m_hDIB); VC 数字图像处理编程讲座之一 9 / 27 EndWaitCursor(); SetModifiedFlag(FALSE); / 将文档设为“干净”标志,表示此后文档不需要存盘提示; return TRUE; 二、 调色板的操作 通过上面的操作, 我们已经可以获取图像中的数据了, 现在的又一个问题是如何在窗口中显示出图像数据。灰度图像要正确显示,必须实现逻辑调色板和系统调色板。首先我们介绍一下逻辑调色板结构 LOGPALETTE,该结构定义如下: typedef struct tagLOGPALETTE WORD palVersion;/调色板的板本号,应该指定该值为 0x300; WORD palNumEntries;/调色板中的表项数,对于灰度图像该值为 256; PALETEENTRY palPalEntry1;/调色板中的颜色表项,由于该表项的数目不一定,所以这里数组长度定义为 1,灰度图像对应的该数组的长度为 256; LOGPALETTE; 颜色表项结构 PALETTEENTRY 定义了调色板中的每一个颜色表项的颜色和使用方式, 定义如下: typedef struct tagPALETTEENTRY BYTE peRed; /R 分量值; BYTE peGreen; /G 分量值; BYTE peBlue; /B 分量值; BYTE peFlags; / 该颜色被使用的方式,一般情况下设为“0”; PALETTEENTRY; Windows 系统使用调色板管理器来管理与调色板有关的操作, 通常活动窗口的调色板即是当前系统调色板, 所有的非活动窗口都必须按照此系统调色板来显示自己的颜色, 此时调色板管理器将自动的用系统调色板中的最近似颜色来映射相应的显示颜色。 如果窗口或应用程序按自己的调色板显示颜色, 就必须将自己的调色板载入到系统调色板中, 这种操作叫作实现调色板,实现调色板包括两个步骤,既首先将调色板选择到设备上下文中,然后在设备上下文中实现它。可以通过 CDC: :SelectPalette()、CDC: :RealizePalette()或相应的 API 函数来实现上述的两个步骤。在实现调色板的过程中,通过在主框架类中处理 Windows 定义的消息 WM_QUERYNEWPALETTE 、WM_PALETTECHANGED 及视图类中处理自定义消息 WM_DOREALIZE(该消息在主框架窗口定义如下:#define WM_REALIZEPAL (WM_USER+101))来实现调色板的操作。当系统需要处理调色板的变化时,将向程序的主窗口发送 WM_QUERYNEWPALETTE 、WM_PALETTECHANGED,例如当某一窗口即将激活时,主框架窗口将收到 WM_QUERYNEWPALETTE 消息,通知该窗口将要收到输入焦点,给它一次机会实现其自身的逻辑调色板;当系统调色板改变后,主框架窗口将收到WM_PALETTECHANGED 消息,通知其它窗口系统调色板已经改变,此时每一窗口都应该实现其逻辑调色板,重画客户区。 由于上述的调色板变更消息是发往主框架窗口的, 所以我们只能在主窗口中响应这两个消息,然后由主框架窗口通知各个视窗,使得程序激活时能自动装载自己的调色板。我们定义的用户消息 WM_REALIZEPAL 用于主框架窗口通知视窗它已经收到调色板变更消息, 视窗应该协调其调色板。下面我们给出了各个消息的响应处理函数的具体实现代码和注释: VC 数字图像处理编程讲座之一 10 / 27 / void CMainFrame:OnPaletteChanged(CWnd* pFocusWnd) /总实现活动视的调色板 CMDIFrameWnd:OnPaletteChanged(pFocusWnd); CMDIChildWnd* pMDIChildWnd = MDIGetActive();/得到活动的子窗口指针; if (pMDIChildWnd = NULL) return CView* pView = pMDIChildWnd-GetActiveView();/得到视图的指针; ASSERT(pView != NULL); SendMessageToDescendants(WM_DOREALIZE, (WPARAM)pView-m_hWnd); /通知所有子窗口系统调色板已改变 / BOOL CMainFrame:OnQueryNewPalette()/提供实现系统调色板的机会 / 实现活动视的调色板 CMDIChildWnd* pMDIChildWnd = MDIGetActive();/得到活动的子窗口指针; if (pMDIChildWnd = NULL) return FALSE;/no active MDI child frame (no new palette) CView* pView = pMDIChildWnd-GetActiveView();/得到活动子窗口的视图指针; ASSERT(pView != NULL); /通知活动视图实现系统调色板 pView-SendMessage(WM_DOREALIZE, (WPARAM)pView-m_hWnd); return TRUE; / BOOL CDibView:OnDoRealize(WPARAM wParam, LPARAM)/实现系统调色板 ASSERT(wParam != NULL); CDibDoc* pDoc = GetDocument(); if (pDoc-m_hDIB = NULL) return FALSE; / must be a new document CPalette* pPal = pDoc-m_palDIB; /调色板的颜色表数据在 InitDIBData()函数中实现 if (pPal != NULL) CMainFrame* pAppFrame = (CMainFrame*) AfxGetApp()-m_pMainWnd;/得到程序的主框架指针; ASSERT_KINDOF(CMainFrame, pAppFrame); CClientDC appDC(pAppFrame);/获取主框架的设备上下文; CPalette* oldPalette = appDC.SelectPalette(pPal, (HWND)wParam) != m_hWnd); /只有活动视才可以设为FALSE,即根据活动视的调色板设为前景调色板; if (oldPalette != NULL) VC 数字图像处理编程讲座之一 11 / 27 UINT nColorsChanged = appDC.RealizePalette();/实现系统调色板 if (nColorsChanged 0) pDoc-UpdateAllViews(NULL);/更新视图 appDC.SelectPalette(oldPalette, TRUE); /将原系统调色板置为背景调色板 else TRACE0(“tSelectPalette failed in”); return TRUE; 注:在调用 API 函数显示位图时,不要忘记设置逻辑调色板,即背景调色板,否则位图将无法正确显示,读者可以从后面的显示部分的实现看出我们在显示时实现了逻辑调色板。上述的处理相对来说比较繁琐复杂,可能对于初学者来说也比较难于理解,所以如果我们的程序仅仅限于处理灰度图象, 可以采用另外一种相对简单的办法, 即在文档类的初始化阶段定义一个灰度调色板, 然后在设备上下文中实现它, 这样作的好处是在度取灰度位图时可以不再考虑文件中的颜色表信息, 提高了文件读取速度, 笔者在开发一个基于机器视觉的项目时采用的就是这种方法,取的了比较满意的效果。首先定义一个指向逻辑颜色表结构LOGPALETTE 的指针 pPal,填充该指针,然后将该指针与调色板指针联系起来,该方法的具体实现如下: / CDibDoc:CDibDoc() . LOGPALETTE *Pal; Pal=new LOGPALETTE; m_palDIB=new Cpalette; pPal-palVersion=0x300; pPal-palNumEntries=256; for(int i=0;ipalPalentryi.peRed=i; pPal-palPalentryi.peGreen=i; pPal-palPalentryi.peBlue=i; pPal-palPalentryi.peFlags=0; m_palDIB-CreatePalette(pPal); . 三、 图像的显示 显示 DIB 位图数据可以通过设备上下文 CDC 对象的成员函数 CDC: : Bitblt()或 CDC: :StretchBlt()来实现,也可以通过 API 函数 SetDIBBitsToDevice()或 StretchDIBBits()来实现,函数中具体所用到的各个参数的意义可以参考 MSDN。其中 StretchDIBBits()和 CDC: :VC 数字图像处理编程讲座之一 12 / 27 StretchBlt()可以将图像进行放大和缩小显示。当从文档中装入位图文件时,CDIBView 类的OnInitialUpdate 函数将被调用,因此可以在该函数中实现对视图尺寸的设置,用于正确的显示位图,然后就可以在视图类的 OnDraw()函数中正确的显示位图了。这两个函数的具体实现代码分别如下所示: / void CDIBView: :OnInitialUpdate() CscrollView: :OnInitalUpdate(); CDIBDoc *pDoc=GetDocument(); If(pDoc-m_hDIB=NULL)/如果位图数据为空,设置 m_sizeDoc 的默认尺寸; pDoc-m_sizeDoc.cx=pDoc-m_sizeDoc.cy=100; SetScrollSizes(MM_TEXT,pDoc- m_sizeDoc); / void CDIBView: :OnDraw(CDC *pDC) BITMAPINFOHEADER *lpDIBHdr;/位图信息头结构指针; BYTE *lpDIBBits;/指向位图像素灰度值的指针; BOOL bSuccess=FALSE; CPalette*OldPal=NULL;/调色板指针; HDC hDC=pDC-GetSafeHdc();/获取当前设备上下文的句柄; CDIBDoc *pDoc=GetDocument();/获取活动文档的指针; If(pDoc-m_hDIB =NULL) /判断图像数据是否为空; AfxMessageBox(图像数据不能为空,请首先读取图像数据!); return; lpDIBHdr=( BITMAPINFOHEADER *)GlobalLock(pDoc-m_hDIB);/得到图像的位图头信息 lpDIBBits=lpDIBHdr+sizeof(BITMAPINFOHEADER)+256*sizeof(RGBQUAD);/获取保存图像像素值的缓冲区的指针; if(pDoc- m_palDIB) /如果存在调色板信息,实现逻辑调色板; OldPal=pDC- SelectPalette(pDoc- m_palDIB,TRUE); PDC-RealizePalette(); else AfxMessageBox(图像的调色板数据不能为空,请首先读取调色板信息!); return ; SetStretchBltMode(hDC,COLORONCOLOR); /显示图像 BSuccess=StretchDIBBits(hDC,0,0,pDoc- m_sizeDoc.cx, pDoc- m_sizeDoc.cy, 0, pDoc- m_sizeDoc.cy,0, pDoc- m_sizeDoc.cy, VC 数字图像处理编程讲座之一 13 / 27 lpDIBBits,(LPBITMAPINFO)lpDIBHdr, DIB_RGB_COLORS, SRCCOPY); GlobalUnlock(pDoc-m_hDIB); If(OldPal)/恢复调色板; PDC-SelectPalette(OldPal,FALSE); retrun; 四、 小结 在本期讲座里我们主要介绍了如何操作灰度位图, 它具有较强的代表性, 同时为后续的图像处理编程的学习作了必要的准备工作,经过学习,对于如何操作其它类型的 BMP 格式的图像文件,可以达到举一反三的作用。 BMP 图像显示的特效操作 上期讲座中我们主要讲述了 BMP 图像数据的存取、 图像的显示和调色板的操作等内容,在上面的学习基础上, 我们可以进一步深化, 学习并掌握图像特效显示技术。 有了这种技术,可以用来在今后的项目开发中美化我们的软件界面, 提高软件的视觉效果。 在如今的商业软件中,几乎每一幅图像的显示都采用了图像特效显示,例如读者比较熟悉的 Windows 的屏幕保护程序就采用了各种各样的图像特效显示, 使人感到眼花缭乱和耳目一新。 专业图像处理软件更是提供了丰富的显示方式供用户使用,可以方便的在程序中实现图像的特效显示,如 PhotoShop 、Authorware 等。本节主要介绍如何实现图像的浮雕、雕刻、百页窗、旋转、扫描、栅条、马赛克、和渐显渐隐显示等效果。通过这期讲座的学习,读者朋友们也可以自己动手制作拥有特效显示效果的软件了。 图像的显示我们讲过主要有 BitBlt()、SetDIBitsToDevice()和 StretchDIBits()等函数。需要读者注意的是,在特效显示时,并不是每个显示函数都适宜,BitBlt()函数主要是用来显示设备有关位图(DDB) ,后两个函数用来显示设备无关位图(DIB) 。由于我们讲座里处理的是设备无关位图,所以我们主要关心的是后两个函数的应用,其中 SetDIBitsToDevice()使用起来较死板,远不如 StretchDIBits()用的灵活,并且对大多数的特效显示无能为力,所以为了实现图像的特效显示效果,需要使用 StretchDIBits()函数来显示图像,具体什么原因,我想可能是微软在实现这些函数时使用的方法不同吧。这些函数如何使用,各个参数的含义,可以参考微软的 MSDN。 实现图像的特殊效果的显示的基本思路是要么是操作图像的像素, 要么是对图像分块按一定的方向或次序,分阶段的显示或擦除对应的图像块。对于第二种显示的思路,其中的要点是:1.划分图像块;2.确定图像块的操作次序;3.显示或清除对应的图像块;4.在两个连续显示的图像块之间插入一个固定的延迟。 其中图像块的划分决定了图像的显示方式, 图像块的显示顺序决定了显示的方向和细分的依据。不同的效果决定了不同的分块方法和显示次序, 我们将在后面的各种特效显示中介绍如何分块和决定次序。 为了使图像的显示过程明显的表现出来,实现显示的特效,就需要在图像块的依此显示中插入固定的延迟。也许读者朋友会想到利用 sleep()函数或用 Settime()来实现延迟,由于 Windows 是个基于消息的多任务操作系统, 这些方法所产生的延迟时间对于图像的显示来说是不精确的, 为了实现与机器无关的更精确的时间延迟,可以采用 timeGetTime()函数来产生微秒级的延迟。使用这个函数时为了编译不产生错误,要在连接设置中引入“Winmm.lib”库,并要包含头文件“Mmsystem.h”。这里我们首先给出一个延迟函数,它用来实现固定时间的延迟: void DelayTime(DWORD time) VC 数字图像处理编程讲座之一 14 / 27 DWORD BeginTime ,EndTime; BeginTime=timeGetTime();/得到当前的系统时间、单位为微秒; do EndTime=TimeGetTime();/再次得到当前的系统时间; while(EndTime-BeginTime) m_hDIB;/拷贝存放已经读取的图像文件数据句柄; lpBi=(LPBITMAPINFOHEADER)GlobalLock(HGLOBAL)hdib);/获取图像信息头 pData=(unsigned char*)FindDIBBits(LPSTR)lpBi); /FindDIBBits 是我定义的一个函数、根据图像的结构得到位图的灰度值数据、 pDoc-SetModifiedFlag(TRUE); /设置文档修改标志为“真”、为后续的修改存盘作准备; data1handle=GlobalAlloc(GMEM_SHARE,WIDTHBYTES(lpBi-biWidth*8)*lpBi-biHeight); /声明一个缓冲区用来暂存处理后的图像数据; data=(unsigned char*)GlobalLock(HGLOBAL)data1handle);/得到该缓冲区的指针; AfxGetApp()-BeginWaitCursor(); int i,j,buf; for( i=lpBi-biHeight; i=2; i-)/从图像右下角开始对图像的各个像素进行“浮雕”处理; VC 数字图像处理编程讲座之一 15 / 27 for( j=lpBi-biWidth; j=2; j-) /浮雕处理 buf=*(pData+(lpBi-biHeight-i)*WIDTHBYTES(lpBi-biWidth*8)+j)-*(pData+(lpBi-biHeight-i+1)*WIDTHBYTES(lpBi-biWidth*8)+j-1)+128; if(buf255) buf=255; if(bufbiHeight-i)*WIDTHBYTES(lpBi-biWidth*8)+j)=(BYTE)buf; for( j=0; jbiHeight; j+) for( i=0; ibiWidth; i+) /重新写回原始图像的数据缓冲区; *(pData+i*WIDTHBYTES(lpBi-biWidth*8)+j)=*(data+i*WIDTHBYTES(lpBi-biWidth*8)+j); AfxGetApp()-EndWaitCursor(); pDoc-m_hDIB =hdib/将处理过的图像数据写回 pDoc 中的图像缓冲区; GlobalUnlock(HGLOBAL)hdib);/解锁、释放缓冲区; GlobalUnlock(HGLOBAL)data1handle); GlobalFree(HGLOBAL)hdib); GlobalFree(HGLOBAL)data1handle); Invalidate(TRUE);/显示图像 2雕刻图像 上面讲述了通过求一个像素和它左上方像素之间的差值并加上一个常数的方法生成浮雕效果的灰度图像,雕刻图像与之相反,它是通过取一个像素和它右下方的像素之间的差值并加上一个常数,这里我也取 128,经过这样处理,就可以得到雕刻图像,这时候图像的前景凹陷进背景之中。 同样需要读者注意的是为了避免重复使用处理过的图像像素, 处理图像时要从图像的左上方的像素开始处理。实现代码如下: void CDibView:OnDKImage() / TODO: Add your command handler code here HANDLE data1handle;/这里的内部变量与前面的含义一致、这里不再赘述; LPBITMAPINFOHEADER lpBi; CDibDoc *pDoc=GetDocument(); HDIB hdib; unsigned char *pData; unsigned char *data; hdib=pDoc-m_hDIB;/拷贝图像数据的句柄; lpBi=(LPBITMAPINFOHEADER)GlobalLock(HGLOBAL)hdib); pData=(unsigned char*)FindDIBBits(LPSTR)lpBi); pDoc-SetModifiedFlag(TRUE); data1handle=GlobalAlloc(GMEM_SHARE,WIDTHBYTES(lpBi-biWidth*8)*lpBi-biHeight);/申请缓冲区; data=(unsigned char*)GlobalLock(HGLOBAL)data1handle);/得到新的缓冲去的指针; AfxGetApp()-BeginWaitCursor(); VC 数字图像处理编程讲座之一 16 / 27 int i,j,buf; for( i=0;ibiHeight-2; i+)/对图像的各个像素循环进行雕刻处理; for( j=0;jbiWidth-2; j+) buf=*(pData+(lpBi-biHeight-i)*WIDTHBYTES(lpBi-biWidth*8)+j)-*(pData+(lpBi-biHeight-i-1)*WIDTHBYTES(lpBi-biWidth*8)+j+1)+128;/“雕刻”处理; if(buf255) buf=255; if(bufbiHeight-i)*WIDTHBYTES(lpBi-biWidth*8)+j)=(BYTE)buf; for( j=0; jbiHeight; j+) for( i=0; ibiWidth; i+) /重新将处理后的图像数据写入原始的图像缓冲区内; *(pData+i*WIDTHBYTES(lpBi-biWidth*8)+j)=*(data+i*WIDTHBYTES(lpBi-biWidth*8)+j); pDoc-m_hDIB =hdib/将处理过的图像数据写回 pDoc 中的图像缓冲区; GlobalUnlock(HGLOBAL)hdib);/解锁、释放缓冲区; GlobalUnlock(HGLOBAL)data1handle); GlobalFree(HGLOBAL)hdib); GlobalFree(HGLOBAL)data1handle); Invalidate(TRUE);/显示图像 3图像的旋转 根据图像像素的位置来调节该位置的灰度可以实现许多显示的特效,例如图像的镜像、翻转等。 灰度图像旋转就是根据这一个思想实现的, 它是指把定义的图像绕某一点以逆时针或顺时针方向旋转一定的角度, 通常是指绕图像的中心以逆时针方向旋转。 首先根据旋转的角度、 图像对角线的长度计算旋转后的图像的最大宽度、 高度, 根据旋转后图象最大的宽度、高度生成新的缓冲区,假设图像的左上角为(left, top) ,右下角为(right, bottom) ,则图像上任意点(x, y)绕其中心(xcenter, ycenter)逆时针旋转 angle 角度后,新的坐标位置(x1, y1)的计算公式为: xcenter = (width+1)/2+left; ycenter = (height+1)/2+top; x1 = (x-xcenter) cos - (y - ycenter) sin+xcenter; y1 = (x-xcenter) sin+ (y- ycenter) cos+ ycenter; 与图像的镜像变换相类似,下一步就是把原图中的(x,y)处象素的灰度值读入新缓冲区的(x1, y1)点处。 注意在新缓冲区中与原图没有对应的象素点的值用白色或指定的灰度代替。 二、图像的分块显示和清除 1 图像的扫描显示和清除 扫描显示图像是最基本的特效显示方法,它表现为图像一行行(或一列列)地显示出来或从屏幕上清除掉,有种大戏院种的拉幕效果。根据扫描的方向的不同,可以分为上、下、左、右、水平平分和垂直平分等六种扫描。这里以向下移动为例,分别介绍显示和清除的实现。其余的扫描效果可以依次类推。向下扫描显示的实现方法是:从图像的底部开始将图像一行一行的复制到目标区域的顶部。每复制一行后,复制的行数便要增加一行,并加上一些延迟; 向下移动清除的实现方法是图像向下移动显示, 并在显示区域的上部画不断增高的矩形。 VC 数字图像处理编程讲座之一 17 / 27 1)扫描显示的代码: CdibView:OnImageDownScan() CDibDoc *pDoc=GetDocument(); HDIB hdib; CClientDC pDC(this); hdib=pDoc-m_hDIB;/获取图像数据句柄; BITMAPINFOHEADER *lpDIBHdr;/位图信息头结构指针; BYTE *lpDIBBits;/指向位图像素灰度值的指针; HDC hDC=pDC.GetSafeHdc();/获取当前设备上下文的句柄; lpDIBHdr=( BITMAPINFOHEADER *)GlobalLock(hdib);/得到图像的位图头信息; lpDIBBits=(BYTE*)lpDIBHdr+sizeof(BITMAPINFOHEADER)+256*sizeof(RGBQUAD);/获取指向图像像素值; SetStretchBltMode(hDC,COLORONCOLOR); /显示图像; for(int i=0;ibiHeight;i+) /每次循环显示图象的“0”到“i”行数据; SetDIBitsToDevice (hDC,0,0,lpDIBHdr-biWidth, lpDIBHdr-biHeight, 0, 0,0, i, lpDIBBits,(LPBITMAPINFO)lpDIBHdr, DIB_RGB_COLORS ); DelayTime(50);/延迟; GlobalUnlock(hdib); return; 2)清除代码: /由于篇幅的限制,省略了与上面的相同代码 Cbrush brush(crWhite);/定义一个“白色”的刷子; Cbrush *oldbrush=pDC-SelectObject(&brush); for(int i=0;i biHeight ;i+) /每次循环将目标区域中的“0”到“i”行刷成“白色”; pDC-Rectangle(0,0,lpDIBHdr-biWidth,lpDIBHdr-biHeight); DelayTime(50); 2 百页窗效果 所谓百页窗显示效果, 就如同关闭和开启百页窗一样, 图像被分为一条条或一列列地分别显示或清除掉, 根据显示时以行或列为单位可以将该效果分为垂直或水平两种方式。 以垂直百页窗为例来说明如何实现这种特效显示。 实现垂直百页窗显示时, 需要将图像垂直等分为 n 部分由上向下扫描显示,其中每一部分包括 m 个条、这个 n 可以根据具体应用时的需要来决定、 m 既为图像的高度除 n。 扫描显示时, 依照差值进行扫描显示, 即第 k 次显示 k-1、VC 数字图像处理编程讲座之一 18 / 27 k*m-1、k*n-1 条扫描线。同样,垂直百页窗清除的实现与垂直百页窗的显示相似,不同的是将绘制位图换成画矩形而已。在下面的例子中,我将图像的分成 8 份。 int m=8; int n=lpDIBHdr-biHeight/m;/图像的高度能够整除 8; for(int l=1;l=m;l+) for(int k=0;kbiWidth,1, 0, lpDIBHdr-biHeight-4*k-l+1,lpDIBHdr-biWidth,1, lpDIBBits,(LPBITMAPINFO)lpDIBHdr, DIB_RGB_COLORS, SRCCOPY);/juanlianxiaoguo DelayTime(50); 3.栅条显示特效 栅条特效是移动特效的复杂组合, 可以分为垂直栅条和水平栅条两类。 它的基本思想是将图像分为垂直或水平的的小条,奇数条向上或向左显示/清除,偶数条向下或向右显示/清除。当然也可以规定进行相反的方向显示/清除。下面的代码是实现垂直栅条的例子: int m=8; for(int i=0;ibiHeight;i+) for(int j=0;jbiWidth;j+=m) /向下显示偶数条; StretchDIBits (hDC,j,0,m,i,j,lpDIBHdr-biHeight-i, m,i, lpDIBBits,(LPBITMAPINFO)lpDIBHdr, DIB_RGB_COLORS, SRCCOPY);/juanlianxiaoguo j=j+m; /向上显示奇数条; StretchDIBits (hDC,j,lpDIBHdr-biHeight-i,m,i,j,0, m,i, lpDIBBits,(LPBITMAPINFO)lpDIBHdr, DIB_RGB_COLORS, SRCCOPY);/ DelayTime(20); 4.马赛克效果 马赛克显示是指图像被分成许多的小块, 它们以随机的次序显示出来, 直到图像显示完毕。 实现马赛克的效果主要解决的问题是如何定义显示随机序列的小方块, 这个问题的解决可以在定义过小方块的基础上, 用一个数组来记录各个方块的左上角的坐标的位置。 显示图像过程中, 产生一个随机数来挑选即将显示的小方块, 显示后将该方块的位置坐标从数组中VC 数字图像处理编程讲座之一 19 / 27 剔除。 清除过程与之相仿。 剔除显示过的方块的位置坐标的方法是将该数组中的最后的一个点的坐标拷贝到当前位置, 然后删除数组中的最后点的坐标, 经过实现发现这样处理有时显示的图像是不完整的, 分析其原因是生成随机数的过程有舍入溢出误差。 读者可以采用其它的办法解决这个问题, 例如可以生成固定的随机数组或采用一个动态的数组来跟踪未显示的图像方块的坐标等方法。 int m,n; int RectSize=60;/方块的宽、高尺寸为 60 个像素; if(lpDIBHdr-biWidth%RectSize!=0)/得到图像水平方块的个数; m= lpDIBHdr-biWidth/RectSize+1; else m= lpDIBHdr-biWidth/RectSize; if(lpDIBHdr-biHeight%RectSize!=0)/得到图像垂直方块的个数; n= lpDIBHdr-biHeight/RectSize+1; else n=lpDIBHdr-biHeight/RectSize; POINT *point=new POINTn*m;/申请一个数组用来记录各个方块的左上角的坐标; POINT point1; for(int a=0;am;a+)/将各个方块的左上角的坐标记录到数组中; for(int b=0;b=0;k-) int c=(int)(double)(m*n)*rand()/fMax); int mx=pointc.x; int my=pointc.y; /显示对应的图像的小块; StretchDIBits (hDC,mx,my,RectSize,RectSize, mx,lpDIBHdr-biHeight-my,RectSize,RectSize, lpDIBBits,(LPBITMAPINFO)lpDIBHdr, DIB_RGB_COLORS, SRCCOPY); pointc.x=pointk.x; pointc.y=pointk.y; DelayTime(50); 5图像的淡入淡出效果 VC 数字图像处理编程讲座之一 20 / 27 图像的淡入淡出的显示效果被广泛的应用在多媒体娱乐软件中, 是一种特别重要的特效显示方法。淡入就是将显示图像的目标区域由本色逐渐过度的图像中的各个像素点的颜色;淡出就是由显示的图像逐渐过度到目标区域的本色。 实现图像的淡入淡出有两种办法: 一是均匀的改变图像的调色板中的颜色索引值; 另一种方法是改变图像像素的灰度值。 第一种方法实现起来比较繁琐, 第二种方法就比较简单。 下面是我们采用第二种方法实现图像淡入效果的代码: /申请一个与图像缓冲区相同大小的内存; hdibcopy=(HDIB)GlobalAlloc(GMEM_SHARE,lpDIBHdr-biWidth*lpDIBHdr-biHeight); lpbits=(BYTE*)GlobalLock(hdibcopy); /将缓冲区的数据初始化; for(int k=0;kbiWidth*lpDIBHdr-biHeight;k+) *(lpbits+k)=(BYTE)255; /显示最初的图像为“白色” StretchDIBits (hDC,0,0,lpDIBHdr-biWidth,lpDIBHdr-biHeight,0,0, lpDIBHdr-biWidth,lpDIBHdr-biHeight, lpbits,(LPBITMAPINFO)lpDIBHdr, DIB_RGB_COLORS, SRCCOPY); /布尔变量 end 用来标志何时淡入处理结束; BOOL end=false; while(!end) int a=0; for(int k=0;kbiWidth*lpDIBHdr-biHeight;k+) /判断是否待显示的像素的灰度值已经小于原始图像对应点的灰度值,如是则计数; if(*(lpbits+k)biWidth,lpDIBHdr-biHeight,0,0, lpDIBHdr-biWidth,lpDIBHdr-biHeight, lpbits,(LPBITMAPINFO)lpDIBHdr, DIB_RGB_COLORS, SRCCOPY); /如果所有的点的灰度值的都小于或等于原始图像的像素点的灰度值,则认为图像的淡入处理结束。 if(a=lpDIBHdr-biWidth*lpDIBHdr-biHeight) end=true; DelayTime(50); VC 数字图像处理编程讲座之一 21 / 27 本文上面的内容介绍了几种图像的特殊显示效果, 代码在Windows2000和Visual C+6.0编程环境下编译通过,运行正常,处理达到了预期的效果。读者可以将上面介绍的显示图像的函数和处理思路结合起来,实现更多效果。 第四节 BMP 图像操作的补充篇 上一讲中我们介绍了图像特效显示操作的实现方法,如随机显示效果、马赛克效果、拉幕显示效果等,由于篇幅的限制,还有许多效果没有介绍;本期讲座将接着上一讲的内容,继续介绍一些图像特效显示效果。 另外,我们前面的学习都是针对现成的 BMP 图像,在实际工作学习中,绝大部分处理图像过程都是在一个系统环境中, 也就是说需要和图像数据的获取设备直接打交道, 一般情况下,计算机图像处理系统从系统层次上可分为高、中、低档三个层次,目前比较普及的是低档次的系统,该系统由 CCD(摄像头) 、图像采集卡、计算机三个部分组成,其结构简单,应用方便,效果也比较不错,得到的图像较清晰,所以目前在工程应用中采用的比较多。这就给开发人员带来一个现实的问题, 如何使用图像采集卡呢?目前虽然各种编程资源中基于VC 开发经验的文章不少,但是关于如何在 VC 开发平台上使用图像采集卡的文章的确没发现, 笔者借这期讲座的宝贵机会, 补充介绍一下如何在程序中编写自己的代码来操作图像采集卡,从而搭建一个完整的图像处理系统。希望通过这部分内容的学习,在读者的脑海里就可以建立一个完整的图像操作系统概念; 同时也能够给目前正需要利用图像采集卡开发自己的图像处理系统的朋友有所帮助。 1 抖动图像 在上一节讲座中,我们讲到了如何实现图象的雕刻和浮雕效果,它们的实现思想是通过求取没有处理过的相邻两个像素之间的差值来实现的。如果没有限制以前没有处理过的两个像素之间的操作,取而代之的是处理以前已经操作过的像素,那末就可以将一个像素的灰度值传递到与其相邻的若干像素。 事实上, 有时后我们必须通过上述的约定才能实现一些效果,如图像的抖动效果。例如,为了使图象看起来好象从左上角向右下角扫过,以产生运动的感觉,必须要反复拷贝左上方的那些像素的灰度值,逐步把它们融合在一起,看起来好象图像后边有一些颜色在逐渐的消失, 这就是我们要讲的图象的抖动效果。 下面给出了该效果的实现代码: void CDibView:OnDouDongImage() /产生抖动效果图函数 HANDLE data1handle;/用来存放图像数据的句柄; LPBITMAPINFOHEADER lpBi;/图像的信息头结构; CDibDoc *pDoc=GetDocument();/得到文挡指针; HDIB hdib;/用来存放图像数据的句柄; unsigned char *pData;/指向原始图像数据的指针; unsigned char *data;/指向处理后图像数据的指针; hdib=pDoc-m_hDIB;/拷贝存放已经读取的图像文件数据句柄; lpBi=(LPBITMAPINFOHEADER)GlobalLock(HGLOBAL)hdib);/获取图像信息头; pData=(unsigned char*)FindDIBBits(LPSTR)lpBi); /FindDIBBits 是我定义的一个函数,根据图像的结构得到位图的灰度值数据; pDoc-SetModifiedFlag(TRUE); /设置文档修改标志为真,为后续的修改存盘作准备; data1handle=GlobalAlloc(GMEM_SHARE,WIDTHBYTES(lpBi-biWidth*8)*lpBi-biHeight); /声明一个缓冲区用来暂存处理后的图像数据; data=(unsigned char*)GlobalLock(HGLOBAL)data1handle);/得到该缓冲区的指针; VC 数字图像处理编程讲座之一 22 / 27 AfxGetApp()-BeginWaitCursor(); int i,j,buf; for( i=lpBi-biHeight; i=2; i-)/从图像右下角开始对图像的各个像素进行抖动处理; for( j=lpBi-biWidth; j=2; j-) /抖动处理、从图像的右下角开始计算图像斜上方相邻像素的均值; buf=(*(pData+(lpBi-biHeight-i)*WIDTHBYTES(lpBi-biWidth*8)+j)+*(pData+(lpBi-biHeight-i+1)*WIDTHBYTES(lpBi-biWidth*8)+j-1)/2; if(buf255) buf=255;/限制像素点的灰度范围为 0-255; if(bufbiHeight-i)*WIDTHBYTES(lpBi-biWidth*8)+j)=(BYTE)buf; for( j=0; jbiHeight; j+) for( i=0; ibiWidth*8)+j)=*(data+i*WIDTHBYTES(lpBi-biWidth*8)+j); AfxGetApp()-EndWaitCursor(); pDoc-m_hDIB =hdib/将处理过的图像数据写回 pDoc 中的图像缓冲区; GlobalUnlock(HGLOBAL)hdib);/解锁、释放缓冲区; GlobalUnlock(HGLOBAL)data1handle); GlobalFree(HGLOBAL)hdib); GlobalFree(HGLOBAL)data1handle); Invalidate(TRUE);/显示图像 对于比较复杂的图像, 计算当前像素的灰度和斜上方像素的均值产生的抖动效果可能不明显,为了解决这个问题,笔者的解决办法是隔行隔列的计算,比如说计算当前位置(i j)的灰度值,我取(i,j)和(i-2,j-2)两个位置的像素的灰度值的平均。 2.图像合成技术 图像合成技术很重要, 其实质是操作将两幅或两幅以上的图像, 将它们的信息融合在一起,产生 1+12 的效果。我们在进行图像合成的时候可以采用 Alpha 值的方法,下面来看一下如何利用 Alpha 值来合成两张图片。 采用 Alpha 图象合成的方法, 就是最终合成的图象的各点像素值是由用来制作合成图的两张图片的相应点的像素值按一定比例混合而成的,这个比例由 Alpha 值决定, 具体算式如下: resultPixe= (pixel1*(255-Alpha)+pixel2*Alpha)/255; / Alpha 取值范围从 0 到 255 上面的算式中,pixel1 代表图像 1 的当前像素点的灰度值,pixel2 代表图像 2 的当前像素点的灰度值,Alpha 可以看作两个像素在最终合成的结果中所占的权重。可以看出,只要修改 Alpha 的值,就可以改变合成后的图象中用来合成的两张图片各自所占的比值, 改变合成后的显示效果。我们可以利用这个方法,按一定的时间间隔修改 Alpha 的值、这样就可以很轻易的制作出生动的淡入淡出效果、 实现两幅图片间的平滑过度效果。 下面给出一个制作合成图的具体源码: BOOL CompoundImage(HANDLE HDib1,HANDLE HDib2,int alpha) BYTE lpData1,lpData2; VC 数字图像处理编程讲座之一 23 / 27 / 源图象 2 的信息 /由于待合成的两个图象的格式、大小是一样的,所以我只获取一个图像文件的图像信息就可以了。 LPBITMAPINFO lpBi=(LPBITMAPINFO)HDib2; / 计算图象数据偏移量 lpData2=(LPVOID)(LPBYTE)lpBi-bmiColors+256*sizeof(RGBQUAD); /获取源图像 2 的图像数据; lpBi=(LPBITMAPINFO)HDib1; lpData1=(LPVOID)(LPBYTE)lpBi-bmiColors+256*sizeof(RGBQUAD); /通过 alpha 值合并两张图象的像素值 for ( int i=0;ibiWidth; i+ ) for(int j=0;jbiHeight;j+ ) /套用 alpha 图像混合公式; *(lpData1+i*WIDTHBYTES(lpBi-biWidth*8)+j)=(*(lpData1+i*WIDTHBYTES(lpBi-biWidth*8)+j)*(255-alpha)+ *(lpData2+i*WIDTHBYTES(lpBi-biWidth*8)+j)*alpha)/255; return lpData1; 以上内容我们主要是讲述了 alpha 图像混合的实现原理和方法,其实读者大可不必自己写这么多代码,微软给我们提供了一个名为 AlphaBlend()的函数,它就可以直接实现图像合成的功能,具体怎么使用,还请读者参考 MSDN。 3.采集卡的操作 图像处理所涉及的应用领域有军事应用、医学诊断、工业监控、物体的自动分检识别等等,这些应用系统无不需要计算机提供实时动态,效果逼真的图像。目前获取实时图像一般都需要在计算机内部安装一个图像采集卡, 用来实现 CCD 端获取的模拟图像的数字化转换。笔者结合自己在项目开发中积累的一些经验, 谈一下如何操作图像采集卡、 然后再此基础基础上再实现一些特殊处理。 笔者的摄像机采用台湾 BENTECH INDUSTRIAL 有限公司生产的 CV-155L 黑白摄像机。该摄像机分辨率为 752x582。图象采集卡采用的是北京中科院科技嘉公司开发的基于PCI 总线的 CA-MPE 1000 黑白图象采集卡。一般情况下,使用图像采集卡分三步,首先安装采集卡的驱动程序,并将虚拟驱动文件 VxD.vxd 拷贝到 Windows 的 SYSTEM 目录下;这时候就可以进入开发状态了,进入 VC 开发平台,生成新的项目,由于生产厂家为图像采集卡提供了以 mpew32.dll、 mpew32.lib 命名的库文件, 库中提供了初始硬件、 采集图像等函数,为使用这些函数,需要在新项目上连接该动态库;最后一步就是采集图像并显示处理了,这一步要设置系统调色板,因为采集卡提供的是裸图形式,既纯图像数据,没有图像的规格和调色板信息,这些需要开发者自己规定实现,下面是实现的部分代码: / CTestView:CTestView() W32_Init_MPE1000();/初始化采集卡 W32_Modify_Contrast(50);/下面的函数是为了对采集卡进行预设置 W32_Modify_Brightness(45);/设置亮度 W32_Set_HP_Value(945);/设置水平采集点数 VC 数字图像处理编程讲座之一 24 / 27 wCurrent_Frame = 1;/当前帧为 1,获取的图像就是从这帧取得的 / 设置采集信号源,仅对 MPE1000 有效 W32_Set_Input_Source(1);/该图像采集卡支持三路视频,目前采集的图像来自第二路输入端; W32_Set_PAL_Range(1250, 1024);/设置水平采集范围 W32_Set_VGA_Mode ( 1 ); 采用 PAL 制式; wGrabWinX1 = 0; / 采集窗口的左上角的坐标 wGrabWinY1 = 0; firstTime=TRUE; /第一次采集; bGrabMode = FRAME; /抓图模式为?格式; bZipMode = ZIPPLE; /压缩模式为 ZIPPLE; lpDib=NULL;/存放获取的图像数据缓冲区为空; / CTestView:CTestView() W32_Close_MPE1000();/关闭采集卡 / void CTestView:OnGraboneframe()/显示采集的图象,双击鼠标采集停止 / TODO: Add your command handler code here wCurrent_Frame = 1; / 设置采集目标为内存 W32_CACardParam (AD_SETGRABDEST, CA_GRABMEM); / 启动采集 if (lpDib != NULL)/如果图像缓冲区不为空,释放该缓冲区; GlobalUnlock( hglbDIB ); GlobalFree( hglbDIB ); /为采集到的图像数据分配内存; hglbDIB=GlobalAlloc(GHND, (DWORD)wImgWidth*(DWORD)wImgHeight ); lpDib = (BYTE *)GlobalLock( hglbDIB ); /得到图像数据的指针; hdc = GetDC()-GetSafeHdc( ) ; /获取视图的设备上下文句柄; if(lpDib != NULL) cxDib = wImgWidth; cyDib = wImgHeight; SetLogicPal( hdc, cxDib, cyDib, 8 ); /设置调色板; SetStretchBltMode (hdc, COLORONCOLOR) ; bGrabMark = TRUE; while (bGrabMark = TRUE) VC 数字图像处理编程讲座之一 25 / 27 if(msg.message=WM_LBUTTONDBLCLK) /分析是否为鼠标双击消息; bGrabMark = FALSE;/如为鼠标双击消息,停止采集图象; W32_ReadXMS2Buf (wCurrent_Frame,lpDib) ; /将图象数据读入到图像数据缓冲区; SetDIBitsToDevice (hdc, 0, 0, cxDib, cyDib, 0, 0, 0, cyDib, (LPSTR) lpDib, bmi, DIB_RGB_COLORS) ; /显示图像; / 停止采集 W32_CAStopCapture(); :ReleaseDC( GetSafeHwnd(), hdc ); return ; /将下面这个函数添加在视图类的 CTestView:OnSize()函数中,就可以对系统的调色板进行设置。 void WINAPI InitLogicPal( HDC hdc , short width, short height, WORD bitCount ) int j, i; short cxDib, cyDib; LOGPALETTE * pLogPal; j=256 ; if(pLogPal=(LOGPALETTE*)malloc(sizeof(LOGPALETTE)+ (j*sizeof(PALETTEENTRY) = NULL) return ; pLogPal-palVersion=0x300; /设置调色版的颜色信息; pLogPal-palNumEntries=j; for (i=0;i pLogPal-palPalEntryi.peRed = i ; pLogPal-palPalEntryi.peGreen = i ; pLogPal-palPalEntryi.peBlue = i ; pLogPal-palPalEntryi.peFlags = 0; hPal = :CreatePalette(pLogPal); /创建调色板; delete pLogPal; :SelectPalette(hdc,hPal,0);/系统实现调色板; :RealizePalette(hdc); cxDib = width; cyDib = height; if ( (bmi = (BITMAPINFO *)malloc(sizeof(BITMAPINFOHEADER) + j*sizeof(RGBQUAD) = NULL ) return ; /定义图 VC数 字 图 像 处 理 编 程 讲 座 之 一 - 数 字 图 像 处 理 的 起 源 和 应VC 数字图像处理编程讲座之一 26 / 27 用 数字图像处理方法的研究源于两个主要应用领域: 其一是为了便于人们分析而对图像信息的改进;其二是为使机器自动理解而对图像数据进行存储、传输及显示。 数 字图像处理最早的应用之一是在报纸业,当时,图像第一次通过海底电缆从伦敦。早在20 世纪 20 年代曾引入 Bartlane 电缆图片传输系统,把横跨大西洋 传送一幅图片所需的时间从一个多星期减少到 3 小时。 不过这种传输方式没有考虑数字图像处理的结果, 主要是因为没有涉及到计算机,数字图像处理要求非常大的 存储和计算能力,因此必须依靠数字计算机及数据存储、显示和传输等相关技术的发展。 第一台可以执行有意义的图像处理任务的大型计算机出现在 20 世纪 60 年代早期。 数字图像处理技术的诞生可追溯至这一时期这些机器人的使用和空间项目的开 发,这两大发展大人们的注意力集中到数字图像处理的潜能上。 利用计算机技术改善空间探测器发回的图像的工作。始于 1964 年美国加利福尼亚的喷气推进实验 室。当时由“旅行者七号”卫星传送的月球图像由一台计算机进行了处理, 以校正航天器上电视摄像机中各种类型的图象畸变进行空间应用的同时,数字图像处理技 术在 20 世纪 60 年代末和 20 世纪 70 年代初开始用于医学图像、 地球遥感监测和天文学等领域。 早在 20 世纪 70 年代发明的计算机轴向断层术(CAT)是图 像处理在医学诊断领域最重要的应用之一。今天几乎不存在数字图像处理无关的技术领域,通常使用的典型问题是自动字符识别、用于生产线及检测的工业机器视 觉、军事识别、指纹的自动处理、X 射线和血样分类处理、用于天气预报和环境鉴定的航空与卫星图像的机器处理。 数字图像处理一般分为两类,一类是输入输出都是图像;另一类,输入可能是图像,但是输出是从图像中提取的特征属性。数字图像处理的基本步骤作了一下概括, 但并不意味着每一步处理都用于图像, 更确切的目的是给出所有方法的一个概念, 这些方法可以用于不同的目的,带有不同的目标。基本步骤如下: 1 图像获取 2 图像增强 3 图像复原 4 图像压缩 5 形态学处理 6 图像分割 7 图像识别 VC 数字图像处理编程讲座之一 27 / 27 这里先大概的列举一下,事实上,在某些情况下并不是所有步骤都需要,通常随着图像处理任务复杂度的增加要求做更多的处理才能决问题。 在以后的帖子里会详细对每个步骤加以说明 图像处理与识别的应用工程: 1二维条码身份证系统 系统概况:给身份证一种防伪的手段,要求伪造困难,识别可靠。还有一点就是实现方便,成本低。 主要技术: 二维条码的编码与解码 图像压缩,图像识别 2 弱可视环境下的图像清晰化 该项技术是为了解决对在很微弱的情况下获取的图像进行处理,获得现场的某些细节信息。 3印章的真伪识别系统 印章的真伪鉴别难点是: 相同印章所加盖的印鉴是不同的; 伪造技术高,导致逼真程度高; 印鉴加盖在有底纹的凭单或文件上。 4车牌识别系统 基于模糊识别技术的车牌自动识别系统是计算机及视频技术的发展与应用的结果, 该技术在交通领域内的多方面成功应用使其成为 ITS 的重要组成部分。 5汉字识别
网站客服QQ:2055934822
金锄头文库版权所有
经营许可证:蜀ICP备13022795号 | 川公网安备 51140202000112号