OpenCV如何使用 GDAL 读取地理空间栅格文件(72)

返回:OpenCV系列文章目录(持续更新中......)
上一篇:OpenCV的周期性噪声去除滤波器(70)
下一篇 :OpenCV系列文章目录(持续更新中......)

目录

目标

代码:

解释: 

如何使用 GDAL 读取栅格数据

注意

通常应避免使用经度/纬度(地理)坐标

查找拐角坐标

结果

地理空间栅格数据是地理信息系统和摄影测量中大量使用的产品。栅格数据通常可以表示影像和数字高程模型 (DEM)。用于加载 GIS 影像的标准库是地理数据抽象库 (GDAL)。在此示例中,我们将展示使用本机 OpenCV 函数加载 GIS 栅格格式的技术。此外,我们将展示一些示例,说明OpenCV如何将这些数据用于新颖有趣的目的。

目标

本教程的主要目标:

  • 如何使用 OpenCV imread 加载卫星图像。
  • 如何使用 OpenCV imread 加载 SRTM 数字高程模型
  • 给定图像和 DEM 的角坐标,将高程数据与图像相关联,以查找每个像素的高程。
  • 显示一个基本、易于实施的地形热图示例。
  • 显示 DEM 数据与正射校正影像的基本用法。

为了实现这些目标,以下代码采用数字高程模型以及旧金山的 GeoTiff 图像作为输入。对影像和 DEM 数据进行处理并生成影像的地形热图,并标注在海湾水位上升 10、50 和 100 米时将受到影响的城市区域。

代码如下:

