讀寫影像可以說是圖像處理最基礎的一步。關於使用GDAL讀寫影像,平時也在網上查了很多資料,就想結合自己的使用心得,做做簡單的總結。
在這裡寫一個例子:裁剪lena圖像的某部分內容,將其放入到新創建的.tif文。以此來說明GDAL讀寫影像的具體實現。
用GDAL打開lena.bmp,實現如下。注意這裡打開圖像,指的是獲取圖像的頭文件,以此得到圖像的一些信息,沒有涉及到讀取像素操作。
GDALAllRegister(); //GDAL所有操作都需要先注冊格式
const char* imgPath = "E:\\Data\\lena.bmp";
GDALDataset* img = (GDALDataset *)GDALOpen(imgPath, GA_ReadOnly);
if (img == nullptr)
{
cout << "Can't Open Image!" << endl;
return 1;
}
圖像需要關注的信息很多,可以重點關注以下四個值。圖像寬、高總所周知了,而波段數就是通道,如RGB圖像的波段數為3。深度標識的就是圖像的存儲單位,比如一般圖像就是8位,用無字節字符型unsigned char來表達0~255的像素值;而除以8標識1個字節,方便讀取像素buf。
int imgWidth = img->GetRasterXSize(); //圖像寬度
int imgHeight = img->GetRasterYSize(); //圖像高度
int bandNum = img->GetRasterCount(); //波段數
int depth = GDALGetDataTypeSize(img->GetRasterBand(1)->GetRasterDataType()) / 8; //圖像深度
如果已經讀取完畢或者不需要這張圖像的相關操作了,最後要關閉打開的文件,否則會內存洩漏。
GDALClose(img);
用GDAL創建一個新的圖像,例如這裡創建了一個256X256大小,被讀取圖像波段,深度8位的tif。
GDALDriver *pDriver = GetGDALDriverManager()->GetDriverByName("GTIFF"); //圖像驅動
char** ppszOptions = NULL;
ppszOptions = CSLSetNameValue(ppszOptions, "BIGTIFF", "IF_NEEDED"); //配置圖像信息
const char* dstPath = "E:\\Data\\dst.tif";
int bufWidth = 256;
int bufHeight = 256;
GDALDataset* dst = pDriver->Create(dstPath, bufWidth, bufHeight, bandNum, GDT_Byte, ppszOptions);
if (dst == nullptr)
{
printf("Can't Write Image!");
return false;
}
需要注意的是創建圖像可能需要一些特別的設置信息,是需要到GDAL對應格式的文檔中去查看的,也可以什麼都不設置用默認值。我這裡設置的是如果需要的話,就創建支持大小超過4G的bigtiff。
如果已經寫入完畢或者不需要這張圖像的相關操作了,最後一定要注意關閉關閉打開的文件,之前只會內存洩漏,而這裡還會可能創建失敗。
GDALClose(dst);
如果創建後什麼都不做,關閉後GDAL會自動寫入0像素值,打開後就是純黑色圖像。
GDAL讀寫圖像是通過RasterIO()這個函數實現的,這個函數提供了非常強大的功能,目前筆者也只總結了這以下方面的內容。
GDAL讀取圖像是以左上角為起點的,讀取起點位置開始的256X256的內容,寫入dst.tif中的實現如下:
//申請buf
size_t imgBufNum = (size_t) bufWidth * bufHeight * bandNum * depth;
GByte *imgBuf = new GByte[imgBufNum];
//讀取
img->RasterIO(GF_Read, 0, 0, bufWidth, bufHeight, imgBuf, bufWidth, bufHeight,
GDT_Byte, bandNum, nullptr, bandNum*depth, bufWidth*bandNum*depth, depth);
//寫入
dst->RasterIO(GF_Write, 0, 0, bufWidth, bufHeight, imgBuf, bufWidth, bufHeight,
GDT_Byte, bandNum, nullptr, bandNum*depth, bufWidth*bandNum*depth, depth);
//釋放
delete[] imgBuf;
imgBuf = nullptr;
逐個說明RasterIO()參數的含義:
有的參數推薦使用上面的標准寫法而不是采用默認值0,可以更好地理解圖像buf的存放排布。最後得到的dst.tif如下:
上述RasterIO()的寫法可以兼容16為圖像的讀寫,只不過要注意的是buf中是用2個Gbyte來表達1個16像素值的。當然為了更方便圖像處理,也可以采用16位整型來讀取buf:
//申請buf
size_t imgBufNum = (size_t)bufWidth * bufHeight * bandNum;
GUInt16 *imgBuf = new GUInt16[imgBufNum];
//讀取
img->RasterIO(GF_Read, 0, 0, bufWidth, bufHeight, imgBuf, bufWidth, bufHeight,
GDT_UInt16, bandNum, nullptr, bandNum*depth, bufWidth*bandNum*depth, depth);
//寫入
dst->RasterIO(GF_Write, 0, 0, bufWidth, bufHeight, imgBuf, bufWidth, bufHeight,
GDT_UInt16, bandNum, nullptr, bandNum*depth, bufWidth*bandNum*depth, depth);
//釋放
delete[] imgBuf;
imgBuf = nullptr;
可以發現,除了要更改buf的容量和RasterIO()的第九個參數GDT_UInt16,其余什麼都不需要更改。注意創建16位圖像時參數也需要更改成16位:
GDALDataset* dst = pDriver->Create(dstPath, bufWidth, bufHeight, bandNum, GDT_UInt16, ppszOptions);
某些情況下需要讀取特定波段,或者需要重組波段順序。例如VC中顯示圖像往往需要將buf按照BGR傳遞給BITMAP,再顯示BITMAP。這時只需要修改第11個參數就行了:
//波段索引
int panBandMap[3] = { 3,2,1 };
//申請buf
size_t imgBufNum = (size_t) bufWidth * bufHeight * bandNum * depth;
GByte *imgBuf = new GByte[imgBufNum];
//讀取
img->RasterIO(GF_Read, 0, 0, bufWidth, bufHeight, imgBuf, bufWidth, bufHeight,
GDT_Byte, bandNum, panBandMap, bandNum*depth, bufWidth*bandNum*depth, depth);
//寫入
dst->RasterIO(GF_Write, 0, 0, bufWidth, bufHeight, imgBuf, bufWidth, bufHeight,
GDT_Byte, bandNum, nullptr, bandNum*depth, bufWidth*bandNum*depth, depth);
//釋放
delete[] imgBuf;
imgBuf = nullptr;
這時得到的dst.tif為:
默認情況RasterIO()是以左上角起點讀寫的,不過也是可以以左下角為起點讀寫,只需要重新設置排布buf的位置。這裡讀寫lena圖像上同一塊位置:
//申請buf
size_t imgBufNum = (size_t) bufWidth * bufHeight * bandNum * depth;
size_t imgBufOffset = (size_t) bufWidth * (bufHeight-1) * bandNum * depth;
GByte *imgBuf = new GByte[imgBufNum];
//讀取
img->RasterIO(GF_Read, 0, 0, bufWidth, bufHeight, imgBuf + imgBufOffset, bufWidth, bufHeight,
GDT_Byte, bandNum, nullptr, bandNum*depth, -bufWidth*bandNum*depth, depth);
//寫入
dst->RasterIO(GF_Write, 0, 0, bufWidth, bufHeight, imgBuf + imgBufOffset, bufWidth, bufHeight,
GDT_Byte, bandNum, nullptr, bandNum*depth, -bufWidth*bandNum*depth, depth);
//釋放
delete[] imgBuf;
imgBuf = nullptr;
注意這裡Y方向起點位置,也就是第三個參數仍然要用左上角起算,但是buf已經是左下角起點了。
RasterIO()另外一個用法是可以自動縮放,重采樣讀寫影像,例如這裡將512X512大小的lena圖像重采樣成256X256大小:
//申請buf
size_t imgBufNum = (size_t) bufWidth * bufHeight * bandNum * depth;
size_t imgBufOffset = (size_t) bufWidth * (bufHeight-1) * bandNum * depth;
GByte *imgBuf = new GByte[imgBufNum];
//讀取
img->RasterIO(GF_Read, 0, 0, imgWidth, imgHeight, imgBuf + imgBufOffset, bufWidth, bufHeight,
GDT_Byte, bandNum, nullptr, bandNum*depth, -bufWidth*bandNum*depth, depth);
//寫入
dst->RasterIO(GF_Write, 0, 0, bufWidth, bufHeight, imgBuf + imgBufOffset, bufWidth, bufHeight,
GDT_Byte, bandNum, nullptr, bandNum*depth, -bufWidth*bandNum*depth, depth);
//釋放
delete[] imgBuf;
imgBuf = nullptr;
可以看到重采樣讀寫只需要修改參數4,參數5就行了。查閱網上資料得知,RasterIO()重采樣方式默認是最臨近的方法,只有建立金字塔時可以設置重采樣方式,但也僅限於縮小。最後得到的dst.tif結果:
GDAL功能非常豐富,本文僅僅做了一點關於圖像讀寫的總結,自認為算的上“簡明”了。當然也希望大家批評指正。
OpenGL 渲染篇 http://www.linuxidc.com/Linux/2011-10/45756.htm
Ubuntu 13.04 安裝 OpenGL http://www.linuxidc.com/Linux/2013-05/84815.htm
OpenGL三維球體數據生成與繪制【附源碼】 http://www.linuxidc.com/Linux/2013-04/83235.htm
Ubuntu下OpenGL編程基礎解析 http://www.linuxidc.com/Linux/2013-03/81675.htm
如何在Ubuntu使用eclipse for c++配置OpenGL http://www.linuxidc.com/Linux/2012-11/74191.htm
《OpenGL超級寶典》學習筆記 http://www.linuxidc.com/Linux/2013-10/91414.htm