OpenCV  3.0.0-rc1
Open Source Computer Vision
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros Groups Pages
Reading Geospatial Raster files with GDAL

Geospatial raster data is a heavily used product in Geographic Information Systems and Photogrammetry. Raster data typically can represent imagery and Digital Elevation Models (DEM). The standard library for loading GIS imagery is the Geographic Data Abstraction Library (GDAL). In this example, we will show techniques for loading GIS raster formats using native OpenCV functions. In addition, we will show some an example of how OpenCV can use this data for novel and interesting purposes.

Goals

The primary objectives for this tutorial:

To implement these goals, the following code takes a Digital Elevation Model as well as a GeoTiff image of San Francisco as input. The image and DEM data is processed and generates a terrain heat map of the image as well as labels areas of the city which would be affected should the water level of the bay rise 10, 50, and 100 meters.

Code

1 /*
2  * gdal_image.cpp -- Load GIS data into OpenCV Containers using the Geospatial Data Abstraction Library
3 */
4 
5 // OpenCV Headers
6 #include "opencv2/core/core.hpp"
9 
10 // C++ Standard Libraries
11 #include <cmath>
12 #include <iostream>
13 #include <stdexcept>
14 #include <vector>
15 
16 using namespace std;
17 
18 // define the corner points
19 // Note that GDAL library can natively determine this
20 cv::Point2d tl( -122.441017, 37.815664 );
21 cv::Point2d tr( -122.370919, 37.815311 );
22 cv::Point2d bl( -122.441533, 37.747167 );
23 cv::Point2d br( -122.3715, 37.746814 );
24 
25 // determine dem corners
26 cv::Point2d dem_bl( -122.0, 38);
27 cv::Point2d dem_tr( -123.0, 37);
28 
29 // range of the heat map colors
30 std::vector<std::pair<cv::Vec3b,double> > color_range;
31 
32 
33 // List of all function prototypes
34 cv::Point2d lerp( const cv::Point2d&, const cv::Point2d&, const double& );
35 
36 cv::Vec3b get_dem_color( const double& );
37 
38 cv::Point2d world2dem( const cv::Point2d&, const cv::Size&);
39 
40 cv::Point2d pixel2world( const int&, const int&, const cv::Size& );
41 
42 void add_color( cv::Vec3b& pix, const uchar& b, const uchar& g, const uchar& r );
43 
44 
45 
46 /*
47  * Linear Interpolation
48  * p1 - Point 1
49  * p2 - Point 2
50  * t - Ratio from Point 1 to Point 2
51 */
52 cv::Point2d lerp( cv::Point2d const& p1, cv::Point2d const& p2, const double& t ){
53  return cv::Point2d( ((1-t)*p1.x) + (t*p2.x),
54  ((1-t)*p1.y) + (t*p2.y));
55 }
56 
57 /*
58  * Interpolate Colors
59 */
60 template <typename DATATYPE, int N>
61 cv::Vec<DATATYPE,N> lerp( cv::Vec<DATATYPE,N> const& minColor,
62  cv::Vec<DATATYPE,N> const& maxColor,
63  double const& t ){
64 
65  cv::Vec<DATATYPE,N> output;
66  for( int i=0; i<N; i++ ){
67  output[i] = (uchar)(((1-t)*minColor[i]) + (t * maxColor[i]));
68  }
69  return output;
70 }
71 
72 /*
73  * Compute the dem color
74 */
75 cv::Vec3b get_dem_color( const double& elevation ){
76 
77  // if the elevation is below the minimum, return the minimum
78  if( elevation < color_range[0].second ){
79  return color_range[0].first;
80  }
81  // if the elevation is above the maximum, return the maximum
82  if( elevation > color_range.back().second ){
83  return color_range.back().first;
84  }
85 
86  // otherwise, find the proper starting index
87  int idx=0;
88  double t = 0;
89  for( int x=0; x<(int)(color_range.size()-1); x++ ){
90 
91  // if the current elevation is below the next item, then use the current
92  // two colors as our range
93  if( elevation < color_range[x+1].second ){
94  idx=x;
95  t = (color_range[x+1].second - elevation)/
96  (color_range[x+1].second - color_range[x].second);
97 
98  break;
99  }
100  }
101 
102  // interpolate the color
103  return lerp( color_range[idx].first, color_range[idx+1].first, t);
104 }
105 
106 /*
107  * Given a pixel coordinate and the size of the input image, compute the pixel location
108  * on the DEM image.
109 */
110 cv::Point2d world2dem( cv::Point2d const& coordinate, const cv::Size& dem_size ){
111 
112 
113  // relate this to the dem points
114  // ASSUMING THAT DEM DATA IS ORTHORECTIFIED
115  double demRatioX = ((dem_tr.x - coordinate.x)/(dem_tr.x - dem_bl.x));
116  double demRatioY = 1-((dem_tr.y - coordinate.y)/(dem_tr.y - dem_bl.y));
117 
118  cv::Point2d output;
119  output.x = demRatioX * dem_size.width;
120  output.y = demRatioY * dem_size.height;
121 
122  return output;
123 }
124 
125 /*
126  * Convert a pixel coordinate to world coordinates
127 */
128 cv::Point2d pixel2world( const int& x, const int& y, const cv::Size& size ){
129 
130  // compute the ratio of the pixel location to its dimension
131  double rx = (double)x / size.width;
132  double ry = (double)y / size.height;
133 
134  // compute LERP of each coordinate
135  cv::Point2d rightSide = lerp(tr, br, ry);
136  cv::Point2d leftSide = lerp(tl, bl, ry);
137 
138  // compute the actual Lat/Lon coordinate of the interpolated coordinate
139  return lerp( leftSide, rightSide, rx );
140 }
141 
142 /*
143  * Add color to a specific pixel color value
144 */
145 void add_color( cv::Vec3b& pix, const uchar& b, const uchar& g, const uchar& r ){
146 
147  if( pix[0] + b < 255 && pix[0] + b >= 0 ){ pix[0] += b; }
148  if( pix[1] + g < 255 && pix[1] + g >= 0 ){ pix[1] += g; }
149  if( pix[2] + r < 255 && pix[2] + r >= 0 ){ pix[2] += r; }
150 }
151 
152 
153 /*
154  * Main Function
155 */
156 int main( int argc, char* argv[] ){
157 
158  /*
159  * Check input arguments
160  */
161  if( argc < 3 ){
162  cout << "usage: " << argv[0] << " <image> <dem>" << endl;
163  return 1;
164  }
165 
166  // load the image (note that we don't have the projection information. You will
167  // need to load that yourself or use the full GDAL driver. The values are pre-defined
168  // at the top of this file
170 
171  // load the dem model
173 
174  // create our output products
175  cv::Mat output_dem( image.size(), CV_8UC3 );
176  cv::Mat output_dem_flood( image.size(), CV_8UC3 );
177 
178  // for sanity sake, make sure GDAL Loads it as a signed short
179  if( dem.type() != CV_16SC1 ){ throw std::runtime_error("DEM image type must be CV_16SC1"); }
180 
181  // define the color range to create our output DEM heat map
182  // Pair format ( Color, elevation ); Push from low to high
183  // Note: This would be perfect for a configuration file, but is here for a working demo.
184  color_range.push_back( std::pair<cv::Vec3b,double>(cv::Vec3b( 188, 154, 46), -1));
185  color_range.push_back( std::pair<cv::Vec3b,double>(cv::Vec3b( 110, 220, 110), 0.25));
186  color_range.push_back( std::pair<cv::Vec3b,double>(cv::Vec3b( 150, 250, 230), 20));
187  color_range.push_back( std::pair<cv::Vec3b,double>(cv::Vec3b( 160, 220, 200), 75));
188  color_range.push_back( std::pair<cv::Vec3b,double>(cv::Vec3b( 220, 190, 170), 100));
189  color_range.push_back( std::pair<cv::Vec3b,double>(cv::Vec3b( 250, 180, 140), 200));
190 
191  // define a minimum elevation
192  double minElevation = -10;
193 
194  // iterate over each pixel in the image, computing the dem point
195  for( int y=0; y<image.rows; y++ ){
196  for( int x=0; x<image.cols; x++ ){
197 
198  // convert the pixel coordinate to lat/lon coordinates
199  cv::Point2d coordinate = pixel2world( x, y, image.size() );
200 
201  // compute the dem image pixel coordinate from lat/lon
202  cv::Point2d dem_coordinate = world2dem( coordinate, dem.size() );
203 
204  // extract the elevation
205  double dz;
206  if( dem_coordinate.x >= 0 && dem_coordinate.y >= 0 &&
207  dem_coordinate.x < dem.cols && dem_coordinate.y < dem.rows ){
208  dz = dem.at<short>(dem_coordinate);
209  }else{
210  dz = minElevation;
211  }
212 
213  // write the pixel value to the file
214  output_dem_flood.at<cv::Vec3b>(y,x) = image.at<cv::Vec3b>(y,x);
215 
216  // compute the color for the heat map output
217  cv::Vec3b actualColor = get_dem_color(dz);
218  output_dem.at<cv::Vec3b>(y,x) = actualColor;
219 
220  // show effect of a 10 meter increase in ocean levels
221  if( dz < 10 ){
222  add_color( output_dem_flood.at<cv::Vec3b>(y,x), 90, 0, 0 );
223  }
224  // show effect of a 50 meter increase in ocean levels
225  else if( dz < 50 ){
226  add_color( output_dem_flood.at<cv::Vec3b>(y,x), 0, 90, 0 );
227  }
228  // show effect of a 100 meter increase in ocean levels
229  else if( dz < 100 ){
230  add_color( output_dem_flood.at<cv::Vec3b>(y,x), 0, 0, 90 );
231  }
232 
233  }}
234 
235  // print our heat map
236  cv::imwrite( "heat-map.jpg" , output_dem );
237 
238  // print the flooding effect image
239  cv::imwrite( "flooded.jpg", output_dem_flood);
240 
241  return 0;
242 }
bool imwrite(const String &filename, InputArray img, const std::vector< int > &params=std::vector< int >())
Saves an image to a specified file.
int rows
the number of rows and columns or (-1, -1) when the matrix has more than 2 dimensions ...
Definition: mat.hpp:1865
Mat imread(const String &filename, int flags=IMREAD_COLOR)
Loads an image from a file.
N
Definition: modelConvert.m:31
_Tp width
Definition: types.hpp:302
Definition: imgcodecs.hpp:68
Template class for specifying the size of an image or rectangle.
Definition: types.hpp:284
Template class for 2D points specified by its coordinates x and y.
Definition: types.hpp:147
Template class for short numerical vectors, a partial case of Matx.
Definition: matx.hpp:300
int cols
Definition: mat.hpp:1865
Definition: imgcodecs.hpp:66
MatSize size
Definition: mat.hpp:1882
unsigned char uchar
Definition: defs.h:284
Point_< double > Point2d
Definition: types.hpp:180
_Tp height
Definition: types.hpp:302
for i
Definition: modelConvert.m:63
int type() const
Returns the type of a matrix element.
_Tp x
Definition: types.hpp:175
#define CV_8UC3
Definition: cvdef.h:118
_Tp y
Definition: types.hpp:175
int main(int argc, const char *argv[])
Definition: facerec_demo.cpp:67
n-dimensional dense array class
Definition: mat.hpp:726
_Tp & at(int i0=0)
Returns a reference to the specified array element.
#define CV_16SC1
Definition: cvdef.h:134
Definition: imgcodecs.hpp:65

