VC++数字图像处理

更新时间:2024-07-01 13:13:01 阅读量: 综合文库 文档下载

说明:文章内容仅供预览,部分内容可能不全。下载后的文档,内容与下面显示的完全一致。下载之前请确认下面内容是否您想要的,是否完整无缺。

Visual C++数字图像处理

广义地讲,凡是记录在纸介质上的、拍摄在底片和照片上的、显示在电视、投影仪和计算机屏幕上的所有具有视觉效果的画面都可以称为图像。根据图像记录方式的不同,图像可分为两大类:一类是模拟图像(Analog Image),另一类是数字图像(Digital Image)。模拟图像是通过某种物理量(光、电等)的强弱变化来记录图像上各点的亮度信息的,例如模拟电视图像;而数字图像则完全是用数字(即计算机存储的数据)来记录图像亮度信息的。

所谓数字图像处理(Digital Image Processing),就是指用数字计算机及其他相关的数字技术,对数字图像施加某种或某些运算和处理,从而达到某种预期的处理目的。随着数字技术和数字计算机技术的飞速发展,数字图像处理技术在近20多年的时间里,迅速发展成为一门独立的有强大生命力的学科,其应用领域十分广泛。

作为数字图像处理技术的实现环节,本书将在Visual C++环境下介绍图像各种典型算法的编程实现。而作为一本书的开始,本章我们将介绍图像编程的基础知识,如数字图像的点阵数据、调色板概念、BMP文件结构以及设备无关位图(DIB)等,它是后面章节学习的基础。

1.1 图像、颜色表和色彩空间

1.1.1 图像

组成数字图像的基本单位是像素(Pixel),也就是说,数字图像是像素的集合。如图1-1所示,图中每个格点代表一个像素,该图是一个白色背景下包含灰色矩形的图像。

图1-1 放大后的矩形图像

数字图像通常存放在计算机的外存储器设备中,例如硬盘、光盘

等,在需要进行显示和处理时才被

调入内存的数组中。从本质上讲,图像数据在计算机内存或硬盘中是以字符型数据存在的,这与其他整型数据或者浮点型数据没有任何区别,都是一种数字表达符号,当把它在计算机屏幕上显示出来时,才是我们人眼看到的真正有意义的数字图像。普通的显示器屏幕也是由许多点(像素)构成的,显示时,电子枪每次从左到右、从上到下进行扫描,为每个像素着色,利用

人眼的视觉暂留效应就可以显示出一屏完整的图像。比如,我们常说的屏幕分辨率为1024′768,刷新率为80Hz,意思是说屏幕上每行像素为1024个,共768行,而且每秒重复扫描80次。目前的显示设备的刷新率都在80Hz以上,一般屏幕刷新频率大于80Hz时,人眼感受不到屏幕刷新而产生的闪烁,这种显示器被称为位映像设备。所谓位映像,即是指一个二维的像素矩阵,而位图就是采用位映像方法显示和存储的图像。一幅图像的显示就是将图像的像素映射到屏幕的像素上并显示一定的颜色。

图1-1所示是一个灰度图像的例子,当一幅图像的像素由彩色表示时就是我们通常所说的彩色图像了。对于彩色图像的表达,将在调色板和彩色空间两部分内容中谈到。

1.1.2 图像的矩阵表示

数字图像数据可以用矩阵来表示,因此可以采用矩阵理论和矩阵算法对数字图像进行分析和处理。最典型的例子是灰度图像,如图1-2所示。灰度图像的像素数据就是一个矩阵,矩阵的行对应图像的高(单位为像素),矩阵的列对应图像的宽(单位为像素),矩阵的元素对应图像的像素,矩阵元素的值就是像素的灰度值。

图1-2 数字图像与图像矩阵

由于数字图像可以表示为矩阵的形式,所以在计算机数字图像处理程序中,通常用二维数组来存放图像数据,参见图1-3。二维数组的行对应图像的高,二维数组的列对应图像的宽,二维数组的元素对应图像的像素,二维数组元素的值就是像素的灰度值。采用二维数组来存储数字图像,符合二维图像的行列特性,同时也便于程序的寻址操作,使得计算机图像编程十分方便。