/** gdal_image.cpp -- Load GIS data into OpenCV Containers using the Geospatial Data Abstraction Library
*/// OpenCV Headers
#include "opencv2/core.hpp"
#include "opencv2/imgproc.hpp"
#include "opencv2/highgui.hpp"// C++ Standard Libraries
#include <cmath>
#include <iostream>
#include <stdexcept>
#include <vector>using namespace std;// define the corner points
// Note that GDAL library can natively determine this
cv::Point2d tl( -122.441017, 37.815664 );
cv::Point2d tr( -122.370919, 37.815311 );
cv::Point2d bl( -122.441533, 37.747167 );
cv::Point2d br( -122.3715, 37.746814 );// determine dem corners
cv::Point2d dem_bl( -122.0, 38);
cv::Point2d dem_tr( -123.0, 37);// range of the heat map colors
std::vector<std::pair<cv::Vec3b,double> > color_range;// List of all function prototypes
cv::Point2d lerp( const cv::Point2d&, const cv::Point2d&, const double& );cv::Vec3b get_dem_color( const double& );cv::Point2d world2dem( const cv::Point2d&, const cv::Size&);cv::Point2d pixel2world( const int&, const int&, const cv::Size& );void add_color( cv::Vec3b& pix, const uchar& b, const uchar& g, const uchar& r );/** Linear Interpolation* p1 - Point 1* p2 - Point 2* t - Ratio from Point 1 to Point 2
*/
cv::Point2d lerp( cv::Point2d const& p1, cv::Point2d const& p2, const double& t ){return cv::Point2d( ((1-t)*p1.x) + (t*p2.x),((1-t)*p1.y) + (t*p2.y));
}/** Interpolate Colors
*/
template <typename DATATYPE, int N>
cv::Vec<DATATYPE,N> lerp( cv::Vec<DATATYPE,N> const& minColor,cv::Vec<DATATYPE,N> const& maxColor,double const& t ){cv::Vec<DATATYPE,N> output;for( int i=0; i<N; i++ ){output[i] = (uchar)(((1-t)*minColor[i]) + (t * maxColor[i]));}return output;
}/** Compute the dem color
*/
cv::Vec3b get_dem_color( const double& elevation ){// if the elevation is below the minimum, return the minimumif( elevation < color_range[0].second ){return color_range[0].first;}// if the elevation is above the maximum, return the maximumif( elevation > color_range.back().second ){return color_range.back().first;}// otherwise, find the proper starting indexint idx=0;double t = 0;for( int x=0; x<(int)(color_range.size()-1); x++ ){// if the current elevation is below the next item, then use the current// two colors as our rangeif( elevation < color_range[x+1].second ){idx=x;t = (color_range[x+1].second - elevation)/(color_range[x+1].second - color_range[x].second);break;}}// interpolate the colorreturn lerp( color_range[idx].first, color_range[idx+1].first, t);
}/** Given a pixel coordinate and the size of the input image, compute the pixel location* on the DEM image.
*/
cv::Point2d world2dem( cv::Point2d const& coordinate, const cv::Size& dem_size ){// relate this to the dem points// ASSUMING THAT DEM DATA IS ORTHORECTIFIEDdouble demRatioX = ((dem_tr.x - coordinate.x)/(dem_tr.x - dem_bl.x));double demRatioY = 1-((dem_tr.y - coordinate.y)/(dem_tr.y - dem_bl.y));cv::Point2d output;output.x = demRatioX * dem_size.width;output.y = demRatioY * dem_size.height;return output;
}/** Convert a pixel coordinate to world coordinates
*/
cv::Point2d pixel2world( const int& x, const int& y, const cv::Size& size ){// compute the ratio of the pixel location to its dimensiondouble rx = (double)x / size.width;double ry = (double)y / size.height;// compute LERP of each coordinatecv::Point2d rightSide = lerp(tr, br, ry);cv::Point2d leftSide = lerp(tl, bl, ry);// compute the actual Lat/Lon coordinate of the interpolated coordinatereturn lerp( leftSide, rightSide, rx );
}/** Add color to a specific pixel color value
*/
void add_color( cv::Vec3b& pix, const uchar& b, const uchar& g, const uchar& r ){if( pix[0] + b < 255 && pix[0] + b >= 0 ){ pix[0] += b; }if( pix[1] + g < 255 && pix[1] + g >= 0 ){ pix[1] += g; }if( pix[2] + r < 255 && pix[2] + r >= 0 ){ pix[2] += r; }
}/** Main Function
*/
int main( int argc, char* argv[] ){/** Check input arguments*/if( argc < 3 ){cout << "usage: " << argv[0] << " <image_name> <dem_model_name>" << endl;return -1;}// load the image (note that we don't have the projection information. You will// need to load that yourself or use the full GDAL driver. The values are pre-defined// at the top of this filecv::Mat image = cv::imread(argv[1], cv::IMREAD_LOAD_GDAL | cv::IMREAD_COLOR );// load the dem modelcv::Mat dem = cv::imread(argv[2], cv::IMREAD_LOAD_GDAL | cv::IMREAD_ANYDEPTH );// create our output productscv::Mat output_dem( image.size(), CV_8UC3 );cv::Mat output_dem_flood( image.size(), CV_8UC3 );// for sanity sake, make sure GDAL Loads it as a signed shortif( dem.type() != CV_16SC1 ){ throw std::runtime_error("DEM image type must be CV_16SC1"); }// define the color range to create our output DEM heat map// Pair format ( Color, elevation ); Push from low to high// Note: This would be perfect for a configuration file, but is here for a working demo.color_range.push_back( std::pair<cv::Vec3b,double>(cv::Vec3b( 188, 154, 46), -1));color_range.push_back( std::pair<cv::Vec3b,double>(cv::Vec3b( 110, 220, 110), 0.25));color_range.push_back( std::pair<cv::Vec3b,double>(cv::Vec3b( 150, 250, 230), 20));color_range.push_back( std::pair<cv::Vec3b,double>(cv::Vec3b( 160, 220, 200), 75));color_range.push_back( std::pair<cv::Vec3b,double>(cv::Vec3b( 220, 190, 170), 100));color_range.push_back( std::pair<cv::Vec3b,double>(cv::Vec3b( 250, 180, 140), 200));// define a minimum elevationdouble minElevation = -10;// iterate over each pixel in the image, computing the dem pointfor( int y=0; y<image.rows; y++ ){for( int x=0; x<image.cols; x++ ){// convert the pixel coordinate to lat/lon coordinatescv::Point2d coordinate = pixel2world( x, y, image.size() );// compute the dem image pixel coordinate from lat/loncv::Point2d dem_coordinate = world2dem( coordinate, dem.size() );// extract the elevationdouble dz;if( dem_coordinate.x >= 0 && dem_coordinate.y >= 0 &&dem_coordinate.x < dem.cols && dem_coordinate.y < dem.rows ){dz = dem.at<short>(dem_coordinate);}else{dz = minElevation;}// write the pixel value to the fileoutput_dem_flood.at<cv::Vec3b>(y,x) = image.at<cv::Vec3b>(y,x);// compute the color for the heat map outputcv::Vec3b actualColor = get_dem_color(dz);output_dem.at<cv::Vec3b>(y,x) = actualColor;// show effect of a 10 meter increase in ocean levelsif( dz < 10 ){add_color( output_dem_flood.at<cv::Vec3b>(y,x), 90, 0, 0 );}// show effect of a 50 meter increase in ocean levelselse if( dz < 50 ){add_color( output_dem_flood.at<cv::Vec3b>(y,x), 0, 90, 0 );}// show effect of a 100 meter increase in ocean levelselse if( dz < 100 ){add_color( output_dem_flood.at<cv::Vec3b>(y,x), 0, 0, 90 );}}}// print our heat mapcv::imwrite( "heat-map.jpg" , output_dem );// print the flooding effect imagecv::imwrite( "flooded.jpg", output_dem_flood);return 0;
}

