碎片圖像無縫拼合技術的VC++實現

碎片圖像無縫拼合技術的VC++實現


信息產業部電子第二十二研究所青島分所 郎銳
2002-4-5 12:57:40


一、 引言
 
在測繪、文博等行業經常會遇到這樣一種情況:觀測對象比較大,爲保證分辨率又不能將其全部照下,只能進行局部照相,事後再將這些局部照相的重合部分去掉,拼合成一幅完整的圖像。以前多采用手工拼合,誤差較大,往往不能很好的實現無縫拼合,即使有少量的專業設備,成本也普遍較高。其實只需將照片通過掃描儀將其錄入到計算機中,通過程序處理,完全能很好的實現多幅圖像的無縫拼合,滿足實際需要,而且對於文博行業中常會遇到的破碎的、不規則對象如古舊字畫殘片等也能很好的進行無縫拼合。本文就對針對該程序的實現原理及過程做了簡要的介紹。
 
二、 程序設計原理
 
首先我們從實際出發,我們是通過進行局部照相的手段來保存整體的全部信息,而要保證這些局部照片所含的信息之和能包括整體的全部信息就必然的使每兩幅鄰近的圖片有一部分交疊的部分,這樣才能保證在將整體對象劃分爲若干局部照片而後再拼合成整體圖像的過程中不遺漏任何信息,即該劃分、拼合的整個過程是無損的。既然如此,我們只需能保證讓兩相鄰圖片的重疊部分能完全重合,那麼我們也就能夠肯定在此狀態下的這兩幅圖像實現了無縫拼合。所以,問題就轉換爲使相鄰圖片的重疊部分能完全重合,而判斷兩相同的圖像片段是否完全重疊可以用光柵掩碼來進行直觀的判斷,比如我們可以採用"異或"的掩碼,當相同位置上的兩幅圖片的像素相同時就爲0即黑色,所以可以對兩圖片進行移動,只要重疊部分全黑,則表明此時兩圖像的重疊部分已準確的重合了,而此時也實現了圖像的無縫拼合。此後只需再採用"或"的光柵掩碼將合併後的圖像顯示出來,再通過拷屏等手段將其存盤即可。在實現拼合的全過程中主要涉及到圖像的拖放、圖像文件的讀取及顯示、光柵掩碼、拷屏以及內存位圖的保存等多種技術。接下來就對這些技術的具體應用進行介紹。
 
三、 程序的具體實現
 
在進行拼合之前,首先要將從掃描儀錄入的圖像從文件讀取到內存中,並顯示出來。由於在拼合時採取的光柵操作掩碼是"異或",所以爲保持圖像的原始面貌,可以在消息WM_ERASEBKGND 的響應函數中用PatBlt函數將整個客戶區的初始背景設定爲黑色:
 
……
pDC->PatBlt(0,0,rect.Width(),rect.Height(), BLACKNESS);
return TRUE;
 
讀取位圖文件可以用LoadImage函數來實現,m_sPath1指定了文件的路徑,LR_LOADFROMFILE屬性指定從文件中讀取位圖,返回值爲該位圖的句柄:
 
……
HBITMAP hbitmap;
hbitmap=(HBITMAP)LoadImage(AfxGetInstanceHandle(),
m_sPath1,
IMAGE_BITMAP,0,0,
LR_LOADFROMFILE|LR_CREATEDIBSECTION);
 
之後我們就可以創建一個和當前設備環境兼容的內存設備環境hMemDC1,並將剛纔讀取到內存的位圖放置到該設備環境中:
 
hMemDC1=::CreateCompatibleDC(NULL);
SelectObject(hMemDC1,hbitmap);
::DeleteObject(hbitmap); //釋放掉用過的位圖句柄
Invalidate();
 
至於位圖的顯示,由於需要頻繁的拖動和其他處理,將其放置於OnDraw函數中較爲合理,需要更新顯示時只需顯式地用Invalidate()函數刷新即可。OnDraw()中的顯示位圖部分最好用BitBlt函數來完成,該函數負責把hMemDC1中的位圖放置到pDC頁面中以完成內存頁面的置換,其處理速度還是比較快的:
 
……
::BitBlt(pDC->m_hDC,m_nX1,m_nY1, m_nWidth1,m_nHeight1,hMemDC1,0,0,m_dwRop);
……
 
函數中的m_dwRop變量對光柵操作碼進行設置,初始爲SRCINVERT即光柵異或操作,當拼合成功需要顯示合併後的效果時再將其設定爲SRCPAINT光柵或操作。
 