图1-3 数字图像与二维数组 1.1.3 颜色表 图像的位图数据是一个二维数组(矩阵),矩阵的每一个元素对应了图像的一个像素,当保存一幅图像时,不但要保存图像的位图数据矩阵,还要将每个像素的颜色保存下来,颜色的记录是利用颜色表来完成的。 颜色表,也叫颜色查找表,是图像像素数据的颜色索引表。以一个4色位图为例,则其颜色表有4个表项,表中每一行记录一种颜色的R、G、B值,这样,当表示一个像素的颜色时,只需要指出该颜色在第几行,即该颜色在表中的索引值即可。假设该4色位图的颜色表如表1-1所示,并假设该4色位图大小为8行8列,其图像数据阵列如图1-4所示。 表1-1 4色位图的颜色表 颜色索引值 蓝色成分 绿色成分 红色成分 0123 0 00 000 255002550255图1-4 4色位图数据矩阵 该位图有4种颜色(最多也只能有4种颜色),对于位图矩阵中第3行第4列的像素,该像素的值为2,则其颜色由颜色表第3行决定(索引值从0开始),该行的颜色为绿色(0,255,0)。为了简单起见,上面颜色表的例子每一个记录只有3个分量——R、G、B,实际上,真正一幅BMP图像其颜色表的每一个记录是由4个分量组成的,这在1.2.1节“BMP文件结构”中有详细介绍。 有一个特例,对于真彩色图像,每个像素占存储空间3个字节(24位),分别对应R、G、B三个分量,每个像素的值已经将该像素的颜色记录下来了,不再需要颜色表,因此24位真彩色位图没有颜色表。

1.1.4 彩色空间

自然界中的所有颜色都可以由红、绿、蓝(R、G、B)三种颜色合成,数字图像也是如此。针对红(绿/蓝)分量的多少,人为地划分为0~255共256个等级,0表示不含红色(绿/蓝)成分,255表示含有100%红色(绿/蓝)成分。根据红、绿、蓝各种不同的组合就能表示256′256′256种颜色,例如一个像素,当它的红、绿、蓝成分分别为255、0、255时显示为紫色。而对于灰度图像的像素,该像素的红、绿、蓝成分是相等的,只不过随着这三个分量数值的增大,像素颜色从黑色变成白色。

从上面介绍可知,彩色数字图像可以由RGB彩色空间表示。彩色空间是用来表示彩色的数学模型,又被称为彩色模型。RGB彩色空间是最常用的一种彩色空间,但在计算机系统中表达颜色信息的空间不止这一种,此处介绍3种最常用的彩色空间。

1.RGB彩色空间

几乎所有的彩色成像设备和彩色显示设备都采用RGB(Red / Green / Blue,红绿蓝)三基色,不仅如此,数字图像文件的常用存储形式,也以RGB三基色为主,由RGB三基色为坐标形成的空间称为RGB彩色空间。

根据色度学原理,自然界的各种颜色光都可由红、绿、蓝三种颜色的光按不同比例混合而成,同样,自然界的各种颜色光都可分解成红、绿、蓝三种颜色光,所以将红、绿、蓝三种颜色称为三基色。

图1-5所示是RGB三基色合成其他颜色的典型例子和RGB彩色空间以及基色间的关系。由图1-5(a)可以看出,青色可以由绿色和蓝色合成,洋红(或品红)可以由红色和蓝色合成,黄色可以由红色和绿色合成,而青色、洋红和黄色恰好是CMY(Cyan/Magenta/Yellow)三基色。当RGB三基色以等比例或等量进行混合时,可以得到黑、灰或白色,而采用不同比例进行混合时,就得到千变万化的颜色。

洋红

(a)RGB三基色混色 (b)RGB彩色空间 (c)RGB三基色二维对称表示

图1-5 三基色原理图

在RGB彩色空间中,任意彩色光L的配色方程参见公式(1-1)。

(1-1)

其中,

为彩色光L的三基色分量或百分比。

2.CMY彩色空间

自然界物体颜色光的形成方式将物体划分为两类——发光物体和不发光物体,发光物体称为有源物体,不发光物体称为无源物体。有源物体是自身发出光波的物体,其颜色由物体发出的光波决定,因此采用RGB三基色相加模型和RGB彩色空间描述。有源物体的例子包括彩色电视、彩色显示器等。

无源物体是不发出光波的物体,其颜色由该物体吸收或反射哪些光波来决定,因此采用CMY三基色相减模型和CMY彩色空间描述。例如,在彩色印刷和彩色打印时,纸张是不能发射光线而只能反射光线的,因此,彩色印刷机和彩色打印机只能通过一些能够吸收特定光波和反射其他光波的油墨和颜料以及它们的不同比例的混合来印出千变万化的颜色。

