1 // This is core/vpgl/algo/vpgl_backproject_dem.cxx
2 #include <limits>
3 #include <algorithm>
4 #include "vpgl_backproject.h"
5 #include "vpgl_backproject_dem.h"
6 #include <vpgl/file_formats/vpgl_geo_camera.h>
7 #include "vgl/vgl_point_2d.h"
8 #include "vgl/vgl_point_3d.h"
9 #include "vgl/vgl_plane_3d.h"
10 #include "vgl/vgl_ray_3d.h"
11 #include <vpgl/algo/vpgl_ray.h>
12 #include "vil/vil_image_resource.h"
13 #include "vil/vil_math.h"
14 #include "vnl/vnl_cost_function.h"
15 #include <vnl/algo/vnl_brent_minimizer.h>
16 class dem_bproj_cost_function : public vnl_cost_function
17 {
18 public:
dem_bproj_cost_function(vil_image_view<float> const & dem_view,vpgl_geo_camera * geo_cam,vgl_ray_3d<double> const & ray,bool verbose=false)19 dem_bproj_cost_function(vil_image_view<float> const & dem_view,
20 vpgl_geo_camera * geo_cam,
21 vgl_ray_3d<double> const & ray,
22 bool verbose = false)
23 : vnl_cost_function(1)
24 , ray_(ray)
25 , geo_cam_(geo_cam)
26 , dview_(&dem_view)
27 , verbose_(verbose)
28 {}
29 //: x is the parameter that runs along the ray, with x=0 at the ray origin
30 double
f(vnl_vector<double> const & x)31 f(vnl_vector<double> const & x) override
32 {
33 // get the lon and lat values for a given parameter value
34 vgl_point_3d<double> org = ray_.origin(), rayp;
35 vgl_vector_3d<double> dir = ray_.direction();
36 rayp = org + x[0] * dir;
37 double lon = rayp.x(), lat = rayp.y(), elev = rayp.z();
38 double ud, vd;
39 // determine the dem pixel coordinates and check bounds
40 geo_cam_->project(lon, lat, elev, ud, vd);
41 int u = static_cast<int>(ud + 0.5), v = static_cast<int>(vd + 0.5);
42 int ni = dview_->ni(), nj = dview_->nj();
43 if (u < 0 || u >= ni || v < 0 || v >= nj)
44 {
45 if (verbose_)
46 std::cout << "warning: dem backprojection cost function - outside DEM bounds" << std::endl;
47 return std::numeric_limits<double>::max();
48 }
49 // within bounds so get squared distance between dem z and ray z
50 double z = (*dview_)(u, v);
51 return (elev - z) * (elev - z);
52 }
53
54 private:
55 vgl_ray_3d<double> ray_;
56 vpgl_geo_camera * geo_cam_;
57 const vil_image_view<float> * dview_;
58 bool verbose_;
59 };
vpgl_backproject_dem(vil_image_resource_sptr const & dem,double zmin,double zmax)60 vpgl_backproject_dem::vpgl_backproject_dem(vil_image_resource_sptr const & dem, double zmin, double zmax)
61 : verbose_(false)
62 , min_samples_(5000.0)
63 , tail_fract_(0.025)
64 , dem_(dem)
65 {
66 // construct a geo_camera for the dem (an orthographic view looking straight down)
67 if (!vpgl_geo_camera::init_geo_camera(dem_, geo_cam_))
68 {
69 if (verbose_)
70 std::cout << "WARNING! - units unknown in vpgl_backproject_dem - assume degrees meters" << std::endl;
71 }
72 if (!geo_cam_)
73 return;
74 // get the image of elevations
75 dem_view_ = dem_->get_view();
76 unsigned ni = dem_view_.ni(), nj = dem_view_.nj();
77
78 // get the center of the dem
79 unsigned nhi = ni / 2, nhj = nj / 2;
80 double lon, lat;
81 geo_cam_->img_to_global(nhi, nhj, lon, lat);
82 double elev = dem_view_(nhi, nhj);
83 geo_center_.set(lon, lat, elev);
84
85 // get the corners of the dem
86 geo_cam_->img_to_global(0, 0, lon, lat);
87 elev = dem_view_(0, 0);
88 dem_corners_.emplace_back(lon, lat, elev);
89
90 geo_cam_->img_to_global(ni - 1, 0, lon, lat);
91 elev = dem_view_(ni - 1, 0);
92 dem_corners_.emplace_back(lon, lat, elev);
93
94 geo_cam_->img_to_global(ni - 1, nj - 1, lon, lat);
95 elev = dem_view_(ni - 1, nj - 1);
96 dem_corners_.emplace_back(lon, lat, elev);
97
98 geo_cam_->img_to_global(0, nj - 1, lon, lat);
99 elev = dem_view_(0, nj - 1);
100 dem_corners_.emplace_back(lon, lat, elev);
101
102 // check for appropriate zmin/zmax inputs
103 if (zmax > zmin)
104 {
105 z_min_ = zmin;
106 z_max_ = zmax;
107 }
108 else
109 {
110 if (verbose_)
111 std::cout << "Calculating Z-range from DEM..." << std::endl;
112 // get the bounds on elevation by sampling according to a fraction of the dem area
113 // compute the pixel interval (stride) for sampling the fraction
114 double area = ni * nj;
115 double stride_area = area / min_samples_;
116 auto stride_interval = static_cast<unsigned>(std::sqrt(stride_area));
117
118 // sample elevations
119 std::vector<double> z_samples;
120 float zmin_calc = std::numeric_limits<float>::max(), zmax_calc = -zmin_calc;
121 for (unsigned j = 0; j < nj; j += stride_interval)
122 for (unsigned i = 0; i < ni; i += stride_interval)
123 {
124 float z = dem_view_(i, j);
125 if (z <= 0.0)
126 continue;
127 z_samples.push_back(z);
128 }
129 // sort the samples to remove tails due to DEM errors
130 std::sort(z_samples.begin(), z_samples.end());
131 // remove the tails and compute min max elevations
132 auto ns = static_cast<double>(z_samples.size());
133 auto band_size = static_cast<unsigned>(ns * tail_fract_);
134 for (unsigned k = band_size; k < (ns - band_size); ++k)
135 {
136 double z = z_samples[k];
137 if (z < zmin_calc)
138 zmin_calc = static_cast<float>(z);
139 if (z > zmax_calc)
140 zmax_calc = z;
141 }
142 // the final elevation bounds
143 z_min_ = zmin_calc;
144 z_max_ = zmax_calc;
145 }
146
147 if (verbose_)
148 std::cout << "[ZMIN,ZMAX]=[" << z_min_ << "," << z_max_ << "]" << std::endl;
149 }
~vpgl_backproject_dem()150 vpgl_backproject_dem::~vpgl_backproject_dem()
151 {
152 if (geo_cam_)
153 delete geo_cam_;
154 geo_cam_ = nullptr;
155 }
156 // the function to backproject onto the dem using vgl objects
157 bool
bproj_dem(const vpgl_camera<double> * cam,vgl_point_2d<double> const & image_point,double max_z,double min_z,vgl_point_3d<double> const & initial_guess,vgl_point_3d<double> & world_point,double error_tol)158 vpgl_backproject_dem::bproj_dem(const vpgl_camera<double> * cam,
159 vgl_point_2d<double> const & image_point,
160 double max_z,
161 double min_z,
162 vgl_point_3d<double> const & initial_guess,
163 vgl_point_3d<double> & world_point,
164 double error_tol)
165 {
166 if (verbose_)
167 std::cout << "vpgl_backproj_dem " << image_point << " max_z " << max_z << " min_z " << min_z << " init_guess "
168 << initial_guess << " error tol " << error_tol << std::endl;
169 // compute the ray corresponding to the image point
170 double dz = (max_z - min_z);
171 vgl_point_2d<double> initial_xy(initial_guess.x(), initial_guess.y());
172 vgl_ray_3d<double> ray;
173 if (!vpgl_ray::ray(cam, image_point, initial_xy, max_z, dz, ray))
174 {
175 if (verbose_)
176 std::cout << " compute camera ray failed - Fatal!" << std::endl;
177 return false;
178 }
179 vgl_point_3d<double> origin = ray.origin();
180 // find min parameter on ray
181 vgl_vector_3d<double> dir = ray.direction();
182 if (std::fabs(dir.z()) < 0.001)
183 {
184 if (verbose_)
185 std::cout << "Ray parallel to XY plane - Fatal!" << std::endl;
186 return false;
187 }
188 // inital guess at ray parameter
189 double t = (initial_guess.z() - max_z) / dir.z();
190
191 // check if guess is inside DEM - could be too conservative for very oblique cameras
192 vgl_point_3d<double> guess_point = origin + t * dir;
193 double lon = guess_point.x(), lat = guess_point.y(), elev = guess_point.z();
194 double ud, vd;
195 // Note that elevation has no effect in the project function - just required to meet the interface of an abstract
196 // camera
197 geo_cam_->project(lon, lat, elev, ud, vd);
198 int u = static_cast<int>(ud + 0.5), v = static_cast<int>(vd + 0.5);
199 int ni = dem_view_.ni(), nj = dem_view_.nj();
200 if (u < 0 || u >= ni || v < 0 || v >= nj)
201 {
202 if (verbose_)
203 std::cout << "Initial guess for DEM intersection is outside DEM bounds - Fatal!" << std::endl;
204 return false;
205 }
206 // construct the brent minimizer
207 // note the brent minimzier is not fully integrated with the vil_nonlinear_minimizer interface
208 // thus the non standard call for error below
209 dem_bproj_cost_function c(dem_view_, geo_cam_, ray, verbose_);
210 vnl_brent_minimizer brm(c);
211 double tmin = brm.minimize(t);
212 double error = brm.f_at_last_minimum();
213 if (error > error_tol)
214 return false;
215
216 world_point = origin + tmin * dir;
217
218 if (verbose_)
219 std::cout << "success! ray/dem intersection " << world_point << std::endl;
220 return true;
221 }
222
223 bool
bproj_dem(const vpgl_camera<double> * cam,vnl_double_2 const & image_point,double max_z,double min_z,vnl_double_3 const & initial_guess,vnl_double_3 & world_point,double error_tol)224 vpgl_backproject_dem::bproj_dem(const vpgl_camera<double> * cam,
225 vnl_double_2 const & image_point,
226 double max_z,
227 double min_z,
228 vnl_double_3 const & initial_guess,
229 vnl_double_3 & world_point,
230 double error_tol)
231 {
232 vgl_point_2d<double> img_pt;
233 vgl_point_3d<double> init_guess;
234 vgl_point_3d<double> wrld_pt;
235 img_pt.set(image_point[0], image_point[1]);
236 init_guess.set(initial_guess[0], initial_guess[1], initial_guess[2]);
237 bool good = this->bproj_dem(cam, img_pt, max_z, min_z, init_guess, wrld_pt, error_tol);
238
239 if (!good)
240 {
241 return false;
242 }
243 world_point[0] = wrld_pt.x();
244 world_point[1] = wrld_pt.y();
245 world_point[2] = wrld_pt.z();
246 return true;
247 }
248
249 bool
bproj_dem(vpgl_rational_camera<double> const & rcam,vnl_double_2 const & image_point,double max_z,double min_z,vnl_double_3 const & initial_guess,vnl_double_3 & world_point,double error_tol)250 vpgl_backproject_dem::bproj_dem(vpgl_rational_camera<double> const & rcam,
251 vnl_double_2 const & image_point,
252 double max_z,
253 double min_z,
254 vnl_double_3 const & initial_guess,
255 vnl_double_3 & world_point,
256 double error_tol)
257 {
258
259 const auto * cam = dynamic_cast<const vpgl_camera<double> *>(&rcam);
260 return this->bproj_dem(cam, image_point, max_z, min_z, initial_guess, world_point, error_tol);
261 }
262
263 bool
bproj_dem(vpgl_rational_camera<double> const & rcam,vgl_point_2d<double> const & image_point,double max_z,double min_z,vgl_point_3d<double> const & initial_guess,vgl_point_3d<double> & world_point,double error_tol)264 vpgl_backproject_dem::bproj_dem(vpgl_rational_camera<double> const & rcam,
265 vgl_point_2d<double> const & image_point,
266 double max_z,
267 double min_z,
268 vgl_point_3d<double> const & initial_guess,
269 vgl_point_3d<double> & world_point,
270 double error_tol)
271 {
272
273 const auto * cam = dynamic_cast<const vpgl_camera<double> *>(&rcam);
274 return this->bproj_dem(cam, image_point, max_z, min_z, initial_guess, world_point, error_tol);
275 }
276