我們可以通過對鼠標消息響應函數的編程來實現在客戶區內的位圖拖放,按照Windows系統的習慣,首先在鼠標左鍵的響應函數中通過PtInRect()函數判斷鼠標在左鍵按下時是否是落在位圖上,如果是就可以在鼠標左鍵彈起之前將圖片隨鼠標拖動了,顯然這部分應在WM_MOUSEMOVE消息的響應函數內編寫代碼:
 
……
if(m_bCanMove1==true) //在移動之前鼠標左鍵是在圖片上點擊的
{
int dx=m_nOldX1-m_nX1; //計算鼠標距離圖片原點的距離
int dy=m_nOldY1-m_nY1;
m_nX1=point.x-dx; //計算新的圖片原點的座標(客戶區座標)
m_nY1=point.y-dy;
Invalidate(); //更新視圖
}
m_nOldX1=point.x; //保存上一次的鼠標位置
m_nOldY1=point.y;
……
 
到此爲止,可以運行程序對多幅碎片圖像進行拼合了,用鼠標拖動一幅圖像在另一幅圖像邊緣移動,由於採用了"異或"的光柵掩碼,兩幅圖片交疊的地方顏色會發生改變,但只有完全重合時纔會全黑,表明此時的拼合是無縫的,將掩碼換爲"或"即可將拼合後的圖像顯示出來。但此時只是保留在內存中,還要經過進一步的處理,才能將合併後的圖像存盤保留。
 
首先要對合並後的圖像所在的矩形框的位置、大小進行判斷,可以用下面的類似代碼來完成(本例同時最多能有4幅圖像進行拼合):
 
……
int temp1,temp2,x0,y0,x1,y1;
temp1=m_nX1 
if(m_sPath3!="")//如果有3幅圖片參與拼合
{
if(m_sPath4!="")//如果有4幅圖片參與拼合
temp2=m_nX3
else
temp2=m_nX3;
x0=temp1
}
else
x0=temp1;
……
temp1=m_nX1+m_nWidth1>m_nX2+m_nWidth2?m_nX1+m_nWidth1:m_nX2+m_nWidth2;
if(m_sPath3!="")
{
if(m_sPath4!="")
temp2=m_nX3+m_nWidth3>m_nX4+m_nWidth4?m_nX3+m_nWidth3:m_nX4+m_nWidth4;
else
temp2=m_nX3+m_nWidth3;
x1=temp1>temp2?temp1:temp2;
}
else
x1=temp1;
 
可以用類似的代碼計算出y0和y1。在進行屏幕截圖之前必須將由x0,y0,x1,y1構成的矩形由客戶座標轉換成屏幕座標,可以用ClientToScreen()函數來實現。下面是將屏幕指定區域以位圖形式拷貝到內存中去的函數的主要實現代碼:
 
HBITMAP CImageView::CopyScreenToBitmap(LPRECT lpRect)
{
……
// 確保選定區域不爲空矩形
if(IsRectEmpty(lpRect))
return NULL;
//爲屏幕創建設備描述表
hScrDC = CreateDC("DISPLAY", NULL, NULL, NULL);
//爲屏幕設備描述表創建兼容的內存設備描述表
hMemDC = CreateCompatibleDC(hScrDC);
……
// 創建一個與屏幕設備描述表兼容的位圖
hBitmap = CreateCompatibleBitmap(hScrDC, lpRect->Width(),lpRect->Height());
// 把新位圖選到內存設備描述表中
hOldBitmap = (HBITMAP)SelectObject(hMemDC, hBitmap);
// 把屏幕設備描述表拷貝到內存設備描述表中
BitBlt(hMemDC, 0, 0, lpRect->Width(),lpRect->Height,
hScrDC, lpRect->left lpRect->top, SRCCOPY);
//得到屏幕位圖的句柄
hBitmap =(HBITMAP)SelectObject(hMemDC, hOldBitmap);
//清除
DeleteDC(hScrDC);
DeleteDC(hMemDC);
……
// 返回位圖句柄
return hBitmap;
}
 
當把拼合後的區域拷貝到內存,並獲取到該內存位圖的句柄後可以將其通過剪貼板傳送到其他圖形處理軟件中進行進一布的處理,也可以按照位圖的格式直接將其保存成文件,爲方便計,本例採用了後者。其實現過程主要是根據剛纔獲取到的內存位圖句柄按格式填充BMP文件的信息頭以及像素陣列,下面就結合實現的關鍵代碼進行介紹:
 
首先獲取設備描述表句柄,並用函數GetDeviceCaps()獲取到當前顯示分辨率下每個像素所佔字節數,並據此計算出調色板的大小:
 