解释: 

在提供的代码片段中,有几个关键函数,它们负责执行特定的任务,如插值、颜色映射和坐标转换。下面是每个关键函数的代码片段和详细解释:

1. **线性插值函数 `lerp`**

cv::Point2d lerp(cv::Point2d const& p1, cv::Point2d const& p2, const double& t){return cv::Point2d(((1 - t) * p1.x) + (t * p2.x), ((1 - t) * p1.y) + (t * p2.y));
}


这个函数执行二维空间中的线性插值。它接受两个点 `p1` 和 `p2`,以及一个插值比率 `t`。当 `t` 从 0 变到 1 时,这个函数会生成从 `p1` 到 `p2` 的一系列点。

2. **颜色插值模板函数 `lerp`**

template <typename DATATYPE, int N>
cv::Vec<DATATYPE,N> lerp(cv::Vec<DATATYPE,N> const& minColor,cv::Vec<DATATYPE,N> const& maxColor,double const& t){cv::Vec<DATATYPE,N> output;for(int i = 0; i < N; i++){output[i] = static_cast<uchar>(((1 - t) * minColor[i]) + (t * maxColor[i]));}return output;
}


这是一个模板函数,用于在两种颜色之间进行插值。它接受两种颜色 `minColor` 和 `maxColor`,以及插值比率 `t`。对于颜色中的每个通道(对于 `cv::Vec3b` 是三个通道),它计算插值后的颜色值。

3. **计算 DEM 颜色 `get_dem_color`**

cv::Vec3b get_dem_color(const double& elevation){// ... 省略了检查颜色范围和插值的代码 ...return lerp(color_range[idx].first, color_range[idx+1].first, t);
}


这个函数根据给定的海拔高度 `elevation` 从预定义的颜色范围内获取对应的颜色。它首先检查海拔高度是否在颜色范围的边界之外,然后找到合适的颜色对进行插值计算。

4. **坐标转换函数 `world2dem`**

cv::Point2d world2dem(const cv::Point2d& coordinate, const cv::Size& dem_size){// ... 省略了计算比例和输出点坐标的代码 ...return output;
}


此函数将世界坐标转换为 DEM 图像的像素坐标。它接受一个世界坐标 `coordinate` 和 DEM 图像的尺寸 `dem_size`,然后计算对应的像素坐标。

5. **坐标转换函数 `pixel2world`**

cv::Point2d pixel2world(const int& x, const int& y, const cv::Size& size){// ... 省略了计算比例和插值坐标的代码 ...return lerp(leftSide, rightSide, rx);
}


这个函数将像素坐标转换为世界坐标。它接受一个像素坐标 `(x, y)` 和图像的尺寸 `size`,然后计算出对应的世界坐标。

