为了提升遥感数据处理编程体验,我完成了Python语言的yimage库开发,并且上传了pypi.org,支持pip安装。这是一个比GDAL更加友好的遥感影像I/O库,目前还仅支持一些简单的I/O操作,后续会扩展更多的功能,支持一些遥感影像处理的操作。目前最新版本为1.1.0,更新于2021.5.25。
1 开发动机
Python中比较常见的图像库包括OpenCV、PIL、GDAL,同时也有一些小众的tifffile、rasterio等等。但是这些库都有自己的优缺点。
OpenCV:Open Computer Vision Library,非常通用的计算机视觉库,但是仅能很好的支持单波段以及三波段影像。对于四波段影像,在某些情况下,第四波段无法正常读取,同时会导致前三波段影像色彩失真。对于遥感的更多波段数据,OpenCV库完全无法支持。cv2.imread返回的数据格式为numpy数组,以(H,W)或者(H,W,C)形式存储,波段顺序一般为(gray)、(b,g,r)或者(b,g,r,a)储存在内存中。以下附上OpenCV读取影像的代码。
img = cv2.imread('test.jpg', -1)
GDAL:Geospatial Data Abstraction Library,非常通用的遥感数据转换库,支持几十上百种遥感数据格式,同时也支持常见的jpg、png、bmp等CV领域格式。能够完美的支持任意波段数量的数据,同时支持地理投影与地理坐标。但是GDAL读取数据的代码比较复杂,数据存储形式与OpenCV不太一致。ReadAsArray返回的数据格式为numpy数组,以(C,H,W)形式存储,波段顺序在三波段与四波段时分别为(r,g,b)以及(r,g,b,nir),大于四波段则按照波长存储,例如(b,g,r,nir,swir1,tir,swir2)。以下附上GDAL读取影像的代码。
dataset = gdal.Open('test.tif') img = dataset.ReadAsArray() del dataset
如果需要读取地理信息,则需要加上以下代码。
proj = dataset.GetProjection() coord = dataset.GetGeoTransform()
在某些的情况下,需要按波段依次读取数据时,代码非常冗长,示例如下。
dataset = gdal.Open('test.tif') width = dataset.RasterXSize height = dataset.RasterYSize band_count = dataset.RasterCount img = None for i in range(band_count): band = dataset.GetRasterBand(i + 1) _img = band.ReadAsArray().reshape(1, height, width) if i == 0: img = _img else: img.append(img, _img, axis=0) del dataset
PIL库读取图像时,也采用(C,H,W)形式存储,波段顺序为(r,g,b)顺序。tifffile库用过一次,但是仅仅只能支持TIFF格式数据,甚至连常见的ENVI标准格式数据都无法支持。rasterio库是一个基于GDAL的库,与yimage库类似,但是我在开发yimage库的时候,并不知道有这个库的存在,另外有一些需求rasterio库并不能满足。
为了消除以上不同库读取影像的差异,统一遥感影像读写的接口,因此最终我开发了yimage库。
2 设计思路
yimage库以GDAL与OpenCV作为底层,以便于选择在两种接口中选择合适的接口读取数据,并且返回的数据也以统一的格式存储于内存中。因为OpenCV库中集成了大量的成熟的图像处理算法,因此我将返回的数据统一默认以OpenCV的格式进行存储,即(H,W)或(H,W,C),波段顺序为(b,g,r,nir,……)。但是另一个比较常见的图像处理库skimage则采用(C,H,W)形式,波段顺序为(r,g,b),而PyTorch库中的Tensor数据也是采用(C,H,W)形式存储的。对于仅采用skimage编程的用户和为了优化PyTorch数据读取效率,yimage库对(C,H,W)格式和(r,g,b)顺序进行了兼容性支持。
GDAL在三波段与四波段两种数据读写时,会以(r,g,b)或者(r,g,b,nir)的形式读写,这与OpenCV不太一样。因此很多时候会出现这样的状况,我用GDAL读取了一个四波段数据后,打算写出rgb真彩色影像。由于GDAL写影像只能按波段写出,代码比较复杂,测试代码时会用OpenCV写出数据。原本数据在硬盘中存储顺序为(b,g,r,nir),GDAL读入的时候会读成(r,g,b,nir),此时用OpenCV写出前三个波段,就变成了(r,g,b),波段顺序完全就反掉了,这个是不合理的。因此,yimage也需要处理掉GDAL在三四波段读写顺序的问题。
尽管yimage库支持了OpenCV的格式以及PIL的格式,但是我们依然建议大家采用默认的OpenCV格式进行加载,即(H,W,C)格式以及(b,g,r,nir,…)顺序。对于维度顺序,推荐(H,W,C)仅仅出于调用OpenCV库函数更加方便的原因,如果不需要调用OpenCV库函数时,(C,H,W)也是很方便使用的,在使用PyTorch加载数据到Tensor时,(C,H,W)能够避免transpose带来的额外时间开销。对于波段顺序,推荐(b,g,r,nir,…)是出于影像成像波长规律的原因,尽管大多数时候我们习惯于RGB这样的顺序,但是对于遥感,除了RGB外,还有更多的波段,这个时候使用(r,g,b,nir,…)则会使得数据处理出现一些意想不到的问题,例如会不小心将NDVI等指数计算错误。
大多数情况下,通过GDAL读写数据的代码都是一致的,我们不需要每次都反复写复杂的读写函数,因此yimage库将两种接口封装成了一个函数,方便调用,同时也解决了两种接口对数据处理方式不同而产生的一些问题。
另外yimage库也封装了一些常用的功能,避免了每次开发时代码冗余,也避免了某些通用代码出现了bug时,改一处到处都得改的麻烦。
3 yimage库的安装方法
目前已经上传到了pypi.org服务器上。支持pip安装。
pip install yimage
yimage目前支持以下平台,在Anaconda环境下,也使用pip安装。
Linux 64bit | Windows 64bit | macOS 64bit | |
Python 3.5 | No | No | No |
Python 3.6 | Yes | Yes | didn’t build |
Python 3.7 | Yes | Yes | didn’t build |
Python 3.8 | Yes | Yes | didn’t build |
Python 3.9 | Yes | Yes | didn’t build |
目前本项目是支持macOS操作系统的,但是暂时没有进行编译,后续有编译发行计划。
yimage包含以下依赖库。
3.1 Numpy
一般情况下,在安装yimage库时,会自动安装numpy库。
pip install numpy
3.2 OpenCV
一般情况下,在安装yimage库时,会自动安装opencv-python库。
pip install opencv-python
3.3 GDAL
由于使用pip命令安装GDAL库时,会出现编译错误,因此不建议使用pip命令安装GDAL。建议使用conda命令安装GDAL库。
conda install GDAL -c conda-forge
我们强烈建议安装的gdal库版本号大于等于3.2.2。因为在早期发布的版本中,我们发现当gdal版本低于3.2时,yimage库中部分函数的运行速度非常慢,当安装3.2.2或者更高版本时,速度能提升上百倍。
3.4 Cython
一般情况下,在安装yimage库时,会自动安装Cython库。
pip install Cython
4 文档与使用示例
4.1 导入包与版本查看
import yimage yimage.__version__
终端会显示版本信息。
'1.1.0'
4.2 io类
io类位于yimage.io中,包含了一些基本的读写数据接口。
yimage.io.read_image(filename, driver=’GDAL’, read_range=None, band_list=None, use_RGB=False, use_CHW=False, with_geo_info=False, only_image_info=False)
本函数调用GDAL或者OpenCV接口读取影像数据。
参数说明:
- filename:文件路径。
- driver:底层接口。’GDAL’为使用GDAL库读取影像,’OpenCV’为使用OpenCV库读取影像。由于GDAL库完全兼容OpenCV的支持,因此一般建议使用GDAL接口。可选参数。默认值:’GDAL’。
- read_range:影像读取范围。有时候,我们是不需要读取整张影像的,我们可以通过此参数仅仅加载局部影像,提高读取速度。该参数为长度为4的元组,分别指的是x轴起点下标、y轴起点下标、x轴长度、y轴长度,例如:(100, 100, 480, 480)表示从(100, 100)处作为左上角起点,读取一幅480×480大小的影像。当该值为None时,表示读取整张影像。可选参数,仅适用于GDAL接口。默认值:None。
- band_list:影像读取波段。有时候,我们时不需要读取所有波段,或者希望在读取数据时采用指定的波段顺序进行排列,则可以使用本参数,节省内存资源,减少额外的波段重排开销。该参数为任意长度的列表,例如:[3, 5, 2, 8]表示按照第三、第五、第二、第八波段的顺序加载这四个波段数据到内存中。需要注意的是,波段序号是从1开始而不是从0开始。如果不需要指定波段顺序,则将该值设定为None即可。可选参数,仅适用于GDAL接口。默认值:None。
- use_RGB:采用RGB顺序或者BGR顺序加载影像。仅适用于3波段或者4波段影像,并且band_list参数需要指定为None,否则该参数将被屏蔽。当该值为True时,采用RGB顺序,为False时,采用BGR顺序。可选参数。默认值:False。
- use_CHW:采用CHW形式或者HWC形式加载影像。当该值为True时,采用CHW形式,为False时,采用HWC形式。可选参数。默认值:False。
- with_geo_info:是否返回影像的地理投影信息和地理坐标信息。当该值为True时,函数返回值将包含一个额外的地理信息字典,该字典中包括’proj’和’coord’两个键,分别表示地理投影与地理坐标。当该值为False时,函数不返回额外的字典。可选参数,仅适用于GDAL接口。默认值:False。
- only_image_info:是否仅返回影像高宽波段数信息。有时候仅仅希望获取影像的高度、宽度、波段数信息,然后根据这个信息去有选择性读取部分数据,避免加载整张影像可能导致内存不足的问题。当该值为True时,函数不读取影像数据,仅返回影像的高度、宽度以及波段数信息字典,该字典包括’height’、’width’和’channel’三个键。当该值为False时,函数正常读取数据并返回numpy数组。可选参数,仅适用于GDAL接口。默认值:False。
返回值:
- 以下其中之一:
- numpy数组。
- numpy数组,地理信息字典。(仅适用于GDAL接口)
- 高宽波段数字典。(仅适用于GDAL接口)
- 高宽波段数字典,地理信息字典。(仅适用于GDAL接口)
- None。(当数据读取失败时)
代码示例:
img1 = yimage.io.read_image('test1.jpg', driver='OpenCV') img2 = yimage.io.read_image('test2.tif') img3, img3_geo_info = yimage.io.read_image('test3.dat', read_range=(1000, 1000, 5000, 5000), with_geo_info=True) img4_info, img4_geo_info = yimage.io.read_image('test4.tif', with_geo_info=True, only_image_info=True) img5, img5_geo_info = yimage.io.read_image('test5.tif', band_list=[2,3,4], use_CHW=True, with_geo_info=True)
yimage.io.write_image(filename, img, driver=’GDAL’, band_list=None, use_RGB=False, use_CHW=False, dtype=’uint8′, gdal_driver=’GTiff’, compress=None, geo_info=None, color_table=None, no_data_value=None)
本函数调用GDAL或者OpenCV接口保存影像数据。
参数说明:
- filename:文件路径。
- img:numpy数组。
- driver:底层接口。’GDAL’为使用GDAL库写出影像,’OpenCV’为使用OpenCV库写出影像。可选参数。默认值:’GDAL’。
- band_list:影像写出波段。有时候,我们时不需要保存所有波段,或者希望在保存数据时采用指定的波段顺序进行排列,则可以使用本参数。该参数为任意长度的列表,例如:[3, 5, 2, 8]表示按照第三、第五、第二、第八波段的顺序写出这四个波段数据到硬盘中。需要注意的是,波段序号是从1开始而不是从0开始。如果不需要指定波段顺序,则将该值设定为None即可。可选参数,仅适用于GDAL接口。默认值:None。
- use_RGB:采用RGB顺序或者BGR顺序保存影像。仅适用于3波段或者4波段影像,并且band_list参数需要指定为None,否则该参数将被屏蔽。当该值为True时,采用RGB顺序,为False时,采用BGR顺序。可选参数。默认值:False。
- use_CHW:采用CHW形式或者HWC形式保存影像。当该值为True时,采用CHW形式,为False时,采用HWC形式。可选参数。默认值:False。
- dtype:GDAL写出数据的类型。参数值取自于numpy数组类型关键字,目前支持uint8、uint16、int16、uint32、int32、float32、float64。可选参数,仅适用于GDAL接口。默认值:’uint8’。
- gdal_driver:GDAL数据驱动。该参数与GDAL保持一致,具体可参照GDAL的说明文档:https://gdal.org/drivers/raster/index.html。常用的有:GTiff,GeoTiff格式,后缀名tif;ENVI,ENVI标准格式,输出文件附带hdr后缀的头文件;HFA,ERDAS格式;BMP,微软位图,后缀名bmp;JPEG,JPEG格式,后缀名jpg;PNG,便携网络格式,后缀名png;此外还有几十上百种格式,可自行查阅上述文档。可选参数,仅支持GDAL接口,默认值:’GTiff’。
- compress:GTiff驱动压缩算法。支持LZW和CCITTFAX4。如果保存时不进行压缩,则该值设为None。更多压缩算法可参照GDAL说明文档。可选参数,仅支持GDAL接口,仅支持GTiff驱动。默认值:None。
- geo_info:地理投影信息和地理坐标信息字典。包括’proj’、’coord’两个键。该参数为None时,不写入地理信息。可选参数,仅支持GDAL接口。默认值:None。
- color_table:颜色表List。用于给输出的单波段文件附上RGB颜色信息。其格式为多元组列表,每个元组以(r,g,b)保存颜色值。例如:[(0, 0, 0), (255, 0, 0), (0, 255, 0), (0, 0, 255), (255, 255, 255)]。当该值为None时,将不对单波段数据进行上色。可选参数,仅支持GDAL接口,仅支持单波段uint8类型数据。默认值为:None。
- no_data_value:设置NoData值。用于将某个值透明化,以便于ENVI与ArcGIS软件加载显示。一般情况下为整数。当该值为None时,将不设置NoData值。可选参数,仅支持GDAL接口。默认值:None。
返回值:
- 保存成功返回True,失败返回False。
代码示例:
yimage.io.write_image('out1.jpg', img1, driver='OpenCV') yimage.io.write_image('out2.tif', img2) yimage.io.write_image('out3.dat', img3, dtype='float32', gdal_driver='ENVI') yimage.io.write_image('out4.tif', img4, geo_info=img4_geo_info, color_table=ct_list) yimage.io.write_image('out5.tif', img5, no_data_value=0, compress='LZW') yimage.io.write_image('out6.tif', img6, band_list=[2,3,4], use_CHW=True)
yimage.io.img_ordered_by_band_list(img, band_list)
本函数可以实现波段重新排序,或者可根据列表筛选出部分波段,组成新的图像数组。
参数说明:
- img:待重排的numpy数组。
- band_list:波段顺序列表。比如:大多数数据中,[1, 2, 3]指的是真彩色,[2, 3, 4]指的是假彩色;对于一些高光谱数据,我们可以用[10, 23, 35]组成彩色影像。如果需要将BGR数据转为RGB数据,以适用于PIL等库的话,我们可以用[3, 2, 1]完成波段交换。
- use_CHW:numpy数组采用CHW形式或者HWC形式。当该值为True时,采用CHW形式,为False时,采用HWC形式。此参数仅用于指定numpy数组的格式,并不能将数组转换为CHW或者HWC形式。若要转换,可以用numpy的transpose函数。可选参数。默认值:False。
返回值:
- numpy数组。
代码示例:
img1 = yimage.io.img_ordered_by_band_list(img1, [3, 2, 1]) img2 = yimage.io.img_ordered_by_band_list(img2, [3, 10, 23, 35], use_CHW=True)
yimage.io.polygonize(raster_filename, shapefile_filename, band_num=None, layer_name=None)
本函数用于将栅格数据矢量化。保存ArcGIS Shapefile文件。
需要注意的是,如果gdal库的版本号低于3.2.2时,本函数运行速度将会非常缓慢。gdal版本号大于等于3.2.2时,能够提速上百倍。
参数说明:
- raster_filename:输入的栅格数据路径。
- shapefile_filenname:写出的矢量文件路径。
- band_num:矢量化的栅格文件的波段号。一般情况下为大于等于1,小于等于波段数的整数。当为None时,矢量化第1波段数据。可选参数。默认值为:None。
- layer_name:写出的矢量文件的Layer名称。当为None时,该名称自动设置为’polygon’。可选参数。默认值为:None。
返回值:
- 矢量化成功返回True,失败返回False。
代码示例:
yimage.io.polygonize('test.tif', 'out.shp')
yimage.io.load_color_table_file(filename)
本函数用于加载颜色表文件。
参数说明:
- filename:颜色表文件路径。
返回值:
- 两个列表。包括类别名称列表和颜色表列表。
颜色表文件说明:颜色表文件为yimage库自定义,仅用于yimage库,其本质为txt文件,每一行表示一种颜色。颜色表文件示例:
0 / 0 / 0 # Color 1 255 / 0 / 0 # Color 2 0 / 255 / 0 # Color 3 0 / 0 / 255 # Color 4 255 / 255 / 255 # Color 5
读取后的类别名称列表为[‘Color 1’, ‘Color 2’, ‘Color 3’, ‘Color 4’, ‘Color 5’],颜色表列表为[(0, 0, 0), (255, 0, 0), (0, 255, 0), (0, 0, 255), (255, 255, 255)]。
代码示例:
name_list, color_table = yimage.io.load_color_table_file('color_table.txt')
【注意】本函数在实际调用过程中,可能会报错,目前解决方案是采用以下临时api调用
name_list, color_table = yimage._internal._io._load_color_table_file('color_table.txt')
此bug将在后续更新中解决,目前yimage库正在开发新的更好用的功能。
5 更新日志
Version 1.1.0 (05/25/2021)
- 新增BGR、RGB模式支持
- 新增HWC、HWC模式支持
- 新增GTiff驱动的LZW压缩算法支持
Version 1.0.3 (06/20/2020)
- 增加了直接读取影像大小及地理信息而不读取影像数据的选项
- 修复了yimage.io.write_image方法中一个致命bug
Version 1.0.2 (06/19/2020)
- 增加了yimage.io.write_image方法中的driver的默认参数
Version 1.0.1 (06/12/2020)
- 删除了yimage.io.write_image方法中的offset参数
Version 1.0.0 (06/12/2020)
- 支持读写遥感数据
- 支持波段重新排序
- 支持矢量化栅格数据
- 支持读取颜色表文件
Yannx
2021年5月30日