影像處理技術已經滲透到人類生活的各個領域並得到越來越多的應用,影像處理所涉及的映像格式有很多種,如TIF、JEMP、BMP等等,工程 應用中經常要處理256級的灰階BMP映像,如通過黑白採集卡採集得到的映像。BMP灰階映像作為Windows環境下主要的映像格式之一,以其格式簡 單,適應性強而倍受歡迎。在進行影像處理時,操作映像中的像素值就要得到映像陣列;經過處理後的映像的像素值儲存起來;顯示映像時要正確實現調色盤,結合 這些問題,文章針對性的給出了操作灰階BMP映像時的部分函數實現代碼及注釋。
一、 BMP位元影像操作
BMP位元影像包括位元影像檔案頭結構BITMAPFILEHEADER、位元影像資訊頭結構BITMAPINFOHEADER、位元影像顏色表 RGBQUAD和位元影像像素資料四部分。處理位元影像時要根據檔案的這些結構得到位元影像檔案大小、位元影像的寬、高、實現調色盤、得到位元影像像素值等等。對於256級灰 度映像每個像素用8bit表示顏色的索引值,這裡要注意的一點是在BMP位元影像中,位元影像的每行像素值要填充到一個四位元組邊界,即位元影像每行所佔的儲存長度為四 位元組的倍數,不足時將多餘位用0填充。
在處理映像應用程式的文檔類(CdibDoc.h)中聲明如下宏及公有變數:
#define WIDTHBYTES(bits) (((bits) + 31) / 32 * 4)//計算映像每行象素所佔的位元組數目
HANDLE m_hDIB;//存放位元影像資料的控制代碼
CPalette* m_palDIB;//指向調色盤Cpalette類的指標
CSize m_sizeDoc; file://初始化視圖的尺寸
1、 讀取灰階BMP位元影像
根據BMP位元影像檔案的結構,操作BMP位元影像檔案讀入資料,重載了文擋類的OnOpenDocument函數如下:
BOOL CDibDoc::OnOpenDocument(LPCTSTR lpszPathName)
{
CFile file;
CFileException fe;
if (!file.Open(lpszPathName, CFile::modeRead | CFile::shareDenyWrite, &fe))
{
AfxMessageBox("檔案打不開");
return FALSE;
}//開啟檔案
DeleteContents();//刪除文擋
BeginWaitCursor();
BITMAPFILEHEADER bmfHeader;//定義位元影像檔案頭結構
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) file://檢查是否為BMP檔案
return FALSE;
hDIB=(HANDLE) ::GlobalAlloc(GMEM_MOVEABLE |
GMEM_ZEROINIT, dwBitsSize);
file://申請緩衝區
if (hDIB == 0)
{
return FALSE;
}
pDIB = (LPSTR) ::GlobalLock((HGLOBAL)hDIB);
file://得到申請的緩衝區的指標
if (file.ReadHuge(pDIB, dwBitsSize - sizeof(BITMAPFILEHEADER)) !=
dwBitsSize - sizeof(BITMAPFILEHEADER) )
{
::GlobalUnlock((HGLOBAL)hDIB);
hDIB=NULL;
return FALSE;
}//讀資料,包括位元影像資訊、位元影像顏色表、映像像素的灰階值
bmhdr=(BITMAPINFOHEADER*)pDIB;//為指向位元影像資訊頭結構的指標付值
::GlobalUnlock((HGLOBAL)hDIB);
if ((*bmhdr).biBitCount!=8) file://驗證是否為8bit位元影像
return FALSE;
m_hDIB=hDIB;
InitDIBData();
file://自訂函數,根據讀入的資料得到位元影像的寬、高、顏色表
file:// 來得到初始化視的尺寸、產生調色盤
EndWaitCursor();
SetPathName(lpszPathName);//設定儲存路徑
SetModifiedFlag(FALSE); // 設定檔案修改標誌為FALSE
return TRUE;
}
2、 灰階位元影像資料的儲存
為了將影像處理後所得到的像素值儲存起來,重載了文檔類的OnSaveDocument函數,其具體實現如下:
BOOL CDibDoc::OnSaveDocument(LPCTSTR lpszPathName)
{
CFile file;
CFileException fe;
BITMAPFILEHEADER bmfHdr; // 位元影像檔案頭結構
LPBITMAPINFOHEADER lpBI; file://指向位元影像資訊結構的指標
DWORD dwDIBSize;
if (!file.Open(lpszPathName, CFile::modeCreate |
CFile::modeReadWrite | CFile::shareExclusive, &fe))
{
AfxMessageBox("檔案打不開");
}//開啟檔案
BOOL bSuccess = FALSE;
BeginWaitCursor();
lpBI = (LPBITMAPINFOHEADER) ::GlobalLock((HGLOBAL) m_hDIB);
if (lpBI == NULL)
return FALSE;
dwDIBSize = *(LPDWORD)lpBI + 256*sizeof(RGBQUAD);
// Partial Calculation
DWORD dwBmBitsSize;//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); // back to unmodified
return TRUE;
}
二、 調色盤的操作
灰階映像要正確顯示,必須實現邏輯調色盤和系統調色盤 ,通過在主架構類中處理Windows定義的訊息WM_QUERYNEWPALETTE 、WM_PALETTECHANGED及視圖類中處理自訂訊息WM_DOREALIZE(該訊息在主架構視窗定義如下:#define WM_REALIZEPAL (WM_USER+100))來實現調色盤的操作。
void CMainFrame::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://通知所有子視窗系統調色盤已改變
}
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);
file://通知活動視圖實現系統調色盤
pView->SendMessage(WM_DOREALIZE, (WPARAM)pView->m_hWnd);
return TRUE;
}
LRESULT CDibView::OnDoRealize(WPARAM wParam, LPARAM)//實現系統調色盤
{
ASSERT(wParam != NULL);
CDibDoc* pDoc = GetDocument();
if (pDoc->m_hDIB == NULL)
return 0L; // must be a new document
CPalette* pPal = pDoc->m_palDIB;
file://調色盤的顏色表資料在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);
file://只有活動視才可以設為"FALSE",
// 即根據活動視的調色盤設為"前景"調色盤
if (oldPalette != NULL)
{
UINT nColorsChanged = appDC.RealizePalette();//實現系統調色盤
if (nColorsChanged > 0)
pDoc->UpdateAllViews(NULL);//更新視圖
appDC.SelectPalette(oldPalette, TRUE);
file://將原系統調色盤置為邏輯調色盤
}
else
{
TRACE0("\tSelectPalette failed in
CDibView::OnPaletteChanged\n");
}
}
註:在調用API函數顯示位元影像時,不要忘記設定邏輯調色盤,即"背景"調色盤,否則位元影像將無法正確顯示。
三、映像的數字化處理
通過以上讀檔案的操作,已經得到映像資料,由於得到的資料包括多餘資訊,所以在進行數位影像處理時要進一步刪除多餘資訊,只對位元影像的像素進行操作,以基於模板的高通濾波為例來講述數位影像處理的實現 :
void CDibView::OnMENUHighPass()
{ HANDLE data1handle;
LPBITMAPINFOHEADER lpBi;
CDibDoc *pDoc=GetDocument();
HDIB hdib; unsigned char *hData; unsigned char *data;
hdib=pDoc->GetHDIB();
BeginWaitCursor();
lpBi=(LPBITMAPINFOHEADER)GlobalLock((HGLOBAL)hdib);
hData=(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();
int i,j,s,t,ms=1;
int sum=0,sumw=0;
int mask[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()在新的左上方座標位置處重新顯示原映像。
其中,擦除原映像的方法與圖形變換中擦除原圖形的方法一致,在實現中仍採用XOR異或方式畫圖擦除原映像。對於新座標值的計算還需要考慮邊界情況,不要在映像平移後超出允許的螢幕範圍。
2、映像顛倒
映像顛倒是指把定義好的映像地區上下翻轉地顯示在螢幕上。分析映像顛倒的過程,可發現每行的映像資訊都保持不變,而只是改變了行的順序,將第 一行與最後的第n行相互交換,第二行與第n - 1行交換……,依此類推,從而實現了映像的顛倒。只需採用按行交換的方式,即可方便地修改緩衝區內容,實現映像的顛倒。基本步驟如下:
(1)將原映像讀入緩衝區,並擦除原映像;
(2) 計算映像的高度,即行數height;計算映像寬度width;根據寬度、高度產生新緩衝區;
(3)把第一行與最末行交換,第2行與第n-1行交換……,依此類推,直至全部交換完畢。既原圖中的(x、y)點,在新產生的圖象中對應為x1=x,y1=height-1-y。把原圖中的象素值讀入新緩衝區的(x1,y1)點處。
(4)把交換後的映像緩衝區內容重新顯示在螢幕上。
3、映像鏡像變換
鏡像變換是指將指定地區的映像左右翻轉地顯示在螢幕。分析鏡像變換過程可以發現:每行映像資訊的處理方式是相同的,而且行順序不發生變化,只是每一行的像素資訊按從左至右的順序進行了左右顛倒,從而實現了鏡像變換。因此,採用按行逐點變換的方式實現映像的鏡像。
給出原圖中的任意點(x, y)鏡像變換後的新座標(x1, y1)的座標變換公式:
x1 = width-x-1
y1 = y
根據以上公式,對各個像素點計算新座標後,把原圖中的象素值讀入新緩衝區的(x1,y1)點處。
4、映像任意角度的旋轉
映像旋轉是指把定義的映像繞某一點以逆時針或順時針方向旋轉一定的角度,通常是指繞映像的中心以逆時針方向旋轉。
首先根據旋轉的角度、圖象對角線的長度計算旋轉後的映像的最大寬度、高度,根據旋轉後圖象最大的寬度、高度產生新的緩衝區,假設映像的左上方 為(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;
與映像的鏡像變換相類似,把原圖中的象素值讀入新緩衝區的(x1,y1)點處。注意在新緩衝區中與原圖沒有對應的象素點的值用白色代替。