整理8位灰度图像BMP的保存.docx
《整理8位灰度图像BMP的保存.docx》由会员分享,可在线阅读,更多相关《整理8位灰度图像BMP的保存.docx(16页珍藏版)》请在冰豆网上搜索。
![整理8位灰度图像BMP的保存.docx](https://file1.bdocx.com/fileroot1/2023-6/16/da29327e-3e85-4f70-aa17-79decc4cba14/da29327e-3e85-4f70-aa17-79decc4cba141.gif)
整理8位灰度图像BMP的保存
8位灰度图像BMP的保存[zz]2012-04-0721:
01在图像处理中,我们经常需要将真彩色图像转换为黑白图像。
严格的讲应该是灰度图,因为真正的黑白图像是二色,即只有纯黑,纯白二色。
开始之前,我们先简单补充一下计算机中图像的表示原理。
计算机中的图像大致可以分成两类:
位图(Bitmap)和矢量图(Metafile)。
位图可以视为一个二维的网格,整个图像就是由很多个点组成的,点的个数等于位图的宽乘以高。
每个点被称为一个像素点,每个像素点有确定的颜色,当很多个像素合在一起时就形成了一幅完整的图像。
我们通常使用的图像大部分都是位图,如数码相机拍摄的照片,都是位图。
因为位图可以完美的表示图像的细节,能较好的还原图像的原景。
但位图也有缺点:
第一是体积比较大,所以人们开发了很多压缩图像格式来储存位图图像,目前应用最广的是JPEG格式,在WEB上得到了广泛应用,另外还有GIF,PNG等等。
第二是位图在放大时,不可避免的会出现“锯齿”现象,这也由位图的本质特点决定的。
所以在现实中,我们还需要使用到另一种图像格式:
矢量图。
同位图不同,矢量图同位图的原理不同,矢量图是利用数学公式通过圆,线段等绘制出来的,所以不管如何放大都不会出现变形,但矢量图不能描述非常复杂的图像。
所以矢量图都是用来描述图形图案,各种CAD软件等等都是使用矢量格式来保存文件的。
在讲解颜色转换之前,我们要先对位图的颜色表示方式做一了解。
位图中通常是用RGB三色方式来表示颜色的(位数很少时要使用调色板)。
所以每个像素采用不同的位数,就可以表示出不同数量的颜色。
如下图所示:
每像素的位数
一个像素可分配到的颜色数量
1
2^1=2
2
2^2=4
4
2^4=16
8
2^8=256
16
2^16=65,536
24
2^24=16,777,216
从中我们可以看出,当使用24位色(3个字节)时,我们可以得到1600多万种颜色,这已经非常丰富了,应该已接近人眼所能分辨的颜色了。
现在计算机中使用最多的就是24位色,别外在GDI+中还有一种32位色,多出来的一个通道用来描述Alpha,即透明分量。
24位色中3个字节分别用来描述R,G,B三种颜色分量,我们看到这其中是没有亮度分量的,这是因为在RGB表示方式中,亮度也是直接可以从颜色分量中得到的,每一颜色分量值的范围都是从0到255,某一颜色分量的值越大,就表示这一分量的亮度值越高,所以255表示最亮,0表示最暗。
那么一个真彩色像素点转换为灰度图时它的亮度值应该是多少呢,首先我们想到的平均值,即将R+G+B/3。
但现实中我们使用的却是如下的公式:
Y=0.299R+0.587G+0.114B
这个公式通常都被称为心理学灰度公式。
这里面我们看到绿色分量所占比重最大。
因为科学家发现使用上述公式进行转换时所得到的灰度图最接近人眼对灰度图的感觉。
因为灰度图中颜色数量一共只有256种(1个字节),所以转换后的图像我们通常保存为8位格式而不是24位格式,这样比较节省空间。
而8位图像是使用调色板方式来保存颜色的。
而不是直接保存颜色值。
调色板中可以保存256颜色,所以可以正好可以将256种灰度颜色保存到调色版中。
下面是个私人例子:
BITMAPFILEHEADERtargetfileheader;
BITMAPINFOHEADERtargetinfoheader;
memset(&targetfileheader,0,sizeof(BITMAPFILEHEADER));
memset(&targetinfoheader,0,sizeof(BITMAPINFOHEADER));
//构造灰度图的文件头
targetfileheader.bfOffBits=(DWORD)sizeof(BITMAPFILEHEADER)+(DWORD)sizeof(BITMAPINFOHEADER)+sizeof(RGBQUAD)*256;
targetfileheader.bfSize=192*192+sizeof(RGBQUAD)*256+sizeof(BITMAPFILEHEADER)+sizeof(BITMAPINFOHEADER);
targetfileheader.bfReserved1=0;
targetfileheader.bfReserved2=0;
targetfileheader.bfType=0x4d42;
//构造灰度图的信息头
targetinfoheader.biBitCount=8;
targetinfoheader.biSize=sizeof(BITMAPINFOHEADER);
targetinfoheader.biHeight=192;
targetinfoheader.biWidth=192;
targetinfoheader.biPlanes=1;
targetinfoheader.biCompression=BI_RGB;
targetinfoheader.biSizeImage=0;
targetinfoheader.biXPelsPerMeter=0;
targetinfoheader.biYPelsPerMeter=0;
targetinfoheader.biClrImportant=0;
targetinfoheader.biClrUsed=0;
//构造灰度图的调色版
RGBQUADrgbquad[256];
inti;
for(i=0;i<256;i++)
{
rgbquad[i].rgbBlue=i;
rgbquad[i].rgbGreen=i;
rgbquad[i].rgbRed=i;
rgbquad[i].rgbReserved=0;
}
BYTE*targetbuf;
targetbuf=newBYTE[192*192];
//由于BMP图像对于行是倒置的,即图像显示的第一行是最后一行数据,所以要倒置,这里的pcutface已经////是灰度图像了
for(longi=191;i>=0;i--)
{
for(longj=0;j<192;j++)
{
targetbuf[i*192+j]=pcutface[(191-i)*192+j];
}
}
CFilecf;
if(!
cf.Open(LPCTSTR("f:
\\fire.BMP"),CFile:
:
modeCreate|CFile:
:
modeWrite))
return;
cf.Write(&targetfileheader,sizeof(BITMAPFILEHEADER));
cf.Write(&targetinfoheader,sizeof(BITMAPINFOHEADER));
cf.Write(&rgbquad,sizeof(RGBQUAD)*256);
cf.Write(targetbuf,192*192);//这里targetbuf的大小为192x192
cf.Close();
开发灰度位图处理
图像处理技术已经渗透到人类生活的各个领域并得到越来越多的应用,图像处理所涉及的图像格式有很多种,如TIF、JEMP、BMP等等,工程应用中经常要处理256级的灰度BMP图像,如通过黑白采集卡采集得到的图像。
BMP灰度图像作为Windows环境下主要的图像格式之一,以其格式简单,适应性强而倍受欢迎。
在进行图像处理时,操作图像中的像素值就要得到图像阵列;经过处理后的图像的像素值存储起来;显示图像时要正确实现调色板,结合这些问题,文章针对性的给出了操作灰度BMP图像时的部分函数实现代码及注释。
一、BMP位图操作
BMP位图包括位图文件头结构BITMAPFILEHEADER、位图信息头结构BITMAPINFOHEADER、位图颜色表RGBQUAD和位图像素数据四部分。
处理位图时要根据文件的这些结构得到位图文件大小、位图的宽、高、实现调色板、得到位图像素值等等。
对于256级灰度图像每个像素用8bit表示颜色的索引值,这里要注意的一点是在BMP位图中,位图的每行像素值要填充到一个四字节边界,即位图每行所占的存储长度为四字节的倍数,不足时将多余位用0填充。
在处理图像应用程序的文档类(CdibDoc.h)中声明如下宏及公有变量:
#defineWIDTHBYTES(bits)(((bits)+31)/32*4)//计算图像每行象素所占的字节数目
HANDLEm_hDIB;//存放位图数据的句柄
CPalette*m_palDIB;//指向调色板Cpalette类的指针
CSizem_sizeDoc;file:
//初始化视图的尺寸
1、读取灰度BMP位图
根据BMP位图文件的结构,操作BMP位图文件读入数据,重载了文挡类的OnOpenDocument函数如下:
BOOLCDibDoc:
:
OnOpenDocument(LPCTSTRlpszPathName)
{
CFilefile;
CFileExceptionfe;
if(!
file.Open(lpszPathName,CFile:
:
modeRead|CFile:
:
shareDenyWrite,&fe))
{
AfxMessageBox("文件打不开");
returnFALSE;
}//打开文件
DeleteContents();//删除文挡
BeginWaitCursor();
BITMAPFILEHEADERbmfHeader;//定义位图文件头结构
DWORDdwBitsSize;
HANDLEhDIB;
LPSTRpDIB;
BITMAPINFOHEADER*bmhdr;//指向位图信息头结构的指针
dwBitsSize=file.GetLength();//得到文件长度
if(file.Read((LPSTR)&bmfHeader,sizeof(bmfHeader))!
=
sizeof(bmfHeader))
returnFALSE;
if(bmfHeader.bfType!
=0x4d42)file:
//检查是否为BMP文件
returnFALSE;
hDIB=(HANDLE):
:
GlobalAlloc(GMEM_MOVEABLE|
GMEM_ZEROINIT,dwBitsSize);
file:
//申请缓冲区
if(hDIB==0)
{
returnFALSE;
}
pDIB=(LPSTR):
:
GlobalLock((HGLOBAL)hDIB);
file:
//得到申请的缓冲区的指针
if(file.ReadHuge(pDIB,dwBitsSize-sizeof(BITMAPFILEHEADER))!
=
dwBitsSize-sizeof(BITMAPFILEHEADER))
{
:
:
GlobalUnlock((HGLOBAL)hDIB);
hDIB=NULL;
returnFALSE;
}//读数据,包括位图信息、位图颜色表、图像像素的灰度值
bmhdr=(BITMAPINFOHEADER*)pDIB;//为指向位图信息头结构的指针付值
:
:
GlobalUnlock((HGLOBAL)hDIB);
if((*bmhdr).biBitCount!
=8)file:
//验证是否为8bit位图
returnFALSE;
m_hDIB=hDIB;
InitDIBData();
file:
//自定义函数,根据读入的数据得到位图的宽、高、颜色表
file:
//来得到初始化视的尺寸、生成调色板
EndWaitCursor();
SetPathName(lpszPathName);//设置存储路径
SetModifiedFlag(FALSE);//设置文件修改标志为FALSE
returnTRUE;
}
2、灰度位图数据的存储
为了将图像处理后所得到的像素值保存起来,重载了文档类的OnSaveDocument函数,其具体实现如下:
BOOLCDibDoc:
:
OnSaveDocument(LPCTSTRlpszPathName)
{
CFilefile;
CFileExceptionfe;
BITMAPFILEHEADERbmfHdr;//位图文件头结构
LPBITMAPINFOHEADERlpBI;file:
//指向位图信息结构的指针
DWORDdwDIBSize;
if(!
file.Open(lpszPathName,CFile:
:
modeCreate|
CFile:
:
modeReadWrite|CFile:
:
shareExclusive,&fe))
{
AfxMessageBox("文件打不开");
}//打开文件
BOOLbSuccess=FALSE;
BeginWaitCursor();
lpBI=(LPBITMAPINFOHEADER):
:
GlobalLock((HGLOBAL)m_hDIB);
if(lpBI==NULL)
returnFALSE;
dwDIBSize=*(LPDWORD)lpBI+256*sizeof(RGBQUAD);
//PartialCalculation
DWORDdwBmBitsSize;//BMP文件信息结构所占的字节数
dwBmBitsSize=WIDTHBYTES((lpBI->biWidth)*((DWORD)lpBI->biBitCount)) *lpBI->biHeight;//存储时位图所有像素所占的总字节数
dwDIBSize+=dwBmBitsSize;
lpBI->biSizeImage=dwBmBitsSize;//位图所有像素所占的总字节数
file:
//以下五句为文件头结构填充值
bmfHdr.bfType=0x4d42;//文件为"BMP"类型
bmfHdr.bfSize=dwDIBSize+sizeof(BITMAPFILEHEADER);//文件总长度
bmfHdr.bfReserved1=0;
bmfHdr.bfReserved2=0;
bmfHdr.bfOffBits=(DWORD)sizeof(BITMAPFILEHEADER)+lpBI->biSize
+256*sizeof(RGBQUAD);
file:
//位图数据距问件头的偏移量
file.Write((LPSTR)&bmfHdr,sizeof(BITMAPFILEHEADER));//写文件头
file.WriteHuge(lpBI,dwDIBSize);
file:
//将位图信息(信息头结构、颜色表、像素数据)写入文件
:
:
GlobalUnlock((HGLOBAL)m_hDIB);
EndWaitCursor();
SetModifiedFlag(FALSE);//backtounmodified
returnTRUE;
}
二、调色板的操作
灰度图像要正确显示,必须实现逻辑调色板和系统调色板,通过在主框架类中处理Windows定义的消息WM_QUERYNEWPALETTE、WM_PALETTECHANGED及视图类中处理自定义消息WM_DOREALIZE(该消息在主框架窗口定义如下:
#defineWM_REALIZEPAL(WM_USER+100))来实现调色板的操作。
voidCMainFrame:
:
OnPaletteChanged(CWnd*pFocusWnd)
{file:
//总实现活动视的调色板
CMDIFrameWnd:
:
OnPaletteChanged(pFocusWnd);
CMDIChildWnd*pMDIChildWnd=MDIGetActive();
if(pMDIChildWnd==NULL)
return
CView*pView=pMDIChildWnd->GetActiveView();
ASSERT(pView!
=NULL);
SendMessageToDescendants(WM_DOREALIZE,(WPARAM)pView->m_hWnd);
file:
//通知所有子窗口系统调色板已改变
}
BOOLCMainFrame:
:
OnQueryNewPalette()//提供实现系统调色板的机会
{
//实现活动视的调色板
CMDIChildWnd*pMDIChildWnd=MDIGetActive();
if(pMDIChildWnd==NULL)
returnFALSE;//noactiveMDIchildframe(nonewpalette)
CView*pView=pMDIChildWnd->GetActiveView();
ASSERT(pView!
=NULL);
file:
//通知活动视图实现系统调色板
pView->SendMessage(WM_DOREALIZE,(WPARAM)pView->m_hWnd);
returnTRUE;
}
LRESULTCDibView:
:
OnDoRealize(WPARAMwParam,LPARAM)//实现系统调色板
{
ASSERT(wParam!
=NULL);
CDibDoc*pDoc=GetDocument();
if(pDoc->m_hDIB==NULL)
return0L;//mustbeanewdocument
CPalette*pPal=pDoc->m_palDIB;
file:
//调色板的颜色表数据在InitDIBData()函数中实现
if(pPal!
=NULL)
{
CMainFrame*pAppFrame=(CMainFrame*)AfxGetApp()->m_pMainWnd;
ASSERT_KINDOF(CMainFrame,pAppFrame);
CClientDCappDC(pAppFrame);
CPalette*oldPalette=appDC.SelectPalette(pPal,((HWND)wParam)!
=m_hWnd);
file:
//只有活动视才可以设为"FALSE",
//即根据活动视的调色板设为"前景"调色板
if(oldPalette!
=NULL)
{
UINTnColorsChanged=appDC.RealizePalette();//实现系统调色板
if(nColorsChanged>0)
pDoc->UpdateAllViews(NULL);//更新视图
appDC.SelectPalette(oldPalette,TRUE);
file:
//将原系统调色板置为逻辑调色板
}
else
{
TRACE0("\tSelectPalettefailedin
CDibView:
:
OnPaletteChanged\n");
}
}
注:
在调用API函数显示位图时,不要忘记设置逻辑调色板,即"背景"调色板,否则位图将无法正确显示。
三、图像的数字化处理
通过以上读文件的操作,已经得到图像数据,由于得到的数据包括多余信息,所以在进行数字图像处理时要进一步删除多余信息,只对位图的像素进行操作,以基于模板的高通滤波为例来讲述数字图像处理的实现:
voidCDibView:
:
OnMENUHighPass()
{HANDLEdata1handle;
LPBITMAPINFOHEADERlpBi;
CDibDoc*pDoc=GetDocument();
HDIBhdib;unsignedchar*hData;unsignedchar*data;
hdib=pDoc->GetHDIB();
BeginWaitCursor();
lpBi=(LPBITMAPINFOHEADER)GlobalLock((HGLOBAL)hdib);
hData=(unsignedchar*)FindDIBBits((LPSTR)lpBi);
pDoc->SetModifiedFlag(TRUE);
data1handle=GlobalAlloc(GMEM_SHARE,WIDTHBYTES(lpBi->biWidth*8)*lpBi->biHeight);
data=(unsignedchar*)GlobalLock((HGLOBAL)data1handle);
AfxGetApp()->BeginWaitCursor();
inti,j,s,t,ms=1;
intsum=0,sumw=0;
intmask[3][3]={{-1,-1,-1},{-1,9,-1},{-1,-1,-1}};
for(i=0;ibiHeight;i++)
for(j=0;jbiWidth;j++)
{
sumw=0;sum=0;
for(s=(-ms);s<=ms;s++)
for(t=(-ms);t<=ms;t++)
if(((i+s)>=0)&&((j+t)>=0)&&((i+s)biHeight)&&((j+t)biWidth))
{
sumw+=mask[1+s][1+t];
sum+=*(hData+(i+s)*WIDTHBYTES(lpBi->biWidth*8)+(j+t))*mask[1+s][1+t];
}
if(sumw==0)sumw=1;sum/=sumw;
if(sum>255)sum=255;
if(sum<0)sum=0;
*(data+i*WIDTHBYTES(lpBi->biWidth*8)+j)=sum;
}
for(j=0;jbiHeight;j++)
for(i=0;ibiWidth;i++)
*(hData+i*WIDTHBYTES(lpBi->biWidth*8)+j)=*(data+i*WIDTHBYTES(lpBi->biWidth*8)+j);
AfxGetApp()->EndWaitCursor();
GlobalUnlock((HGLOBAL)hdib);
GlobalUnlock(data1handle);
EndWaitCursor();
Invalidate(TRUE);
}
四、图像的基本操作处理
1、图像平移
图像平移只是改变图像在屏幕上的位置,图像本身并不发生变化。
假设原图像区域左上角坐标为(x0,y0),右下角坐标为(x1,y1),将图像分别沿x和y轴平移dx和dy,则新图像的左上角坐标为(x0+dx,y0+dy),右下角坐标为(x1+dx,y1+dy)。
坐标平移变换公式为:
x1=x+dx
y1=y+dy
在屏幕上实现图像的移动分为四个步骤:
⑴读原图像到缓冲区;
⑵擦除视图上原图像;
⑶计算平移后的新坐标。
⑷利用API函数:
:
StretchDIBits()在新的左