油墨和颜料的三基色是CMY(Cyan / Magenta / Yellow,青/洋红/黄)而不是RGB,CMY三基色的特点是油墨和颜料用得越多,颜色越暗(或越黑),所以将CMY称为三减色,而RGB称为三加色。理论上讲,等量的CMY可以合成黑色,但实际上纯黑色是很难合成出来的,所以彩色印刷机和彩色打印机要提供专门的黑色油墨,被人们称为四色印刷,四色印刷的彩色模型为CMYK模型。

3.HSI彩色空间

另一种常见的彩色模型是HSI(Hue / Saturation / Intensity,色调/饱和度/强度)模型。采用色调和饱和度来描述颜色,是从人类的色视觉机理出发提出的。

色调Hue表示颜色,颜色与彩色光的波长有关,将颜色按红橙黄绿青蓝紫顺序排列定义色调值,并且用角度值(0o~360o)来表示。例如红、黄、绿、青、蓝、洋红的角度值分别为0o、60o、120o、180o、240o和300o。

饱和度Saturation表示色的纯度,也就是彩色光中搀杂白光的程度。白光越多饱和度越低,白光越少饱和度越高且颜色越纯。饱和度的取值采用百分数(0%~100%),0%表示灰色光或白光,100%表示纯色光。

强度Intensity表示人眼感受到彩色光的颜色的强弱程度,它与彩色光的能量大小(或彩色光的亮度)有关,因此有时也用亮度Brightness来表示。

通常把色调和饱和度统称为色度,用来表示颜色的类别与深浅程度。人类的视觉系统对亮度的敏感程度远强于对颜色浓淡的敏感程度,对比RGB彩色空间,人类的视觉系统的这种特性采用HSI彩色空间来解释更为适合。

HSI彩色描述对人来说是自然的、直观的,符合人的视觉特性,HSI模型对于开发基于彩色描述的图像处理方法也是一个较为理想的工具,例如在HSI彩色空间中,可以通过算法直接对色调、饱和度和亮度独立地进行操作。采用HSI彩色空间有时可以减少彩色图像处理的复杂性,提高处理的快速性,同时更接近人对彩色的认识和解释。

图1-6 HSI彩色空间示意图

HSI彩色空间是一个圆锥型空间模型,如图1-6(a)所示。圆锥模型可以将色调、强度以及饱和度的关系变化清楚地表现出来。圆锥型空间的竖直轴表示光强I,顶部最亮表示白色,底部最暗表示黑色,中间是在最亮和最暗之间过渡的灰度。圆锥型空间中部的水平面圆周是表示色调H的角度坐标,如图1-6(b)所示。

在处理彩色图像时,为了处理方便,经常要把RGB三基色表示的图像数据转换成HSI数据。RGB彩色空间转换到HSI彩色空间的转换公式参见(1-2)式、(1-3)式、(1-4)式。

(1-2)

(1-3)

其中,

(14)

1.1.5 灰度图像和彩色图像

计算机上显示的图像经常有二值图像、灰度图像、伪彩色图像及真彩色图像等不同格式类型。而灰度和彩色格式是数字图像处理中最常用到的类型,本书所涉及的各种图像处理算法都是以这两种类型图像为处理对象的。

1.灰度图像

灰度图像是数字图像的最基本形式,灰度图像可以由黑白照片数字化得到,或从彩色图像进行去色处理得到。灰度图像只表达图像的亮度信息而没有颜色信息,因此,灰度图像的每个像素点上只包含一个量化的灰度级(即灰度值),用来表示该点的亮度水平,并且通常用1个字节(8个二进制位)来存储灰度值。

如果灰度值用1个字节表示,则可以表示的正整数范围是0~255,也就是说,像素灰度值取值在0~255之间,灰度级数为256级。注意到人眼对灰度的分辨能力通常在20~60级,因此,灰度值存储以字节为单位既保证了人眼的分辨能力,又符合计算机数据寻址的习惯。在特殊应用中,可能需要采用更高的灰度级数,例如CT图像的灰度级数高达数千,需要采用12位或16位二进制位存储数据,但这类图像通常都采用专用的显示设备和软件来进行显示和处理。

2.彩色图像

彩色图像的数据不仅包含亮度信息,还包含颜色信息。彩色的表示方法是多样化的,最常见的是三基色模型,例如RGB(Red / Green / Blue,红绿蓝)三基色模型,利用RGB三基色可以混合成任意颜色。因此,RGB模型在各种彩色成像设备和彩色显示设备中使用,常规的彩色图像也都是用RGB三基色来表示的,每个像素包括RGB三基色数据,每个基色用1个字节(8位二进制位)表示,则每个像素的数据为3个字节(即24位二进制位),这就是人们常说的24位真彩色。

