不完全掌握图像数据之前,获取当前图像的轮廓数据。
GIF文件格式分为87和89两个版本,对于87这个版本,该文件主要是有五个部分组成,它,们是按顺序出现的:文件头块、逻辑屏幕描述块、可选择的调色板块、图像数据块、最后是标志文件结束的尾块,该块总是取固定的值3BH。其中第一和第二两个块用GIF图像文件头结构描述:
GIFHEADER:{
DB Signature; //该字段占六个字节,为了用于指明图像为GIF格式,前三个字符必须为\,后三字符用于指定是哪个版本,87或89。 DW ScreenWidth;//
DW ScreenDepth;//占两个字节,以像素为单位表示图像的宽、高 DB GlobalFlagByte;//该字节的各个位用于调色版的描述 DB BackGroundColor;//代表图象的背景颜色的索引 DB AspectRatio;图像的长宽比 }
GIF格式中的调色板有通用调色板和局部调色板之分,因为GIF格式允许一个文件中存储多个图像,因此有这两种调色板,其中通用调色板适于文件中的所有图像,而局部调色板只适用于某一个图像。格式中的数据区域一般分为四个部分,图像数据识别区域,局部调色板数据,采用压缩算法得到的图象数据区域和结束标志区域。
在GIF89版本中,它包含七个部分,分别是文件头、通用调色板数据、图像数据区和四个补充数据区,它们主要是用于提示程序如何处理图像的。
三、JEPG图像文件
JEPG简称为联合摄影专家小组,作为一种技术,主要用于数字化图像的标准编码,JPEG主要采用有损的压缩编码方式,它比GIF、BMP图像文件要复杂的多,这不是短短的几页篇幅可以将清楚的,万幸的是,我们可以通过一些别的方法将该格式转化为BMP格式。读者需要知道的是在对JEPG文件格式编码时,通常需要分为以下四步:颜色转化、DCT变换、量化、编码。
以上介绍了一些常用的图像文件,对比较复杂的格式,如GIF和JEPG,仅仅作了极其浮浅的介绍,后文我们会和它们作进一步的接触。实际应用中,还有许多图像格式,文章中都没有提到,读者如果需要做进一步的研究,还需要参考一些关于图像格式方面的资料。 VC数字图像处理编程讲座之三
BMP图像的基本操作
上一讲我们主要介绍了图像的格式,其中重点说明了BMP文件的存储格式,同时对JEPG和GIF等常用格式作了简单的介绍。本节主要讲述如何操作BMP文
件,如对其读、写和显示等。
在实现数字图象处理的过程中,主要是通过对图像中的每一个像素点运用各种图像处理算法来达到预期的效果,所以进行图像处理的第一步,也是我们最关心的问题,是如何得到图像中每一个像素点的亮度值;为了观察和验证处理的图像效果,另一个需要解决的问题是如何将处理前后的图像正确的显示出来。我们这章内容就是解决这些问题。
随着科技的发展,图像处理技术已经渗透到人类生活的各个领域并得到越来越多的应用,但是突出的一个矛盾是图像的格式也是越来越多,目前图像处理所涉及的主要的图像格式就有很多种,如TIF、JEMP、BMP等等,一般情况下,为了处理简单方便,进行数字图像处理所采用的都是BMP格式的图像文件(有时也称为DIB格式的图像文件),并且这种格式的文件是没有压缩的。我们通过操作这种格式的文件,可以获取正确显示图像所需的调色板信息,图像的尺寸信息,图像中各个像素点的亮度信息等等,有了这些数据,开发人员就可以对图像施加各种处理算法,进行相应的处理。如果特殊情况下需要处理其它某种格式的图像,如GIF、JEMP等格式的图象文件,可以首先将该格式转换为BMP格式,然后再进行相应的处理。这一点需要读者清楚。
BMP格式的图像文件又可以分为许多种类,如真彩色位图、256色位图,采用RLE(游程编码)压缩格式的BMP位图等等。由于在实际的工程应用和图像算法效果验证中经常要处理的是256级并且是没有压缩的BMP灰度图像,例如通过黑白采集卡采集得到的图像就是这种格式,所以我们在整个讲座中范例所处理的文件格式都是BMP灰度图像。如果读者对这种格式的位图能够作到熟练的操作,那么对于其余形式的BMP位图的操作也不会很困难。
BMP灰度图像作为Windows环境下主要的图像格式之一,以其格式简单,适应性强而倍受欢迎。正如我们在上一讲中介绍过的那样,这种文件格式就是每一个像素用8bit表示,显示出来的图像是黑白效果,最黑的像素的灰度(也叫作亮度)值为\,最白的像素的灰度值为\,整个图像各个像素的灰度值随机的分布在\到\的区间中,越黑的像素,其灰度值越接近于\,越白(既越亮)的像素,其灰度值越接近于\;与此对应的是在该文件类型中的颜色表项的各个RGB分量值是相等的,并且颜色表项的数目是256个。
在进行图像处理时,操作图像中的像素值就要得到图像阵列;经过处理后的图像的像素值需要存储起来;显示图像时要正确实现调色板、得到位图的尺寸信息等。结合这些问题,下面我们针对性的给出了操作灰度BMP图像时的部分函数实现代码及注释。
一、 BMP位图操作
首先我们回顾一下上讲中的重要信息:BMP位图包括位图文件头结构
BITMAPFILEHEADER、位图信息头结构 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修改为:
\bmp;*.dib)\\\\n.bmp\\\\nDib.Document\\\\nDib Document\。这样作的目的是为了在程序文件对话框中可以选择BMP或DIB格式的位图文件。
1、 读取灰度BMP位图
可以根据BMP位图文件的结构,操作BMP位图文件并读入图像数据,为此我们充分利用了VC的文档视图结构,重载了文挡类的 OnOpenDocument()函数,这样用户就可以在自动生成程序的打开文件对话框中选择所要打开的位图文件,然后程序将自动调用该函数执行读取数据的操作。该函数的实现代码如下所示:
BOOL CDibDoc::OnOpenDocument(LPCTSTR lpszPathName) {
LOGPALETTE *pPal;//定义逻辑调色板指针; pPal=new LOGPALETTE;//初始化该指针; CFile file;
CFileException fe;
if (!file.Open(lpszPathName, CFile::modeRead | CFile::shareDenyWrite, &fe))
{//以\读\的方式打开文件;
AfxMessageBox(\图像文件打不开!\return FALSE; }
DeleteContents();//删除文挡; BeginWaitCursor();
BITMAPFILEHEADER bmfHeader;//定义位图文件头结构; LPBITMAPINFO lpbmi; DWORD dwBitsSize; HANDLE hDIB;
LPSTR pDIB;//指向位图数据的指针;
BITMAPINFOHEADER *bmhdr;//指向位图信息头结构的指针 dwBitsSize = file.GetLength();//得到文件长度 if (file.Read((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 (file.ReadHuge(pDIB, dwBitsSize - sizeof(BITMAPFILEHEADER)) != dwBitsSize - sizeof(BITMAPFILEHEADER) ) {
::GlobalUnlock((HGLOBAL)hDIB); hDIB=NULL; return FALSE;
}//此时pDIB数据块中读取的数据包括位图头信息、位图颜色表、图像像素的灰度值;
bmhdr=(BITMAPINFOHEADER*)pDIB;//为指向位图信息头结构的指针赋值; ::GlobalUnlock((HGLOBAL)hDIB);
if ((*bmhdr).biBitCount!=8)//验证是否为8bit位图 {
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;i<256;i++)
{//每个颜色表项的R、G、B值相等,并且各个值从\到\序列展开; Pal->palPalentry[i].peRed=lpbmi->bmiColors[i].rgbRed;
pPal->palPalentry[i].peGreen=lpbmi->bmiColors[i].rgbGreen; pPal->palPalentry[i].peBlue= lpbmi->bmiColors[i].rgbBlue;; pPal->palPalentry[i].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为资源的名字\If(hRes==NULL) return NULL HGLOBAL hData=::LoadResource(hInst, hRes);//转载资源数据并返回该句柄; return hData; }