6. **添加颜色到像素 `add_color`**

void add_color(cv::Vec3b& pix, const uchar& b, const uchar& g, const uchar& r){// ... 省略了颜色添加的代码 ...
}


此函数将特定的颜色值(蓝、绿、红通道)添加到像素中。它确保添加的颜色值不会超过 255(因为颜色值是以 0 到 255 的整数表示的)。

7. **主函数 `main`**

int main(int argc, char* argv[]){// ... 省略了加载图像、DEM 数据和颜色范围设置的代码 ...for(int y = 0; y < image.rows; y++){for(int x = 0; x < image.cols; x++){// ... 省略了像素处理的代码 ...}}// ... 省略了保存图像的代码 ...return 0;
}


`main` 函数是程序的入口点。它首先检查命令行参数,然后加载所需的图像和 DEM 数据。接着,它通过两个嵌套循环遍历图像的每个像素,使用上述函数来计算世界坐标、DEM 坐标、海拔高度和颜色,并根据这些信息生成热图和洪水效果图像。

这些函数共同工作,实现了一个地理空间数据可视化的程序,它可以根据 DEM 数据生成热图,并且模拟不同海拔高度下的洪水效果。

如何使用 GDAL 读取栅格数据

此演示使用默认的 OpenCV imread 函数。主要区别在于,为了强制 GDAL 加载映像,您必须使用适当的标志。

 cv::Mat image = cv::imread(argv[1], cv::IMREAD_LOAD_GDAL | cv::IMREAD_COLOR );

加载数字高程模型时,每个像素的实际数值是必不可少的,不能缩放或截断。例如,对于图像数据,表示为值为 1 的双精度值的像素与表示为值为 255 的无符号字符的像素具有相同的外观。对于地形数据,像素值表示以米为单位的高程。为了确保 OpenCV 保留本机值,请在 imread 中使用 GDAL 标志和 ANYDEPTH 标志。

 // load the dem modelcv::Mat dem = cv::imread(argv[2], cv::IMREAD_LOAD_GDAL | cv::IMREAD_ANYDEPTH );

如果您事先知道要加载的 DEM 模型的类型,那么使用断言或其他机制测试 Mat::type() 或 Mat::d epth() 可能是一个安全的选择。NASA 或 DOD 规范文档可以提供各种高程模型的输入类型。主要类型,SRTM 和 DTED,都是签名短裤。

注意

通常应避免使用经度/纬度(地理)坐标

地理坐标系是一个球面坐标系,这意味着将它们与笛卡尔数学一起使用在技术上是不正确的。此演示使用它们来增加可读性,并且足够准确以说明重点。更好的坐标系是通用横轴墨卡托坐标系。

查找拐角坐标

查找图像角坐标的一种简单方法是使用命令行工具 gdalinfo。对于正射校正且包含投影信息的影像,可以使用 USGS EarthExplorer。