1.2 BMP文件结构及其存取

数字图像在外存储器设备中的存储形式是图像文件,图像必须按照某个已知的、公认的数据存储顺序和结构进行存储,才能使不同的程序对图像文件顺利进行打开或存盘操作,实现数据共享。图像数据在文件中的存储顺序和结构称为图像文件格式。目前广为流传的图像文件格式有许多种,常见的格式包括BMP、GIF、JPEG、TIFF、PSD、DICOM、MPEG等。在各种图像文件格式中,一部分是由某个软硬件厂商提出并被广泛接受和采用的格式,例如BMP、GIF和PSD格式;另一部分是由各种国际标准组织提出的格式,例如JPEG、TIFF和DICOM,其中JPEG是国际静止图像压缩标准组织提出的格式,TIFF是由部分厂商组织提出的格式,DICOM是医学图像国际标准组织提出的医学图像专用格式。

BMP文件是Windows操作系统所推荐和支持的图像文件格式,是一种将内存或显示器的图像数据不经过压缩而直接按位存盘的文件格式,所以称为位图(bitmap)文件,因其文件扩展名

为BMP,故称为BMP文件格式,简称BMP文件。本书对图像的算法编程都是针对BMP图像文件的,因此在本章中我们详细介绍BMP文件结构及其读写操作,以加深对图像数据的理解。

1.2.1 BMP文件结构

如图1-7所示,BMP图像文件被分成4个部分:位图文件头(Bitmap File Header)、位图信息头(Bitmap Info Header)、颜色表(Color Map)和位图数据(即图像数据,Data Bits或Data Body)。

第1部分为位图文件头BITMAPFILEHEADER,是一个结构体类型,该结构的长度是固定的,为14个字节。其定义如下:

typedef struct tagBITMAPFILEHEADER {

WORD bfType; DWORD bfSize; WORD bfReserved1; WORD bfReserved2; DWORD bfOffBits;

} BITMAPFILEHEADER, FAR *LPBITMAPFILEHEADER, *PBITMAPFILEHEADER; BITMAPFILEHEADER结构的各个域详细说明如下:

— bfType:位图文件类型,必须是0x424D,即字符串“BM”,也就是说,所有的“*.bmp”文件的头两个字节都是“BM”。

— bfSize:位图文件大小,包括这14个字节。

— bfReserved1, bfReserved2:Windows保留字,暂不用。

— bfOffBits:从文件头到实际的位图数据的偏移字节数,图1-7中前3个部分的长度之和。

图1-7 BMP文件结构示意图

第2部分为位图信息头BITMAPINFOHEADER,也是一个结构体类型的数据结构,该结构的长度也是

固定的,为40个字节(WORD为无符号16位整数,DWORD为无符号32位整数,LONG为32位整数)。其定义如下:

typedef struct tagBITMAPINFOHEAD

ER

{

DWORD biSize; LONG biWidth; LONG biHeight; WORD biPlanes; WORD biBitCount DWORD biCompression; DWORD biSizeImage; LONG biXPelsPerMeter; LONG biYPelsPerMeter; DWORD biClrUsed; DWORD biClrImportant;

} BITMAPINFOHEADER, FAR *LPBITMAPINFOHEADER, *PBITMAPINFOHEADER;

BITMAPINFOHEADER结构的各个域的详细说明如下:

— biSize:本结构的长度,为40个字节。 — biWidth:位图的宽度,以像素为单位。

— biHeight:位图的高度,以像素为单位。 — biPlanes:目标设备的级别,必须是1。

— biBitCount:每个像素所占的位数(bit),其值必须为1(黑白图像)、4(16色图)、8(256色)、24(真彩色图),新的BMP格式支持32位色。

— biCompresssion:位图压缩类型,有效的值为BI_RGB(未经压缩)、BI_RLE8、BI_RLE4、BI_BITFILEDS(均为Windows定义常量)。这里只讨论未经压缩的情况,即biCompression=BI_RGB。 — biSizeImage:实际的位图数据占用的字节数,该值的大小在第4部分位图数据中有具体解释。 — biXPelsPerMeter:指定目标设备的水平分辨率,单位是像素/米。 — biYPelsPerMeter:指定目标设备的垂直分辨率,单位是像素/米。

— biClrUsed:位图实际用到的颜色数,如果该值为零,则用到的颜色数为2的biBitCount次幂。 — biClrImportant:位图显示过程中重要的颜色数,如果该值为零,则认为所有的颜色都是重要的。