……
hDC = CreateDC("DISPLAY",NULL,NULL,NULL);
iBits = GetDeviceCaps(hDC, BITSPIXEL) * GetDeviceCaps(hDC, PLANES);
DeleteDC(hDC);
if (iBits <= 1)
wBitCount = 1;
else if (iBits<= 4)
wBitCount = 4;
else if (iBits<= 8)
wBitCount = 8;
else if (iBits <= 24)
wBitCount = 24; //計算調色板大小
……
然後就可以設置位圖信息頭結構了,其中bi 是BITMAPINFOHEADER 結構的實例對象:
……
if (wBitCount <= 8)
dwPaletteSize = (1<
GetObject(hBitmap, sizeof(BITMAP), (LPSTR)&Bitmap);
bi.biSize = sizeof(BITMAPINFOHEADER);
bi.biWidth = Bitmap.bmWidth;
bi.biHeight = Bitmap.bmHeight;
bi.biPlanes = 1;
bi.biBitCount = wBitCount;
bi.biCompression = BI_RGB;
bi.biSizeImage = 0;
bi.biXPelsPerMeter = 0;
bi.biYPelsPerMeter = 0;
bi.biClrUsed = 0;
bi.biClrImportant = 0;
用GlobalAlloc()函數根據計算的結果爲位圖內容分配內存,並返回分配得到的內存句柄hDib,
並用GetStockObject()來設置缺省狀態下的調色板:
……
dwBmBitsSize = ((Bitmap.bmWidth*wBitCount+31)/32)*4*Bitmap.bmHeight;
hDib = GlobalAlloc(GHND,dwBmBitsSize+dwPaletteSize+sizeof(BITMAPINFOHEADER));
lpbi = (LPBITMAPINFOHEADER)GlobalLock(hDib);
*lpbi = bi; // 處理調色板
hPal = GetStockObject(DEFAULT_PALETTE);
if (hPal)
{
hDC = ::GetDC(NULL);
hOldPal =SelectPalette(hDC, (HPALETTE)hPal, FALSE);
RealizePalette(hDC);
}
// 獲取該調色板下新的像素值
GetDIBits(hDC, hBitmap, 0, (UINT) Bitmap.bmHeight,
(LPSTR)lpbi + sizeof(BITMAPINFOHEADER)+dwPaletteSize,
(BITMAPINFO*)lpbi, DIB_RGB_COLORS);
//恢復調色板
if (hOldPal)
{
SelectPalette(hDC,(HPALETTE)hOldPal, TRUE);
RealizePalette(hDC);
::ReleaseDC(NULL,hDC);
}
……
 
最後的工作就是創建位圖文件了,需要把設置好的位圖文件頭和像素點陣信息依次保存到文件中,其中bmfHdr 是BITMAPFILEHEADER位圖文件頭結構的實例對象,需要按照BMP位圖的存盤格式對其進行設置:
 
……
fh = CreateFile(lpFileName,
GENERIC_WRITE, 0, NULL,
CREATE_ALWAYS,
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN,
NULL);
// 設置位圖文件頭
bmfHdr.bfType = 0x4D42; // "BM"
dwDIBSize = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER)
+ dwPaletteSize + dwBmBitsSize;
bmfHdr.bfSize = dwDIBSize;
bmfHdr.bfReserved1 = 0;
bmfHdr.bfReserved2 = 0;
bmfHdr.bfOffBits = (DWORD)sizeof(BITMAPFILEHEADER) +
(DWORD)sizeof(BITMAPINFOHEADER)+ dwPaletteSize;
//寫入位圖文件頭
WriteFile(fh, (LPSTR)&bmfHdr, sizeof(BITMAPFILEHEADER), &dwWritten, NULL);
// 寫入位圖文件其餘內容
WriteFile(fh, (LPSTR)lpbi, dwDIBSize,&dwWritten, NULL);
……
 
四、程序的實例檢測
 
下面就通過一個實例--拼合一幅古代國畫殘片來對程序的拼合效果進行檢測。其中圖一到圖三是拼合前的三幅古代國畫殘片,圖四是經過本程序處理後存盤得到的經過無縫合成的圖片。經過檢測,拼合效果還是相當不錯的,在碎片圖像的銜接處根本沒有接縫的存在:

拼合前的圖象碎片

拼合後的圖象

小結:本程序通過一個實例講述了處理圖片無縫拼合的一種實用方法,在測繪、勘察、文博等行業均有較大的應用潛力。在理解了程序的設計思路和編程思想的前提下,結合具體的實際需求,通過對本文具體代碼的改動可以設計出更適合本單位實際情況的類似軟件。另外,本文所講述的截取並保存屏幕技術在類似程序的編制上也可以提供一定的參考。本程序在Windows 98下,由Microsoft Visual C++ 6.0編譯通過。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章