\f$> gdalinfo N37W123.hgtDriver: SRTMHGT/SRTMHGT File FormatFiles: N37W123.hgtSize is 3601, 3601Coordinate System is:GEOGCS["WGS 84",DATUM["WGS_1984",... more output ...Corner Coordinates:Upper Left (-123.0001389, 38.0001389) (123d 0' 0.50"W, 38d 0' 0.50"N)Lower Left (-123.0001389, 36.9998611) (123d 0' 0.50"W, 36d59'59.50"N)Upper Right (-121.9998611, 38.0001389) (121d59'59.50"W, 38d 0' 0.50"N)Lower Right (-121.9998611, 36.9998611) (121d59'59.50"W, 36d59'59.50"N)Center (-122.5000000, 37.5000000) (122d30' 0.00"W, 37d30' 0.00"N)... more output ...

结果

以下是程序的输出。使用第一个图像作为输入。对于 DEM 模型,请在此处下载位于 USGS 的 SRTM 文件。

http://dds.cr.usgs.gov/srtm/version2_1/SRTM1/Region_04/N37W123.hgt.zip

输入图像

热图

热图叠加


参考文献:

1、《Reading Geospatial Raster files with GDAL》-----Marvin Smith

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/832057.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

C++进阶----多态

1.多态的概念 1.1 概念 多态的概念&#xff1a;通俗来说&#xff0c;就是多种形态&#xff0c;具体点就是去完成某个行为&#xff0c;当不同类型的对象去完成时会 产生出不同的状态。 举个例子&#xff1a;比如有一个基类Animal&#xff0c;它有两个子类Dog和Cat。每个…

数据结构---时间复杂度+空间复杂度

算法(algorithm)简单说就是解决问题的方法。方法有好坏&#xff0c;同样算法也是&#xff0c;有效率高的算法&#xff0c;也有效率低的算法。衡量算法的好坏一般从时间和空间两个维度衡量&#xff0c;也就是本文要介绍的时间复杂度和空间复杂度。有些时候&#xff0c;时间与空间…

2024年第七届大数据技术国际会议(ICBDT 2024)即将召开!

2024年第七届大数据技术国际会议&#xff08;ICBDT 2024&#xff09;将于2024年9月20-22日在中国杭州的浙江工商大学举行。数据驱动未来&#xff0c;技术引领潮流。从数据挖掘算法的优化&#xff0c;到数据处理速度的提升&#xff0c;再到数据安全与隐私保护的进步&#xff0c;…

Scikit是什么?

目录 一、Scikit是什么&#xff1f; 二、用Scikit做一个简单房价预测例子 三、sklearn知识点 一、Scikit是什么&#xff1f; Scikit就是scikit-learn&#xff0c;是一个免费软件机器学习库。 https://scikit-learn.org/stable/https://scikit-learn.org/stable/ 用于预测数…

SoundStream: 下一代的神经网络音频编解码器,实时压缩不牺牲音质

音频编解码技术的目标是&#xff0c;通过减少音频文件的大小来节省存储空间或减轻网络传输的负担。理想的情况下&#xff0c;即使音频被压缩&#xff0c;我们听到的声音与原版也应该没有任何区别。 过去&#xff0c;已经有不少编解码技术被开发出来&#xff0c;满足了这些需求…

Day13-JavaWeb开发-事务管理(回顾/进阶)AOP基础(入门/概念)AOP进阶(通知类型/顺序/切点表达式)AOP案例

1. 事务管理 1.1 事务管理-事务回顾 1.2 事务管理-事务进阶 rollbackFor propagetion 2. AOP基础 2.1 AOP基础-快速入门 2.2 AOP基础-核心概念 3. AOP进阶 3.1 AOP进阶-通知类型 3.2 AOP进阶-通知顺序 3.3 切入点表达式-execution 3.4 切入点表达式-annottation 3.5 AOP进阶…

如何构建用于从收据中提取信息的生成式人工智能工具

原文地址&#xff1a;how-to-build-a-generative-ai-tool-for-information-extraction-from-receipts 使用 LangChain 和 OpenAI 工具从 Google Drive 中存储的收据图像中提取结构化信息 2024 年 4 月 10 日 纸质收据有各种样式和格式&#xff0c;是自动信息提取的一个有趣目…

Web服务器手动配置

目录 配置环境 http配置 配置步骤 1、首先安装Nginx&#xff08;已经安装的跳过这步&#xff09; 2、查看一下下Nginx的配置文件结构&#xff0c;了解如何配置&#xff0c;以及配置的各个条目有什么作用&#xff08;为接下来的配置打基础&#xff09; 3、创建你的网页 4、…

【JAVA项目】基于ssm的协同过滤算法的【图书推荐系统】

技术简介&#xff1a;采用B/S架构、ssm 框架、Java技术、MySQL等技术实现。 系统简介&#xff1a;系统权限按管理员和用户这两类涉及用户划分。&#xff08;1&#xff09;管理员功能需求 管理员登陆后&#xff0c;主要包括首页、个人中心、用户管理、书籍管理、书籍分类管理、热…

牛客NC382 切割木头【中等 二分超找 Java/Go/C++】

题目 题目链接&#xff1a; https://www.nowcoder.com/practice/707d98cee255448c838c76918a702be0 核心 二分查找Java代码 import java.util.*;public class Solution {/*** 代码中的类名、方法名、参数名已经指定&#xff0c;请勿修改&#xff0c;直接返回方法规定的值即可…

安卓 app icon大小 安卓app界面尺寸大小

移动应用的界面设计画布尺寸设计多大&#xff08;特别是Android&#xff09;、图标和字体大小怎么定、需要设计多套设计稿么、如何切图以配合开发的实现&#xff1f; 本篇将结合iOS和android官方的设计规范、搜集的资料以及工作中的摸索&#xff0c;来分享移动应用界面设计中的…

gcc编译器分析

gcc编译器分析 参考词法分析语法分析预读一个符号语法分析函数调用关系重点函数分析c_parse_filec_parser_translation_unit 参考 《gcc源码分析》 词法分析 词法分析的过程就是将源代码识别成一个一个的词法符号&#xff0c;并在词法分析的过程中创建一些树节点&#xff0c…

tomcat+nginx 动静分离

一、单机反向代理 7-1 7-2 测试 二、多机反向代理 1. 环境准备 机器IP地址服务7-1172.168.1.11nginx7-2172.168.1.12tomcat7-3172.168.1.13tomcat 2. 配置7-1 Nginx 服务器 vim /apps/nginx/conf/nginx.confhttp:upstream tomcat {server 172.168.1.12:8080;server …

探索设计模式的魅力:分布式模式让业务更高效、更安全、更稳定

​&#x1f308; 个人主页&#xff1a;danci_ &#x1f525; 系列专栏&#xff1a;《设计模式》 &#x1f4aa;&#x1f3fb; 制定明确可量化的目标&#xff0c;坚持默默的做事。 ✨欢迎加入探索分布式模式之旅✨ 在数字化时代&#xff0c;企业面临着前所未有的挑战和机遇。…

STM32开发笔记-新建标准库工程

1.STM32开发方式 STM32开发一般包括三种方式&#xff1a;基于寄存器开发、基于标准外设库开发、基于HAL库开发。 标准外设库是最基础的STM32开发方式&#xff0c;提供了一系列函数用于配置和控制STM32的外设&#xff0c;如GPIO、USART、SPI等。使用标准外设库需要手动编写代码…

(39)4.29数据结构(栈,队列和数组)栈

#include<stdlib.h> #include<stdio.h> #define MaxSize 10 #define Elemtype int 1.栈的基本概念 2.栈的基本操作 typedef struct { Elemtype data[MaxSize]; int top; }Sqstack;//初始化栈 void InitStack(Sqstack& S) { S.top -1; //初始化…

Golang日志管理:使用log/slog实现高级功能和性能优化

Golang日志管理&#xff1a;使用log/slog实现高级功能和性能优化 简介基础使用初始化和配置日志级别 高级技巧自定义日志格式器条件日志处理 实战案例场景一&#xff1a;API请求日志记录场景二&#xff1a;错误跟踪和用户通知 性能优化优化日志记录的性能异步日志处理选择合适的…

Vue的项目启动指令分析

通过Vue CLI脚手架创建的项目&#xff0c;默认的启动项目方式是 npm run serve 这里的serve是可以修改的。 在创建的项目目录中&#xff0c;找到package.json 双击打开&#xff0c;找到scripts部分 在scripts部分&#xff0c;有一个"serve"键值对&#xff0c;这里的…

机器学习-K近邻算法(KNN)

目录 什么是KNN算法 图解KNN基本算法 &#xff08;1&#xff09;k近邻算法中k的选取 &#xff08;2&#xff09;距离函数 &#xff08;3&#xff09;归一化处理 &#xff08;4&#xff09;概率kNN KNN算法的优缺点 优势 缺点 KNN算法总结 什么是KNN算法 k近邻算法&…

[Spring Cloud] (6)gateway整体加解密

文章目录 简述整体效果后端增加配置nacos增加配置GlobalConfig 添加请求整体解密拦截器DecryptionFilter添加响应整体解密拦截器EncryptionFilter 前端请求拦截器添加整体加密逻辑请求头中添加sessionId 响应拦截器添加整体解密逻辑 简述 本文网关gateway&#xff0c;微服务&a…