第3部分为颜色表。颜色表实际上是一个RGBQUAD结构的数组,数组的长度由biClrUsed指定(如果该值为零,则由biBitCount指定,即2的biBitCount次幂个元素)。RGBQUAD结构是一个结构体类型,占4个字节,其定义如下:

typedef struct tagRGBQUAD {

BYTE rgbBlue; BYTE rgbGreen; BYTE rgbRed; BYTE rgbReserved; }RGBQUAD;

RGBQUAD结构的各个域的详细说明如下:

— rgbBlue:该颜色的蓝色分量; — rgbGreen:该颜色的绿色分量; — rgbRed:该颜色的红色分量; — rgbReserved:保留字节,暂不用。

有些位图需要颜色表;有些位图(如真彩色图)则不需要颜色表,颜色表的长度由BITMAP

INFOHEADER结构中biBitCount分量决定。对于biBitCount值为1的二值图像,每像素占1bi

t,图像中只有两种(如黑白)颜色,颜色表也就有21=2个表项,整个颜色表的大小为

个字节;对于biBitCount值为8的灰度图像,每像素占8bit,图

像中有28=256种颜色,颜色表也就有256个表项,且每个表项的R、G、B分量相等,整个颜色表的大小为

个字节;而对于biBitCount=24的真彩色图像,由于每

像素3个字节中分别代表了R、G、B三分量的值,此时不需要颜色表,因此真彩色图的BITMAPINFOHEADER结构后面直接就是位图数据。

第4部分是位图数据,即图像数据,其紧跟在位图文件头、位图信息头和颜色表(如果有颜色表的话)之后,记录了图像的每一个像素值。对于有颜色表的位图,位图数据就是该像素颜色在调色板中的索引值;对于真彩色图,位图数据就是实际的R、G、B值(三个分量的存储顺序是B、G、R)。下面分别就2色、16色、256色和真彩色位图的位图数据进行说明:

— 对于2色位图,用1位就可以表示该像素的颜色,所以1个字节能存储8个像素的颜色值。 — 对于16色位图,用4位可以表示一个像素的颜色。所以一个字节可以存储2个像素的颜色值。 — 对于256色位图,1个字节刚好存储1个像素的颜色值。 — 对于真彩色位图,3个字节才能表示1个像素的颜色值。

需要注意两点:

第一,Windows规定一个扫描行所占的字节数必须是4的倍数,不足4的倍数则要对其进行扩充。假设图像的宽为biWidth个像素、每像素biBitCount个比特,其一个扫描行所占的真实字节数的计算公式如下:

DataSizePerLine = (biWidth * biBitCount /8+ 3) / 4*4

那么,不压缩情况下位图数据的大小(BITMAPINFOHEADER结构中的biSizeImage成员)计算如下:

biSizeImage = DataSizePerLine * biHeight

第二,一般来说,BMP文件的数据是从图像的左下角开始逐行扫描图像的,即从下到上、从左到右,将图像的像素值一一记录下来,因此图像坐标零点在图像左下角。

1.2.2 BMP图像文件的读写

分析了BMP文件结构后,让我们用简单的C程序实现一个给定BMP位图文件的读写操作,来进一步巩固对图像数据的理解,这也是我们后续图像可视化编程的基础。此部分的代码以及后面两节所讲述的代码在工程chap1-1中的bmpReadWrite.cpp文件中,读者可以查阅。

1.BMP文件的读入

BMP文件分为4个组成部分,那么BMP文件的读入也要按照4个组成部分依次进行处理,即先处理BITMAPFILEHEADER结构,然后是BITMAPINFOHEADER结构、颜色表,最后是位图数据。

首先,有关BITMAPFILEHEADER、BITMAPINFOHEADER、RGBQUAD等结构的定义包含在头文件“Windows.h”中,应把其包含进来。

#include \

其次,为了后面对图像进行修改及存盘方便,我们定义了几个全局变量,用来存放读入图像的位图数据、宽、高、颜色表及每像素位数等信息。所定义的全局变量如下: unsigned char *pBmpBuf;//读入图像数据的指针 int bmpWidth;//图像的宽 int bmpHeight;//图像的高

RGBQUAD *pColorTable;//颜色表指针 int biBitCount;//图像类型,每像素位数

根据BMP文件结构,BMP文件读入操作的基本流程如图1-8所示。

图1-8 BMP文件读入操作流程图

readBmp()函数实现了BMP文件的读取操作,下面的代码是对readBmp()函数的说明和实现。

