GDAL创建影像

创建单波段影像

driver.Create(Driver self, char const * utf8_path, int xsize, int ysize, int bands=1, GDALDataType eType=GDT_Byte, char ** options=None)

该函数用于创建影像。

char const * utf8_path指保存的路径和文件名称;

int xsize, int ysize指生成影像的长和宽;

int bands指生成影像的波段数目,默认是1;

GDALDataType eType=GDT_Byte指影像的DataType,一般与原始影像的DT相同。

from osgeo import gdal, osr, gdal_array
from osgeo.gdalconst import *
import numpy

dataset = gdal.Open('I:\\MasterLife\\资源与环境遥感\\实验作业一\\caijian2.tif')
band1 = dataset.GetRasterBand(1)
driver = gdal.GetDriverByName('gtiff')
# help(driver.Create)
# print(dataset.GetGeoTransform())  # (435435.0, 30.0, 0.0, 4467435.0, 0.0, -30.0)
# print(band1.XSize, band1.YSize)  # 2387 2637
# 文件夹路径必须存在,否则无法正确生成tif
new_filename = 'temp/demo8_create.tif'
# 通过driver创建一个影像
dst_ds = driver.Create(new_filename, 2700, 2700, 1, gdal.GDT_Byte)
# 设置影像的地理仿式坐标
dst_ds.SetGeoTransform([435435.0, 30.0, 0.0, 4467435.0, 0.0, -30.0])
# 设置空间参考
# 原文的Projection是自定义的
# 我们根据原有图像进行赋值
'''srs = osr.SpatialReference()
srs.SetUTM(11, 1)
srs.SetWellKnownGeogCS('NAD27')
dst_ds.SetProjection(srs.ExportToWkt())'''
dst_ds.SetProjection(dataset.GetProjection())

# 生成一个影像范围的0值矩阵
raster = numpy.zeros((2700, 2700))
# 将上述矩阵写入影像
dst_ds.GetRasterBand(1).WriteArray(raster)

生成影像的基本流程是:

  1. 先声明一个指定图像格式的Driver
  2. 通过Driver.Create函数生成初始化的影像,同时指明路径、大小、波段和DT;
  3. Create后的影像设置ProjectionGeoTransform
  4. 写入具有值的矩阵到新影像中。

注意:SetGeoTransform之前的new_filename一定要自己创建完整路径,gdal并不会贴心的给我创建文件夹。


创建多波段影像

from osgeo import gdal
from osgeo.gdalconst import *

# 获取原始数据集
dataset = gdal.Open('I:\\MasterLife\\资源与环境遥感\\实验作业一\\caijian2.tif')

projection = dataset.GetProjection()
geotransform = dataset.GetGeoTransform()
width = dataset.RasterXSize
height = dataset.RasterYSize
bands = dataset.RasterCount
datas = dataset.ReadAsArray(0, 0, width, height)

driver = gdal.GetDriverByName('gtiff')
filename = "temp/demo10_create.tif"
# 读取数据集不需要eType吧, 有默认值
# 新建数据集
# 写入数据
new_dataset = driver.Create(filename, width, height, bands=bands, options=["INTERLEAVE=PIXEL"])
new_dataset.WriteRaster(0, 0, width, height, datas, width, height, band_list=[1, 2, 3])
new_dataset.SetProjection(projection)
new_dataset.SetGeoTransfrom(geotransform)

创建多波段关键在于写入数据:

  1. 创建新数据集driver.Create,数据集的创建不同于单波段,不需要指定eType,但是需要标注options,两个选择options=["INTERLEAVE=PIXEL"]或者options=["INTERLEAVE=BAND"]
  2. 写入读取的datas,读取采用ReadAsArrayWriteRaster函数具有以下参数:

WriteRaster(xoff, yoff, xsize, ysize, buf_string, buf_xsize=None, buf_ysize=None, buf_type=None, band_list=None, buf_pixel_space=None, buf_line_space=None, buf_band_space=None)

buf_string代表写入的所有波段数据,band_list需要指定写入的波段列表,因为ReadAsArray读取的结果是numpy的数组。


