数字图像处理(极简) 第三章 BMP文件的读取与显示(docx)

建议先修课程:高等数学(微积分)、线性代数。
参考书目:
1、图像工程(上册)——图像处理(第4版) 章毓晋 清华大学出版社


链接:https://pan.baidu.com/s/1hEMGRUotQFL_RtGap6JaUg
提取码:0000

三 BMP文件的读取与显示

BMP文件是一个非常简单的图像存储格式。学习处理图像时,我们先从最简单的格式入手。处理其它复杂的格式时,往往采用先解压缩再进行后续处理的方法。解压缩后的数据往往与BMP等非压缩格式(通常是,压缩的BMP很少见)相仿。
BMP文件主要应用于Windows平台。因此,继续下面的学习之前,你应当具备一些Windows编程的知识。

BMP文件的结构分为四部分:
【1】文件头。
【2】信息头。
【3】调色板(可选)。
【4】图像数据。

文件头的格式定义在<wingdi.h>中:
typedef struct tagBITMAPFILEHEADER {
WORD bfType;
DWORD bfSize;
WORD bfReserved1;
WORD bfReserved2;
DWORD bfOffBits;
} BITMAPFILEHEADER, FAR *LPBITMAPFILEHEADER, *PBITMAPFILEHEADER;
其中
<minwindef.h>:
#define far
#define FAR far
typedef unsigned long DWORD;
typedef unsigned short WORD;
这个结构的长度固定为14字节。各个成员的解释如下:

bfType
指定文件类型,其值为42 4D,即BM。也就是说所有.bmp文件的头两个字节都是“BM”。

bfSize
指定文件大小(包括文件头)。该值为DWORD值,即双字(4字节),因此BMP文件的大小最大为4 GB(232字节)。

bfReserved1,bfReserved2
保留字,这里不考虑。

bfOffBits
为从文件头到实际的位图数据的偏移字节数。