/*********************************************************************** * 函数名称: * readBmp() *

*函数参数:

* char *bmpName -文件名字及路径 * *返回值:

* 0为失败,1为成功 *

*说明:给定一个图像文件名及其路径,读图像的位图数据、宽、高、颜色表及每像素 * 位数等数据进内存,存放在相应的全局变量中

***********************************************************************/ bool readBmp(char *bmpName) {

//二进制读方式打开指定的图像文件 FILE *fp=fopen(bmpName,\ if(fp==0) return 0;

//跳过位图文件头结构BITMAPFILEHEADER fseek(fp, sizeof(BITMAPFILEHEADER),0);

//定义位图信息头结构变量,读取位图信息头进内存,存放在变量head中 BITMAPINFOHEADER head;

fread(&head, sizeof(BITMAPINFOHEADER), 1,fp); //获取图像宽、高、每像素所占位数等信息

bmpWidth = head.biWidth; bmpHeight = head.biHeight; biBitCount = head.biBitCount;

//定义变量,计算图像每行像素所占的字节数(必须是4的倍数) int lineByte=(bmpWidth * biBitCount/8+3)/4*4; //灰度图像有颜色表,且颜色表表项为256 if(biBitCount==8){

//申请颜色表所需要的空间,读颜色表进内存 pColorTable=new RGBQUAD[256];

fread(pColorTable,sizeof(RGBQUAD),256,fp); }

//申请位图数据所需要的空间,读位图数据进内存 pBmpBuf=new unsigned char[lineByte * bmpHeight]; fread(pBmpBuf,1,lineByte * bmpHeight,fp); //关闭文件 fclose(fp); return 1; }

2.BMP文件的存盘

给定图像路径名以及图像的数据,对图像的写操作也是按照BMP文件4个组成部分进行分别处理的。其基本流程如图1-9所示。

saveBmp()函数实现了BMP文件的写操作,该函数的说明及代码实现如下。