分段读取创建多光谱影像

这里和上面直接读取的区别在于,写入形成新影像的时候,先获取DataSet的波段,然后对波段写入对应波段的数据数组。

from osgeo import gdal
from osgeo.gdalconst import *
import numpy as np

# 获取原始数据集
dataset = gdal.Open('I:\\MasterLife\\资源与环境遥感\\实验作业一\\caijian2.tif')
# 获取必要数据信息
projection = dataset.GetProjection()
geotransform = dataset.GetGeoTransform()
width = dataset.RasterXSize
height = dataset.RasterYSize
bands = dataset.RasterCount
# 读取整个影像数据
datas = dataset.ReadAsArray(0, 0, width, height)

driver = gdal.GetDriverByName('gtiff')
filename = "temp/demo11_create.tif"
# 读取数据集不需要eType吧,有默认的值
# 新建波段
out_dataset = driver.Create(filename, width, height, bands=bands, eType=gdal.GDT_Byte, options=["INTERLEAVE=PIXEL"])
# 设定数据集的投影和GT
out_dataset.SetProjection(projection)
out_dataset.SetGeoTransform(geotransform)

for i in range(bands):
    floop_band = out_dataset.GetRasterBand(i + 1)
    # floop_band.WriteRaster(0, 0, width, height, datas[i])
    floop_band.WriteArray(datas[i], 0, 0)

总结这两种方法的区别:

两者使用的写入函数有点差异。
new_dataset.WriteRasterfloop_band.WriteArray

使用help查看一下两个函数的参数。

WriteArray(array, xoff=0, yoff=0, resample_alg=0, callback=None, callback_data=None) method of osgeo.gdal.Band instance
WriteRaster(xoff, yoff, xsize, ysize, buf_string, buf_xsize=None, buf_ysize=None, buf_type=None, buf_pixel_space=None, buf_line_space=None) method of osgeo.gdal.Band instance

首先:在BandRasterSet中各有一个WriteRasterWriteArray函数。

Band类中的WriteArray和WriteRaster

先在Band类中进行分析。

WriteArray(array, xoff=0, yoff=0)这三个参数比较重要,建议都需要写入。Array的重点是写入的数据需要是Array格式,所以用ReadAsArray读入的数据可以放在array参数进行写入。

WriteRaster(xoff, yoff, xsize, ysize, buf_string)这五个参数比较重要,一般情况下的写入都是xoff=0, yoff=0, xsize=width, ysize=height,而buf_string则是需要写入的数据。

所以对demo11来说,下面这两句话是等价的,都可以实现相同的效果。

floop_band.WriteRaster(0, 0, width, height, datas[i])
floop_band.WriteArray(datas[i], 0, 0)

假设,我们读取数据集的方式是ReadRaster,能不能用WriteRaster进行写入?

不能,读取的结果是个ByteArray,需要进行转换;那么使用decode或者使用str变成字符串格式,结果也是报错,所以这个读取方式暂时不考虑。

Dataset类中的WriteArray和WriteRaster

有了以上的经验,按道理说在Dataset类中,两种方法也都可以使用。

因为是写入多个波段,所以band_list参数必须存在。

WriteRaster(xoff, yoff, xsize, ysize, buf_string, buf_xsize=None, buf_ysize=None,band_list=None)这八个参数需要全写。若是不想全写,那要把前五个参数必须写上,同时把band_list用命名写上。

WriteArray(array, xoff=0, yoff=0, band_list=None, interleave='band')这五个参数需要写上。所以下面这两句话是等价的。

new_dataset.WriteRaster(xoff=0, yoff=0, xsize=width, ysize=height, buf_string=datas, band_list=[1, 2, 3])
new_dataset.WriteArray(datas, 0, 0, band_list=[1, 2, 3], interleave='band')

那么,有了个新的问题,针对Dataset类中,有个参数是interleave或者在使用driver.Create的一个可选参数options=["INTERLEAVE=PIXEL"]

首先在驱动创建时的可选参数是PIXELBAND对结果并没有什么影响;

