yimage,一个友好的遥感影像I/O库

为了提升遥感数据处理编程体验,我完成了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日

Leave a Comment