1 /*M///////////////////////////////////////////////////////////////////////////////////////
2 //
3 // IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING.
4 //
5 // By downloading, copying, installing or using the software you agree to this license.
6 // If you do not agree to this license, do not download, install,
7 // copy or use the software.
8 //
9 //
10 // Intel License Agreement
11 // For Open Source Computer Vision Library
12 //
13 // Copyright (C) 2000, Intel Corporation, all rights reserved.
14 // Third party copyrights are property of their respective owners.
15 //
16 // Redistribution and use in source and binary forms, with or without modification,
17 // are permitted provided that the following conditions are met:
18 //
19 // * Redistribution's of source code must retain the above copyright notice,
20 // this list of conditions and the following disclaimer.
21 //
22 // * Redistribution's in binary form must reproduce the above copyright notice,
23 // this list of conditions and the following disclaimer in the documentation
24 // and/or other materials provided with the distribution.
25 //
26 // * The name of Intel Corporation may not be used to endorse or promote products
27 // derived from this software without specific prior written permission.
28 //
29 // This software is provided by the copyright holders and contributors "as is" and
30 // any express or implied warranties, including, but not limited to, the implied
31 // warranties of merchantability and fitness for a particular purpose are disclaimed.
32 // In no event shall the Intel Corporation or contributors be liable for any direct,
33 // indirect, incidental, special, exemplary, or consequential damages
34 // (including, but not limited to, procurement of substitute goods or services;
35 // loss of use, data, or profits; or business interruption) however caused
36 // and on any theory of liability, whether in contract, strict liability,
37 // or tort (including negligence or otherwise) arising in any way out of
38 // the use of this software, even if advised of the possibility of such damage.
39 //
40 //M*/
41
42 #include "test_precomp.hpp"
43 #include "opencv2/calib3d/calib3d_c.h"
44
45 using namespace cv;
46 using namespace std;
47
cvTsRodrigues(const CvMat * src,CvMat * dst,CvMat * jacobian)48 int cvTsRodrigues( const CvMat* src, CvMat* dst, CvMat* jacobian )
49 {
50 int depth;
51 int i;
52 float Jf[27];
53 double J[27];
54 CvMat _Jf, matJ = cvMat( 3, 9, CV_64F, J );
55
56 depth = CV_MAT_DEPTH(src->type);
57
58 if( jacobian )
59 {
60 assert( (jacobian->rows == 9 && jacobian->cols == 3) ||
61 (jacobian->rows == 3 && jacobian->cols == 9) );
62 }
63
64 if( src->cols == 1 || src->rows == 1 )
65 {
66 double r[3], theta;
67 CvMat _r = cvMat( src->rows, src->cols, CV_MAKETYPE(CV_64F,CV_MAT_CN(src->type)), r);
68
69 assert( dst->rows == 3 && dst->cols == 3 );
70
71 cvConvert( src, &_r );
72
73 theta = sqrt(r[0]*r[0] + r[1]*r[1] + r[2]*r[2]);
74 if( theta < DBL_EPSILON )
75 {
76 cvSetIdentity( dst );
77
78 if( jacobian )
79 {
80 memset( J, 0, sizeof(J) );
81 J[5] = J[15] = J[19] = 1;
82 J[7] = J[11] = J[21] = -1;
83 }
84 }
85 else
86 {
87 // omega = r/theta (~[w1, w2, w3])
88 double itheta = 1./theta;
89 double w1 = r[0]*itheta, w2 = r[1]*itheta, w3 = r[2]*itheta;
90 double alpha = cos(theta);
91 double beta = sin(theta);
92 double gamma = 1 - alpha;
93 double omegav[] =
94 {
95 0, -w3, w2,
96 w3, 0, -w1,
97 -w2, w1, 0
98 };
99 double A[] =
100 {
101 w1*w1, w1*w2, w1*w3,
102 w2*w1, w2*w2, w2*w3,
103 w3*w1, w3*w2, w3*w3
104 };
105 double R[9];
106 CvMat _omegav = cvMat(3, 3, CV_64F, omegav);
107 CvMat matA = cvMat(3, 3, CV_64F, A);
108 CvMat matR = cvMat(3, 3, CV_64F, R);
109
110 cvSetIdentity( &matR, cvRealScalar(alpha) );
111 cvScaleAdd( &_omegav, cvRealScalar(beta), &matR, &matR );
112 cvScaleAdd( &matA, cvRealScalar(gamma), &matR, &matR );
113 cvConvert( &matR, dst );
114
115 if( jacobian )
116 {
117 // m3 = [r, theta]
118 double dm3din[] =
119 {
120 1, 0, 0,
121 0, 1, 0,
122 0, 0, 1,
123 w1, w2, w3
124 };
125 // m2 = [omega, theta]
126 double dm2dm3[] =
127 {
128 itheta, 0, 0, -w1*itheta,
129 0, itheta, 0, -w2*itheta,
130 0, 0, itheta, -w3*itheta,
131 0, 0, 0, 1
132 };
133 double t0[9*4];
134 double dm1dm2[21*4];
135 double dRdm1[9*21];
136 CvMat _dm3din = cvMat( 4, 3, CV_64FC1, dm3din );
137 CvMat _dm2dm3 = cvMat( 4, 4, CV_64FC1, dm2dm3 );
138 CvMat _dm1dm2 = cvMat( 21, 4, CV_64FC1, dm1dm2 );
139 CvMat _dRdm1 = cvMat( 9, 21, CV_64FC1, dRdm1 );
140 CvMat _dRdm1_part;
141 CvMat _t0 = cvMat( 9, 4, CV_64FC1, t0 );
142 CvMat _t1 = cvMat( 9, 4, CV_64FC1, dRdm1 );
143
144 // m1 = [alpha, beta, gamma, omegav; A]
145 memset( dm1dm2, 0, sizeof(dm1dm2) );
146 dm1dm2[3] = -beta;
147 dm1dm2[7] = alpha;
148 dm1dm2[11] = beta;
149
150 // dm1dm2(4:12,1:3) = [0 0 0 0 0 1 0 -1 0;
151 // 0 0 -1 0 0 0 1 0 0;
152 // 0 1 0 -1 0 0 0 0 0]'
153 // -------------------
154 // 0 0 0 0 0 0 0 0 0
155 dm1dm2[12 + 6] = dm1dm2[12 + 20] = dm1dm2[12 + 25] = 1;
156 dm1dm2[12 + 9] = dm1dm2[12 + 14] = dm1dm2[12 + 28] = -1;
157
158 double dm1dw[] =
159 {
160 2*w1, w2, w3, w2, 0, 0, w3, 0, 0,
161 0, w1, 0, w1, 2*w2, w3, 0, w3, 0,
162 0, 0, w1, 0, 0, w2, w1, w2, 2*w3
163 };
164
165 CvMat _dm1dw = cvMat( 3, 9, CV_64FC1, dm1dw );
166 CvMat _dm1dm2_part;
167
168 cvGetSubRect( &_dm1dm2, &_dm1dm2_part, cvRect(0,12,3,9) );
169 cvTranspose( &_dm1dw, &_dm1dm2_part );
170
171 memset( dRdm1, 0, sizeof(dRdm1) );
172 dRdm1[0*21] = dRdm1[4*21] = dRdm1[8*21] = 1;
173
174 cvGetCol( &_dRdm1, &_dRdm1_part, 1 );
175 cvTranspose( &_omegav, &_omegav );
176 cvReshape( &_omegav, &_omegav, 1, 1 );
177 cvTranspose( &_omegav, &_dRdm1_part );
178
179 cvGetCol( &_dRdm1, &_dRdm1_part, 2 );
180 cvReshape( &matA, &matA, 1, 1 );
181 cvTranspose( &matA, &_dRdm1_part );
182
183 cvGetSubRect( &_dRdm1, &_dRdm1_part, cvRect(3,0,9,9) );
184 cvSetIdentity( &_dRdm1_part, cvScalarAll(beta) );
185
186 cvGetSubRect( &_dRdm1, &_dRdm1_part, cvRect(12,0,9,9) );
187 cvSetIdentity( &_dRdm1_part, cvScalarAll(gamma) );
188
189 matJ = cvMat( 9, 3, CV_64FC1, J );
190
191 cvMatMul( &_dRdm1, &_dm1dm2, &_t0 );
192 cvMatMul( &_t0, &_dm2dm3, &_t1 );
193 cvMatMul( &_t1, &_dm3din, &matJ );
194
195 _t0 = cvMat( 3, 9, CV_64FC1, t0 );
196 cvTranspose( &matJ, &_t0 );
197
198 for( i = 0; i < 3; i++ )
199 {
200 _t1 = cvMat( 3, 3, CV_64FC1, t0 + i*9 );
201 cvTranspose( &_t1, &_t1 );
202 }
203
204 cvTranspose( &_t0, &matJ );
205 }
206 }
207 }
208 else if( src->cols == 3 && src->rows == 3 )
209 {
210 double R[9], A[9], I[9], r[3], W[3], U[9], V[9];
211 double tr, alpha, beta, theta;
212 CvMat matR = cvMat( 3, 3, CV_64F, R );
213 CvMat matA = cvMat( 3, 3, CV_64F, A );
214 CvMat matI = cvMat( 3, 3, CV_64F, I );
215 CvMat _r = cvMat( dst->rows, dst->cols, CV_MAKETYPE(CV_64F, CV_MAT_CN(dst->type)), r );
216 CvMat matW = cvMat( 1, 3, CV_64F, W );
217 CvMat matU = cvMat( 3, 3, CV_64F, U );
218 CvMat matV = cvMat( 3, 3, CV_64F, V );
219
220 cvConvert( src, &matR );
221 cvSVD( &matR, &matW, &matU, &matV, CV_SVD_MODIFY_A + CV_SVD_U_T + CV_SVD_V_T );
222 cvGEMM( &matU, &matV, 1, 0, 0, &matR, CV_GEMM_A_T );
223
224 cvMulTransposed( &matR, &matA, 0 );
225 cvSetIdentity( &matI );
226
227 if( cvNorm( &matA, &matI, CV_C ) > 1e-3 ||
228 fabs( cvDet(&matR) - 1 ) > 1e-3 )
229 return 0;
230
231 tr = (cvTrace(&matR).val[0] - 1.)*0.5;
232 tr = tr > 1. ? 1. : tr < -1. ? -1. : tr;
233 theta = acos(tr);
234 alpha = cos(theta);
235 beta = sin(theta);
236
237 if( beta >= 1e-5 )
238 {
239 double dtheta_dtr = -1./sqrt(1 - tr*tr);
240 double vth = 1/(2*beta);
241
242 // om1 = [R(3,2) - R(2,3), R(1,3) - R(3,1), R(2,1) - R(1,2)]'
243 double om1[] = { R[7] - R[5], R[2] - R[6], R[3] - R[1] };
244 // om = om1*vth
245 // r = om*theta
246 double d3 = vth*theta;
247
248 r[0] = om1[0]*d3; r[1] = om1[1]*d3; r[2] = om1[2]*d3;
249 cvConvert( &_r, dst );
250
251 if( jacobian )
252 {
253 // var1 = [vth;theta]
254 // var = [om1;var1] = [om1;vth;theta]
255 double dvth_dtheta = -vth*alpha/beta;
256 double d1 = 0.5*dvth_dtheta*dtheta_dtr;
257 double d2 = 0.5*dtheta_dtr;
258 // dvar1/dR = dvar1/dtheta*dtheta/dR = [dvth/dtheta; 1] * dtheta/dtr * dtr/dR
259 double dvardR[5*9] =
260 {
261 0, 0, 0, 0, 0, 1, 0, -1, 0,
262 0, 0, -1, 0, 0, 0, 1, 0, 0,
263 0, 1, 0, -1, 0, 0, 0, 0, 0,
264 d1, 0, 0, 0, d1, 0, 0, 0, d1,
265 d2, 0, 0, 0, d2, 0, 0, 0, d2
266 };
267 // var2 = [om;theta]
268 double dvar2dvar[] =
269 {
270 vth, 0, 0, om1[0], 0,
271 0, vth, 0, om1[1], 0,
272 0, 0, vth, om1[2], 0,
273 0, 0, 0, 0, 1
274 };
275 double domegadvar2[] =
276 {
277 theta, 0, 0, om1[0]*vth,
278 0, theta, 0, om1[1]*vth,
279 0, 0, theta, om1[2]*vth
280 };
281
282 CvMat _dvardR = cvMat( 5, 9, CV_64FC1, dvardR );
283 CvMat _dvar2dvar = cvMat( 4, 5, CV_64FC1, dvar2dvar );
284 CvMat _domegadvar2 = cvMat( 3, 4, CV_64FC1, domegadvar2 );
285 double t0[3*5];
286 CvMat _t0 = cvMat( 3, 5, CV_64FC1, t0 );
287
288 cvMatMul( &_domegadvar2, &_dvar2dvar, &_t0 );
289 cvMatMul( &_t0, &_dvardR, &matJ );
290 }
291 }
292 else if( tr > 0 )
293 {
294 cvZero( dst );
295 if( jacobian )
296 {
297 memset( J, 0, sizeof(J) );
298 J[5] = J[15] = J[19] = 0.5;
299 J[7] = J[11] = J[21] = -0.5;
300 }
301 }
302 else
303 {
304 r[0] = theta*sqrt((R[0] + 1)*0.5);
305 r[1] = theta*sqrt((R[4] + 1)*0.5)*(R[1] >= 0 ? 1 : -1);
306 r[2] = theta*sqrt((R[8] + 1)*0.5)*(R[2] >= 0 ? 1 : -1);
307 cvConvert( &_r, dst );
308
309 if( jacobian )
310 memset( J, 0, sizeof(J) );
311 }
312
313 if( jacobian )
314 {
315 for( i = 0; i < 3; i++ )
316 {
317 CvMat t = cvMat( 3, 3, CV_64F, J + i*9 );
318 cvTranspose( &t, &t );
319 }
320 }
321 }
322 else
323 {
324 assert(0);
325 return 0;
326 }
327
328 if( jacobian )
329 {
330 if( depth == CV_32F )
331 {
332 if( jacobian->rows == matJ.rows )
333 cvConvert( &matJ, jacobian );
334 else
335 {
336 _Jf = cvMat( matJ.rows, matJ.cols, CV_32FC1, Jf );
337 cvConvert( &matJ, &_Jf );
338 cvTranspose( &_Jf, jacobian );
339 }
340 }
341 else if( jacobian->rows == matJ.rows )
342 cvCopy( &matJ, jacobian );
343 else
344 cvTranspose( &matJ, jacobian );
345 }
346
347 return 1;
348 }
349
350
Rodrigues(const Mat & src,Mat & dst,Mat * jac)351 void cvtest::Rodrigues(const Mat& src, Mat& dst, Mat* jac)
352 {
353 CvMat _src = src, _dst = dst, _jac;
354 if( jac )
355 _jac = *jac;
356 cvTsRodrigues(&_src, &_dst, jac ? &_jac : 0);
357 }
358
359
test_convertHomogeneous(const Mat & _src,Mat & _dst)360 static void test_convertHomogeneous( const Mat& _src, Mat& _dst )
361 {
362 Mat src = _src, dst = _dst;
363 int i, count, sdims, ddims;
364 int sstep1, sstep2, dstep1, dstep2;
365
366 if( src.depth() != CV_64F )
367 _src.convertTo(src, CV_64F);
368
369 if( dst.depth() != CV_64F )
370 dst.create(dst.size(), CV_MAKETYPE(CV_64F, _dst.channels()));
371
372 if( src.rows > src.cols )
373 {
374 count = src.rows;
375 sdims = src.channels()*src.cols;
376 sstep1 = (int)(src.step/sizeof(double));
377 sstep2 = 1;
378 }
379 else
380 {
381 count = src.cols;
382 sdims = src.channels()*src.rows;
383 if( src.rows == 1 )
384 {
385 sstep1 = sdims;
386 sstep2 = 1;
387 }
388 else
389 {
390 sstep1 = 1;
391 sstep2 = (int)(src.step/sizeof(double));
392 }
393 }
394
395 if( dst.rows > dst.cols )
396 {
397 CV_Assert( count == dst.rows );
398 ddims = dst.channels()*dst.cols;
399 dstep1 = (int)(dst.step/sizeof(double));
400 dstep2 = 1;
401 }
402 else
403 {
404 assert( count == dst.cols );
405 ddims = dst.channels()*dst.rows;
406 if( dst.rows == 1 )
407 {
408 dstep1 = ddims;
409 dstep2 = 1;
410 }
411 else
412 {
413 dstep1 = 1;
414 dstep2 = (int)(dst.step/sizeof(double));
415 }
416 }
417
418 double* s = src.ptr<double>();
419 double* d = dst.ptr<double>();
420
421 if( sdims <= ddims )
422 {
423 int wstep = dstep2*(ddims - 1);
424
425 for( i = 0; i < count; i++, s += sstep1, d += dstep1 )
426 {
427 double x = s[0];
428 double y = s[sstep2];
429
430 d[wstep] = 1;
431 d[0] = x;
432 d[dstep2] = y;
433
434 if( sdims >= 3 )
435 {
436 d[dstep2*2] = s[sstep2*2];
437 if( sdims == 4 )
438 d[dstep2*3] = s[sstep2*3];
439 }
440 }
441 }
442 else
443 {
444 int wstep = sstep2*(sdims - 1);
445
446 for( i = 0; i < count; i++, s += sstep1, d += dstep1 )
447 {
448 double w = s[wstep];
449 double x = s[0];
450 double y = s[sstep2];
451
452 w = w ? 1./w : 1;
453
454 d[0] = x*w;
455 d[dstep2] = y*w;
456
457 if( ddims == 3 )
458 d[dstep2*2] = s[sstep2*2]*w;
459 }
460 }
461
462 if( dst.data != _dst.data )
463 dst.convertTo(_dst, _dst.depth());
464 }
465
466
467 void
test_projectPoints(const Mat & _3d,const Mat & Rt,const Mat & A,Mat & _2d,RNG * rng,double sigma)468 test_projectPoints( const Mat& _3d, const Mat& Rt, const Mat& A, Mat& _2d, RNG* rng, double sigma )
469 {
470 CV_Assert( _3d.isContinuous() );
471
472 double p[12];
473 Mat P( 3, 4, CV_64F, p );
474 gemm(A, Rt, 1, Mat(), 0, P);
475
476 int i, count = _3d.cols;
477
478 Mat noise;
479 if( rng )
480 {
481 if( sigma == 0 )
482 rng = 0;
483 else
484 {
485 noise.create( 1, _3d.cols, CV_64FC2 );
486 rng->fill(noise, RNG::NORMAL, Scalar::all(0), Scalar::all(sigma) );
487 }
488 }
489
490 Mat temp( 1, count, CV_64FC3 );
491
492 for( i = 0; i < count; i++ )
493 {
494 const double* M = _3d.ptr<double>() + i*3;
495 double* m = temp.ptr<double>() + i*3;
496 double X = M[0], Y = M[1], Z = M[2];
497 double u = p[0]*X + p[1]*Y + p[2]*Z + p[3];
498 double v = p[4]*X + p[5]*Y + p[6]*Z + p[7];
499 double s = p[8]*X + p[9]*Y + p[10]*Z + p[11];
500
501 if( !noise.empty() )
502 {
503 u += noise.at<Point2d>(i).x*s;
504 v += noise.at<Point2d>(i).y*s;
505 }
506
507 m[0] = u;
508 m[1] = v;
509 m[2] = s;
510 }
511
512 test_convertHomogeneous( temp, _2d );
513 }
514
515
516 /********************************** Rodrigues transform ********************************/
517
518 class CV_RodriguesTest : public cvtest::ArrayTest
519 {
520 public:
521 CV_RodriguesTest();
522
523 protected:
524 int read_params( CvFileStorage* fs );
525 void fill_array( int test_case_idx, int i, int j, Mat& arr );
526 int prepare_test_case( int test_case_idx );
527 void get_test_array_types_and_sizes( int test_case_idx, vector<vector<Size> >& sizes, vector<vector<int> >& types );
528 double get_success_error_level( int test_case_idx, int i, int j );
529 void run_func();
530 void prepare_to_validation( int );
531
532 bool calc_jacobians;
533 bool test_cpp;
534 };
535
536
CV_RodriguesTest()537 CV_RodriguesTest::CV_RodriguesTest()
538 {
539 test_array[INPUT].push_back(NULL); // rotation vector
540 test_array[OUTPUT].push_back(NULL); // rotation matrix
541 test_array[OUTPUT].push_back(NULL); // jacobian (J)
542 test_array[OUTPUT].push_back(NULL); // rotation vector (backward transform result)
543 test_array[OUTPUT].push_back(NULL); // inverse transform jacobian (J1)
544 test_array[OUTPUT].push_back(NULL); // J*J1 (or J1*J) == I(3x3)
545 test_array[REF_OUTPUT].push_back(NULL);
546 test_array[REF_OUTPUT].push_back(NULL);
547 test_array[REF_OUTPUT].push_back(NULL);
548 test_array[REF_OUTPUT].push_back(NULL);
549 test_array[REF_OUTPUT].push_back(NULL);
550
551 element_wise_relative_error = false;
552 calc_jacobians = false;
553
554 test_cpp = false;
555 }
556
557
read_params(CvFileStorage * fs)558 int CV_RodriguesTest::read_params( CvFileStorage* fs )
559 {
560 int code = cvtest::ArrayTest::read_params( fs );
561 return code;
562 }
563
564
get_test_array_types_and_sizes(int,vector<vector<Size>> & sizes,vector<vector<int>> & types)565 void CV_RodriguesTest::get_test_array_types_and_sizes(
566 int /*test_case_idx*/, vector<vector<Size> >& sizes, vector<vector<int> >& types )
567 {
568 RNG& rng = ts->get_rng();
569 int depth = cvtest::randInt(rng) % 2 == 0 ? CV_32F : CV_64F;
570 int i, code;
571
572 code = cvtest::randInt(rng) % 3;
573 types[INPUT][0] = CV_MAKETYPE(depth, 1);
574
575 if( code == 0 )
576 {
577 sizes[INPUT][0] = cvSize(1,1);
578 types[INPUT][0] = CV_MAKETYPE(depth, 3);
579 }
580 else if( code == 1 )
581 sizes[INPUT][0] = cvSize(3,1);
582 else
583 sizes[INPUT][0] = cvSize(1,3);
584
585 sizes[OUTPUT][0] = cvSize(3, 3);
586 types[OUTPUT][0] = CV_MAKETYPE(depth, 1);
587
588 types[OUTPUT][1] = CV_MAKETYPE(depth, 1);
589
590 if( cvtest::randInt(rng) % 2 )
591 sizes[OUTPUT][1] = cvSize(3,9);
592 else
593 sizes[OUTPUT][1] = cvSize(9,3);
594
595 types[OUTPUT][2] = types[INPUT][0];
596 sizes[OUTPUT][2] = sizes[INPUT][0];
597
598 types[OUTPUT][3] = types[OUTPUT][1];
599 sizes[OUTPUT][3] = cvSize(sizes[OUTPUT][1].height, sizes[OUTPUT][1].width);
600
601 types[OUTPUT][4] = types[OUTPUT][1];
602 sizes[OUTPUT][4] = cvSize(3,3);
603
604 calc_jacobians = cvtest::randInt(rng) % 3 != 0;
605 if( !calc_jacobians )
606 sizes[OUTPUT][1] = sizes[OUTPUT][3] = sizes[OUTPUT][4] = cvSize(0,0);
607
608 for( i = 0; i < 5; i++ )
609 {
610 types[REF_OUTPUT][i] = types[OUTPUT][i];
611 sizes[REF_OUTPUT][i] = sizes[OUTPUT][i];
612 }
613 test_cpp = (cvtest::randInt(rng) & 256) == 0;
614 }
615
616
get_success_error_level(int,int,int j)617 double CV_RodriguesTest::get_success_error_level( int /*test_case_idx*/, int /*i*/, int j )
618 {
619 return j == 4 ? 1e-2 : 1e-2;
620 }
621
622
fill_array(int test_case_idx,int i,int j,Mat & arr)623 void CV_RodriguesTest::fill_array( int test_case_idx, int i, int j, Mat& arr )
624 {
625 if( i == INPUT && j == 0 )
626 {
627 double r[3], theta0, theta1, f;
628 Mat _r( arr.rows, arr.cols, CV_MAKETYPE(CV_64F,arr.channels()), r );
629 RNG& rng = ts->get_rng();
630
631 r[0] = cvtest::randReal(rng)*CV_PI*2;
632 r[1] = cvtest::randReal(rng)*CV_PI*2;
633 r[2] = cvtest::randReal(rng)*CV_PI*2;
634
635 theta0 = sqrt(r[0]*r[0] + r[1]*r[1] + r[2]*r[2]);
636 theta1 = fmod(theta0, CV_PI*2);
637
638 if( theta1 > CV_PI )
639 theta1 = -(CV_PI*2 - theta1);
640
641 f = theta1/(theta0 ? theta0 : 1);
642 r[0] *= f;
643 r[1] *= f;
644 r[2] *= f;
645
646 cvtest::convert( _r, arr, arr.type() );
647 }
648 else
649 cvtest::ArrayTest::fill_array( test_case_idx, i, j, arr );
650 }
651
652
prepare_test_case(int test_case_idx)653 int CV_RodriguesTest::prepare_test_case( int test_case_idx )
654 {
655 int code = cvtest::ArrayTest::prepare_test_case( test_case_idx );
656 return code;
657 }
658
659
run_func()660 void CV_RodriguesTest::run_func()
661 {
662 CvMat v2m_jac, m2v_jac;
663
664 if( calc_jacobians )
665 {
666 v2m_jac = test_mat[OUTPUT][1];
667 m2v_jac = test_mat[OUTPUT][3];
668 }
669
670 if( !test_cpp )
671 {
672 CvMat _input = test_mat[INPUT][0], _output = test_mat[OUTPUT][0], _output2 = test_mat[OUTPUT][2];
673 cvRodrigues2( &_input, &_output, calc_jacobians ? &v2m_jac : 0 );
674 cvRodrigues2( &_output, &_output2, calc_jacobians ? &m2v_jac : 0 );
675 }
676 else
677 {
678 cv::Mat v = test_mat[INPUT][0], M = test_mat[OUTPUT][0], v2 = test_mat[OUTPUT][2];
679 cv::Mat M0 = M, v2_0 = v2;
680 if( !calc_jacobians )
681 {
682 cv::Rodrigues(v, M);
683 cv::Rodrigues(M, v2);
684 }
685 else
686 {
687 cv::Mat J1 = test_mat[OUTPUT][1], J2 = test_mat[OUTPUT][3];
688 cv::Mat J1_0 = J1, J2_0 = J2;
689 cv::Rodrigues(v, M, J1);
690 cv::Rodrigues(M, v2, J2);
691 if( J1.data != J1_0.data )
692 {
693 if( J1.size() != J1_0.size() )
694 J1 = J1.t();
695 J1.convertTo(J1_0, J1_0.type());
696 }
697 if( J2.data != J2_0.data )
698 {
699 if( J2.size() != J2_0.size() )
700 J2 = J2.t();
701 J2.convertTo(J2_0, J2_0.type());
702 }
703 }
704 if( M.data != M0.data )
705 M.reshape(M0.channels(), M0.rows).convertTo(M0, M0.type());
706 if( v2.data != v2_0.data )
707 v2.reshape(v2_0.channels(), v2_0.rows).convertTo(v2_0, v2_0.type());
708 }
709 }
710
711
prepare_to_validation(int)712 void CV_RodriguesTest::prepare_to_validation( int /*test_case_idx*/ )
713 {
714 const Mat& vec = test_mat[INPUT][0];
715 Mat& m = test_mat[REF_OUTPUT][0];
716 Mat& vec2 = test_mat[REF_OUTPUT][2];
717 Mat* v2m_jac = 0, *m2v_jac = 0;
718 double theta0, theta1;
719
720 if( calc_jacobians )
721 {
722 v2m_jac = &test_mat[REF_OUTPUT][1];
723 m2v_jac = &test_mat[REF_OUTPUT][3];
724 }
725
726
727 cvtest::Rodrigues( vec, m, v2m_jac );
728 cvtest::Rodrigues( m, vec2, m2v_jac );
729 cvtest::copy( vec, vec2 );
730
731 theta0 = norm( vec2, CV_L2 );
732 theta1 = fmod( theta0, CV_PI*2 );
733
734 if( theta1 > CV_PI )
735 theta1 = -(CV_PI*2 - theta1);
736 vec2 *= theta1/(theta0 ? theta0 : 1);
737
738 if( calc_jacobians )
739 {
740 //cvInvert( v2m_jac, m2v_jac, CV_SVD );
741 double nrm = cvtest::norm(test_mat[REF_OUTPUT][3], CV_C);
742 if( FLT_EPSILON < nrm && nrm < 1000 )
743 {
744 gemm( test_mat[OUTPUT][1], test_mat[OUTPUT][3],
745 1, Mat(), 0, test_mat[OUTPUT][4],
746 v2m_jac->rows == 3 ? 0 : CV_GEMM_A_T + CV_GEMM_B_T );
747 }
748 else
749 {
750 setIdentity(test_mat[OUTPUT][4], Scalar::all(1.));
751 cvtest::copy( test_mat[REF_OUTPUT][2], test_mat[OUTPUT][2] );
752 }
753 setIdentity(test_mat[REF_OUTPUT][4], Scalar::all(1.));
754 }
755 }
756
757
758 /********************************** fundamental matrix *********************************/
759
760 class CV_FundamentalMatTest : public cvtest::ArrayTest
761 {
762 public:
763 CV_FundamentalMatTest();
764
765 protected:
766 int read_params( CvFileStorage* fs );
767 void fill_array( int test_case_idx, int i, int j, Mat& arr );
768 int prepare_test_case( int test_case_idx );
769 void get_test_array_types_and_sizes( int test_case_idx, vector<vector<Size> >& sizes, vector<vector<int> >& types );
770 double get_success_error_level( int test_case_idx, int i, int j );
771 void run_func();
772 void prepare_to_validation( int );
773
774 int method;
775 int img_size;
776 int cube_size;
777 int dims;
778 int f_result;
779 double min_f, max_f;
780 double sigma;
781 bool test_cpp;
782 };
783
784
CV_FundamentalMatTest()785 CV_FundamentalMatTest::CV_FundamentalMatTest()
786 {
787 // input arrays:
788 // 0, 1 - arrays of 2d points that are passed to %func%.
789 // Can have different data type, layout, be stored in homogeneous coordinates or not.
790 // 2 - array of 3d points that are projected to both view planes
791 // 3 - [R|t] matrix for the second view plane (for the first one it is [I|0]
792 // 4, 5 - intrinsic matrices
793 test_array[INPUT].push_back(NULL);
794 test_array[INPUT].push_back(NULL);
795 test_array[INPUT].push_back(NULL);
796 test_array[INPUT].push_back(NULL);
797 test_array[INPUT].push_back(NULL);
798 test_array[INPUT].push_back(NULL);
799 test_array[TEMP].push_back(NULL);
800 test_array[TEMP].push_back(NULL);
801 test_array[OUTPUT].push_back(NULL);
802 test_array[OUTPUT].push_back(NULL);
803 test_array[REF_OUTPUT].push_back(NULL);
804 test_array[REF_OUTPUT].push_back(NULL);
805
806 element_wise_relative_error = false;
807
808 method = 0;
809 img_size = 10;
810 cube_size = 10;
811 dims = 0;
812 min_f = 1;
813 max_f = 3;
814 sigma = 0;//0.1;
815 f_result = 0;
816
817 test_cpp = false;
818 }
819
820
read_params(CvFileStorage * fs)821 int CV_FundamentalMatTest::read_params( CvFileStorage* fs )
822 {
823 int code = cvtest::ArrayTest::read_params( fs );
824 return code;
825 }
826
827
get_test_array_types_and_sizes(int,vector<vector<Size>> & sizes,vector<vector<int>> & types)828 void CV_FundamentalMatTest::get_test_array_types_and_sizes( int /*test_case_idx*/,
829 vector<vector<Size> >& sizes, vector<vector<int> >& types )
830 {
831 RNG& rng = ts->get_rng();
832 int pt_depth = cvtest::randInt(rng) % 2 == 0 ? CV_32F : CV_64F;
833 double pt_count_exp = cvtest::randReal(rng)*6 + 1;
834 int pt_count = cvRound(exp(pt_count_exp));
835
836 dims = cvtest::randInt(rng) % 2 + 2;
837 method = 1 << (cvtest::randInt(rng) % 4);
838
839 if( method == CV_FM_7POINT )
840 pt_count = 7;
841 else
842 {
843 pt_count = MAX( pt_count, 8 + (method == CV_FM_8POINT) );
844 if( pt_count >= 8 && cvtest::randInt(rng) % 2 )
845 method |= CV_FM_8POINT;
846 }
847
848 types[INPUT][0] = CV_MAKETYPE(pt_depth, 1);
849
850 if( cvtest::randInt(rng) % 2 )
851 sizes[INPUT][0] = cvSize(pt_count, dims);
852 else
853 {
854 sizes[INPUT][0] = cvSize(dims, pt_count);
855 if( cvtest::randInt(rng) % 2 )
856 {
857 types[INPUT][0] = CV_MAKETYPE(pt_depth, dims);
858 if( cvtest::randInt(rng) % 2 )
859 sizes[INPUT][0] = cvSize(pt_count, 1);
860 else
861 sizes[INPUT][0] = cvSize(1, pt_count);
862 }
863 }
864
865 sizes[INPUT][1] = sizes[INPUT][0];
866 types[INPUT][1] = types[INPUT][0];
867
868 sizes[INPUT][2] = cvSize(pt_count, 1 );
869 types[INPUT][2] = CV_64FC3;
870
871 sizes[INPUT][3] = cvSize(4,3);
872 types[INPUT][3] = CV_64FC1;
873
874 sizes[INPUT][4] = sizes[INPUT][5] = cvSize(3,3);
875 types[INPUT][4] = types[INPUT][5] = CV_MAKETYPE(CV_64F, 1);
876
877 sizes[TEMP][0] = cvSize(3,3);
878 types[TEMP][0] = CV_64FC1;
879 sizes[TEMP][1] = cvSize(pt_count,1);
880 types[TEMP][1] = CV_8UC1;
881
882 sizes[OUTPUT][0] = sizes[REF_OUTPUT][0] = cvSize(3,1);
883 types[OUTPUT][0] = types[REF_OUTPUT][0] = CV_64FC1;
884 sizes[OUTPUT][1] = sizes[REF_OUTPUT][1] = cvSize(pt_count,1);
885 types[OUTPUT][1] = types[REF_OUTPUT][1] = CV_8UC1;
886
887 test_cpp = (cvtest::randInt(rng) & 256) == 0;
888 }
889
890
get_success_error_level(int,int,int)891 double CV_FundamentalMatTest::get_success_error_level( int /*test_case_idx*/, int /*i*/, int /*j*/ )
892 {
893 return 1e-2;
894 }
895
896
fill_array(int test_case_idx,int i,int j,Mat & arr)897 void CV_FundamentalMatTest::fill_array( int test_case_idx, int i, int j, Mat& arr )
898 {
899 double t[12]={0};
900 RNG& rng = ts->get_rng();
901
902 if( i != INPUT )
903 {
904 cvtest::ArrayTest::fill_array( test_case_idx, i, j, arr );
905 return;
906 }
907
908 switch( j )
909 {
910 case 0:
911 case 1:
912 return; // fill them later in prepare_test_case
913 case 2:
914 {
915 double* p = arr.ptr<double>();
916 for( i = 0; i < arr.cols*3; i += 3 )
917 {
918 p[i] = cvtest::randReal(rng)*cube_size;
919 p[i+1] = cvtest::randReal(rng)*cube_size;
920 p[i+2] = cvtest::randReal(rng)*cube_size + cube_size;
921 }
922 }
923 break;
924 case 3:
925 {
926 double r[3];
927 Mat rot_vec( 3, 1, CV_64F, r );
928 Mat rot_mat( 3, 3, CV_64F, t, 4*sizeof(t[0]) );
929 r[0] = cvtest::randReal(rng)*CV_PI*2;
930 r[1] = cvtest::randReal(rng)*CV_PI*2;
931 r[2] = cvtest::randReal(rng)*CV_PI*2;
932
933 cvtest::Rodrigues( rot_vec, rot_mat );
934 t[3] = cvtest::randReal(rng)*cube_size;
935 t[7] = cvtest::randReal(rng)*cube_size;
936 t[11] = cvtest::randReal(rng)*cube_size;
937 Mat( 3, 4, CV_64F, t ).convertTo(arr, arr.type());
938 }
939 break;
940 case 4:
941 case 5:
942 t[0] = t[4] = cvtest::randReal(rng)*(max_f - min_f) + min_f;
943 t[2] = (img_size*0.5 + cvtest::randReal(rng)*4. - 2.)*t[0];
944 t[5] = (img_size*0.5 + cvtest::randReal(rng)*4. - 2.)*t[4];
945 t[8] = 1.;
946 Mat( 3, 3, CV_64F, t ).convertTo( arr, arr.type() );
947 break;
948 }
949 }
950
951
prepare_test_case(int test_case_idx)952 int CV_FundamentalMatTest::prepare_test_case( int test_case_idx )
953 {
954 int code = cvtest::ArrayTest::prepare_test_case( test_case_idx );
955 if( code > 0 )
956 {
957 const Mat& _3d = test_mat[INPUT][2];
958 RNG& rng = ts->get_rng();
959 double Idata[] = { 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0 };
960 Mat I( 3, 4, CV_64F, Idata );
961 int k;
962
963 for( k = 0; k < 2; k++ )
964 {
965 const Mat& Rt = k == 0 ? I : test_mat[INPUT][3];
966 const Mat& A = test_mat[INPUT][k == 0 ? 4 : 5];
967 Mat& _2d = test_mat[INPUT][k];
968
969 test_projectPoints( _3d, Rt, A, _2d, &rng, sigma );
970 }
971 }
972
973 return code;
974 }
975
run_func()976 void CV_FundamentalMatTest::run_func()
977 {
978 // cvFindFundamentalMat calls cv::findFundamentalMat
979 CvMat _input0 = test_mat[INPUT][0], _input1 = test_mat[INPUT][1];
980 CvMat F = test_mat[TEMP][0], mask = test_mat[TEMP][1];
981 f_result = cvFindFundamentalMat( &_input0, &_input1, &F, method, MAX(sigma*3, 0.01), 0, &mask );
982 }
983
984
prepare_to_validation(int test_case_idx)985 void CV_FundamentalMatTest::prepare_to_validation( int test_case_idx )
986 {
987 const Mat& Rt = test_mat[INPUT][3];
988 const Mat& A1 = test_mat[INPUT][4];
989 const Mat& A2 = test_mat[INPUT][5];
990 double f0[9], f[9];
991 Mat F0(3, 3, CV_64FC1, f0), F(3, 3, CV_64F, f);
992
993 Mat invA1, invA2, R=Rt.colRange(0, 3), T;
994
995 cv::invert(A1, invA1, CV_SVD);
996 cv::invert(A2, invA2, CV_SVD);
997
998 double tx = Rt.at<double>(0, 3);
999 double ty = Rt.at<double>(1, 3);
1000 double tz = Rt.at<double>(2, 3);
1001
1002 double _t_x[] = { 0, -tz, ty, tz, 0, -tx, -ty, tx, 0 };
1003
1004 // F = (A2^-T)*[t]_x*R*(A1^-1)
1005 cv::gemm( invA2, Mat( 3, 3, CV_64F, _t_x ), 1, Mat(), 0, T, CV_GEMM_A_T );
1006 cv::gemm( R, invA1, 1, Mat(), 0, invA2 );
1007 cv::gemm( T, invA2, 1, Mat(), 0, F0 );
1008 F0 *= 1./f0[8];
1009
1010 uchar* status = test_mat[TEMP][1].ptr();
1011 double err_level = get_success_error_level( test_case_idx, OUTPUT, 1 );
1012 uchar* mtfm1 = test_mat[REF_OUTPUT][1].ptr();
1013 uchar* mtfm2 = test_mat[OUTPUT][1].ptr();
1014 double* f_prop1 = test_mat[REF_OUTPUT][0].ptr<double>();
1015 double* f_prop2 = test_mat[OUTPUT][0].ptr<double>();
1016
1017 int i, pt_count = test_mat[INPUT][2].cols;
1018 Mat p1( 1, pt_count, CV_64FC2 );
1019 Mat p2( 1, pt_count, CV_64FC2 );
1020
1021 test_convertHomogeneous( test_mat[INPUT][0], p1 );
1022 test_convertHomogeneous( test_mat[INPUT][1], p2 );
1023
1024 cvtest::convert(test_mat[TEMP][0], F, F.type());
1025
1026 if( method <= CV_FM_8POINT )
1027 memset( status, 1, pt_count );
1028
1029 for( i = 0; i < pt_count; i++ )
1030 {
1031 double x1 = p1.at<Point2d>(i).x;
1032 double y1 = p1.at<Point2d>(i).y;
1033 double x2 = p2.at<Point2d>(i).x;
1034 double y2 = p2.at<Point2d>(i).y;
1035 double n1 = 1./sqrt(x1*x1 + y1*y1 + 1);
1036 double n2 = 1./sqrt(x2*x2 + y2*y2 + 1);
1037 double t0 = fabs(f0[0]*x2*x1 + f0[1]*x2*y1 + f0[2]*x2 +
1038 f0[3]*y2*x1 + f0[4]*y2*y1 + f0[5]*y2 +
1039 f0[6]*x1 + f0[7]*y1 + f0[8])*n1*n2;
1040 double t = fabs(f[0]*x2*x1 + f[1]*x2*y1 + f[2]*x2 +
1041 f[3]*y2*x1 + f[4]*y2*y1 + f[5]*y2 +
1042 f[6]*x1 + f[7]*y1 + f[8])*n1*n2;
1043 mtfm1[i] = 1;
1044 mtfm2[i] = !status[i] || t0 > err_level || t < err_level;
1045 }
1046
1047 f_prop1[0] = 1;
1048 f_prop1[1] = 1;
1049 f_prop1[2] = 0;
1050
1051 f_prop2[0] = f_result != 0;
1052 f_prop2[1] = f[8];
1053 f_prop2[2] = cv::determinant( F );
1054 }
1055 /******************************* find essential matrix ***********************************/
1056 class CV_EssentialMatTest : public cvtest::ArrayTest
1057 {
1058 public:
1059 CV_EssentialMatTest();
1060
1061 protected:
1062 int read_params( CvFileStorage* fs );
1063 void fill_array( int test_case_idx, int i, int j, Mat& arr );
1064 int prepare_test_case( int test_case_idx );
1065 void get_test_array_types_and_sizes( int test_case_idx, vector<vector<Size> >& sizes, vector<vector<int> >& types );
1066 double get_success_error_level( int test_case_idx, int i, int j );
1067 void run_func();
1068 void prepare_to_validation( int );
1069
1070 double sampson_error(const double* f, double x1, double y1, double x2, double y2);
1071
1072 int method;
1073 int img_size;
1074 int cube_size;
1075 int dims;
1076 double min_f, max_f;
1077 double sigma;
1078 };
1079
1080
CV_EssentialMatTest()1081 CV_EssentialMatTest::CV_EssentialMatTest()
1082 {
1083 // input arrays:
1084 // 0, 1 - arrays of 2d points that are passed to %func%.
1085 // Can have different data type, layout, be stored in homogeneous coordinates or not.
1086 // 2 - array of 3d points that are projected to both view planes
1087 // 3 - [R|t] matrix for the second view plane (for the first one it is [I|0]
1088 // 4 - intrinsic matrix for both camera
1089 test_array[INPUT].push_back(NULL);
1090 test_array[INPUT].push_back(NULL);
1091 test_array[INPUT].push_back(NULL);
1092 test_array[INPUT].push_back(NULL);
1093 test_array[INPUT].push_back(NULL);
1094 test_array[TEMP].push_back(NULL);
1095 test_array[TEMP].push_back(NULL);
1096 test_array[TEMP].push_back(NULL);
1097 test_array[TEMP].push_back(NULL);
1098 test_array[TEMP].push_back(NULL);
1099 test_array[OUTPUT].push_back(NULL); // Essential Matrix singularity
1100 test_array[OUTPUT].push_back(NULL); // Inliers mask
1101 test_array[OUTPUT].push_back(NULL); // Translation error
1102 test_array[OUTPUT].push_back(NULL); // Positive depth count
1103 test_array[REF_OUTPUT].push_back(NULL);
1104 test_array[REF_OUTPUT].push_back(NULL);
1105 test_array[REF_OUTPUT].push_back(NULL);
1106 test_array[REF_OUTPUT].push_back(NULL);
1107
1108 element_wise_relative_error = false;
1109
1110 method = 0;
1111 img_size = 10;
1112 cube_size = 10;
1113 dims = 0;
1114 min_f = 1;
1115 max_f = 3;
1116 sigma = 0;
1117 }
1118
1119
read_params(CvFileStorage * fs)1120 int CV_EssentialMatTest::read_params( CvFileStorage* fs )
1121 {
1122 int code = cvtest::ArrayTest::read_params( fs );
1123 return code;
1124 }
1125
1126
get_test_array_types_and_sizes(int,vector<vector<Size>> & sizes,vector<vector<int>> & types)1127 void CV_EssentialMatTest::get_test_array_types_and_sizes( int /*test_case_idx*/,
1128 vector<vector<Size> >& sizes, vector<vector<int> >& types )
1129 {
1130 RNG& rng = ts->get_rng();
1131 int pt_depth = cvtest::randInt(rng) % 2 == 0 ? CV_32F : CV_64F;
1132 double pt_count_exp = cvtest::randReal(rng)*6 + 1;
1133 int pt_count = MAX(5, cvRound(exp(pt_count_exp)));
1134
1135 dims = cvtest::randInt(rng) % 2 + 2;
1136 dims = 2;
1137 method = CV_LMEDS << (cvtest::randInt(rng) % 2);
1138
1139 types[INPUT][0] = CV_MAKETYPE(pt_depth, 1);
1140
1141 if( 0 && cvtest::randInt(rng) % 2 )
1142 sizes[INPUT][0] = cvSize(pt_count, dims);
1143 else
1144 {
1145 sizes[INPUT][0] = cvSize(dims, pt_count);
1146 if( cvtest::randInt(rng) % 2 )
1147 {
1148 types[INPUT][0] = CV_MAKETYPE(pt_depth, dims);
1149 if( cvtest::randInt(rng) % 2 )
1150 sizes[INPUT][0] = cvSize(pt_count, 1);
1151 else
1152 sizes[INPUT][0] = cvSize(1, pt_count);
1153 }
1154 }
1155
1156 sizes[INPUT][1] = sizes[INPUT][0];
1157 types[INPUT][1] = types[INPUT][0];
1158
1159 sizes[INPUT][2] = cvSize(pt_count, 1 );
1160 types[INPUT][2] = CV_64FC3;
1161
1162 sizes[INPUT][3] = cvSize(4,3);
1163 types[INPUT][3] = CV_64FC1;
1164
1165 sizes[INPUT][4] = cvSize(3,3);
1166 types[INPUT][4] = CV_MAKETYPE(CV_64F, 1);
1167
1168 sizes[TEMP][0] = cvSize(3,3);
1169 types[TEMP][0] = CV_64FC1;
1170 sizes[TEMP][1] = cvSize(pt_count,1);
1171 types[TEMP][1] = CV_8UC1;
1172 sizes[TEMP][2] = cvSize(3,3);
1173 types[TEMP][2] = CV_64FC1;
1174 sizes[TEMP][3] = cvSize(3, 1);
1175 types[TEMP][3] = CV_64FC1;
1176 sizes[TEMP][4] = cvSize(pt_count,1);
1177 types[TEMP][4] = CV_8UC1;
1178
1179 sizes[OUTPUT][0] = sizes[REF_OUTPUT][0] = cvSize(3,1);
1180 types[OUTPUT][0] = types[REF_OUTPUT][0] = CV_64FC1;
1181 sizes[OUTPUT][1] = sizes[REF_OUTPUT][1] = cvSize(pt_count,1);
1182 types[OUTPUT][1] = types[REF_OUTPUT][1] = CV_8UC1;
1183 sizes[OUTPUT][2] = sizes[REF_OUTPUT][2] = cvSize(1,1);
1184 types[OUTPUT][2] = types[REF_OUTPUT][2] = CV_64FC1;
1185 sizes[OUTPUT][3] = sizes[REF_OUTPUT][3] = cvSize(1,1);
1186 types[OUTPUT][3] = types[REF_OUTPUT][3] = CV_8UC1;
1187
1188 }
1189
1190
get_success_error_level(int,int,int)1191 double CV_EssentialMatTest::get_success_error_level( int /*test_case_idx*/, int /*i*/, int /*j*/ )
1192 {
1193 return 1e-2;
1194 }
1195
1196
fill_array(int test_case_idx,int i,int j,Mat & arr)1197 void CV_EssentialMatTest::fill_array( int test_case_idx, int i, int j, Mat& arr )
1198 {
1199 double t[12]={0};
1200 RNG& rng = ts->get_rng();
1201
1202 if( i != INPUT )
1203 {
1204 cvtest::ArrayTest::fill_array( test_case_idx, i, j, arr );
1205 return;
1206 }
1207
1208 switch( j )
1209 {
1210 case 0:
1211 case 1:
1212 return; // fill them later in prepare_test_case
1213 case 2:
1214 {
1215 double* p = arr.ptr<double>();
1216 for( i = 0; i < arr.cols*3; i += 3 )
1217 {
1218 p[i] = cvtest::randReal(rng)*cube_size;
1219 p[i+1] = cvtest::randReal(rng)*cube_size;
1220 p[i+2] = cvtest::randReal(rng)*cube_size + cube_size;
1221 }
1222 }
1223 break;
1224 case 3:
1225 {
1226 double r[3];
1227 Mat rot_vec( 3, 1, CV_64F, r );
1228 Mat rot_mat( 3, 3, CV_64F, t, 4*sizeof(t[0]) );
1229 r[0] = cvtest::randReal(rng)*CV_PI*2;
1230 r[1] = cvtest::randReal(rng)*CV_PI*2;
1231 r[2] = cvtest::randReal(rng)*CV_PI*2;
1232
1233 cvtest::Rodrigues( rot_vec, rot_mat );
1234 t[3] = cvtest::randReal(rng)*cube_size;
1235 t[7] = cvtest::randReal(rng)*cube_size;
1236 t[11] = cvtest::randReal(rng)*cube_size;
1237 Mat( 3, 4, CV_64F, t ).convertTo(arr, arr.type());
1238 }
1239 break;
1240 case 4:
1241 t[0] = t[4] = cvtest::randReal(rng)*(max_f - min_f) + min_f;
1242 t[2] = (img_size*0.5 + cvtest::randReal(rng)*4. - 2.)*t[0];
1243 t[5] = (img_size*0.5 + cvtest::randReal(rng)*4. - 2.)*t[4];
1244 t[8] = 1.;
1245 Mat( 3, 3, CV_64F, t ).convertTo( arr, arr.type() );
1246 break;
1247 }
1248 }
1249
1250
prepare_test_case(int test_case_idx)1251 int CV_EssentialMatTest::prepare_test_case( int test_case_idx )
1252 {
1253 int code = cvtest::ArrayTest::prepare_test_case( test_case_idx );
1254 if( code > 0 )
1255 {
1256 const Mat& _3d = test_mat[INPUT][2];
1257 RNG& rng = ts->get_rng();
1258 double Idata[] = { 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0 };
1259 Mat I( 3, 4, CV_64F, Idata );
1260 int k;
1261
1262 for( k = 0; k < 2; k++ )
1263 {
1264 const Mat& Rt = k == 0 ? I : test_mat[INPUT][3];
1265 const Mat& A = test_mat[INPUT][4];
1266 Mat& _2d = test_mat[INPUT][k];
1267
1268 test_projectPoints( _3d, Rt, A, _2d, &rng, sigma );
1269 }
1270 }
1271
1272 return code;
1273 }
1274
1275
run_func()1276 void CV_EssentialMatTest::run_func()
1277 {
1278 Mat _input0(test_mat[INPUT][0]), _input1(test_mat[INPUT][1]);
1279 Mat K(test_mat[INPUT][4]);
1280 double focal(K.at<double>(0, 0));
1281 cv::Point2d pp(K.at<double>(0, 2), K.at<double>(1, 2));
1282
1283 RNG& rng = ts->get_rng();
1284 Mat E, mask1(test_mat[TEMP][1]);
1285 E = cv::findEssentialMat( _input0, _input1, focal, pp, method, 0.99, MAX(sigma*3, 0.0001), mask1 );
1286 if (E.rows > 3)
1287 {
1288 int count = E.rows / 3;
1289 int row = (cvtest::randInt(rng) % count) * 3;
1290 E = E.rowRange(row, row + 3) * 1.0;
1291 }
1292
1293 E.copyTo(test_mat[TEMP][0]);
1294
1295 Mat R, t, mask2;
1296 recoverPose( E, _input0, _input1, R, t, focal, pp, mask2 );
1297 R.copyTo(test_mat[TEMP][2]);
1298 t.copyTo(test_mat[TEMP][3]);
1299 mask2.copyTo(test_mat[TEMP][4]);
1300 }
1301
sampson_error(const double * f,double x1,double y1,double x2,double y2)1302 double CV_EssentialMatTest::sampson_error(const double * f, double x1, double y1, double x2, double y2)
1303 {
1304 double Fx1[3] = {
1305 f[0] * x1 + f[1] * y1 + f[2],
1306 f[3] * x1 + f[4] * y1 + f[5],
1307 f[6] * x1 + f[7] * y1 + f[8]
1308 };
1309 double Ftx2[3] = {
1310 f[0] * x2 + f[3] * y2 + f[6],
1311 f[1] * x2 + f[4] * y2 + f[7],
1312 f[2] * x2 + f[5] * y2 + f[8]
1313 };
1314 double x2tFx1 = Fx1[0] * x2 + Fx1[1] * y2 + Fx1[2];
1315
1316 double error = x2tFx1 * x2tFx1 / (Fx1[0] * Fx1[0] + Fx1[1] * Fx1[1] + Ftx2[0] * Ftx2[0] + Ftx2[1] * Ftx2[1]);
1317 error = sqrt(error);
1318 return error;
1319
1320 }
1321
prepare_to_validation(int test_case_idx)1322 void CV_EssentialMatTest::prepare_to_validation( int test_case_idx )
1323 {
1324 const Mat& Rt0 = test_mat[INPUT][3];
1325 const Mat& A = test_mat[INPUT][4];
1326 double f0[9], f[9], e[9];
1327 Mat F0(3, 3, CV_64FC1, f0), F(3, 3, CV_64F, f);
1328 Mat E(3, 3, CV_64F, e);
1329
1330 Mat invA, R=Rt0.colRange(0, 3), T1, T2;
1331
1332 cv::invert(A, invA, CV_SVD);
1333
1334 double tx = Rt0.at<double>(0, 3);
1335 double ty = Rt0.at<double>(1, 3);
1336 double tz = Rt0.at<double>(2, 3);
1337
1338 double _t_x[] = { 0, -tz, ty, tz, 0, -tx, -ty, tx, 0 };
1339
1340 // F = (A2^-T)*[t]_x*R*(A1^-1)
1341 cv::gemm( invA, Mat( 3, 3, CV_64F, _t_x ), 1, Mat(), 0, T1, CV_GEMM_A_T );
1342 cv::gemm( R, invA, 1, Mat(), 0, T2 );
1343 cv::gemm( T1, T2, 1, Mat(), 0, F0 );
1344 F0 *= 1./f0[8];
1345
1346 uchar* status = test_mat[TEMP][1].ptr();
1347 double err_level = get_success_error_level( test_case_idx, OUTPUT, 1 );
1348 uchar* mtfm1 = test_mat[REF_OUTPUT][1].ptr();
1349 uchar* mtfm2 = test_mat[OUTPUT][1].ptr();
1350 double* e_prop1 = test_mat[REF_OUTPUT][0].ptr<double>();
1351 double* e_prop2 = test_mat[OUTPUT][0].ptr<double>();
1352 Mat E_prop2 = Mat(3, 1, CV_64F, e_prop2);
1353
1354 int i, pt_count = test_mat[INPUT][2].cols;
1355 Mat p1( 1, pt_count, CV_64FC2 );
1356 Mat p2( 1, pt_count, CV_64FC2 );
1357
1358 test_convertHomogeneous( test_mat[INPUT][0], p1 );
1359 test_convertHomogeneous( test_mat[INPUT][1], p2 );
1360
1361 cvtest::convert(test_mat[TEMP][0], E, E.type());
1362 cv::gemm( invA, E, 1, Mat(), 0, T1, CV_GEMM_A_T );
1363 cv::gemm( T1, invA, 1, Mat(), 0, F );
1364
1365 for( i = 0; i < pt_count; i++ )
1366 {
1367 double x1 = p1.at<Point2d>(i).x;
1368 double y1 = p1.at<Point2d>(i).y;
1369 double x2 = p2.at<Point2d>(i).x;
1370 double y2 = p2.at<Point2d>(i).y;
1371 // double t0 = sampson_error(f0, x1, y1, x2, y2);
1372 // double t = sampson_error(f, x1, y1, x2, y2);
1373 double n1 = 1./sqrt(x1*x1 + y1*y1 + 1);
1374 double n2 = 1./sqrt(x2*x2 + y2*y2 + 1);
1375 double t0 = fabs(f0[0]*x2*x1 + f0[1]*x2*y1 + f0[2]*x2 +
1376 f0[3]*y2*x1 + f0[4]*y2*y1 + f0[5]*y2 +
1377 f0[6]*x1 + f0[7]*y1 + f0[8])*n1*n2;
1378 double t = fabs(f[0]*x2*x1 + f[1]*x2*y1 + f[2]*x2 +
1379 f[3]*y2*x1 + f[4]*y2*y1 + f[5]*y2 +
1380 f[6]*x1 + f[7]*y1 + f[8])*n1*n2;
1381 mtfm1[i] = 1;
1382 mtfm2[i] = !status[i] || t0 > err_level || t < err_level;
1383 }
1384
1385 e_prop1[0] = sqrt(0.5);
1386 e_prop1[1] = sqrt(0.5);
1387 e_prop1[2] = 0;
1388
1389 e_prop2[0] = 0;
1390 e_prop2[1] = 0;
1391 e_prop2[2] = 0;
1392 SVD::compute(E, E_prop2);
1393
1394
1395
1396 double* pose_prop1 = test_mat[REF_OUTPUT][2].ptr<double>();
1397 double* pose_prop2 = test_mat[OUTPUT][2].ptr<double>();
1398 double terr1 = cvtest::norm(Rt0.col(3) / norm(Rt0.col(3)) + test_mat[TEMP][3], NORM_L2);
1399 double terr2 = cvtest::norm(Rt0.col(3) / norm(Rt0.col(3)) - test_mat[TEMP][3], NORM_L2);
1400 Mat rvec;
1401 Rodrigues(Rt0.colRange(0, 3), rvec);
1402 pose_prop1[0] = 0;
1403 // No check for CV_LMeDS on translation. Since it
1404 // involves with some degraded problem, when data is exact inliers.
1405 pose_prop2[0] = method == CV_LMEDS || pt_count == 5 ? 0 : MIN(terr1, terr2);
1406
1407
1408 // int inliers_count = countNonZero(test_mat[TEMP][1]);
1409 // int good_count = countNonZero(test_mat[TEMP][4]);
1410 test_mat[OUTPUT][3] = true; //good_count >= inliers_count / 2;
1411 test_mat[REF_OUTPUT][3] = true;
1412
1413
1414 }
1415
1416
1417 /********************************** convert homogeneous *********************************/
1418
1419 class CV_ConvertHomogeneousTest : public cvtest::ArrayTest
1420 {
1421 public:
1422 CV_ConvertHomogeneousTest();
1423
1424 protected:
1425 int read_params( CvFileStorage* fs );
1426 void get_test_array_types_and_sizes( int test_case_idx, vector<vector<Size> >& sizes, vector<vector<int> >& types );
1427 void fill_array( int test_case_idx, int i, int j, Mat& arr );
1428 double get_success_error_level( int test_case_idx, int i, int j );
1429 void run_func();
1430 void prepare_to_validation( int );
1431
1432 int dims1, dims2;
1433 int pt_count;
1434 };
1435
1436
CV_ConvertHomogeneousTest()1437 CV_ConvertHomogeneousTest::CV_ConvertHomogeneousTest()
1438 {
1439 test_array[INPUT].push_back(NULL);
1440 test_array[OUTPUT].push_back(NULL);
1441 test_array[REF_OUTPUT].push_back(NULL);
1442 element_wise_relative_error = false;
1443
1444 pt_count = dims1 = dims2 = 0;
1445 }
1446
1447
read_params(CvFileStorage * fs)1448 int CV_ConvertHomogeneousTest::read_params( CvFileStorage* fs )
1449 {
1450 int code = cvtest::ArrayTest::read_params( fs );
1451 return code;
1452 }
1453
1454
get_test_array_types_and_sizes(int,vector<vector<Size>> & sizes,vector<vector<int>> & types)1455 void CV_ConvertHomogeneousTest::get_test_array_types_and_sizes( int /*test_case_idx*/,
1456 vector<vector<Size> >& sizes, vector<vector<int> >& types )
1457 {
1458 RNG& rng = ts->get_rng();
1459 int pt_depth1 = cvtest::randInt(rng) % 2 == 0 ? CV_32F : CV_64F;
1460 int pt_depth2 = cvtest::randInt(rng) % 2 == 0 ? CV_32F : CV_64F;
1461 double pt_count_exp = cvtest::randReal(rng)*6 + 1;
1462 int t;
1463
1464 pt_count = cvRound(exp(pt_count_exp));
1465 pt_count = MAX( pt_count, 5 );
1466
1467 dims1 = 2 + (cvtest::randInt(rng) % 3);
1468 dims2 = 2 + (cvtest::randInt(rng) % 3);
1469
1470 if( dims1 == dims2 + 2 )
1471 dims1--;
1472 else if( dims1 == dims2 - 2 )
1473 dims1++;
1474
1475 if( cvtest::randInt(rng) % 2 )
1476 CV_SWAP( dims1, dims2, t );
1477
1478 types[INPUT][0] = CV_MAKETYPE(pt_depth1, 1);
1479
1480 if( cvtest::randInt(rng) % 2 )
1481 sizes[INPUT][0] = cvSize(pt_count, dims1);
1482 else
1483 {
1484 sizes[INPUT][0] = cvSize(dims1, pt_count);
1485 if( cvtest::randInt(rng) % 2 )
1486 {
1487 types[INPUT][0] = CV_MAKETYPE(pt_depth1, dims1);
1488 if( cvtest::randInt(rng) % 2 )
1489 sizes[INPUT][0] = cvSize(pt_count, 1);
1490 else
1491 sizes[INPUT][0] = cvSize(1, pt_count);
1492 }
1493 }
1494
1495 types[OUTPUT][0] = CV_MAKETYPE(pt_depth2, 1);
1496
1497 if( cvtest::randInt(rng) % 2 )
1498 sizes[OUTPUT][0] = cvSize(pt_count, dims2);
1499 else
1500 {
1501 sizes[OUTPUT][0] = cvSize(dims2, pt_count);
1502 if( cvtest::randInt(rng) % 2 )
1503 {
1504 types[OUTPUT][0] = CV_MAKETYPE(pt_depth2, dims2);
1505 if( cvtest::randInt(rng) % 2 )
1506 sizes[OUTPUT][0] = cvSize(pt_count, 1);
1507 else
1508 sizes[OUTPUT][0] = cvSize(1, pt_count);
1509 }
1510 }
1511
1512 types[REF_OUTPUT][0] = types[OUTPUT][0];
1513 sizes[REF_OUTPUT][0] = sizes[OUTPUT][0];
1514 }
1515
1516
get_success_error_level(int,int,int)1517 double CV_ConvertHomogeneousTest::get_success_error_level( int /*test_case_idx*/, int /*i*/, int /*j*/ )
1518 {
1519 return 1e-5;
1520 }
1521
1522
fill_array(int,int,int,Mat & arr)1523 void CV_ConvertHomogeneousTest::fill_array( int /*test_case_idx*/, int /*i*/, int /*j*/, Mat& arr )
1524 {
1525 Mat temp( 1, pt_count, CV_MAKETYPE(CV_64FC1,dims1) );
1526 RNG& rng = ts->get_rng();
1527 CvScalar low = cvScalarAll(0), high = cvScalarAll(10);
1528
1529 if( dims1 > dims2 )
1530 low.val[dims1-1] = 1.;
1531
1532 cvtest::randUni( rng, temp, low, high );
1533 test_convertHomogeneous( temp, arr );
1534 }
1535
1536
run_func()1537 void CV_ConvertHomogeneousTest::run_func()
1538 {
1539 CvMat _input = test_mat[INPUT][0], _output = test_mat[OUTPUT][0];
1540 cvConvertPointsHomogeneous( &_input, &_output );
1541 }
1542
1543
prepare_to_validation(int)1544 void CV_ConvertHomogeneousTest::prepare_to_validation( int /*test_case_idx*/ )
1545 {
1546 test_convertHomogeneous( test_mat[INPUT][0], test_mat[REF_OUTPUT][0] );
1547 }
1548
1549
1550 /************************** compute corresponding epipolar lines ************************/
1551
1552 class CV_ComputeEpilinesTest : public cvtest::ArrayTest
1553 {
1554 public:
1555 CV_ComputeEpilinesTest();
1556
1557 protected:
1558 int read_params( CvFileStorage* fs );
1559 void get_test_array_types_and_sizes( int test_case_idx, vector<vector<Size> >& sizes, vector<vector<int> >& types );
1560 void fill_array( int test_case_idx, int i, int j, Mat& arr );
1561 double get_success_error_level( int test_case_idx, int i, int j );
1562 void run_func();
1563 void prepare_to_validation( int );
1564
1565 int which_image;
1566 int dims;
1567 int pt_count;
1568 };
1569
1570
CV_ComputeEpilinesTest()1571 CV_ComputeEpilinesTest::CV_ComputeEpilinesTest()
1572 {
1573 test_array[INPUT].push_back(NULL);
1574 test_array[INPUT].push_back(NULL);
1575 test_array[OUTPUT].push_back(NULL);
1576 test_array[REF_OUTPUT].push_back(NULL);
1577 element_wise_relative_error = false;
1578
1579 pt_count = dims = which_image = 0;
1580 }
1581
1582
read_params(CvFileStorage * fs)1583 int CV_ComputeEpilinesTest::read_params( CvFileStorage* fs )
1584 {
1585 int code = cvtest::ArrayTest::read_params( fs );
1586 return code;
1587 }
1588
1589
get_test_array_types_and_sizes(int,vector<vector<Size>> & sizes,vector<vector<int>> & types)1590 void CV_ComputeEpilinesTest::get_test_array_types_and_sizes( int /*test_case_idx*/,
1591 vector<vector<Size> >& sizes, vector<vector<int> >& types )
1592 {
1593 RNG& rng = ts->get_rng();
1594 int fm_depth = cvtest::randInt(rng) % 2 == 0 ? CV_32F : CV_64F;
1595 int pt_depth = cvtest::randInt(rng) % 2 == 0 ? CV_32F : CV_64F;
1596 int ln_depth = cvtest::randInt(rng) % 2 == 0 ? CV_32F : CV_64F;
1597 double pt_count_exp = cvtest::randReal(rng)*6;
1598
1599 which_image = 1 + (cvtest::randInt(rng) % 2);
1600
1601 pt_count = cvRound(exp(pt_count_exp));
1602 pt_count = MAX( pt_count, 1 );
1603 bool few_points = pt_count < 5;
1604
1605 dims = 2 + (cvtest::randInt(rng) % 2);
1606
1607 types[INPUT][0] = CV_MAKETYPE(pt_depth, 1);
1608
1609 if( cvtest::randInt(rng) % 2 && !few_points )
1610 sizes[INPUT][0] = cvSize(pt_count, dims);
1611 else
1612 {
1613 sizes[INPUT][0] = cvSize(dims, pt_count);
1614 if( cvtest::randInt(rng) % 2 || few_points )
1615 {
1616 types[INPUT][0] = CV_MAKETYPE(pt_depth, dims);
1617 if( cvtest::randInt(rng) % 2 )
1618 sizes[INPUT][0] = cvSize(pt_count, 1);
1619 else
1620 sizes[INPUT][0] = cvSize(1, pt_count);
1621 }
1622 }
1623
1624 types[INPUT][1] = CV_MAKETYPE(fm_depth, 1);
1625 sizes[INPUT][1] = cvSize(3, 3);
1626
1627 types[OUTPUT][0] = CV_MAKETYPE(ln_depth, 1);
1628
1629 if( cvtest::randInt(rng) % 2 && !few_points )
1630 sizes[OUTPUT][0] = cvSize(pt_count, 3);
1631 else
1632 {
1633 sizes[OUTPUT][0] = cvSize(3, pt_count);
1634 if( cvtest::randInt(rng) % 2 || few_points )
1635 {
1636 types[OUTPUT][0] = CV_MAKETYPE(ln_depth, 3);
1637 if( cvtest::randInt(rng) % 2 )
1638 sizes[OUTPUT][0] = cvSize(pt_count, 1);
1639 else
1640 sizes[OUTPUT][0] = cvSize(1, pt_count);
1641 }
1642 }
1643
1644 types[REF_OUTPUT][0] = types[OUTPUT][0];
1645 sizes[REF_OUTPUT][0] = sizes[OUTPUT][0];
1646 }
1647
1648
get_success_error_level(int,int,int)1649 double CV_ComputeEpilinesTest::get_success_error_level( int /*test_case_idx*/, int /*i*/, int /*j*/ )
1650 {
1651 return 1e-5;
1652 }
1653
1654
fill_array(int test_case_idx,int i,int j,Mat & arr)1655 void CV_ComputeEpilinesTest::fill_array( int test_case_idx, int i, int j, Mat& arr )
1656 {
1657 RNG& rng = ts->get_rng();
1658
1659 if( i == INPUT && j == 0 )
1660 {
1661 Mat temp( 1, pt_count, CV_MAKETYPE(CV_64FC1,dims) );
1662 cvtest::randUni( rng, temp, cvScalar(0,0,1), cvScalarAll(10) );
1663 test_convertHomogeneous( temp, arr );
1664 }
1665 else if( i == INPUT && j == 1 )
1666 cvtest::randUni( rng, arr, cvScalarAll(0), cvScalarAll(10) );
1667 else
1668 cvtest::ArrayTest::fill_array( test_case_idx, i, j, arr );
1669 }
1670
1671
run_func()1672 void CV_ComputeEpilinesTest::run_func()
1673 {
1674 CvMat _points = test_mat[INPUT][0], _F = test_mat[INPUT][1], _lines = test_mat[OUTPUT][0];
1675 cvComputeCorrespondEpilines( &_points, which_image, &_F, &_lines );
1676 }
1677
1678
prepare_to_validation(int)1679 void CV_ComputeEpilinesTest::prepare_to_validation( int /*test_case_idx*/ )
1680 {
1681 Mat pt( 1, pt_count, CV_MAKETYPE(CV_64F, 3) );
1682 Mat lines( 1, pt_count, CV_MAKETYPE(CV_64F, 3) );
1683 double f[9];
1684 Mat F( 3, 3, CV_64F, f );
1685
1686 test_convertHomogeneous( test_mat[INPUT][0], pt );
1687 test_mat[INPUT][1].convertTo(F, CV_64F);
1688 if( which_image == 2 )
1689 cv::transpose( F, F );
1690
1691 for( int i = 0; i < pt_count; i++ )
1692 {
1693 double* p = pt.ptr<double>() + i*3;
1694 double* l = lines.ptr<double>() + i*3;
1695 double t0 = f[0]*p[0] + f[1]*p[1] + f[2]*p[2];
1696 double t1 = f[3]*p[0] + f[4]*p[1] + f[5]*p[2];
1697 double t2 = f[6]*p[0] + f[7]*p[1] + f[8]*p[2];
1698 double d = sqrt(t0*t0 + t1*t1);
1699 d = d ? 1./d : 1.;
1700 l[0] = t0*d; l[1] = t1*d; l[2] = t2*d;
1701 }
1702
1703 test_convertHomogeneous( lines, test_mat[REF_OUTPUT][0] );
1704 }
1705
TEST(Calib3d_Rodrigues,accuracy)1706 TEST(Calib3d_Rodrigues, accuracy) { CV_RodriguesTest test; test.safe_run(); }
TEST(Calib3d_FindFundamentalMat,accuracy)1707 TEST(Calib3d_FindFundamentalMat, accuracy) { CV_FundamentalMatTest test; test.safe_run(); }
TEST(Calib3d_ConvertHomogeneoous,accuracy)1708 TEST(Calib3d_ConvertHomogeneoous, accuracy) { CV_ConvertHomogeneousTest test; test.safe_run(); }
TEST(Calib3d_ComputeEpilines,accuracy)1709 TEST(Calib3d_ComputeEpilines, accuracy) { CV_ComputeEpilinesTest test; test.safe_run(); }
TEST(Calib3d_FindEssentialMat,accuracy)1710 TEST(Calib3d_FindEssentialMat, accuracy) { CV_EssentialMatTest test; test.safe_run(); }
1711
TEST(Calib3d_FindFundamentalMat,correctMatches)1712 TEST(Calib3d_FindFundamentalMat, correctMatches)
1713 {
1714 double fdata[] = {0, 0, 0, 0, 0, -1, 0, 1, 0};
1715 double p1data[] = {200, 0, 1};
1716 double p2data[] = {170, 0, 1};
1717
1718 Mat F(3, 3, CV_64F, fdata);
1719 Mat p1(1, 1, CV_64FC2, p1data);
1720 Mat p2(1, 1, CV_64FC2, p2data);
1721 Mat np1, np2;
1722
1723 correctMatches(F, p1, p2, np1, np2);
1724
1725 cout << np1 << endl;
1726 cout << np2 << endl;
1727 }
1728
1729 /* End of file. */
1730