信息头的格式也定义在<wingdi.h>中:
typedef struct tagBITMAPINFOHEADER{
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;
其中
<winnt.h>:
typedef long LONG;
这个结构的长度固定为40字节。各个成员的解释如下:

biSize
值为常量40,指定此结构的长度。

biWidth,biHeight
指定图像的宽和高(单位:像素)。

biPlanes
值固定为1,这里不考虑。

biBitCount
指定表示颜色时要用到的位数,取值可以为:1(黑白二色图)、4(16色图)、8(256色)、24(真彩色图)。

biCompression
指定位图是否压缩。Windows位图可以采用压缩格式,但用得不多。我们只讨论不压缩的情况,biCompression为BI_RGB。
<wingdi.h>:
#define BI_RGB 0L
#define BI_RLE8 1L
#define BI_RLE4 2L
#define BI_BITFIELDS 3L
#define BI_JPEG 4L
#define BI_PNG 5L

biSizeImage
在图像为压缩格式时,刻画位图数据的长度。图像未压缩时,此值为零。

biXPelsPerMeter,biYPelsPerMeter
指定目标设备的水平和垂直分辨率。单位:每米的像素个数。

biClrUsed
指定本图像实际用到的颜色数(决定调色板数组元素的个数),如果该值为零,则用到的颜色数为2的biBitCount次方。
一个规范的BMP文件应当只有在真彩色时才将该值置为零。

biClrImportant
指定本图像中重要的颜色数量。该值通常为零,即认为所有的颜色都是重要的。

当图像不是真彩色时,在信息头之后还有调色板。调色板实际上是一个数组,共有biClrUsed个元素(如果该值为零,则有2的biBitCount次方个元素)。数组中每个元素的是一个RGBQUAD结构,占4个字节,其定义位于<wingdi.h>:
typedef struct tagRGBQUAD {
BYTE rgbBlue;
BYTE rgbGreen;
BYTE rgbRed;
BYTE rgbReserved;
} RGBQUAD;
一个标准的256灰度图像,其调色板的第0到255项的前三项rgbBlue、rgbGreen、rgbRed均分别为0到255。后续的位图数据中,每1个字节代表1个像素,索引值正好就是灰度值。

对于用到调色板的位图,图像数据就是该像素颜色在调色板中的索引值;对于真彩色图,图像数据就是实际的RGB值。下面就2色、16色、256色位图和真彩色位图分别介绍。
2色位图,用1位就可以表示该像素的颜色(一般0表示黑,1表示白),一个字节可以表示8个像素。
16色位图,用4位可以表示一个像素的颜色,所以一个字节可以表示2个像素。
256色位图,一个字节表示1个像素。
真彩色图,三个字节表示1个像素。

但是每一行的字节数并不简单等于宽度×单个像素占用的空间。BMP格式要求:每一行的字节数必须是4的整数倍。如果不是,则需要在一行的位图数据后补齐。

BMP文件的数据是从下到上,从左到右的。也就是说,从文件中最先读到的是图像最下面一行的左边第一个像素,然后是左边第二个像素……接下来是倒数第二行左边第一个像素,左边第二个像素……依次类推,最后得到的是最上面一行的最右一个像素。

在应用程序的GUI中显示位图,需要用到<wingdi.h>中的Windows API函数StretchDIBits:
StretchDIBits函数将DIB,JPEG或PNG图像中像素矩形的颜色数据复制到指定的目标矩形。如果目标矩形大于源矩形,则此函数会拉伸颜色数据的行和列以适合目标矩形。如果目标矩形小于源矩形,则此函数使用指定的栅格操作压缩行和列。
其语法如下:
int StretchDIBits(
HDC hdc,
int xDest,
int yDest,
int DestWidth,
int DestHeight,
int xSrc,
int ySrc,
int SrcWidth,
int SrcHeight,
const VOID * lpBits,
const BITMAPINFO* lpbmi,
UINT iUsage,
DWORD rop
);
其中
<winnt.h>:
#define VOID void
<minwindef.h>:
typedef unsigned int UINT;

每个参数的解释如下:

hdc
目标设备上下文的句柄。
设备上下文也称设备描述表,是一种Windows数据结构,其中包含有关设备(例如显示器或打印机)的绘图属性的信息。 所有绘图调用都通过设备上下文对象进行,该对象封装用于绘制线条、形状和文本的Windows API。设备上下文允许Windows中与设备无关的绘图。设备上下文可用于绘制到屏幕、打印机或元文件(metafile)。

xDest
目标矩形左上角的x坐标(以逻辑单位表示)。

yDest
目标矩形左上角的y坐标(以逻辑单位表示)。

DestWidth
目标矩形的宽度,以逻辑单位为单位。

DestHeight
目标矩形的高度,以逻辑单位为单位。

xSrc
图像中源矩形的x坐标(以像素为单位)。

ySrc
图像中源矩形的y坐标(以像素为单位)。

SrcWidth
图像中源矩形的宽度(以像素为单位)。

SrcHeight
图像中源矩形的高度(以像素为单位)。

lpBits
指向图像位的指针,图像位存储为字节数组。

lpbmi
指向包含有关DIB信息的BITMAPINFO结构的指针。

iUsage
指定是否提供了BITMAPINFO结构的bmiColors成员。如果提供,则指定bmiColors是否包含显式的红色,绿色,蓝色(RGB)值或索引。iUsage参数必须是以下值之一:
DIB_PAL_COLORS
该数组包含进入源设备上下文的逻辑调色板的16位索引。
DIB_RGB_COLORS
颜色表包含文字RGB值。

rop
光栅操作代码,指定如何将源像素,目标设备上下文的当前画笔和目标像素组合在一起以形成新图像。

以MFC(提示:MFC已经被市场淘汰,这里只做演示用。除非维护老项目,否则不应选用MFC。)为例,在视图类(负责前台响应)的OnDraw函数中,使用StretchDIBits显示位图,则每次窗口被重绘时就会显示指定的位图:
void CTestView::OnDraw(CDC* pDC) {
CTESTDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
if (!pDoc) return;

// TODO: add draw code for native data here
if (nullptr == lpBitsInfo) return;
LPVOID lpBits = (LPVOID)&lpBitsInfo->bmiColors[lpBitsInfo->bmiHeader.biClrUsed];
StretchDIBits(pDC->GetSafeHdc(),
	0, 0, lpBitsInfo->bmiHeader.biWidth, lpBitsInfo->bmiHeader.biHeight,
	0, 0, lpBitsInfo->bmiHeader.biWidth, lpBitsInfo->bmiHeader.biHeight,
	lpBits, lpBitsInfo, DIB_RGB_COLORS, SRCCOPY);

}
其中
<minwindef.h>:
typedef void far LPVOID;
<wingdi.h>:
#define DIB_RGB_COLORS 0 /
color table in RGBs /
#define DIB_PAL_COLORS 1 /
color table in palette indices /
#define SRCCOPY (DWORD)0x00CC0020 /
dest = source */

此代码中,原图像与目标显示区域是按照1:1的比例来显示的,无放缩。

lpbmi参数这里为lpBitsInfo,它是一个BITMAPINFO结构的指针,此结构定义于<wingdi.h>:
typedef struct tagBITMAPINFO {
BITMAPINFOHEADER bmiHeader;
RGBQUAD bmiColors[1];
} BITMAPINFO, FAR *LPBITMAPINFO, *PBITMAPINFO;
第一个成员bmiHeader便是BMP文件的信息头;第二个成员的长度通常远远大于1,包含调色板(如果有的话)和位图数据。

lpBits参数这里为lpBits,是一个void型指针。这里指向调色板(如果有)之后的位图数据的首个字节。

处理图像内容时要注意:如要访问像素点<i,j>的数据,则其在内存中的位置应当为:
S+L_LINE (h-1-i)+j
其中S为位图数据实际的开始位置,即上文的lpBits;L_LINE为一行的字节数:
L_LINE=(wb+31)/32×4
w为图像的宽度,b为位图的位数(1、4、8、24)。
可见,L_LINE是对齐之后的长度。wb是一整行像素占用的位数。而当32 | wb时,上式也可写为
L_LINE=(wb+31)/32×4=wb/32×4=8wb
此时一行的长度已经按4字节对齐。多出来的31会被整除运算截断。容易看出:已对齐的情况下,一旦一行的数据再多出1位,那么就要多占用4个字节。

如果需要遍历一个BMP图像的每一个像素点<i,j>,那么循环应当这样写:
for (LONG i = 0; i < h; ++i)
for (LONG j = 0; j < w; ++j) {

}
i对应行编号,j对应列编号。
注意:一幅w×h的图像,其水平像素(列数)和垂直像素(行数)的取值范围分别是[0, w-1]和[0, h-1]之间的整数。

猜你喜欢

转载自blog.csdn.net/COFACTOR/article/details/109854282