2015年9月1日 星期二

影像處理-提取像素

這幾天寫到一些簡單的影像處理,本來是用Bitmap.GetPixel 卻發現寫出來的程式佔用很多的cpu,還超慢 上網查到一篇有用的網誌,為了避免該網誌掛掉,我這邊也貼一份 我參考的網誌: http://mermerism.blogspot.tw/2014/04/c-bitmap.html 出處:http://blog.csdn.net/ma_jiang/article/details/7725458 一.Bitmap類別 Bitmap對象封裝了GDI+中的一個位元圖,此位元圖由圖形圖像及其屬性的像素數據組成. 見維基百科:http://zh.wikipedia.org/wiki/BMP 即對每一個單一像素在一個位元圖上的處理。 因此Bitmap是用於處理由像素數據定義的圖像的對象.該類的主要方法和屬性如下: 1. GetPixel方法和SetPixel方法:和設置一個圖像的指定像素的顏色. 2. PixelFormat屬性:返回圖像的像素格式. 3. Palette屬性:獲取和設置圖像所使用的顏色調色板. 4. Height Width屬性:返回圖像的高度和寬度. 5. LockBits方法和UnlockBits方法:分別鎖定和解鎖系統內存中的位圖像素. 在基於像素點的圖像處理方法中使用LockBits和UnlockBits是一個很好的方式, 這兩種方法可以使我們指定像素的範圍來控制位圖的任意一部分, 從而消除了通過循環對位圖的像素逐個進行處理,每用LockBits之後都應該調用一次UnlockBits. 二.BitmapData類別 BitmapData對象指定了位元圖的屬性,指定點陣圖影像的屬性 (Attribute)。 BitmapData 類別是由 Bitmap 類別的 LockBits 和 UnlockBits 方法所使用。 無法被繼承。 1. Height屬性:被鎖定位圖的高度. 2. Width屬性:被鎖定位圖的高度. 3. PixelFormat屬性:數據的實際像素格式. 4. Scan0屬性:被鎖定數組的首字節地址,如果整個圖像被鎖定,則是圖像的第一個字節地址. 5. Stride屬性:步幅,也稱為掃描寬度. 如上圖所示,數組的長度並不一定等於圖像像素數組的長度,還有一部分未用區域, 這涉及到位圖的本身結構,系統要保證每行的字節數必須為4的倍數. 否則上圖右側灰色區域的未使用空間(offset)就是一排中非4的倍數中的畸零地 三.Graphics類別 Graphics對像是GDI+的關鍵所在,許多對像都是由Graphics類表示的,該類別定義了繪製和填充圖形對象的方法和屬性,一個應用程序只要需要進行繪製或著色,它就必須使用Graphics對象. 就是在一張圖上要做些甚麼繪製的動作時,就是使用這個類別 四.Image類別 這個類提供了位元圖和位元文件操作的函數. Image類被聲明為abstract,也就是說Image類不能實例化對象,而只能做為一個基本類 1.FromFile方法:從指定的檔案建立 Image 物件。 它根據輸入的文件名產生一個Image對象,它有兩種函數形式: public static Image FromFile(string filename); //從指定的檔案建立 Image 物件。 public static Image FromFile(string filename, bool useEmbeddedColorManagement); //使用指定之檔案中的內嵌色彩管理資訊,從該檔案建立 Image 2.FromHBitmap方法:從 Windows 控制代碼建立 Bitmap。 它從一個windows句柄​​處創建一個bitmap對象,它也包括兩種函數形式: public static bitmap fromhbitmap(intptr hbitmap); //從 GDI 點陣圖控制代碼建立 Bitmap。 public static bitmap fromhbitmap(intptr hbitmap, intptr hpalette); //從 GDI 點陣圖控制代碼和 GDI 調色盤控制代碼建立 Bitmap 3. FromStream方法:從指定的資料流建立 Image。 從一個數據流中創建一個image對象,它包含三種函數形式: public static image fromstream(stream stream); //從指定的資料流建立 Image。 public static image fromstream(stream stream, bool useembeddedcolormanagement); //選擇性地使用指定之資料流中的內嵌色彩管理資訊,從該資料流建立 Image。 fromstream(stream stream, bool useembeddedcolormanagement, bool validateimagedata); //選擇性地使用内嵌色彩管理資訊並驗證影像資料,從指定的資料流建立 Image。 有了上面了理解,在看下面的部分 一. 開檔、存檔、讀檔、寫檔 開檔、存檔、讀檔、寫檔 分別有其代表之意義。 開檔:是打開檔案顯示在銀幕上 存檔:是將做好的檔案儲存在電腦的資料夾 讀檔:是將檔案中的資料讀成Byte或Bit的檔案,讓電腦看得懂 寫檔:是將檔案讀出來之後,使用者才做更改、修正 所以在檔案處理的順序來說,應該是要 開檔-->讀檔-->寫檔-->存檔 private Bitmap srcBitmap = null;//原始的Bitmap private Bitmap showBitmap = null;//顯示用的Bitmap //打開文件 private void menuFileOpen_Click(object sender, EventArgs e) { OpenFileDialog ofd = new OpenFileDialog(); ofd.Filter = @"Bitmap文件(*.bmp)|*.bmp|Jpeg文件(*.jpg)|*.jpg|所有合適文件 (*.bmp,*.jpg)|*.bmp;*.jpg "; ofd.FilterIndex = 3; ofd.RestoreDirectory = true; if (DialogResult.OK == openFileDialog.ShowDialog()) { srcBitmap = (Bitmap)Bitmap.FromFile(ofd.FileName, false); showBitmap = srcBitmap; this.AutoScroll = true; this.AutoScrollMinSize =newSize((int)(showBitmap.Width), (int)(showBitmap.Height)); this.Invalidate(); } } //保存圖像文件 private void menuFileSave_Click(object sender, EventArgs e) { if (showBitmap != null) { SaveFileDialog sfd = new SaveFileDialog(); sfd.Filter =@"Bitmap文件(*.bmp)|*.bmp|Jpeg文件(*.jpg)|*.jpg|所有合適文件 (*.bmp,*.jpg)|*.bmp;*.jpg"; sfd.FilterIndex = 3; sfd.RestoreDirectory = true; if (DialogResult.OK == sfd.ShowDialog()) { ImageFormat format = ImageFormat.Jpeg; switch (Path.GetExtension(sfd.FileName).ToLower()) { case".jpg": format = ImageFormat.Jpeg; break; case".bmp": format = ImageFormat.Bmp; break; default: MessageBox.Show(this,"Unsupported image format was specified","Error", MessageBoxButtons.OK, MessageBoxIcon.Error); return; } try { showBitmap.Save(sfd.FileName,format ); } catch (Exception) { MessageBox.Show(this, "Failed writing image file", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); } } } } c#中將bitmap或者image保存為清晰的gif 在c#中默認可以講bitmap保存為gif等格式,但是這種保存方法保存的gif會嚴重失真,正常情況下的代碼: System.Drawing.Bitmap b = new System.Drawing.Bitmap(“c://original_image.gif“); System.Drawing.Image thmbnail = b.GetThumbnailImage(100,75,null,new IntPtr()); thmbnail.Save(“c://thumnail.gif“, System.Drawing.Imaging.ImageFormat.Gif); 一個批量處理圖片的軟件,包括各種處理方式,處理效果,但是在保存為gif的時候出現了問題,在網上查了很久也沒有發現一個可用的改善gif圖片質量的方法,找到了一個解決辦法,保存出來的gif容量大減,但是效果基本符合常規這中方法就是就是“Octree“ 算法。 “Octree“ 算法允許我們插入自己的算法來量子化我們的圖像。 一個好的“顏色量子化”算法應該考慮在兩個像素顆粒之間填充與這兩個像素顏色相近的過渡顏色,提供更多可視顏色空間。 Morgan Skinner提供了很好的“Octree“ 算法代碼,大家可以下載參考使用。 使用OctreeQuantizer很方便:  System.Drawing.Bitmap b = new System.Drawing.Bitmap(“c://original_image.gif“);  System.Drawing.Image thmbnail = b.GetThumbnailImage(100,75,null,new IntPtr());  OctreeQuantizer quantizer = new OctreeQuantizer ( 255 , 8 ) ;  using ( Bitmap quantized = quantizer.Quantize ( thmbnail ) )  {    quantized.Save(“c://thumnail.gif“, System.Drawing.Imaging.ImageFormat.Gif);  }  OctreeQuantizer grayquantizer = new GrayscaleQuantizer ( ) ;  using ( Bitmap quantized = grayquantizer.Quantize ( thmbnail ) )  {    quantized.Save(“c://thumnail.gif“, System.Drawing.Imaging.ImageFormat.Gif); } 你可以點擊這裡下載類的文件(項目文件 ),根據我的試用,只需要兩個類文件(OctreeQuantizer.cs,Quantizer.cs)即可運行,將這兩個類文件的namespace改成 你項目的名稱就行,還有,需要在不安全編譯的方式下編譯,右擊項目名稱,在生成選項卡里選擇"允許不安全代碼"即可 //窗口重繪,在窗體上顯示圖像,重載Paint privatevoid frmMain_Paint(object sender, System.Windows.Forms.PaintEventArgs e) { if (showBitmap != null) { Graphics g = e.Graphics; g.DrawImage(showBitmap, newRectangle(this.AutoScrollPosition.X, this.AutoScrollPosition.Y , (int)(showBitmap.Width), (int)(showBitmap.Height))); } } //灰度化 privatevoid menu2Gray_Click(object sender, EventArgs e) { if (showBitmap == null) return; showBitmap = RGB2Gray(showBitmap);//下面都以RGB2Gray為例 this.Invalidate(); } 二. 提取像素法(超級無敵慢) 即用C#中的getPixel和setPixel 來做取得和設定像素。 這種方法簡單易懂,但相當耗時,完全不可取. public staticBitmap RGB2Gray(Bitmap srcBitmap) { Color srcColor; int wide = srcBitmap.Width; int height = srcBitmap.Height; for (int y = 0; y < height; y++) for (int x = 0; x < wide; x++) { //獲取像素的RGB顏色值 srcColor = srcBitmap.GetPixel(x, y); byte temp = (byte)(srcColor.R * .299 + srcColor.G * .587 + srcColor.B * .114); //設置像素的RGB顏色值 srcBitmap.SetPixel(x, y, Color.FromArgb(temp, temp, temp)); } return srcBitmap ; } 三. 內存法(速度較快) 這是比較常用的方法,即http://softwarebydefault.com/2013/05/18/image-median-filter/所用之法 大概的步驟如下: 先建立一個rect,這是用來放圖的框框 public static Bitmap RGB2Gray(Bitmap srcBitmap) { int wide = srcBitmap.Width; int height = srcBitmap.Height; Rectangle rect = newRectangle(0, 0, wide, height); //將srcBitmap鎖定到系統內的記憶體的某個區塊中,並將這個結果交給BitmapData類別的srcBimap BitmapData srcBmData = srcBitmap.LockBits(rect,ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb); //將CreateGrayscaleImage灰階影像,並將這個結果交給Bitmap類別的dstBimap Bitmap dstBitmap = CreateGrayscaleImage(wide, height);//這個函數在後面有定義 //將dstBitmap鎖定到系統內的記憶體的某個區塊中,並將這個結果交給BitmapData類別的dstBimap BitmapData dstBmData = dstBitmap.LockBits(rect,ImageLockMode.ReadWrite, PixelFormat.Format8bppIndexed); //位元圖中第一個像素數據的地址。它也可以看成是位圖中的第一個掃描行 //目的是設兩個起始旗標srcPtr、dstPtr,為srcBmData、dstBmData的掃描行的開始位置 System.IntPtr srcPtr = srcBmData.Scan0; System.IntPtr dstPtr = dstBmData.Scan0; //將Bitmap對象的訊息存放到byte中 int src_bytes = srcBmData.Stride * height; byte[] srcValues​​ = new byte[src_bytes]; int dst_bytes = dstBmData.Stride * height; byte[] dstValues​​ = new byte[dst_bytes]; //複製GRB信息到byte中 System.Runtime.InteropServices.Marshal.Copy(srcPtr, srcValues​​, 0, src_bytes); System.Runtime.InteropServices.Marshal.Copy(dstPtr, dstValues​​, 0, dst_bytes); //根據Y=0.299*R+0.114*G+0.587B,Y為亮度 for (int i = 0; i < height; i++) for (int j = 0; j < wide; j++) { //只處理每行中圖像像素數據,捨棄未用空間 //注意位圖結構中RGB按BGR的順序存儲 int k = 3 * j; byte temp = (byte) (srcValues​​[i * srcBmData.Stride + k + 2] * .299 + srcValues​​[i * srcBmData.Stride + k + 1] * .587+ srcValues​​[i * srcBmData.Stride + k] * .114); dstValues​​[i * dstBmData.Stride + j] = temp; } System.Runtime.InteropServices.Marshal.Copy(dstValues​​, 0, dstPtr, dst_bytes); //解鎖位圖 srcBitmap.UnlockBits(srcBmData); dstBitmap.UnlockBits(dstBmData); return dstBitmap; } 四 指標法(速度最快) C/C++的習慣,不是C#的特點,但是效率奇高 public static Bitmap RGB2Gray(Bitmap srcBitmap) { int wide = srcBitmap.Width; int height = srcBitmap.Height ; Rectangle rect = newRectangle(0, 0, wide, height); //將srcBitmap鎖定到系統內的記憶體的某個區塊中,並將這個結果交給BitmapData類別的srcBimap BitmapData srcBmData = srcBitmap.LockBits(rect,ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb); //將CreateGrayscaleImage灰階影像,並將這個結果交給Bitmap類別的dstBimap Bitmap dstBitmap = CreateGrayscaleImage(wide, height); //將dstBitmap鎖定到系統內的記憶體的某個區塊中,並將這個結果交給BitmapData類別的dstBimap BitmapData dstBmData = dstBitmap.LockBits(rect,ImageLockMode.ReadWrite, PixelFormat.Format8bppIndexed); //位元圖中第一個像素數據的地址。它也可以看成是位圖中的第一個掃描行 //目的是設兩個起始旗標srcPtr、dstPtr,為srcBmData、dstBmData的掃描行的開始位置 System.IntPtr srcScan = srcBmData.Scan0; System.IntPtr dstScan = dstBmData.Scan0; Unsafe //啟動不安全代碼 { byte* srcP = (byte*)(void*) srcScan; byte* dstP = (byte*)(void*) dstScan; int srcOffset = srcBmData.Stride - wide * 3; int dstOffset = dstBmData.Stride - wide ; byte red, green, blue; for (int y = 0; y < height; y++) { for (int x = 0; x

沒有留言:

張貼留言