How to Read Raster Data using GDAL

This demonstration uses the default OpenCV imread function. The primary difference is that in order to force GDAL to load the image, you must use the appropriate flag.

When loading digital elevation models, the actual numeric value of each pixel is essential and cannot be scaled or truncated. For example, with image data a pixel represented as a double with a value of 1 has an equal appearance to a pixel which is represented as an unsigned character with a value of 255. With terrain data, the pixel value represents the elevation in meters. In order to ensure that OpenCV preserves the native value, use the GDAL flag in imread with the ANYDEPTH flag.

If you know beforehand the type of DEM model you are loading, then it may be a safe bet to test the Mat::type() or Mat::depth() using an assert or other mechanism. NASA or DOD specification documents can provide the input types for various elevation models. The major types, SRTM and DTED, are both signed shorts.

Notes

Lat/Lon (Geographic) Coordinates should normally be avoided

The Geographic Coordinate System is a spherical coordinate system, meaning that using them with Cartesian mathematics is technically incorrect. This demo uses them to increase the readability and is accurate enough to make the point. A better coordinate system would be Universal Transverse Mercator.

Finding the corner coordinates

One easy method to find the corner coordinates of an image is to use the command-line tool gdalinfo. For imagery which is ortho-rectified and contains the projection information, you can use the USGS EarthExplorer.

\f$> gdalinfo N37W123.hgt
Driver: SRTMHGT/SRTMHGT File Format
Files: N37W123.hgt
Size is 3601, 3601
Coordinate 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 ...

Results

Below is the output of the program. Use the first image as the input. For the DEM model, download the SRTM file located at the USGS here. http://dds.cr.usgs.gov/srtm/version2_1/SRTM1/Region_04/N37W123.hgt.zip

gdal_output.jpg
Input Image
gdal_heat-map.jpg
Heat Map
gdal_flood-zone.jpg
Heat Map Overlay