讀寫影像可以說是圖像處理最基礎的一步。關於使用GDAL讀寫影像,平時也在網上查了很多資料,就想結合自己的使用心得,做做簡單的總結。
在這里寫一個例子:裁剪lena圖像的某部分內容,將其放入到新創建的.tif文。以此來說明GDAL讀寫影像的具體實現。
1.打開圖像
用GDAL打開lena.bmp,實現如下。注意這里打開圖像,指的是獲取圖像的頭文件,以此得到圖像的一些信息,沒有涉及到讀取像素操作。
GDALAllRegister(); //GDAL所有操作都需要先注冊格式
CPLSetConfigOption("GDAL_FILENAME_IS_UTF8", "NO"); //支持中文路徑
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);
2.創建圖像
用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像素值,打開后就是純黑色圖像。
3.圖像讀寫
GDAL讀寫圖像是通過RasterIO()這個函數實現的,這個函數提供了非常強大的功能,目前筆者也只總結了這以下方面的內容。
3.1.一般情況下讀寫
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()參數的含義:
- 參數1:讀寫標記。如果為GF_Read,則是將影像內容寫入內存,如果為GF_Write,則是將內存中內容寫入文件。
- 參數2、3:讀寫開始位置。相對於圖像左上角頂點(從零開始)的行列偏移量。
- 參數4、5:要讀寫的塊在x方向的象素個數和y方向的象素列數。
- 參數6:指向目標緩沖區的指針,由用戶分配。
- 參數7、8:目標塊在x方向上和y方向上的大小。
- 參數9:目標緩沖區的數據類型,原類型會自動轉換為目標類型。
- 參數10:要處理的波段數。
- 參數11:記錄要操作的波段的索引(波段索引從1開始)的數組,若為空則數組中存放的是前nBandCount個波段的索引。
- 參數12:X方向上兩個相鄰象素之間的字節偏移,默認為0,則列間的實際字節偏移由目標數據類型eBufType確定。
- 參數13:y方向上相鄰兩行之間的字節偏移, 默認為0,則行間的實際字節偏移為eBufType * nBufXSize。
- 參數14:相鄰兩波段之間的字節偏移,默認為0,則意味着波段是順序結構的,其間字節偏移為nLineSpace * nBufYSize。
有的參數推薦使用上面的標准寫法而不是采用默認值0,可以更好地理解圖像buf的存放排布。最后得到的dst.tif如下:
3.2.16位影像讀寫
上述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);
3.3.讀取特定波段
某些情況下需要讀取特定波段,或者需要重組波段順序。例如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為:
3.4.左下角起點讀寫
默認情況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已經是左下角起點了。
3.5.重采樣讀寫
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功能非常豐富,本文僅僅做了一點關於圖像讀寫的總結,自認為算的上“簡明”了。當然也希望大家批評指正。