一、概述

遥感影像和地理坐标进行关联的方式一般有好几种,一种是直接给出了仿射变换系数,即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实现了纠正显示的情况例如以下图:

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvemhvdXh1Z3VhbmcyMzY=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" alt="" />

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官方文档以及源代码

阮秋琦、数字图像处理(第三版)

利用GDAL实现影像的几何校正的更多相关文章

  1. Python 利用GDAL对图像进行几何校正

    原文链接:https://blog.csdn.net/qq_27045589/article/details/81062586 一.几何校正方法 图像校正本质是建立一种从原始图像行列号到某种投影的数学 ...

  2. envi利用矢量数据对影像做多边形裁剪 (转)

    2011-05-25 17:31:10|  分类: ENVI|举报|字号 订阅 来自:http://zhangling357522.blog.163.com/blog/static/184687296 ...

  3. 利用GDAL从内存中直接解析图像数据

    对于网络数据源,调度中可以把数据写入本地,然后读取本地数据格式进行影像的解析(地形有时候也用tif等格式). 此种方式会每次调度进行不必要的IO开销和时间花费. GDAL提供了相应的接口,直接从内存中 ...

  4. AE + GDAL实现影像按标准图幅分割(上)

    最近有个项目,其中有个功能是要将遥感影像按标准图幅分割,一开始用AE的接口,慢的让人抓狂,就改用GDAL,速度提升很大.我主要通过http://blog.csdn.net/liminlu0314/学习 ...

  5. 利用GDAL进行工具开源化改造

    文章版权由作者李晓晖和博客园共有,若转载请于明显处标明出处:http://www.cnblogs.com/naaoveGIS/ 1.背景 为利于项目实施,团队用AE写过一个插件式的工具集,主要包括了数 ...

  6. 【GIS】GDAL Python 影像裁剪

    # -*- coding: utf-8 -*- """ Created on Fri Nov 30 11:45:03 2018 @author: Administrato ...

  7. C#+GDAL读取影像(1)

    环境:VS2010,C#,GDAL1.7 读取影像: using System; using System.Collections.Generic; using System.ComponentMod ...

  8. GDAL读取影像并插值

    影像读取 并缩放 读取大影像某一部分,并缩放到指定大小,我们有时会用如下代码: #include "gdal.h" #include "gdal_priv.h" ...

  9. 翻译:利用GDAL生成cogeoff文件

    翻译自: Introducing the AWS Lambda Tiler https://hi.stamen.com/stamen-aws-lambda-tiler-blog-post-76fc11 ...

随机推荐

  1. JavaScript是按值传递还是按引用传递?

    JavaScript是按值传递的,但是要分情况才知道传递之后原来的值会不会变,不然会出现你想都想不出来的bug 一.按值传递--元类型输入tip:元类型( number, string, boolea ...

  2. 深入理解Redis——(总纲)

    前言 Redis这个东西,我来来回回的也搞了好几遍,之前更偏向于实战,很多时候只知其然而不知其所以然,最近借阅了一本<深入理解Redis>,就系统的整理一下. 为何选择Redis 开源免费 ...

  3. BZOJ 1407 exgcd

    思路: 数据范围不大.. 那我们就枚举M好了.. 再两两判断一下有没有冲突 怎么判断呢? exgcd!!! p[i]*k+c[i]=p[j]*k+c[j]  (mod m) (p[j]-p[i])*k ...

  4. 非内置浏览器WebView 调起H5支付,提示商家参数格式有误

    微信H5 支付开发官方文档参考资料: https://pay.weixin.qq.com/wiki/doc/api/H5.php?chapter=15_4 0. 场景描述:在APP 中使用webVie ...

  5. CNN结构:用于检测的CNN结构进化-结合式方法

    原文链接:何恺明团队提出 Focal Loss,目标检测精度高达39.1AP,打破现有记录     呀 加入Facebook的何凯明继续优化检测CNN网络,arXiv 上发现了何恺明所在 FAIR 团 ...

  6. OpenCV:Adaboost训练时数据扩增

    更准确的模型需要更多的数据,对于传统非神经网络机器学习方法,不同的特征需要有各自相符合的数据扩增方法. 1.   在使用opencv_traincascade.exe 过程中,图像读取在 classi ...

  7. .NET 在序列化时使用全小写的属性名

    基于某些奇怪的需求,需要将一些对象序列化后输出,而且属性名又必须为小写形式. 解决过程 说到在 .NET 平台上序列化操作,那么第一个想到的应该就是 Json.NET 家的 Newtonsoft.Js ...

  8. html中的小知识

    引用外部样式 样式表,如果是引用外部样式,不需要再写style标签了,因为 <link rel="stylesheet" type="text/css" ...

  9. Linux 之根目录介绍

    1. /bin binary二进制 存放系统许多可执行程序文件 执行的相关指令,例如ls pwd whoami,后台的支持文件目录 2. /sbin super binary超级的二进制 存放系统许多 ...

  10. API接口测试用例编写规则(转载)

    API接口测试用例编写规则 (1)必需参数覆盖.对于接口的参数,接口文档一般都会说明哪些儿是必需的,哪儿是非必需的.对于必需的参数,一定要测试传参数和不传参数接口是否报错? (2)必需的参数各种情况覆 ...