天天看点

GDAL源码剖析(七)之GDAL RasterIO使用说明(续)

        之前写了一篇 《GDAL RasterIO使用说明》​​​​,很多人对于RasterIO这个函数的用法还是有很多的不明白,可能之前的那篇文章没有写的很清楚,下面再对这个函数进行说明。

        在进入主题之前,我们先了解一下图像的基本存储方式。所谓的图像,先用单波段图像说明,图像就是一个很大的二维矩阵,这个矩阵的宽高就是图像的宽高,矩阵里面的元素值就是图像的像元值(这里多说一下,对于DEM数据,很多人问怎么读取DEM的高程值,DEM数据的话,高程值就是像元值)。比如下面的图1所示:这个图像的大小是600×600×3。

        首先说明一下GDALRasterBand的RasterIO函数,波段类的RasterIO函数相比GDALDataset类的RasterIO函数比较简单,先从这个简单的说起。图像还是以上面的图为例,我们只取第一个波段为例进行说明。

        我们再把GDALRasterBand的RasterIO函数接口拿过来,形式如下:

CPLErr GDALRasterBand::RasterIO(​​GDALRWFlag​​eRWFlag,int nXOff,int nYOff,int nXSize,int nYSize,void * pData,int nBufXSize,int nBufYSize,​​GDALDataType​​eBufType,int nPixelSpace,int nLineSpace)

        第一个参数eRWFlag就是读写标记,用来指定你是读取图像还是写入图像,很简单。

        接下来的四个参数是用来指定读写图像的范围的,其中前两个用来说明读取的位置,起始行列号,后两个是读写图像的宽度和高度,比如我们要在上面的图2中读取一个大小为300×200的矩形区域,从位置(100,200)开始读取,如图3所示:图中左上角红色的坐标(100,200)就是起始位置,分别对应于第二个参数nXOff和第三个参数nYOff,图中的Width=300就对应于第四个参数nXSize,图中的Height=200就对应于第五个参数nYSize。这样就确定了我们要读取或者写入图像的位置和大小了。

        第六个参数void* pData就是用来存储图像的元素值的地方,如果是读取图像,那么读取出来的图像像素值就存储在这个pData中,如果是写入图像,那么这个pData中的数据会被写入到图像上指定的位置中去。那么这个pData的数组大小是多少呢?别急,这个pData的大小是有后面两个参数确定的,即nBufXSize和nBufYSize,这个pData的大小一定不能小于nBufXSize×nBufYSize,否则RasterIO的返回值是错误的(很多人可能都觉得RasterIO没有返回值,RasterIO是有返回值的,是一个int类型的枚举值,如果这个函数返回的不是CE_None,就是0,那么RasterIO就出错了,出错的意思就是读取的时候可能没读出数据,写入的时候没有写到图像中去)。这两个参数的意义不仅仅这么简单,他们还有更牛逼的功能,就是用来缩放图像。比如图3中,读取图像的范围是300×200,如果我要读取图像中的原始数据,那么这两个参数分别应该设置为nBufXSize=300和nBufYSize=200。这样的用法是最常用的也是最简单的。如果我把这两个参数的值设置为nBufXSize=150和nBufYSize=100会发生什么情况呢?恭喜你,你会得到图3中矩形框的一个缩小的图像,也就是行和列都缩小一半,等等,什么意思,我好想没搞明白。缩小一半的意思就是自动把图3中的矩形区域中的灰度值重采样,重采样结果的大小就是你设定的这个150×100。那再比如说,把这两个参数的值设置为nBufXSize=600和nBufYSize=400,读出来的数据就是把图3中的矩形区域图像重采样至原来的两倍。当然了,这个都是GDAL内部自动实现的,重采样方式默认使用的是最邻近采样。如果设置的nBufXSize和nBufYSize与nXSize和nYSize不一样的话,同时图像没有金字塔的话,速度可能会慢,如果有金字塔的话,速度就会很快了。        图4是图3中矩形区域的图像,图5是设置nBufXSize=150和nBufYSize=100读取出来的图像,图6是设置nBufXSize=600和nBufYSize=400读出来的图像。

        接下来是第九个参数nBufType,意思是将数据读取的数据类型,这个参数和pData有密切的关系(不过从pData开始,后面的参数都是和pData相关的),nBufType就是用来标记pData的类型,比如pData是char,那么nBufType就是GDT_Byte,如果pData是float类型,那么就是GDT_Float32,如果pData是double类型,那么就是GDT_Float64等等。这个参数和图像的数据类型没有多大关系,只要和pData的类型一致就不会出错。如果这个参数和图像的数据类型设置的不一致,不会报错,但是读出来的数据可能是错的,当然,这个是有规律的,具体的规律就是,如果图像的GDT_Byte类型的,那么这个参数设置任何类型都能把图像中的数据读出来。如果图像数据类型是GDT_Float32,这个参数设置为GDT_Byte,那么读出来的数据就是错的。那么这是为什么呢?主要的原因就是C/C++的数据类型转换的时候精度丢失的问题,开始图像是GDT_Byte类型,也就是图像里面的值都是用无符号的char来存的,那么后面不管你的pData类似是char,short,int,float还是double,都可以把无符号的char存进去;但是如果图像是float类型,你后面pData是个无符号char类型,那么在把一个float的数赋值给一个char肯定会丢失啊,假如这个float数是300.0,char是不可能存超过0~255,或者-127~128之外的数据的,这样就会造成数据截断。关于这部分可以参考《深入理解计算机系统》这本书。此外,关于这部分可以参考之前的那篇RasterIO博客。

        到这里就剩下最后两个参数了,nPixelSpace和nLineSpace,这两个参数是用来控制参数pData中像元的存储顺序的,nPixelSpace表示的是当前像素值和下一个像素值之间的间隔,nLineSpace表示当前行和下一行的间隔,单位都是按照字节为单位计算。这两个值默认给0,表示的是,nPixelSpace=sizeof(DataType),nLineSpace=nBufXSize*sizeof(DataType),什么意思呢,意思就是,当前像素和下一个像素是紧接的;当前行和下一行的距离是一行像素。可能这个说法还是有些抽象,简单的说,就是默认读取出来的图像的像素值在pData这个数组中的顺序是按照从左到右,从上到下的顺序存储的。通过这两个参数,可以很容易的对pData中的数据进行排序,比如默认是是从左到右,从上到下的顺序存储,那么假如我要读取按照从上到下,从左到右的顺序存储的话,该怎么设置?OK,从上到下,从左到右,也就是第一个像素的下一个像素是在下一行,所以nPixelSpace应该等于一行像素的大小,即nBufXSize*sizeof(DataType),第一行和下一行的间隔就是最右边的呗,所以nLineSpace的值就是sizeof(DataType)。

        至此,我们已经把GDALRasterBand类的RasterIO的函数做了一个详细的说明。下面开始对GDALDataset类的RasterIO做一个说明。GDALDataset类的RasterIO和GDALRasterBand类的RasterIO的接口基本一样,除了多了几个参数。除了多出来的参数我会在下面说明,没有多出来的意义和上面的一样,下面不再赘述。还是一样,我们先把GDALDataset类的RasterIO接口列出来,形式如下:

CPLErr GDALDataset::RasterIO(​​GDALRWFlag​​eRWFlag,int nXOff,int nYOff,int nXSize,int nYSize,void * pData,int nBufXSize,int nBufYSize,​​GDALDataType​​eBufType,int nBandCount,int * panBandMap,int nPixelSpace,int nLineSpace,int nBandSpace)