一、概述
遥感影像和地理坐标进行关联的方式一般有好几种,一种是直接给出了仿射变换系数,即6个參数,左上角地理坐标,纵横方向上的分辨率,以及旋转系数。在这样的情况下,求出某一像素点的地理坐标非常easy,直接用公式能够求出,详细代码例如以下:
void CPL_STDCALL GDALApplyGeoTransform(double *padfGeoTransform,
double dfPixel, double dfLine,
double *pdfGeoX, double *pdfGeoY )
{
*pdfGeoX = padfGeoTransform[0]+ dfPixel * padfGeoTransform[1]
+ dfLine * padfGeoTransform[2];
*pdfGeoY = padfGeoTransform[3]+ dfPixel * padfGeoTransform[4]
+ dfLine * padfGeoTransform[5];
}
可能有的读者额看到这个觉得非常熟悉。这个是GDAL里面实现的由像素点转换到地理坐标点的转换函数,这个函数的声明在gdal.h中,定义在gdaltransformer.cpp文件里,注意。这个函数计算的是影像像元的左上角的地理坐标,也能够计算中心点坐标,详细看需求。
第二种方式是记录影像控制点坐标的数据,一般控制点都记录着控制点自身的地理坐标。还有控制点在影像中的像素坐标点。还有控制点必须是在某一特定的地理坐标系下,在GDAL中控制点的结构体为:
/** Ground Control Point */
typedef struct
{
/** Unique identifier, often numeric */
char *pszId;
/** Informational message or "" */
char *pszInfo;
/** Pixel (x) location of GCP on raster */
double dfGCPPixel;
/** Line (y) location of GCP on raster */
double dfGCPLine;
/** X position of GCP in georeferenced space*/
double dfGCPX;
/** Y position of GCP in georeferenced space*/
double dfGCPY;
/** Elevation of GCP, or zero if not known */
double dfGCPZ;
} GDAL_GCP;
那读者会有疑问了,这个控制点的数据怎么获得呢。GDAL里面有函数接口能够获得,详细例如以下;
char* pszWkt1 = (char*)poDatasetchangsha->GetProjectionRef();
if (0 == strlen(pszWkt1))
{
pszWkt1= (char*)poDatasetchangsha->GetGCPProjection();
}
int nGCPCount = poDatasetchangsha->GetGCPCount();
const GDAL_GCP *pGCPList= poDatasetchangsha->GetGCPs();
我首先给出一个样例先有一个比較直观的感受,我手上有一个spot传感器的数据,它是带控制点信息的,控制点刚好是图像的四个角点。比方。用arcgis打开就已经是纠正显示了。
没有实施纠正显示的情况和ARCGIS实现了纠正显示的情况例如以下图:
Arcgis显示的时候,边缘的一些像素因为在原始影像上找不到相应的採样点,所以就用白色填充。
二、几何校正方法
关于影像几何校正,一般能够採用以下的几种方法:
1、几何多项式校正模型
这个模型比較简单,回避了遥感影像的成像过程,适用于覆盖面积不大喝地形比較平坦的区域。
2、有理函数数学模型
建立像素和地面位置相应关系的简单数学模型。理论上能够达到跟严格卫星传感器模型相当的定位精度。其长处在于多项式包括高程信息,能够提高校正精度。
这个模型自带了高程信息,一般须要影像提供RPC文件,或者用户自己选择地面控制点。
3、局部区域校正模型
这个模型的基本思想是利用控制点建立不规则三角网。然后分区域利用几何多项式校正,可是这样的模型须要非常多的控制点,对于地形起伏非常大的区域须要的控制点很多其它,往往实施难度比較大。
4、卫星传感器模型
这样的模型是依据卫星遥感成像的几何关系,利用摄影測量学中成像瞬间的地面点、透视中心以及相应的像素点三点共线建立起来的一种模型,可是此模型须要DEM数据,长处是精度比較高。
因为本文主要关注几何多项式模型,所以仅仅利用GDAL中几何多项式的模型来校正。
能够依据地面控制点求算出仿射变换系数,能够调用GDAL的函数。其函数是:
int CPL_DLL CPL_STDCALL
GDALGCPsToGeoTransform(int nGCPCount,const GDAL_GCP*pasGCPs,
double *padfGeoTransform, int bApproxOK );
这个函数的定义在gdal_misc.cpp文件里,改天能够去了解下。
求出仿射变换系数后能够求出图像的大致范围和分辨率,这样就能够目标图像所占的行数和列数。
以下的代码是求出影像的envelope,大概的范围
double dbX[4];
double dbY[4];
dbX[0] = dblGeoTransform[0]+ 0*dblGeoTransform[1] + 0*dblGeoTransform[2];
dbY[0] = dblGeoTransform[3]+ 0*dblGeoTransform[4] + 0*dblGeoTransform[5];
//右上角坐标
dbX[1] = dblGeoTransform[0]+ (lWidth/*-0.5*/)*dblGeoTransform[1] + 0*dblGeoTransform[2];
dbY[1] = dblGeoTransform[3]+ (lWidth/*-0.5*/)*dblGeoTransform[4] + 0*dblGeoTransform[5];
//右下角点坐标
dbX[2] = dblGeoTransform[0]+ (lWidth/*-0.5*/)*dblGeoTransform[1] + (lHeight/*-0.5*/)*dblGeoTransform[2];
dbY[2] = dblGeoTransform[3]+ (lWidth/*-0.5*/)*dblGeoTransform[4] + (lHeight/*-0.5*/)*dblGeoTransform[5];
//左下角坐标
dbX[3] = dblGeoTransform[0]+ 0*dblGeoTransform[1] + (lHeight/*-0.5*/)*dblGeoTransform[2];
dbY[3] = dblGeoTransform[3]+ 0*dblGeoTransform[4] + (lHeight/*-0.5*/)*dblGeoTransform[5];
double dbMinx = 0;
double dbMaxx = 0;
double dbMiny = 0;
double dbMaxy = 0;
dbMinx = min(min(min(dbX[0],dbX[1]),dbX[2]),dbX[3]);
dbMaxx = max(max(max(dbX[0],dbX[1]),dbX[2]),dbX[3]);
dbMiny = min(min(min(dbY[0],dbY[1]),dbY[2]),dbY[3]);
dbMaxy = max(max(max(dbY[0],dbY[1]),dbY[2]),dbY[3]);
可是上面的方法计算出来的范围有可能不是非常准确,所以这里要寻找一种比較精确的方法。那就是求出每一个边缘像素的边缘相应的地理坐标,然后逐个比較大小求出比較精确的范围。这样的方法能够採用GDAL算法模块里面GDALSuggestedWarpOutput2函数。
CPLErr CPL_STDCALL
GDALSuggestedWarpOutput2(GDALDatasetH hSrcDS,
GDALTransformerFunc pfnTransformer,
void *pTransformArg,
double *padfGeoTransformOut,
int *pnPixels, int *pnLines,
double *padfExtent,int nOptions)。
这个函数里面的參数的意义解释能够參考GDAL官方文档,也能够直接看GDAL源代码,以下我就给出各个參数的意思。
第一个參数是数据源,第二个參数是转换的回调函数,比方是GCP控制点转换的函数GDALGCPTransform。或者是RPC校正的函数等等。第三个參数是代表转换的关系,这里的转换关系是控制点几何校正的转换关系,此转换关系能够由void CPL_DLL *
GDALCreateGCPTransformer(int nGCPCount,const GDAL_GCP*pasGCPList,
int nReqOrder, int bReversed );函数生成。第四个參数是图像纠正后的仿射变换的6个參数,pnPixels是纠正后图像的列数,pnLines是纠正后图像的行数。padfExtent就是图像纠正后的地理范围。nOptions是相关选项的设置,默认情况下能够设为空。
三、重採样及其校正实现
到此,我们已经完毕了原始图像的像素坐标到校正后的地理空间坐标建立了关联关系,而且输出影像的大小、地理范围、分辨率、像素位深度都已经知道。这样我们就能够创建一个空的图像了。可是校正后的图像的各个像素的值还没确定。接下来就是怎样确定校正后图像中各个像素的值,也就是图像处理中的重採样技术,一般大家听的比較多的重採样技术可能是最邻近方法、双线性内插重採样以及三次卷积内插重採样等方法。另一些其它的重採样方法。比方平均值法,中值法等。
重採样技术我们能够直接调用GDAL的函数去实现。可是我这里决定自己写重採样的过程,以便今后做一些改进,这里我先用最邻近採样方法实现重採样。
整个过程的代码例如以下:
/*** @brief 几何多项式校正* @param pszSrcFile 输入文件路径* @param pszDstFile 输出文件路径* @param nGCPCount GCP点对个数* @param pasGCPList GCP点对列表* @param pszDstWKT 输出文件投影* @param iOrder 多项式次数* @param dResX 输出图像X方向分辨率* @param dResY 输出图像Y方向分辨率* @param eResampleMethod 重採样方式* @param pszFormat 输出文件格式* @return 返回值,眼下仅仅返回true或者false*/int ImageWarpByGCP(const char * pszSrcFile, const char * pszDstFile, int nGCPCount, const GDAL_GCP *pasGCPList, const char * pszDstWKT, int iOrder, double dResX, double dResY, GDALResampleAlg eResampleMethod, const char * pszFormat){ GDALAllRegister(); CPLSetConfigOption("GDAL_FILENAME_IS_UTF8", "NO"); // 打开原始图像 GDALDatasetH hSrcDS = GDALOpen(pszSrcFile, GA_ReadOnly); if (NULL == hSrcDS) { return 0; } GDALDataType eDataType = GDALGetRasterDataType(GDALGetRasterBand(hSrcDS, 1)); int nBandCount = GDALGetRasterCount(hSrcDS); // 创建几何多项式坐标转换关系 void *hTransform = GDALCreateGCPTransformer(nGCPCount, pasGCPList, iOrder, FALSE ); if( NULL == hTransform ) { GDALClose( hSrcDS ); return 0; } // 计算输出图像四至范围、大小、仿射变换六參数等信息 double adfGeoTransform[6] = {0}; double adfExtent[4] = {0}; int nPixels = 0, nLines = 0; if( GDALSuggestedWarpOutput2( hSrcDS, GDALGCPTransform, hTransform, adfGeoTransform, &nPixels, &nLines, adfExtent, 0 ) != CE_None ) { GDALClose( hSrcDS ); return 0; } // 以下開始依据用户指定的分辨率来反算输出图像的大小和六參数等信息 double dResXSize = dResX; double dResYSize = dResY; //假设为0,则默觉得原始影像的分辨率 if(dResXSize == 0.0 && dResYSize == 0.0 ) { double dbGeoTran[6] = {0}; GDALGCPsToGeoTransform(nGCPCount,pasGCPList,dbGeoTran,0); dResXSize = fabs(dbGeoTran[1]); dResYSize = fabs(dbGeoTran[5]); } // 假设用户指定了输出图像的分辨率 else if ( dResXSize != 0.0 || dResYSize != 0.0 ) { if ( dResXSize == 0.0 ) dResXSize = adfGeoTransform[1]; if ( dResYSize == 0.0 ) dResYSize = adfGeoTransform[5]; } if ( dResXSize < 0.0 ) dResXSize = -dResXSize; if ( dResYSize > 0.0 ) dResYSize = -dResYSize; // 计算输出图像的范围 double minX = adfGeoTransform[0]; double maxX = adfGeoTransform[0] + adfGeoTransform[1] * nPixels; double maxY = adfGeoTransform[3]; double minY = adfGeoTransform[3] + adfGeoTransform[5] * nLines; nPixels = ceil(( maxX - minX ) / dResXSize ); nLines = ceil(( minY - maxY ) / dResYSize ) ; adfGeoTransform[0] = minX; adfGeoTransform[3] = maxY; adfGeoTransform[1] = dResXSize; adfGeoTransform[5] = dResYSize; // 创建输出图像 GDALDriverH hDriver = GDALGetDriverByName( pszFormat ); if (NULL == hDriver) { return 0; } GDALDatasetH hDstDS = GDALCreate( hDriver, pszDstFile, nPixels, nLines, nBandCount, eDataType, NULL ); if (NULL == hDstDS) { return 0; } GDALSetProjection( hDstDS, pszDstWKT); GDALSetGeoTransform( hDstDS, adfGeoTransform ); //获得原始图像的行数和列数 int nXsize = GDALGetRasterXSize(hSrcDS); int nYsize = GDALGetRasterYSize(hSrcDS); //然后是图像重採样 int nFlag = 0; float dfValue = 0; CPLErr err = CE_Failure; //最邻近採样 for (int nBandIndex = 0; nBandIndex < nBandCount; nBandIndex ++) { GDALRasterBandH hSrcBand = GDALGetRasterBand(hSrcDS,nBandIndex+1); GDALRasterBandH hDstBand = GDALGetRasterBand(hDstDS,nBandIndex+1); for (int nRow = 0; nRow < nLines; nRow ++) { for (int nCol = 0; nCol < nPixels; nCol ++) { double dbX = adfGeoTransform[0] + nCol * adfGeoTransform[1] + nRow * adfGeoTransform[2]; double dbY = adfGeoTransform[3] + nCol * adfGeoTransform[4] + nRow * adfGeoTransform[5]; //由输出的图像地理坐标系变换到原始的像素坐标系 GDALGCPTransform(hTransform,TRUE,1,&dbX,&dbY,NULL,&nFlag); int nXCol = (int)(dbX + 0.5); int nYRow = (int)(dbY + 0.5); //超出范围的用0填充 if (nXCol < 0 || nXCol >= nXsize || nYRow < 0 || nYRow >= nYsize) { dfValue = 0; } else { err = GDALRasterIO(hSrcBand,GF_Read,nXCol,nYRow,1,1,&dfValue,1,1,eDataType,0,0); } err = GDALRasterIO(hDstBand,GF_Write,nCol,nRow,1,1,&dfValue,1,1,eDataType,0,0); } } } if (hTransform != NULL) { GDALDestroyGCPTransformer(hTransform); hTransform = NULL; } GDALClose(hSrcDS ); GDALClose(hDstDS ); return 1;}外部调用的实例
GDALDataset* poDataset = (GDALDataset*)GDALOpen("E\\248-279_5\\SCENE01\\IMAGERY.TIF",GA_ReadOnly); char* pszWkt1 = (char*)poDataset->GetProjectionRef(); if (0 == strlen(pszWkt1)) { pszWkt1 = (char*)poDataset->GetGCPProjection(); } int nGCPCount = poDataset->GetGCPCount(); const GDAL_GCP *pGCPList = poDataset->GetGCPs(); GDAL_GCP *pGCPs = new GDAL_GCP[nGCPCount]; memcpy(pGCPs,pGCPList,sizeof(GDAL_GCP)*nGCPCount); GDALClose(poDataset); const char* pszInFile = "E:\\248-279_5\\SCENE01\\IMAGERY.TIF"; const char* pszOutFile = "E:\\248-279_5\\SCENE01\\IMAGERY-校正.TIF"; ImageWarpByGCP(pszInFile,pszOutFile,nGCPCount,pGCPs,pszWkt1,0,0,0,GRA_NearestNeighbour,"GTiff");
对于上述算法的函数。有些读者可能看出来了,算法的瓶颈就是在那个多重循环那,逐点计算是不是效率太低了。的确,这里能够用分块技术来优化一下。甚至有可能的话。用GPU加速也是不错的选择,我这边仅仅是向大家分享一下怎么去校正带控制点的影像,算是抛砖引玉吧。假设有什么不妥的地方也能够指出来,大家一起讨论。
四、兴许研究
1. 怎样进行校正显示,不生成新到的校正影像。
2. 怎样分块处理
3. 怎样用硬件加速,GPU加速
參考文献
张永生,刘军. 高分辨率遥感卫星立体影像RPC模型定位的算法及其优化測绘project, 2004,
刘军,王冬红,毛国苗. 基于RPC模型的IKONOS卫星影像高精度立体定位. 測绘通报, 2004
刘军,张永生,范永弘. 基于通用成像模型———有理函数模型的摄影測量定位方法. 測绘通报, 2003
巩丹超,邓雪清,张云彬. 新型遥感卫星传感器几何模型—有理函数模型. 海洋測绘, 2003
GDAL官方文档以及源代码
阮秋琦、数字图像处理(第三版)