/***************************************** * 函数名称: * saveBmp() *

*函数参数:

* char *bmpName-文件名字及路径

* unsigned char *imgBuf-待存盘的位图数据 * int width-以像素为单位待存盘位图的宽 * int height-以像素为单位待存盘位图高 * int biBitCount-每像素所占位数 * RGBQUAD *pColorTable-颜色表指针 *返回值:

* 0为失败,1为成功 *

*说明:给定一个图像位图数据、宽、高、颜色表指针及每像素所占的位数等信息, * 将其写到指定文件中

***********************************************************************/ bool saveBmp(char *bmpName, unsigned char *imgBuf, int width, int height, int biBitCount, RGBQUAD *pColorTable) {

//如果位图数据指针为0,则没有数据传入,函数返回 if(!imgBuf) return 0;

//颜色表大小,以字节为单位,灰度图像颜色表为1024字节,彩色图像颜色表大小为0 int colorTablesize=0; if(biBitCount==8) colorTablesize=1024;

//待存储图像数据每行字节数为4的倍数 int lineByte=(width * biBitCount/8+3)/4*4; //以二进制写的方式打开文件 FILE *fp=fopen(bmpName,\ if(fp==0) return 0;

//申请位图文件头结构变量,填写文件头信息

BITMAPFILEHEADER fileHead; fileHead.bfType = 0x4D42;//bmp类型 //bfSize是图像文件4个组成部分之和

fileHead.bfSize= sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + colorTablesize + lineByte*height; fileHead.bfReserved1 = 0; fileHead.bfReserved2 = 0;

//bfOffBits是图像文件前3个部分所需空间之和 fileHead.bfOffBits=54+colorTablesize; //写文件头进文件

fwrite(&fileHead, sizeof(BITMAPFILEHEADER),1, fp); //申请位图信息头结构变量,填写信息头信息 BITMAPINFOHEADER head; head.biBitCount=biBitCount; head.biClrImportant=0; head.biClrUsed=0; head.biCompression=0; head.biHeight=height; head.biPlanes=1; head.biSize=40;

head.biSizeImage=lineByte*height; head.biWidth=width; head.biXPelsPerMeter=0; head.biYPelsPerMeter=0; //写位图信息头进内存

fwrite(&head, sizeof(BITMAPINFOHEADER),1, fp); //如果灰度图像,有颜色表,写入文件 if(biBitCount==8)

fwrite(pColorTable, sizeof(RGBQUAD),256, fp); //写位图数据进文件

fwrite(imgBuf, height*lineByte, 1, fp); //关闭文件 fclose(fp); return 1; }

对于readBmp()和saveBmp()函数的简单调用如下:

void main() {

//读入指定BMP文件进内存 char readPath[]=\ readBmp(readPath); //输出图像的信息

printf(\biBitCount=%d\\n\biBitCount); //将图像数据存盘

char writePath[]=\

saveBmp(writePath, pBmpBuf, bmpWidth, bmpHeight, biBitCount, pColorTable); //清除缓冲区,pBmpBuf和pColorTable是全局变量,在文件读入时申请的空间 delete []pBmpBuf; if(biBitCount==8) delete []pColorTable; }

该main()函数将指定BMP文件读入内存,将图像信息打印输出,最后又原样存入指定文件中。读者可以打开程序当前目录下的“dog.bmp”和“dogcpy.bmp”两个文件进行对比。

以上对于BMP文件的读写函数仅针对灰度图像(biBitCount=8)和彩色图像(biBitCount=24)两种格式,对于其他如biBitCount=1的图像类型,读者可以根据需要,自己对程序作简单

的修改即可实现。本书中后续的代码实现也都是围绕灰度和彩色两种格式进行的,希望读者予以注意。

1.2.3 BMP图像位图数据的访问

上面main()函数将图像文件读入内存,又写到文件里去,那么在读入图像数据后、写入文件前的一段时间里,图像的数据是在内存中存在的,这也是我们可以修改(访问)图像数据的时机所在。

假设内存中位图数据的指针为pBmpBuf,一行像素所占的字节数为lineByte(4的倍数)那么,对于灰度图像,第i行第j列的像素指针(所在的存储空间位置)为pBmpBuf+i*lineByte+j,*(pBmpBuf+i*lineByte+j)是该像素的灰度值,如果想让该像素变成指定颜色,只需要给*(pBmpBuf+i*lineByte+j)赋指定的值即可;对于彩色图像,每像素占3个字节,那么pBmpBuf+i*lineByte+j*3+0、pBmpBuf+i*lineByte+j*3+1、pBmpBuf+i*lineByte+j*3+2分别代表了第i行第j列像素B、G、R三个分量的指针,若想给该点指定一种颜色,则需要给三个分量分别赋值。

下面的main()函数中,将读入的图像数据左下角1/4部分置成黑色并存盘,图1-10所示是程序运行前后图像的变化情况。

void main() {

//读入指定BMP文件进内存 char readPath[]=\ readBmp(readPath); //输出图像的信息

printf(\ //循环变量,图像的坐标 int i,j; //每行字节数

int lineByte=(bmpWidth*biBitCount/8+3)/4*4;

//循环变量,针对彩色图像,遍历每像素的三个分量 int k;

//将图像左下角1/4部分置成黑色 if(biBitCount==8){//对于灰度图像 for(i=0;i

else if(biBitCount==24){//彩色图像 for(i=0;i

for(k=0;k<3;k++)//每像素RGB三个分量分别置0才变成黑色 *(pBmpBuf+i*lineByte+j*3+k)=0; } } }

//将图像数据存盘

char writePath[]=\

saveBmp(writePath, pBmpBuf, bmpWidth, bmpHeight, biBitCount, pColorTable); //清除缓冲区,pBmpBuf和pColorTable是全局变量,在文件读入时申请的空间 delete []pBmpBuf; if(biBitCount==8) delete []pColorTable; }

void CChap1_4View:: OnDataAccess () {

//获取文档类句柄

CChap1_4Doc* pDoc = GetDocument(); //如果DIB为空,则返回 if(pDoc->m_pDib==NULL) return;

//定义infoHead变量指向DIB中的BITMAPINFOHEADER结构

BITMAPINFOHEADER *infoHead=(BITMAPINFOHEADER*)pDoc->m_pDib; //获取DIB的宽、高、每像素所占位数 int width=infoHead->biWidth; int height=infoHead->biHeight; int biBitCount=infoHead->biBitCount; //每行像素所占字节数,必须是4的倍数 int lineByte=(width*biBitCount/8+3)/4*4;

//求颜色表的长度,彩色图像颜色表长度为0,非彩色图像(灰度图像) //颜色表长度为pow(2,biBitCount) int colorTableLng; if(biBitCount!=24)

colorTableLng=pow(2,biBitCount); else

colorTableLng=0;

//用pImgData指向DIB的位图数据起始位置

unsigned char* pImgData=(unsigned char*)(pDoc->m_pDib+ sizeof(BITMAPINFOHEADER)+sizeof(RGBQUAD) * colorTableLng); //以下将图像数据左下角1/4置成黑色 //循环变量,图像的坐标 int i, j;

if(biBitCount==8){//灰度图像 for(i=0;i

else{//彩色图像 int k;

for(i=0;i

for(k=0;k<3;k++)//彩色图像,每像素三个分量都置0 *(pImgData+i*lineByte+j*3+k)=0; } } }//刷新显示 Invalidate(); }

图1-19所示是chap1_4应用程序运行的界面及数据访问的结果,其中图1-19(a)是运行界面,图1-19(b)是点击“数据访问”菜单的运行结果。

(a)chap1-4的运行界面 (b)点击“数据访问”菜单的结果

图1-19 chap1_4的运行界面

1.4.4 面向对象的DIB的读写及访问——ImgCenterDib类

上一节我们用面向过程的方式实现了图像的可视化编程,本节我们从另一个全新的角度——面向对象的方式实现图像的可视化编程。但是MFC中没有封装DIB类,我们可以自己完成。我们声明的类叫ImgCenterDib(Image Center DIB,取图像处理中心编写的DIB之意),里面封装了DIB位图处理所需要的基本的成员变量和成员函数。我们在后续章节的算法实现中都是以该类作为基类来派生的,这样既充分利用了面向对象程序设计的封装、继承等特性,又使得代码易于维护和移植。

1.ImgCenterDib类的定义

ImgCenterDib类的定义在头文件“ImageCenterDib.h”中,以下是该类的完整定义。 class ImgCenterDib { public:

//图像数据指针

unsigned char * m_pImgData 1.ImgCenterDib类的定义

ImgCenterDib类的定义在头文件“ImageCenterDib.h”中,以下是该类的完整定义。 class ImgCenterDib { public:

//图像数据指针

unsigned char * m_pImgData //图像颜色表指针

LPRGBQUAD m_lpColorTable; //每像素占的位数 int m_nBitCount; private:

//指向DIB的指针(包含BITMAPFILEHEADER、BITMAPINFOHEADER和颜色表) LPBYTE m_lpDib; //图像信息头指针

LPBITMAPINFOHEADER m_lpBmpInfoHead; //调色板句柄

HPALETTE m_hPalette; //颜色表长度

int m_nColorTableLength; public:

//不带参数的构造函数 ImgCenterDib(); //带参数的构造函数

ImgCenterDib(CSize size, int nBitCount, LPRGBQUAD lpColorTable, unsigned char *pImgData); //析构函数 ~ImgCenterDib(); //DIB读函数

BOOL Read(LPCTSTR lpszPathName); //DIB写函数

BOOL Write(LPCTSTR lpszPathName); //DIB显示函数

BOOL Draw(CDC* pDC, CPoint origin, CSize size); //逻辑调色板生成函数 void MakePalette();

//获取DIB的尺寸(宽、高) CSize GetDimensions(); //清理空间 void Empty();

//用新的数据替换当前DIB

void ReplaceDib(CSize size, int nBitCount, LPRGBQUAD lpColorTable, unsigned char *pImgData); //计算颜色表的长度

int ComputeColorTabalLength(int nBitCount);

protected:

//图像的宽,像素为单位 int m_imgWidth; //图像的高,像素为单位 int m_imgHeight; };

在该类的实现中,可以通过BMP文件读取的方式生成DIB,也可以通过参数传递的方式生成DIB,无论哪种方式,该类都是一次性为DIB的指针m_lpDib分配内存,这与1.4.3节对BMP文件的可视化编程(读写及访问)方式是一致的。

ImgCenterDib类的代码实现在文件“ImgCenterDib.cpp”中,下面分别讲述该类各成员函数的代码实现。

2.构造函数和析构函数

类的构造函数用来完成数据成员的初始化工作,系统在创建类的对象时自动调用构造函数。类的析构函数用来释放被分配的内存空间,当类的对象消失时系统自动调用该函数。以下是ImgCenterDib构造函数和析构函数的代码实现。

/*********************************************************************** * 函数名称: * ImgCenterDib() *

*说明:无参数的构造函数,对成员变量进行初始化,创建一个对象

***********************************************************************/ ImgCenterDib::ImgCenterDib() {

m_lpDib=NULL;//初始化m_lpDib为空 m_lpColorTable=NULL;//颜色表指针为空 m_pImgData=NULL; //图像数据指针为空 m_lpBmpInfoHead=NULL; //图像信息头指针为空 m_hPalette = NULL;//调色板为空 }

本文来源:https://www.bwwdw.com/article/ztc.html

Top