接觸GDAL有四五年多時間了,平時都是在C++下使用,最近需要在C#下調用GDAL,所以就開始學習了下,相比C++調用,C#下使用GDAL做影像處理的效率有點低,但是其簡單易學,適合菜鳥上手,現把自己剛學到的心得跟大夥分享下,以遙感影像的顯示為例。
1、 程式環境搭建
首先,需要編譯GDAL庫的原始碼。
GDAL是一個非常強悍的遙感資料格式解析庫,支援多種遙感資料格式的讀寫,而且還有一些演算法實現。然而,它只是一個開源庫,並不是一個單獨的軟體,而是C++寫的原始碼,需要編譯成動態連結程式庫後才能為我們程式調用,如果需要在C#/.Net環境下調用,還需特別編譯C#版的DLL。關於編譯的方法網上很多教程,我就不介紹了,具體編譯步驟可以參照民錄大哥的部落格:http://blog.csdn.net/liminlu0314/article/details/6937194
如果很難編譯成功,需要編譯後的版本,可以留下郵箱,或者給我發郵件RSyaoxin@163.com。
編譯完成後,我們會得到9個DLL檔案(以GDAL1.10版為例):gdal110.dll、gdal_csharp.dll、gdal_wrap.dll、gdalconst_csharp.dll、gdalconst_wrap.dll、ogr_csharp.dll、ogr_wrap.dll、osr_csharp.dll、osr_wrap.dll。
注意:需要強調的一點是,如果編譯GDAL時添加了額外的依賴庫,需要將其動態連結程式庫一併拷貝過來。比如,我編譯GDAL庫時就添加了HDF4、HDF5、JPEG2000、NetCDF、Proj4等檔案格式支援,那麼調用的時候就需要將hd425m.dll等拷貝到同上面幾個DLL一起。
其次,C#下的環境配置。
我們首先建立一個Windows表單應用程式,將上面得到的9個DLL檔案拷貝到項目所在的檔案夾,然後在解決方案面板下找到引用項,右鍵添加引用,選擇gdal_csharp.dll將它添加進來,這樣就可以在C#調用GDAL函數了。
再次,初始化GDAL環境。具體做法是:
using OSGeo.GDAL;
將上面這一句添加到namespace這一行前面。
然後在建構函式或者Form_Load函數中添加下面兩行:
OSGeo.GDAL.Gdal.AllRegister();
OSGeo.GDAL.Gdal.SetConfigOption("GDAL_FILENAME_IS_UTF8","YES");
說明:第一行是註冊所有的格式驅動,第二行是支援中文路徑和名稱,由於GDAL預設不支援中文路徑,所以在編譯的時候會修改源碼讓它支援中文路徑,C++代碼就可以直接支援了,但是會發現C#版還是會不支援,所以需要加上第二句。
這樣開發環境就搭建好了,可以直接調用GDAL函數進行影像處理了。
2、 關鍵代碼
C#像的顯示方法有多種,最簡單的方法就是構建位元影像。我們可以把GDAL下的Dataset轉換為Bitmap供C#調用。在C#裡面調用GDAL讀取柵格資料的主要函數是ReadRaster,它相當於C++下的RasterIO函數,其有多重形式:
public CPLErr ReadRaster(int xOff, int yOff, int xSize, int ySize, byte[] buffer, int buf_xSize, int buf_ySize, int pixelSpace, int lineSpace)public CPLErr WriteRaster(int xOff, int yOff, int xSize, int ySize, byte[] buffer, int buf_xSize, int buf_ySize, int pixelSpace, int lineSpace)public CPLErr ReadRaster(int xOff, int yOff, int xSize, int ySize, short[] buffer, int buf_xSize, int buf_ySize, int pixelSpace, int lineSpace)public CPLErr WriteRaster(int xOff, int yOff, int xSize, int ySize, short[] buffer, int buf_xSize, int buf_ySize, int pixelSpace, int lineSpace)public CPLErr ReadRaster(int xOff, int yOff, int xSize, int ySize, int[] buffer, int buf_xSize, int buf_ySize, int pixelSpace, int lineSpace)public CPLErr WriteRaster(int xOff, int yOff, int xSize, int ySize, int[] buffer, int buf_xSize, int buf_ySize, int pixelSpace, int lineSpace)public CPLErr ReadRaster(int xOff, int yOff, int xSize, int ySize, float[] buffer, int buf_xSize, int buf_ySize, int pixelSpace, int lineSpace)public CPLErr WriteRaster(int xOff, int yOff, int xSize, int ySize, float[] buffer, int buf_xSize, int buf_ySize, int pixelSpace, int lineSpace)public CPLErr ReadRaster(int xOff, int yOff, int xSize, int ySize, double[] buffer, int buf_xSize, int buf_ySize, int pixelSpace, int lineSpace)public CPLErr WriteRaster(int xOff, int yOff, int xSize, int ySize, double[] buffer, int buf_xSize, int buf_ySize, int pixelSpace, int lineSpace)public CPLErr ReadRaster(int xOff, int yOff, int xSize, int ySize, IntPtr buffer, i nt buf_xSize, int buf_ySize, DataType buf_type, int pixelSpace, int lineSpace)public CPLErr WriteRaster(int xOff, int yOff, int xSize, int ySize, IntPtr buffer, int buf_xSize, int buf_ySize, DataType buf_type, int pixelSpace, int lineSpace)
這裡面,xOff和yOff是指位移量,即從影像的左上方起始座標(xOff,yOff)開始讀取資料。xSize和ySize是指讀取映像資料的行列數,即寬度和高度,單位都是像素。Buffer是映像資料緩衝。buf_xSize和buf_ySize是緩衝區的大小,它們須與buffer申請的大小保持一致,通過這兩個參數可以控制縮放,如果它們小於xSize和ySize就是將原圖縮小,反之如果它們大於xSize和ySize就是將原圖放大。pixelSpace和lineSpace一般預設取0即可。
關鍵代碼如下:
/// <summary> /// GDAL柵格轉換為位元影像 /// </summary> /// <param name="ds">GDAL Dataset</param> /// <param name="showRect">顯示地區</param> /// <param name="bandlist">需要顯示的波段列表</param> /// <returns>返回Bitmap對象</returns> public Bitmap GetImage(OSGeo.GDAL.Dataset ds, Rectangle showRect, int[] bandlist) { int imgWidth = ds.RasterXSize; //影像寬 int imgHeight = ds.RasterYSize; //影像高 float ImgRatio = imgWidth / (float)imgHeight; //影像寬高比 //擷取顯示控制項大小 int BoxWidth = showRect.Width; int BoxHeight = showRect.Height; float BoxRatio = imgWidth / (float)imgHeight; //顯示控制項寬高比 //計算實際顯示地區大小,防止影像畸變顯示 int BufferWidth, BufferHeight; if (BoxRatio >= ImgRatio) { BufferHeight = BoxHeight; BufferWidth = (int)(BoxHeight * ImgRatio); } else { BufferWidth = BoxWidth; BufferHeight = (int)(BoxWidth/ImgRatio); } //構建位元影像 Bitmap bitmap = new Bitmap(BufferWidth, BufferHeight, System.Drawing.Imaging.PixelFormat.Format24bppRgb); if (bandlist.Length==3) //RGB顯示 { int[] r = new int[BufferWidth * BufferHeight]; Band band1 = ds.GetRasterBand(bandlist[0]); band1.ReadRaster(0, 0, imgWidth, imgHeight, r, BufferWidth ,BufferHeight, 0, 0); //讀取映像到記憶體 //為了顯示好看,進行最大最小值展開顯示 double[] maxandmin1 = { 0, 0 }; band1.ComputeRasterMinMax(maxandmin1,0); int[] g = new int[BufferWidth * BufferHeight]; Band band2 = ds.GetRasterBand(bandlist[1]); band2.ReadRaster(0, 0, imgWidth, imgHeight, g, BufferWidth, BufferHeight, 0, 0); double[] maxandmin2 = { 0, 0 }; band2.ComputeRasterMinMax(maxandmin2, 0); int[] b = new int[BufferWidth * BufferHeight]; Band band3 = ds.GetRasterBand(bandlist[2]); band3.ReadRaster(0, 0, imgWidth, imgHeight, b, BufferWidth, BufferHeight, 0, 0); double[] maxandmin3 = { 0, 0 }; band3.ComputeRasterMinMax(maxandmin3, 0); int i, j; for (i = 0; i < BufferWidth; i++) { for (j = 0; j < BufferHeight; j++) { int rVal=Convert.ToInt32(r[i + j * BufferWidth]); rVal = (int)((rVal - maxandmin1[0]) / (maxandmin1[1] - maxandmin1[0]) * 255); int gVal=Convert.ToInt32(g[i + j * BufferWidth]); gVal = (int)((gVal - maxandmin2[0]) / (maxandmin2[1] - maxandmin2[0]) * 255); int bVal=Convert.ToInt32(b[i + j * BufferWidth]); bVal = (int)((bVal - maxandmin3[0]) / (maxandmin3[1] - maxandmin3[0]) * 255); Color newColor = Color.FromArgb(rVal, gVal, bVal); bitmap.SetPixel(i, j, newColor); } } } else //灰階顯示 { int[] r = new int[BufferWidth * BufferHeight]; Band band1 = ds.GetRasterBand(bandlist[0]); band1.ReadRaster(0, 0, imgWidth, imgHeight, r, BufferWidth, BufferHeight, 0, 0); double[] maxandmin1 = { 0, 0 }; band1.ComputeRasterMinMax(maxandmin1, 0); int i, j; for (i = 0; i < BufferWidth; i++) { for (j = 0; j < BufferHeight; j++) { int rVal = Convert.ToInt32(r[i + j * BufferWidth]); rVal = (int)((rVal - maxandmin1[0]) / (maxandmin1[1] - maxandmin1[0]) * 255); Color newColor = Color.FromArgb(rVal, rVal, rVal); bitmap.SetPixel(i, j, newColor); } } } return bitmap; }
3、 主函數調用
得到Bitmap,我們就可以在程式中調用它了。我們可以在表單上加一個PictureBox控制項來顯示映像,其name設為pictureBox1。主要調用代碼如下:
private void ImageShow(){ string filename=""; OpenFileDialog dlg = new OpenFileDialog(); dlg.Filter = "Tiff檔案|*.tif|Erdas img檔案|*.img|Bmp檔案|*.bmp|jpeg檔案|*.jpg|所有檔案|*.*"; if (dlg.ShowDialog() == DialogResult.OK) { filename = dlg.FileName; } if (filename == "") { MessageBox.Show("影像路徑不可為空"); return; } OSGeo.GDAL.Dataset ds= Gdal.Open(filename, Access.GA_ReadOnly); if(ds==null) {MessageBox.Show("影像開啟失敗");return; } Rectangle pictureRect = new Rectangle(); pictureRect.X = 0; pictureRect.Y = 0; pictureRect.Width = this.pictureBox1.Width; pictureRect.Height = this.pictureBox1.Height; int[] disband = {3,2,1}; Bitmap bitmap = GetImage(ds, pictureRect, disband); //遙感影像構建位元影像 pictureBox1.Image = bitmap; //將位元影像傳遞給PictureBox控制項進行顯示}
4、 結果展示
這樣我們就可以很容易地顯示遙感影像了。