ldpk
tde4_ldp_radial_deg_8.h
Go to the documentation of this file.
1 #pragma once
2 
3 #include <ldpk/ldpk_ldp_builtin.h>
5 
8 
12 template <class VEC2,class MAT2>
14  {
15 private:
16  typedef VEC2 vec2_type;
17  typedef MAT2 mat2_type;
19 
21 
22  static const char* _para[4];
23  double _r_clip_factor;
24 // Focal length in dn-coordinates.
25  double _fl_dn;
26 // The radius of the domain where undistort is valid.
27 // We determine this in initializeParameters().
28  double _r_ed_dn_domain;
29  double _r_plain_dn_domain;
30 protected:
31  bool decypher(const char* name,int& i)
32  {
33  typedef base_type bt;
34  int n;
36  for(i = 0;i < n;++i)
37  {
38  if(0 == strcmp(name,_para[i]))
39  {
40  return true;
41  }
42  }
43  return false;
44  }
46  {
47  typedef base_type bt;
48  bt::check_builtin_parameters();
49  _fl_dn = this->fl_cm() / this->r_fb_cm();
50 // At this point we scan in radial direction in order to determine
51 // the domain of the undistort-function. In order to do this, we
52 // iterate over the radius and build a point (e.g. in x-direction)
53 // in dn-coordinates, which we feed into the Jacobian method.
54  int n = 5000;
55  _r_ed_dn_domain = 0.0;
56  double radius_a = 0.0;
57  double radius_b = M_PI * _fl_dn;
58  double r_in_prev = -1.0;
59  double r_out_prev = -1.0;
60  for(int i = 0;i <= n;++i)
61  {
62  double q = double(i) / n;
63 // Scan for instance from 0.0 to 5.0. Tweak this later.
64  double r_in = radius_a * (1.0 - q) + radius_b * q;
65  vec2_type p_esa_dn(r_in,0.0);
66 
67 // Criterion 1: If we are already outside the semisphere, we're out.
68  vec2_type p_plain_dn;
69  if(!remap_esa2plain(p_esa_dn,p_plain_dn))
70  {
71  _r_ed_dn_domain = r_in_prev;
72  _r_plain_dn_domain = r_out_prev;
73  break;
74  }
75  vec2_type q_dn = _distortion.eval(p_plain_dn);
76  double r_out = q_dn[0];
77 
78  if(r_out < r_out_prev)
79  {
80  _r_ed_dn_domain = r_in_prev;
81  _r_plain_dn_domain = r_out_prev;
82  break;
83  }
84  r_in_prev = r_in;
85  r_out_prev = r_out;
86  _r_ed_dn_domain = r_in;
87  _r_plain_dn_domain = r_out;
88 
89 
90 // Criterion 2: If the Jacobian cannot be computed, we're out.
91 // mat2_type m_dn;
92 // if(!get_jacobian_matrix_dn(p_esa_dn,m_dn))
93 // { break; }
94 // Criterion 3: The Derivative of the undistort-function points inward.
95 // if(dot(p_esa_dn,m_dn * p_esa_dn) < 0.0)
96 // { break; }
97 // Criterion 4: The undistort flips to the opposite side. This should
98 // already have been ruled out by criterion 3, but we're not sure...
99 // vec2_type q_dn = _distortion.eval(p_plain_dn);
100 // if(dot(q_dn,p_plain_dn) < 0.0)
101 // { break; }
102 // Success, declare this radius as safe.
103 // _r_ed_dn_domain = r;
104  }
105  return true;
106  }
107  bool getNumParameters(int& n)
108  {
109  n = 4;
110  return true;
111  }
112  bool getParameterName(int i,char* identifier)
113  {
114  strcpy(identifier,_para[i]);
115  return true;
116  }
117  bool setParameterValue(const char *identifier,double v)
118  {
119  typedef base_type bt;
120  int i;
121 // Does the base class know the parameter?
122  if(bt::set_builtin_parameter_value(identifier,v))
123  {
124  return true;
125  }
126  if(!decypher(identifier,i))
127  {
128  return false;
129  }
130  if(_distortion.get_coeff(i) != v)
131  {
132  bt::no_longer_uptodate_lut();
133  }
134  _distortion.set_coeff(i,v);
135  return true;
136  }
140  bool remap_esa2plain(const vec2_type& p_esa_dn,vec2_type& p_plain_dn) const
141  {
142  bool rc = true;
143  double f_dn = this->fl_cm() / this->r_fb_cm();
144 // Remove fisheye projection
145  double r_esa_dn = norm2(p_esa_dn);
146  if(r_esa_dn == 0.0)
147  {
148  p_plain_dn = p_esa_dn;
149  return rc;
150  }
151  double arg = r_esa_dn / (2.0 * f_dn),arg_clip = arg;
152 // Black areas, undefined.
153  if(arg_clip >= 1.0)
154  {
155  arg_clip = 0.9999;
156  }
157  double phi = 2.0 * asin(arg_clip);
158  if(phi >= M_PI / 2.0)
159  {
160 // This can and will happen in re-distorting footage...
161  phi = M_PI / 2.0 - 1e-5;
162  rc = false;
163  }
164  double r_plain_dn = f_dn * tan(phi);
165  if(r_plain_dn > _r_clip_factor)
166  {
167 // ... as well as this.
168  r_plain_dn = _r_clip_factor;
169  }
170  p_plain_dn = r_plain_dn * unit(p_esa_dn);
171  return rc;
172  }
173  bool remap_plain2esa(const vec2_type& p_plain_dn,vec2_type& p_esa_dn) const
174  {
175  typedef base_type bt;
176  double f_dn = bt::fl_cm() / bt::r_fb_cm();
177 // Apply fisheye projection
178  double r_plain_dn = norm2(p_plain_dn);
179  if(r_plain_dn == 0.0)
180  {
181  p_esa_dn = p_plain_dn;
182  return true;
183  }
184  double phi = atan2(r_plain_dn,f_dn);
185  double r_esa_dn = 2.0 * f_dn * std::sin(phi / 2.0);
186  p_esa_dn = r_esa_dn * unit(p_plain_dn);
187  return true;
188  }
189 // Overwriting tde4_ldp_common
190  bool undistort(double x0,double y0,double &x1,double &y1)
191  {
192  typedef base_type bt;
193  vec2_type p_esa_dn = bt::map_unit_to_dn(vec2_type(x0,y0));
194  vec2_type p_plain_dn;
195 // Apply the domain criterion
196  if(norm2(p_esa_dn) > _r_ed_dn_domain)
197  { return false; }
198  if(!remap_esa2plain(p_esa_dn,p_plain_dn))
199  { return false; }
200 // Clipping to a reasonable area, still n times as large as the image.
201  vec2_type q_dn = _distortion.eval(p_plain_dn);
202  if(norm2(q_dn) > 100.0)
203  { q_dn = 100.0 * unit(q_dn); }
204 
205  vec2_type q = bt::map_dn_to_unit(q_dn);
206  x1 = q[0];
207  y1 = q[1];
208  return true;
209  }
210  bool distort(double x0,double y0,double &x1,double &y1)
211  {
212  typedef base_type bt;
213 // The distort-method without initial values is not constant by semantics,
214 // since it may cause an update of the lookup-tables. Implementing a Nuke node
215 // it turned out that we need to prevent threads from trying so simultaneously.
216 // By the following double check of is_uptodate_lut() we keep the mutex lock
217 // out of our frequently called distort stuff (for performance reasons) and
218 // prevent threads from updating without need.
219  if(!bt::is_uptodate_lut())
220  {
221  bt::lock();
222  if(!bt::is_uptodate_lut())
223  {
224  bt::update_lut();
225  }
226  bt::unlock();
227  }
228 // Get initial value from lookup-table. Note that we have defined our LUT
229 // as a table of distortion vales for the model function (i.e. without projection).
230 // We get the initial value and feed it into the distortion class (no projection),
231 // so everything should be fine.
232  vec2_type qs = bt::get_lut().get_initial_value(vec2_type(x0,y0));
233 // Check if we are in the valid domain.
234  vec2_type p_dn = bt::map_unit_to_dn(vec2_type(x0,y0));
235  double r = norm2(p_dn);
236  if(r > _r_plain_dn_domain)
237  { return false; }
238 // Call version of distort with initial value.
239  vec2_type p_plain_dn = _distortion.map_inverse(p_dn,bt::map_unit_to_dn(qs));
240  vec2_type p_esa_dn;
241  if(!remap_plain2esa(p_plain_dn,p_esa_dn))
242  { return false; }
243 
244 // Check for pathological behaviour.
245  if(dot(p_esa_dn,p_dn) < 0.0)
246  { return false; }
247 
248 
249  vec2_type q = bt::map_dn_to_unit(p_esa_dn);
250  x1 = q[0];
251  y1 = q[1];
252  return true;
253  }
254  bool distort(double x0,double y0,double x1_start,double y1_start,double &x1,double &y1)
255  {
256  typedef base_type bt;
257  vec2_type p_plain_dn = _distortion.map_inverse(bt::map_unit_to_dn(vec2_type(x0,y0)),bt::map_unit_to_dn(vec2_type(x1_start,y1_start)));
258  vec2_type p_esa_dn;
259  if(!remap_plain2esa(p_plain_dn,p_esa_dn)) return false;
260 
261  vec2_type q = bt::map_dn_to_unit(p_esa_dn);
262  x1 = q[0];
263  y1 = q[1];
264  return true;
265  }
266  bool undistort_gnomonic(double x0,double y0,double &x1,double &y1)
267  {
268  typedef base_type bt;
269  vec2_type q = bt::map_dn_to_unit(
270  _distortion.eval(
271  bt::map_unit_to_dn(vec2_type(x0,y0))));
272  x1 = q[0];
273  y1 = q[1];
274  return true;
275  }
276  bool distort_gnomonic(double x0,double y0,double &x1,double &y1)
277  {
278  typedef base_type bt;
279 // see distort().
280  if(!bt::is_uptodate_lut())
281  {
282  bt::lock();
283  if(!bt::is_uptodate_lut())
284  {
285  bt::update_lut();
286  }
287  bt::unlock();
288  }
289 // Get initial value from lookup-table. See distort().
290  vec2_type qs = bt::get_lut().get_initial_value(vec2_type(x0,y0));
291 // Call version of distort with initial value.
292  vec2_type p_plain_dn = _distortion.map_inverse(bt::map_unit_to_dn(vec2_type(x0,y0)),bt::map_unit_to_dn(qs));
293  x1 = p_plain_dn[0];
294  y1 = p_plain_dn[1];
295  return true;
296  }
297  bool distort_gnomonic(double x0,double y0,double x1_start,double y1_start,double &x1,double &y1)
298  {
299  typedef base_type bt;
300  vec2_type p_plain_dn = _distortion.map_inverse(bt::map_unit_to_dn(vec2_type(x0,y0)),bt::map_unit_to_dn(vec2_type(x1_start,y1_start)));
301  vec2_type q = bt::map_dn_to_unit(p_plain_dn);
302  x1 = q[0];
303  y1 = q[1];
304  return true;
305  }
306 // Compute the Jacobian in diag-norm coordinates. If the function returns false
307 // m_dn is undefined. If it returns true, we can not be sure that the result is valid.
308 // We need this method in initializeParameters() in order to determine the valid domain
309  bool get_jacobian_matrix_dn(const vec2_type& p_esa_dn,mat2_type m_dn) const
310  {
311  vec2_type p_plain_dn;
312  if(!remap_esa2plain(p_esa_dn,p_plain_dn))
313  { return false; }
314  mat2_type m = _distortion.jacobi(p_plain_dn);
315 // Interior derivative
316  mat2_type e;
317  double f_dn = this->fl_cm() / this->r_fb_cm();
318  double r_esa_dn = norm2(p_esa_dn);
319  double r_esa_div_2f = r_esa_dn / (2.0 * f_dn);
320 // Test (a): make sure argument of arcsine is valid.
321  if(fabs(r_esa_div_2f) > 0.99)
322  { return false; }
323  double asn = asin(r_esa_div_2f);
324 // Test (b): make sure cos(..) is greater than 0.
325  if(fabs(2.0 * asn) > 0.99 * M_PI / 2.0)
326  { return false; }
327  double csn = cos(2.0 * asn);
328 // Argument of square root is positive because of test (a).
329  double sqr = sqrt(1.0 - r_esa_div_2f * r_esa_div_2f);
330  if(r_esa_dn > 1e-12)
331  {
332 // The analytic expression is undefined at 0...
333  e = (mat2_type(1) - tensq(p_esa_dn / r_esa_dn)) * (f_dn / r_esa_dn) * tan(2.0 * asn)
334  + (tensq(p_esa_dn / r_esa_dn)) / (csn * csn * sqr);
335  }
336  else
337  {
338 // ...but has well-defined limit
339  e = mat2_type(1);
340  }
341  m_dn = e;
342  return true;
343  }
344 public:
345 // Mutex initialized and destroyed in baseclass.
346  tde4_ldp_radial_deg_8():_r_clip_factor(50.0)
347  { }
349  { }
350  double r_clip_factor() const
351  { return _r_clip_factor; }
352  void r_clip_factor(double f)
353  { _r_clip_factor = f; }
354  bool getModelName(char *name)
355  {
356 #ifdef LDPK_COMPILE_AS_PLUGIN_SDV
357  strcpy(name,"3DE4 Radial - Fisheye, Degree 8 [Plugin]");
358 #else
359  strcpy(name,"3DE4 Radial - Fisheye, Degree 8");
360 #endif
361  return true;
362  }
363  bool getParameterType(const char* identifier,tde4_ldp_ptype& ptype)
364  {
365  typedef base_type bt;
366  int i;
367  if(bt::get_builtin_parameter_type(identifier,ptype)) return true;
368  if(!decypher(identifier,i)) return false;
369  ptype = TDE4_LDP_ADJUSTABLE_DOUBLE;
370  return true;
371  }
372  bool getParameterDefaultValue(const char* identifier,double& v)
373  {
374  typedef base_type bt;
375  int i;
376  if(!decypher(identifier,i)) return false;
377  v = 0.0;
378  return true;
379  }
380  bool getParameterRange(const char* identifier,double& a,double& b)
381  {
382  typedef base_type bt;
383  int i;
384  if(!decypher(identifier,i)) return false;
385  a = -0.5;
386  b = 0.5;
387  return true;
388  }
389  bool getJacobianMatrix(double x0,double y0,double& m00,double& m01,double& m10,double& m11)
390  {
391  typedef base_type bt;
392 // The function we need to derive is a concatenation of a simple radially symmetric
393 // undistort funtion and the equisolid-angle transform. We compute this by Leipnitz's rule.
394 // Exterior derivative:
395  vec2_type p_esa_dn = bt::map_unit_to_dn(vec2_type(x0,y0));
396  vec2_type p_plain_dn;
397  if(!remap_esa2plain(p_esa_dn,p_plain_dn))
398  {
399  m00 = 1;m01 = 0;m10 = 0;m11 = 1;
400  return false;
401  }
402  mat2_type m = _distortion.jacobi(p_plain_dn);
403 // Interior derivative
404  mat2_type e;
405  double f_dn = this->fl_cm() / this->r_fb_cm();
406  double r_esa_dn = norm2(p_esa_dn);
407  double r_esa_div_2f = r_esa_dn / (2.0 * f_dn);
408 // Test (a): make sure argument of arcsine is valid.
409  if(fabs(r_esa_div_2f) > 0.99)
410  {
411  m00 = 1;m01 = 0;m10 = 0;m11 = 1;
412  return false;
413  }
414  double asn = asin(r_esa_div_2f);
415 // Test (b): make sure cos(..) is greater than 0.
416  if(fabs(2.0 * asn) > 0.99 * M_PI / 2.0)
417  {
418  m00 = 1;m01 = 0;m10 = 0;m11 = 1;
419  return false;
420  }
421  double csn = cos(2.0 * asn);
422 // Argument of square root is positive because of test (a).
423  double sqr = sqrt(1.0 - r_esa_div_2f * r_esa_div_2f);
424  if(r_esa_dn > 1e-12)
425  {
426 // The analytic expression is undefined at 0...
427  e = (mat2_type(1) - tensq(p_esa_dn / r_esa_dn)) * (f_dn / r_esa_dn) * tan(2.0 * asn)
428  + (tensq(p_esa_dn / r_esa_dn)) / (csn * csn * sqr);
429  }
430  else
431  {
432 // ...but has well-defined limit
433  e = mat2_type(1);
434  }
435 
436 // Maps between unit and diagnorm coordinates.
437  mat2_type u2d((bt::w_fb_cm() / 2) / bt::r_fb_cm(),0.0,0.0,(bt::h_fb_cm() / 2) / bt::r_fb_cm());
438  mat2_type d2u(bt::r_fb_cm() / (bt::w_fb_cm() / 2),0.0,0.0,bt::r_fb_cm() / (bt::h_fb_cm() / 2));
439  m = d2u * m * e * u2d;
440 // We have to make sure, that the matrix doesn't run berserk in the outer regions
441 // if(::sqrt(tr(trans(m) * m)) > 100.0)
442 // {
443 // Chill.
444 // m = mat2_type(0);
445 // }
446  m00 = m[0][0];m01 = m[0][1];m10 = m[1][0];m11 = m[1][1];
447  return true;
448  }
449  };
450 
451 template <class VEC2,class MAT2>
453  "Distortion - Degree 2",
454  "Quartic Distortion - Degree 4",
455  "Degree 6",
456  "Degree 8"
457  };
A polynomial radially symmetric model of degree N (even)
mat2d tensq(const vec2d &a)
Tensor (dyadic) product square.
Definition: ldpk_vec2d.h:186
Plugin class for radial distortion. Does not compensate for decentering. Parameters can be calculated...
Definition: tde4_ldp_radial_deg_8.h:13
bool setParameterValue(const char *identifier, double v)
set parameter values... parameters predefined by 3DE4: "tde4_focal_length_cm", "tde4_filmback_width_c...
Definition: tde4_ldp_radial_deg_8.h:117
vec2_type eval(const vec2_type &p) const
Same as method instead of operator.
Definition: ldpk_generic_distortion_base.h:77
bool remap_esa2plain(const vec2_type &p_esa_dn, vec2_type &p_plain_dn) const
esa stands for "equisolid-angle". The method will fill p_plain_dn with some more or less reasonable v...
Definition: tde4_ldp_radial_deg_8.h:140
bool undistort(double x0, double y0, double &x1, double &y1)
warp/unwarp 2D points...
Definition: tde4_ldp_radial_deg_8.h:190
bool getJacobianMatrix(double x0, double y0, double &m00, double &m01, double &m10, double &m11)
calculate the Jacobian matrix of the undistort()-Method. Overwrite this, if you know the Jacobian for...
Definition: tde4_ldp_radial_deg_8.h:389
bool getNumParameters(int &n)
returns the number of plugin parameters...
Definition: tde4_ldp_radial_deg_8.h:107
bool getParameterRange(const char *identifier, double &a, double &b)
returns range for adjustable double parameters...
Definition: tde4_ldp_radial_deg_8.h:380
bool getParameterDefaultValue(const char *identifier, double &v)
returns default value for given parameter (maximum length of "char *v": 1000 bytes)......
Definition: tde4_ldp_radial_deg_8.h:372
bool undistort_gnomonic(double x0, double y0, double &x1, double &y1)
Model function Our base class presents the model functions along with the projection. Internally, we have to carefully keep apart the model function and the projection. For most distortion models, this is no problem, yet for fisheye models it is, so all non-gnomonic distortion classes will have to overwrite these.
Definition: tde4_ldp_radial_deg_8.h:266
mat2_type jacobi(const vec2_type &p_dn) const
Analytic version of the Jacobi-matrix, about 2.5 times faster than the base class version which uses ...
Definition: ldpk_generic_radial_distortion.h:59
bool distort(double x0, double y0, double x1_start, double y1_start, double &x1, double &y1)
potentially more efficient function which uses initial values...
Definition: tde4_ldp_radial_deg_8.h:254
This class handles the built-in parameter and the lookup table. You may find it useful for your own d...
Definition: ldpk_ldp_builtin.h:31
bool getParameterType(const char *identifier, tde4_ldp_ptype &ptype)
returns type of given parameter... The method should return false, if the parameter addressed by iden...
Definition: tde4_ldp_radial_deg_8.h:363
virtual vec2_type map_inverse(const vec2_type &q) const
Inverse mapping by solving the fixed point equation without providing initial values. Virtual, because the derived class might use some smart data structure for calculating an initial value.
Definition: ldpk_generic_distortion_base.h:95
bool initializeParameters()
prepare the current set of parameters...
Definition: tde4_ldp_radial_deg_8.h:45
void set_coeff(int i, double q)
Set coefficient c[i], 0 <= i < N.
Definition: ldpk_generic_radial_distortion.h:38
bool getParameterName(int i, char *identifier)
returns "identifier" name of parameter "i" (maximum length of "identifier": 100 bytes)...
Definition: tde4_ldp_radial_deg_8.h:112
double get_coeff(int i) const
Get coefficient c[i], 0 <= i < N (i.e. coefficient power r^(2i))
Definition: ldpk_generic_radial_distortion.h:32
bool getModelName(char *name)
returns a name for the model as to show up in the GUI (maximum length of "name": 100 bytes)...
Definition: tde4_ldp_radial_deg_8.h:354