vb.net中彩色影象資料的快速獲取
一直以來,影象處理都是VB的禁區,主要的原因可能是因為她沒有指標,而影象的資料量通常都很大。其實,只要有正確的方法,VB同樣可以寫出高效而又快速的影象處理程式的。
我並不是學影象處理這方面的。可以說,我的專業和影象毫不佔邊,但因老闆專案的需要,自學了一些圖形影象學方面的知識。網路上影象方面大部分的程式碼都是用VC寫的,對於我這個對C系列語言不感冒的人來說實在是太痛苦了,好在關鍵的演算法部分還能夠馬馬乎乎的看懂,這樣在學習中也改寫了不少程式碼,這裡想共享一些常用的演算法供大家研究。
要處理一個影象,首先當然要獲得該影象的畫素值,常見很多人直接用兩個迴圈中呼叫GetPixel 來得到資料(最初我也是),這個過程是相當耗時,在處理完畢後又呼叫SetPixel 來更新影象,而SetPixel呢,要進行座標系轉化、剪裁區域判斷、將顏色匹配為裝置支援的最接近的,最後還要根據不同的顏色格式定址、為將顏色寫入其所在位進行位運算,速度可想而知了。
在VB6.0,為了快速得到一副影象的資料,通常需要呼叫API函式GetDIBits,而在呼叫該函式前要做大量的準備工作(API宣告、BITMAPINFO資訊設定等),也有點煩躁,但在.net中提供了BitmapData類,再結合Marshal類的Copy方法可以快速地複製影象資料到一維陣列中。
因為我的專案中只對彩色影象進行處理,而且不涉及到特效,所以沒有考慮到Alpha通道。
以下是影象的讀取和儲存部分的程式碼:
' ******************************************************************************************
'
' 函式名 : ReadBitmap
' 功能 : 讀取影象資料
' 引數 : Bmp ------ Bitmap 待處理點陣圖
' BmpData ------ Byte 儲存影象的資料的陣列(引用)
' 作者 : laviewpbt
' 時間 : 2005-5-20 9:45
' 修改者 :
' 修改時間 :
'
' ******************************************************************************************
Public Shared Sub ReadBitmap(ByVal Bmp As Bitmap, ByRef BmpData() As Byte)
Dim Data As BitmapData = Bmp.LockBits(New Rectangle(0, 0, Bmp.Width, Bmp.Height), ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb) ' 將Bitmap物件鎖定到系統記憶體中
Dim Stride As Integer = Data.Stride ' 掃描寬度
Dim Scan0 As IntPtr = Data.Scan0 ' 點陣圖中第一個畫素資料的地址
Dim Number As Integer = Bmp.Height * Stride - 1 ' 影象資料元素的個數,注意.net中陣列下標是從0開始的
ReDim BmpData(Number)
Marshal.Copy(Scan0, BmpData, 0, Number) ‘將記憶體Scan0後面Number位元組的資料拷貝到BmpData中
Bmp.UnlockBits(Data) ' 從系統記憶體解鎖Bitmap
End Sub
' ******************************************************************************************
'
' 函式名 : WriteBitmap
' 功能 : 將資料寫入影象
' 引數 : Bmp ------ Bitmap 待處理點陣圖
' BmpData ------ Byte 儲存影象的資料的陣列
' 作者 : laviewpbt
' 時間 : 2005-5-20 9:49
' 修改者 :
' 修改時間 :
'
' ******************************************************************************************
Public Shared Sub WriteBitmap(ByVal Bmp As Bitmap, ByVal BmpData() As Byte)
Dim Data As BitmapData = Bmp.LockBits(New Rectangle(0, 0, Bmp.Width, Bmp.Height), ImageLockMode.WriteOnly, PixelFormat.Format24bppRgb) ' ' 將Bitmap物件鎖定到系統記憶體中
Dim stride As Integer = Data.Stride ' 掃描寬度
Dim Scan0 As IntPtr = Data.Scan0 ' 點陣圖中第一個畫素資料的地址
Dim Number As Integer = Bmp.Height * stride - 1 ' 影象資料元素的個數
Marshal.Copy(BmpData, 0, Scan0, Number) '將BmpData中的資料拷貝到Scan0後面的Number位元組中
Bmp.UnlockBits(Data) ' 從系統記憶體解鎖Bitmap
End Sub
Marshal.Copy方法的使用大大加速了資料的獲取,Marshal類還提供了ReadByte,WriteByte之類的方法,但利用這種方的效率也將非常低下。
對於一副1024*768的24位真彩色影象,利用上述函式讀取資料所用的時間是0豪秒(呵呵,當然不是了,估計要用QueryPerformanceCounter函式來得到這個值,順便說下我的機器配置:256MB記憶體,Pentium 3.0G),也就是說,影象資料獲取可以達到毫秒級,這樣,我們就可以把大部分設計的精力投入到演算法的設計中去。
上面所獲取的資料是一維陣列,而24真彩色影象用一三維BmpData(width-1,height-1,2)陣列來表示是相當便於處理的,但是如何快速的把資料寫入到一三維陣列中,我還沒有發現好辦法,Marshal.Copy方法只支援一維陣列的資料複製,如果用For迴圈來做,也是一個很漫長的過程。我曾經試著用CopyMemory ,雖然可以將記憶體中一段資料拷貝到一多維陣列中,但陣列中的資料的順序不符合要求。
不過說明一點,同樣的資料計算量,用一維陣列要比用多維資料的速度要快些,這是很明顯的,所以經過一番思想鬥爭,我決定還是用一維陣列來處理,雖然在計算中有些難以理解計算式的意義。