欢迎关注博主的微信公众号:“智能遥感”。
该公众号将为您奉上Python地学分析、爬虫、数据分析、Web开发、机器学习、深度学习等热门源代码。
本人的GitHub代码资料主页(持续更新中,多给Star,多Fork):
https://github.com/xbr2017
CSDN也在同步更新:
https://blog.csdn.net/XBR_2014
" 当单幅遥感影像较大时,也就是分辨率较高或者像元数量较多时,如果批量处理这些影像,计算机内存可能不够,程序容易报错:内存溢出。这时需要对影像进行分块读取与处理,也是本节所要分享的重点。"
按块读取遥感影像
在上一节中,程序一次读取并保存了整个波段的数据。但是,如果单幅图像尺寸较大(行列数较大)的话,我们可以将其分解为块来读取。可能是因为你只需要图像中的某一块,或者你的本本没有足够的内存来同时存储所有数据。下面我们就来一起看看如何访问图像的子集而不是整幅图像。
ReadAsArray函数有几个可选参数,尽管它们根据你使用的是数据集还是波段而有所不同。这是波段版本的参数:
band.ReadAsArray([xoff], [yoff], [win_xsize], [win_ysize], [buf_xsize],[buf_ysize], [buf_obj])
xoff和yoff参数分别指开始读取的列和行偏移量。默认设置是从第一行和第一列开始读取。win_xsize和win_ysize参数是指要读取的行数和列数,默认值是全部读取它们。buf_xsize和buf_ysize参数允许你指定输出数组的大小。如果这些值与win_xsize和win_ysize值不同,则数据将在读取时重新采样,以匹配输出数组大小。buf_obj参数是一个NumPy数组,数据将被存储,而不是正在创建的新数组。将更改像元数据类型以匹配此数组的数据类型。如果提供的buf_xsize和buf_ysize值与此数组的维度不匹配,则会出现错误。
例如,要读取从图1中所示的第6000行和第1400列开始的六列和三行,你可以执行以下操作:
data = band.ReadAsArray(1400, 6000, 6, 3)
图1 使用ReadAsArray(1400,6000,6,3)从第6000行和第1400列开始读取六列和三行。
如果你需要像元值是浮点型而不是字节型,你可以在读完之后使用NumPy转换它们,如下所示:
data = band.ReadAsArray(1400, 6000, 6, 3).astype(float)
或者你可以让GDAL在读取数据时为你进行转换。要使用此方法,先创建一个浮点数组,然后将其作为buf_obj参数传递给ReadAsArray。确保创建的数组与正在读取的数据具有相同的尺寸。
import numpy as np
data = np.empty((3, 6), dtype=float)
band.ReadAsArray(1400, 6000, 6, 3, buf_obj=data)
NumPy空函数创建一个尚未使用任何值初始化的数组,因此它包含垃圾,直到你以某种方式填充它。函数的第一个参数是一个元组,其中包含要创建的数组的尺寸。如果它是二维数组,则元组包含行数,然后包含列数。dtype参数是可选的,它指定数组将保存的数据类型。如果未提供,则数组将保留浮点数。
要将数据数组写入其他数据集中的特定位置,请将偏移量传递给WriteArray。它会写出你传递给函数的数组中的所有数据,从你提供的偏移量开始写。
band2.WriteArray(data, 1400, 6000)
阅读部分数据集时要记住一件重要的事情,那就是你必须确保读取的子数据尺寸不要大于父数据尺寸,否则程序会报错。例如,如果图像有100行,并且你要求它以偏移75开始读取并读取30行,那么它将超过图像的末尾并将报错。
你如何使用此信息处理不适合RAM的大型数据集?一种方法是一次处理一个块。请记住,栅格将数据存储在磁盘中。因为块中的数据一起存储在磁盘上,所以处理这些块中的图像是有效的。
以下代码显示了如何将数字高程模型从米转换为英尺,一次一个块。这是一个小数据集,因此你可能不需要将其拆分,但是你将以相同的方式处理大型数据集。这也向你展示了在栅格中处理NoData值的示例,这些像元被认为具有空值。像元必须具有值,但是特定值可以指定为NoData,因此会被忽略。
# _*_ coding: utf-8 _*_
__author__ = 'xbr'
__date__ = '2018/12/6 17:19'
import os
import numpy as np
from osgeo import gdal
os.chdir(r'D:\osgeopy-data\Washington\dem')
in_ds = gdal.Open('gt30w140n90.tif')
in_band = in_ds.GetRasterBand(1)
xsize = in_band.XSize
ysize = in_band.YSize
block_xsize, block_ysize = in_band.GetBlockSize()
nodata = in_band.GetNoDataValue()
out_ds = in_ds.GetDriver().Create(
'dem_feet.tif', xsize, ysize, 1, in_band.DataType)
out_ds.SetProjection(in_ds.GetProjection())
out_ds.SetGeoTransform(in_ds.GetGeoTransform())
out_band = out_ds.GetRasterBand(1)
for x in range(0, xsize, block_ysize):
if x + block_xsize < xsize:
cols = block_xsize
else:
cols = xsize - x
for y in range(0, ysize, block_ysize):
if y + block_ysize < ysize:
rows = block_ysize
else:
rows = ysize - y
data = in_band.ReadAsArray(x, y, cols, rows)
# 将米转换为英尺
data = np.where(data == nodata, nodata, data * 3.28084)
out_band.WriteArray(data, x, y)
out_band.FlushCache()
out_band.SetNoDataValue(nodata)
out_band.ComputeStatistics(False)
out_ds.BuildOverviews('average', [2, 4, 8, 16, 32])
del out_ds
程序一开始,打开数据集并获取有关波段的信息,包括块的大小和NoData值。创建输出数据集后,开始在水平(x)方向上循环遍历块。从第0列开始,然后转到最后一列,用索引xsize表示。执行每次循环时,你将x增加一个块中的列数(范围的第三个参数是要增加的量),所以你从一个块的开头跳到下一个块的开头。然后存储要在名为cols的变量中读取的列数。如果剩下要读取的完整块的列数,则将此变量设置为块中的列数。但是如果没有足够的列,如图1中x等于10的情况那样,则使用剩余列的数量(图中为3)。你需要这样做,因为如果你尝试读取比已有数据更多的行或列,你的程序将报错。
计算要读取的列数后,重复执行行数。如图1所示,通过第二个循环的前两次读取五行,但第三次只剩下一行读取。处理完所有行的前五列后,进入外循环的下一次迭代并处理接下来的五列,然后处理最后三列。
data = in_band.ReadAsArray(x, y, cols, rows)
处理完所有块后,你可以计算统计数据并构建概视图。要从统计计算中排除NoData像元,你必须在调用ComputeStatistics之前告诉波段哪个值代表NoData。你可能想要计算循环内的统计信息,但是你希望统计信息基于波段中的所有像元,因此你需要等到所有像元值都已计算完毕。