其次在WriteArray时的interleave必须是band,若是pixel则会报错:

ValueError: array larger than output file, or offset off edge

这个错误可以理解为原数据集是Band排列,那么输入过来的结果就一定是Band排列。

那么为什么驱动创建时的排列方式是P或B都是无所谓的?

这个问题,暂时不清楚。

所以,无论Band类和Dataset类的多光谱图像创建使用WriteArrayWriteRaster都可以,但是如果不愿意多想,那么WriteArray总是可以实现的。数据的读取用ReadAsArray也是如此,正好可以方便numpy的处理。

影像的排列方式最好是BSQ,在处理大气校正可能是会选择BIL/BIP。上下影像的排列方式最好保持一致~~(因为不一致的暂时我不会实现)~~。


创建影像金字塔

使用代码直接创建影像金字塔。

这里的列表中有6级,加上原图级别一共有7级。

new_dataset.BuildOverviews(overviewlist=[2, 4, 8, 16, 32, 64])

打开影像,左下角有级别预览,能够看见6级的影像金字塔。

设定Imagine风格的pyramids。

gdal.SetConfigOption('HFA_USE_RRD', 'YES')

这句代码不知道有什么用。

所以先写第二句,再在代码中引入第一句。


使用CreateCopy创建影像

GDAL创建影像可以使用Create方式直接创建,但是对于有些数据格式,Create方法并不支持(JPEG,PNG等),这些格式可以在Raster Driver中有标注。

所以就用CreateCopy方法来创建这些格式。

CreateCopy其实概念很简单,当我们使用Create方法创建好一个GTIFF格式的影像后,需要将他变成PNG/JPEG格式怎么办?使用CreateCopy完成这一步。

CreateCopy其实就是将参考文件(TIFF)的内容信息,全部赋值给目标文件(JPEG/PNG)。

CreateCopy赋值的目标文件可以存在也可以不存在。不存在会新建一个文件,存在会覆盖原有文件的全部内容。

from osgeo import gdal
from osgeo.gdalconst import *

# 需要变换的目标文件
target_filename = 'temp/demo13.png'
# 参考源文件
ref_filename = 'temp/demo11_create.tif'
# 源文件驱动
driver = gdal.GetDriverByName('GTIFF')
ref_dataset = gdal.Open(ref_filename)

sr_ds = driver.CreateCopy(target_filename, ref_dataset, 1)
help(driver.CreateCopy)

我们查看一下CreateCopy函数。

CreateCopy(Driver self, char const * utf8_path, Dataset src, int strict=1, char ** options=None, GDALProgressFunc callback=0, void * callback_data=None) -> Dataset

char const * utf8_path表示输出结果路径;

Dataset src表示原有数据集,必须gdal.Open的数据集;

int strict=1表示约束限制,参数取值为0(False)或1(True), 取值为非的时候说明即使不能精确的由原数据转化为目标数据, 程序也照样执行CreateCopy方法,不会产生致命错误。 这种错误有可能是输出格式不支持输入数据格式象元的数据类型, 或者是目标数据不支持写入空间参考等等;

char ** options=None表示可选参数,是指在格式转换中所要用到的一些特殊的参数;比如['TILED=YES', 'COMPRESS=PACKBITS']表示输出过程中使用瓦片存储,压缩方式采用PACKBITS具体参数在哪里查找?

GDALProgressFunc callback=0是一个挂钩函数,表示程序运行的进度;

void * callback_data=None是一个回调函数,表示当前运行的数据。

像素存储顺序

通过CreateCreateCopy创建的影像,默认都是['INTERLEAVE=BAND'],BAND(tags[284]=2),在导出时是的284号域(PlanarConfiguration域)默认为2,排列方式是 RRRR……,GGGG……,BBBB……,专业的一些软件是可以支持这种格式的。

而大部分看图软件支持的影像查看方式都是RGBRGBRGBRGB……,所以,在CreateCopyPNG/JPEG这样的格式时,可以在char ** options=None参数中添加'INTERLEAVE=PIXEL',毕竟我们生成的PNG/JPEG本身也不是作为影像处理使用的吧。