Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Tetragramm
GitHub Repository: Tetragramm/opencv
Path: blob/master/modules/calib3d/test/test_stereomatching.cpp
16337 views
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
/*
43
This is a regression test for stereo matching algorithms. This test gets some quality metrics
44
described in "A Taxonomy and Evaluation of Dense Two-Frame Stereo Correspondence Algorithms".
45
Daniel Scharstein, Richard Szeliski
46
*/
47
48
#include "test_precomp.hpp"
49
50
namespace opencv_test { namespace {
51
52
const float EVAL_BAD_THRESH = 1.f;
53
const int EVAL_TEXTURELESS_WIDTH = 3;
54
const float EVAL_TEXTURELESS_THRESH = 4.f;
55
const float EVAL_DISP_THRESH = 1.f;
56
const float EVAL_DISP_GAP = 2.f;
57
const int EVAL_DISCONT_WIDTH = 9;
58
const int EVAL_IGNORE_BORDER = 10;
59
60
const int ERROR_KINDS_COUNT = 6;
61
62
//============================== quality measuring functions =================================================
63
64
/*
65
Calculate textureless regions of image (regions where the squared horizontal intensity gradient averaged over
66
a square window of size=evalTexturelessWidth is below a threshold=evalTexturelessThresh) and textured regions.
67
*/
68
void computeTextureBasedMasks( const Mat& _img, Mat* texturelessMask, Mat* texturedMask,
69
int texturelessWidth = EVAL_TEXTURELESS_WIDTH, float texturelessThresh = EVAL_TEXTURELESS_THRESH )
70
{
71
if( !texturelessMask && !texturedMask )
72
return;
73
if( _img.empty() )
74
CV_Error( Error::StsBadArg, "img is empty" );
75
76
Mat img = _img;
77
if( _img.channels() > 1)
78
{
79
Mat tmp; cvtColor( _img, tmp, COLOR_BGR2GRAY ); img = tmp;
80
}
81
Mat dxI; Sobel( img, dxI, CV_32FC1, 1, 0, 3 );
82
Mat dxI2; pow( dxI / 8.f/*normalize*/, 2, dxI2 );
83
Mat avgDxI2; boxFilter( dxI2, avgDxI2, CV_32FC1, Size(texturelessWidth,texturelessWidth) );
84
85
if( texturelessMask )
86
*texturelessMask = avgDxI2 < texturelessThresh;
87
if( texturedMask )
88
*texturedMask = avgDxI2 >= texturelessThresh;
89
}
90
91
void checkTypeAndSizeOfDisp( const Mat& dispMap, const Size* sz )
92
{
93
if( dispMap.empty() )
94
CV_Error( Error::StsBadArg, "dispMap is empty" );
95
if( dispMap.type() != CV_32FC1 )
96
CV_Error( Error::StsBadArg, "dispMap must have CV_32FC1 type" );
97
if( sz && (dispMap.rows != sz->height || dispMap.cols != sz->width) )
98
CV_Error( Error::StsBadArg, "dispMap has incorrect size" );
99
}
100
101
void checkTypeAndSizeOfMask( const Mat& mask, Size sz )
102
{
103
if( mask.empty() )
104
CV_Error( Error::StsBadArg, "mask is empty" );
105
if( mask.type() != CV_8UC1 )
106
CV_Error( Error::StsBadArg, "mask must have CV_8UC1 type" );
107
if( mask.rows != sz.height || mask.cols != sz.width )
108
CV_Error( Error::StsBadArg, "mask has incorrect size" );
109
}
110
111
void checkDispMapsAndUnknDispMasks( const Mat& leftDispMap, const Mat& rightDispMap,
112
const Mat& leftUnknDispMask, const Mat& rightUnknDispMask )
113
{
114
// check type and size of disparity maps
115
checkTypeAndSizeOfDisp( leftDispMap, 0 );
116
if( !rightDispMap.empty() )
117
{
118
Size sz = leftDispMap.size();
119
checkTypeAndSizeOfDisp( rightDispMap, &sz );
120
}
121
122
// check size and type of unknown disparity maps
123
if( !leftUnknDispMask.empty() )
124
checkTypeAndSizeOfMask( leftUnknDispMask, leftDispMap.size() );
125
if( !rightUnknDispMask.empty() )
126
checkTypeAndSizeOfMask( rightUnknDispMask, rightDispMap.size() );
127
128
// check values of disparity maps (known disparity values musy be positive)
129
double leftMinVal = 0, rightMinVal = 0;
130
if( leftUnknDispMask.empty() )
131
minMaxLoc( leftDispMap, &leftMinVal );
132
else
133
minMaxLoc( leftDispMap, &leftMinVal, 0, 0, 0, ~leftUnknDispMask );
134
if( !rightDispMap.empty() )
135
{
136
if( rightUnknDispMask.empty() )
137
minMaxLoc( rightDispMap, &rightMinVal );
138
else
139
minMaxLoc( rightDispMap, &rightMinVal, 0, 0, 0, ~rightUnknDispMask );
140
}
141
if( leftMinVal < 0 || rightMinVal < 0)
142
CV_Error( Error::StsBadArg, "known disparity values must be positive" );
143
}
144
145
/*
146
Calculate occluded regions of reference image (left image) (regions that are occluded in the matching image (right image),
147
i.e., where the forward-mapped disparity lands at a location with a larger (nearer) disparity) and non occluded regions.
148
*/
149
void computeOcclusionBasedMasks( const Mat& leftDisp, const Mat& _rightDisp,
150
Mat* occludedMask, Mat* nonOccludedMask,
151
const Mat& leftUnknDispMask = Mat(), const Mat& rightUnknDispMask = Mat(),
152
float dispThresh = EVAL_DISP_THRESH )
153
{
154
if( !occludedMask && !nonOccludedMask )
155
return;
156
checkDispMapsAndUnknDispMasks( leftDisp, _rightDisp, leftUnknDispMask, rightUnknDispMask );
157
158
Mat rightDisp;
159
if( _rightDisp.empty() )
160
{
161
if( !rightUnknDispMask.empty() )
162
CV_Error( Error::StsBadArg, "rightUnknDispMask must be empty if _rightDisp is empty" );
163
rightDisp.create(leftDisp.size(), CV_32FC1);
164
rightDisp.setTo(Scalar::all(0) );
165
for( int leftY = 0; leftY < leftDisp.rows; leftY++ )
166
{
167
for( int leftX = 0; leftX < leftDisp.cols; leftX++ )
168
{
169
if( !leftUnknDispMask.empty() && leftUnknDispMask.at<uchar>(leftY,leftX) )
170
continue;
171
float leftDispVal = leftDisp.at<float>(leftY, leftX);
172
int rightX = leftX - cvRound(leftDispVal), rightY = leftY;
173
if( rightX >= 0)
174
rightDisp.at<float>(rightY,rightX) = max(rightDisp.at<float>(rightY,rightX), leftDispVal);
175
}
176
}
177
}
178
else
179
_rightDisp.copyTo(rightDisp);
180
181
if( occludedMask )
182
{
183
occludedMask->create(leftDisp.size(), CV_8UC1);
184
occludedMask->setTo(Scalar::all(0) );
185
}
186
if( nonOccludedMask )
187
{
188
nonOccludedMask->create(leftDisp.size(), CV_8UC1);
189
nonOccludedMask->setTo(Scalar::all(0) );
190
}
191
for( int leftY = 0; leftY < leftDisp.rows; leftY++ )
192
{
193
for( int leftX = 0; leftX < leftDisp.cols; leftX++ )
194
{
195
if( !leftUnknDispMask.empty() && leftUnknDispMask.at<uchar>(leftY,leftX) )
196
continue;
197
float leftDispVal = leftDisp.at<float>(leftY, leftX);
198
int rightX = leftX - cvRound(leftDispVal), rightY = leftY;
199
if( rightX < 0 && occludedMask )
200
occludedMask->at<uchar>(leftY, leftX) = 255;
201
else
202
{
203
if( !rightUnknDispMask.empty() && rightUnknDispMask.at<uchar>(rightY,rightX) )
204
continue;
205
float rightDispVal = rightDisp.at<float>(rightY, rightX);
206
if( rightDispVal > leftDispVal + dispThresh )
207
{
208
if( occludedMask )
209
occludedMask->at<uchar>(leftY, leftX) = 255;
210
}
211
else
212
{
213
if( nonOccludedMask )
214
nonOccludedMask->at<uchar>(leftY, leftX) = 255;
215
}
216
}
217
}
218
}
219
}
220
221
/*
222
Calculate depth discontinuty regions: pixels whose neiboring disparities differ by more than
223
dispGap, dilated by window of width discontWidth.
224
*/
225
void computeDepthDiscontMask( const Mat& disp, Mat& depthDiscontMask, const Mat& unknDispMask = Mat(),
226
float dispGap = EVAL_DISP_GAP, int discontWidth = EVAL_DISCONT_WIDTH )
227
{
228
if( disp.empty() )
229
CV_Error( Error::StsBadArg, "disp is empty" );
230
if( disp.type() != CV_32FC1 )
231
CV_Error( Error::StsBadArg, "disp must have CV_32FC1 type" );
232
if( !unknDispMask.empty() )
233
checkTypeAndSizeOfMask( unknDispMask, disp.size() );
234
235
Mat curDisp; disp.copyTo( curDisp );
236
if( !unknDispMask.empty() )
237
curDisp.setTo( Scalar(std::numeric_limits<float>::min()), unknDispMask );
238
Mat maxNeighbDisp; dilate( curDisp, maxNeighbDisp, Mat(3, 3, CV_8UC1, Scalar(1)) );
239
if( !unknDispMask.empty() )
240
curDisp.setTo( Scalar(std::numeric_limits<float>::max()), unknDispMask );
241
Mat minNeighbDisp; erode( curDisp, minNeighbDisp, Mat(3, 3, CV_8UC1, Scalar(1)) );
242
depthDiscontMask = max( (Mat)(maxNeighbDisp-disp), (Mat)(disp-minNeighbDisp) ) > dispGap;
243
if( !unknDispMask.empty() )
244
depthDiscontMask &= ~unknDispMask;
245
dilate( depthDiscontMask, depthDiscontMask, Mat(discontWidth, discontWidth, CV_8UC1, Scalar(1)) );
246
}
247
248
/*
249
Get evaluation masks excluding a border.
250
*/
251
Mat getBorderedMask( Size maskSize, int border = EVAL_IGNORE_BORDER )
252
{
253
CV_Assert( border >= 0 );
254
Mat mask(maskSize, CV_8UC1, Scalar(0));
255
int w = maskSize.width - 2*border, h = maskSize.height - 2*border;
256
if( w < 0 || h < 0 )
257
mask.setTo(Scalar(0));
258
else
259
mask( Rect(Point(border,border),Size(w,h)) ).setTo(Scalar(255));
260
return mask;
261
}
262
263
/*
264
Calculate root-mean-squared error between the computed disparity map (computedDisp) and ground truth map (groundTruthDisp).
265
*/
266
float dispRMS( const Mat& computedDisp, const Mat& groundTruthDisp, const Mat& mask )
267
{
268
checkTypeAndSizeOfDisp( groundTruthDisp, 0 );
269
Size sz = groundTruthDisp.size();
270
checkTypeAndSizeOfDisp( computedDisp, &sz );
271
272
int pointsCount = sz.height*sz.width;
273
if( !mask.empty() )
274
{
275
checkTypeAndSizeOfMask( mask, sz );
276
pointsCount = countNonZero(mask);
277
}
278
return 1.f/sqrt((float)pointsCount) * (float)cvtest::norm(computedDisp, groundTruthDisp, NORM_L2, mask);
279
}
280
281
/*
282
Calculate fraction of bad matching pixels.
283
*/
284
float badMatchPxlsFraction( const Mat& computedDisp, const Mat& groundTruthDisp, const Mat& mask,
285
float _badThresh = EVAL_BAD_THRESH )
286
{
287
int badThresh = cvRound(_badThresh);
288
checkTypeAndSizeOfDisp( groundTruthDisp, 0 );
289
Size sz = groundTruthDisp.size();
290
checkTypeAndSizeOfDisp( computedDisp, &sz );
291
292
Mat badPxlsMap;
293
absdiff( computedDisp, groundTruthDisp, badPxlsMap );
294
badPxlsMap = badPxlsMap > badThresh;
295
int pointsCount = sz.height*sz.width;
296
if( !mask.empty() )
297
{
298
checkTypeAndSizeOfMask( mask, sz );
299
badPxlsMap = badPxlsMap & mask;
300
pointsCount = countNonZero(mask);
301
}
302
return 1.f/pointsCount * countNonZero(badPxlsMap);
303
}
304
305
//===================== regression test for stereo matching algorithms ==============================
306
307
const string ALGORITHMS_DIR = "stereomatching/algorithms/";
308
const string DATASETS_DIR = "stereomatching/datasets/";
309
const string DATASETS_FILE = "datasets.xml";
310
311
const string RUN_PARAMS_FILE = "_params.xml";
312
const string RESULT_FILE = "_res.xml";
313
314
const string LEFT_IMG_NAME = "im2.png";
315
const string RIGHT_IMG_NAME = "im6.png";
316
const string TRUE_LEFT_DISP_NAME = "disp2.png";
317
const string TRUE_RIGHT_DISP_NAME = "disp6.png";
318
319
string ERROR_PREFIXES[] = { "borderedAll",
320
"borderedNoOccl",
321
"borderedOccl",
322
"borderedTextured",
323
"borderedTextureless",
324
"borderedDepthDiscont" }; // size of ERROR_KINDS_COUNT
325
326
string ROI_PREFIXES[] = { "roiX",
327
"roiY",
328
"roiWidth",
329
"roiHeight" };
330
331
332
const string RMS_STR = "RMS";
333
const string BAD_PXLS_FRACTION_STR = "BadPxlsFraction";
334
const string ROI_STR = "ValidDisparityROI";
335
336
class QualityEvalParams
337
{
338
public:
339
QualityEvalParams() { setDefaults(); }
340
QualityEvalParams( int _ignoreBorder )
341
{
342
setDefaults();
343
ignoreBorder = _ignoreBorder;
344
}
345
void setDefaults()
346
{
347
badThresh = EVAL_BAD_THRESH;
348
texturelessWidth = EVAL_TEXTURELESS_WIDTH;
349
texturelessThresh = EVAL_TEXTURELESS_THRESH;
350
dispThresh = EVAL_DISP_THRESH;
351
dispGap = EVAL_DISP_GAP;
352
discontWidth = EVAL_DISCONT_WIDTH;
353
ignoreBorder = EVAL_IGNORE_BORDER;
354
}
355
float badThresh;
356
int texturelessWidth;
357
float texturelessThresh;
358
float dispThresh;
359
float dispGap;
360
int discontWidth;
361
int ignoreBorder;
362
};
363
364
class CV_StereoMatchingTest : public cvtest::BaseTest
365
{
366
public:
367
CV_StereoMatchingTest()
368
{ rmsEps.resize( ERROR_KINDS_COUNT, 0.01f ); fracEps.resize( ERROR_KINDS_COUNT, 1.e-6f ); }
369
protected:
370
// assumed that left image is a reference image
371
virtual int runStereoMatchingAlgorithm( const Mat& leftImg, const Mat& rightImg,
372
Rect& calcROI, Mat& leftDisp, Mat& rightDisp, int caseIdx ) = 0; // return ignored border width
373
374
int readDatasetsParams( FileStorage& fs );
375
virtual int readRunParams( FileStorage& fs );
376
void writeErrors( const string& errName, const vector<float>& errors, FileStorage* fs = 0 );
377
void writeROI( const Rect& calcROI, FileStorage* fs = 0 );
378
void readErrors( FileNode& fn, const string& errName, vector<float>& errors );
379
void readROI( FileNode& fn, Rect& trueROI );
380
int compareErrors( const vector<float>& calcErrors, const vector<float>& validErrors,
381
const vector<float>& eps, const string& errName );
382
int compareROI( const Rect& calcROI, const Rect& validROI );
383
int processStereoMatchingResults( FileStorage& fs, int caseIdx, bool isWrite,
384
const Mat& leftImg, const Mat& rightImg,
385
const Rect& calcROI,
386
const Mat& trueLeftDisp, const Mat& trueRightDisp,
387
const Mat& leftDisp, const Mat& rightDisp,
388
const QualityEvalParams& qualityEvalParams );
389
void run( int );
390
391
vector<float> rmsEps;
392
vector<float> fracEps;
393
394
struct DatasetParams
395
{
396
int dispScaleFactor;
397
int dispUnknVal;
398
};
399
map<string, DatasetParams> datasetsParams;
400
401
vector<string> caseNames;
402
vector<string> caseDatasets;
403
};
404
405
void CV_StereoMatchingTest::run(int)
406
{
407
string dataPath = ts->get_data_path() + "cv/";
408
string algorithmName = name;
409
assert( !algorithmName.empty() );
410
if( dataPath.empty() )
411
{
412
ts->printf( cvtest::TS::LOG, "dataPath is empty" );
413
ts->set_failed_test_info( cvtest::TS::FAIL_BAD_ARG_CHECK );
414
return;
415
}
416
417
FileStorage datasetsFS( dataPath + DATASETS_DIR + DATASETS_FILE, FileStorage::READ );
418
int code = readDatasetsParams( datasetsFS );
419
if( code != cvtest::TS::OK )
420
{
421
ts->set_failed_test_info( code );
422
return;
423
}
424
FileStorage runParamsFS( dataPath + ALGORITHMS_DIR + algorithmName + RUN_PARAMS_FILE, FileStorage::READ );
425
code = readRunParams( runParamsFS );
426
if( code != cvtest::TS::OK )
427
{
428
ts->set_failed_test_info( code );
429
return;
430
}
431
432
string fullResultFilename = dataPath + ALGORITHMS_DIR + algorithmName + RESULT_FILE;
433
FileStorage resFS( fullResultFilename, FileStorage::READ );
434
bool isWrite = true; // write or compare results
435
if( resFS.isOpened() )
436
isWrite = false;
437
else
438
{
439
resFS.open( fullResultFilename, FileStorage::WRITE );
440
if( !resFS.isOpened() )
441
{
442
ts->printf( cvtest::TS::LOG, "file %s can not be read or written\n", fullResultFilename.c_str() );
443
ts->set_failed_test_info( cvtest::TS::FAIL_BAD_ARG_CHECK );
444
return;
445
}
446
resFS << "stereo_matching" << "{";
447
}
448
449
int progress = 0, caseCount = (int)caseNames.size();
450
for( int ci = 0; ci < caseCount; ci++)
451
{
452
progress = update_progress( progress, ci, caseCount, 0 );
453
printf("progress: %d%%\n", progress);
454
fflush(stdout);
455
string datasetName = caseDatasets[ci];
456
string datasetFullDirName = dataPath + DATASETS_DIR + datasetName + "/";
457
Mat leftImg = imread(datasetFullDirName + LEFT_IMG_NAME);
458
Mat rightImg = imread(datasetFullDirName + RIGHT_IMG_NAME);
459
Mat trueLeftDisp = imread(datasetFullDirName + TRUE_LEFT_DISP_NAME, 0);
460
Mat trueRightDisp = imread(datasetFullDirName + TRUE_RIGHT_DISP_NAME, 0);
461
Rect calcROI;
462
463
if( leftImg.empty() || rightImg.empty() || trueLeftDisp.empty() )
464
{
465
ts->printf( cvtest::TS::LOG, "images or left ground-truth disparities of dataset %s can not be read", datasetName.c_str() );
466
code = cvtest::TS::FAIL_INVALID_TEST_DATA;
467
continue;
468
}
469
int dispScaleFactor = datasetsParams[datasetName].dispScaleFactor;
470
Mat tmp;
471
472
trueLeftDisp.convertTo( tmp, CV_32FC1, 1.f/dispScaleFactor );
473
trueLeftDisp = tmp;
474
tmp.release();
475
476
if( !trueRightDisp.empty() )
477
{
478
trueRightDisp.convertTo( tmp, CV_32FC1, 1.f/dispScaleFactor );
479
trueRightDisp = tmp;
480
tmp.release();
481
}
482
483
Mat leftDisp, rightDisp;
484
int ignBorder = max(runStereoMatchingAlgorithm(leftImg, rightImg, calcROI, leftDisp, rightDisp, ci), EVAL_IGNORE_BORDER);
485
486
leftDisp.convertTo( tmp, CV_32FC1 );
487
leftDisp = tmp;
488
tmp.release();
489
490
rightDisp.convertTo( tmp, CV_32FC1 );
491
rightDisp = tmp;
492
tmp.release();
493
494
int tempCode = processStereoMatchingResults( resFS, ci, isWrite,
495
leftImg, rightImg, calcROI, trueLeftDisp, trueRightDisp, leftDisp, rightDisp, QualityEvalParams(ignBorder));
496
code = tempCode==cvtest::TS::OK ? code : tempCode;
497
}
498
499
if( isWrite )
500
resFS << "}"; // "stereo_matching"
501
502
ts->set_failed_test_info( code );
503
}
504
505
void calcErrors( const Mat& leftImg, const Mat& /*rightImg*/,
506
const Mat& trueLeftDisp, const Mat& trueRightDisp,
507
const Mat& trueLeftUnknDispMask, const Mat& trueRightUnknDispMask,
508
const Mat& calcLeftDisp, const Mat& /*calcRightDisp*/,
509
vector<float>& rms, vector<float>& badPxlsFractions,
510
const QualityEvalParams& qualityEvalParams )
511
{
512
Mat texturelessMask, texturedMask;
513
computeTextureBasedMasks( leftImg, &texturelessMask, &texturedMask,
514
qualityEvalParams.texturelessWidth, qualityEvalParams.texturelessThresh );
515
Mat occludedMask, nonOccludedMask;
516
computeOcclusionBasedMasks( trueLeftDisp, trueRightDisp, &occludedMask, &nonOccludedMask,
517
trueLeftUnknDispMask, trueRightUnknDispMask, qualityEvalParams.dispThresh);
518
Mat depthDiscontMask;
519
computeDepthDiscontMask( trueLeftDisp, depthDiscontMask, trueLeftUnknDispMask,
520
qualityEvalParams.dispGap, qualityEvalParams.discontWidth);
521
522
Mat borderedKnownMask = getBorderedMask( leftImg.size(), qualityEvalParams.ignoreBorder ) & ~trueLeftUnknDispMask;
523
524
nonOccludedMask &= borderedKnownMask;
525
occludedMask &= borderedKnownMask;
526
texturedMask &= nonOccludedMask; // & borderedKnownMask
527
texturelessMask &= nonOccludedMask; // & borderedKnownMask
528
depthDiscontMask &= nonOccludedMask; // & borderedKnownMask
529
530
rms.resize(ERROR_KINDS_COUNT);
531
rms[0] = dispRMS( calcLeftDisp, trueLeftDisp, borderedKnownMask );
532
rms[1] = dispRMS( calcLeftDisp, trueLeftDisp, nonOccludedMask );
533
rms[2] = dispRMS( calcLeftDisp, trueLeftDisp, occludedMask );
534
rms[3] = dispRMS( calcLeftDisp, trueLeftDisp, texturedMask );
535
rms[4] = dispRMS( calcLeftDisp, trueLeftDisp, texturelessMask );
536
rms[5] = dispRMS( calcLeftDisp, trueLeftDisp, depthDiscontMask );
537
538
badPxlsFractions.resize(ERROR_KINDS_COUNT);
539
badPxlsFractions[0] = badMatchPxlsFraction( calcLeftDisp, trueLeftDisp, borderedKnownMask, qualityEvalParams.badThresh );
540
badPxlsFractions[1] = badMatchPxlsFraction( calcLeftDisp, trueLeftDisp, nonOccludedMask, qualityEvalParams.badThresh );
541
badPxlsFractions[2] = badMatchPxlsFraction( calcLeftDisp, trueLeftDisp, occludedMask, qualityEvalParams.badThresh );
542
badPxlsFractions[3] = badMatchPxlsFraction( calcLeftDisp, trueLeftDisp, texturedMask, qualityEvalParams.badThresh );
543
badPxlsFractions[4] = badMatchPxlsFraction( calcLeftDisp, trueLeftDisp, texturelessMask, qualityEvalParams.badThresh );
544
badPxlsFractions[5] = badMatchPxlsFraction( calcLeftDisp, trueLeftDisp, depthDiscontMask, qualityEvalParams.badThresh );
545
}
546
547
int CV_StereoMatchingTest::processStereoMatchingResults( FileStorage& fs, int caseIdx, bool isWrite,
548
const Mat& leftImg, const Mat& rightImg,
549
const Rect& calcROI,
550
const Mat& trueLeftDisp, const Mat& trueRightDisp,
551
const Mat& leftDisp, const Mat& rightDisp,
552
const QualityEvalParams& qualityEvalParams )
553
{
554
// rightDisp is not used in current test virsion
555
int code = cvtest::TS::OK;
556
assert( fs.isOpened() );
557
assert( trueLeftDisp.type() == CV_32FC1 );
558
assert( trueRightDisp.empty() || trueRightDisp.type() == CV_32FC1 );
559
assert( leftDisp.type() == CV_32FC1 && (rightDisp.empty() || rightDisp.type() == CV_32FC1) );
560
561
// get masks for unknown ground truth disparity values
562
Mat leftUnknMask, rightUnknMask;
563
DatasetParams params = datasetsParams[caseDatasets[caseIdx]];
564
absdiff( trueLeftDisp, Scalar(params.dispUnknVal), leftUnknMask );
565
leftUnknMask = leftUnknMask < std::numeric_limits<float>::epsilon();
566
assert(leftUnknMask.type() == CV_8UC1);
567
if( !trueRightDisp.empty() )
568
{
569
absdiff( trueRightDisp, Scalar(params.dispUnknVal), rightUnknMask );
570
rightUnknMask = rightUnknMask < std::numeric_limits<float>::epsilon();
571
assert(rightUnknMask.type() == CV_8UC1);
572
}
573
574
// calculate errors
575
vector<float> rmss, badPxlsFractions;
576
calcErrors( leftImg, rightImg, trueLeftDisp, trueRightDisp, leftUnknMask, rightUnknMask,
577
leftDisp, rightDisp, rmss, badPxlsFractions, qualityEvalParams );
578
579
if( isWrite )
580
{
581
fs << caseNames[caseIdx] << "{";
582
fs.writeComment( RMS_STR, 0 );
583
writeErrors( RMS_STR, rmss, &fs );
584
fs.writeComment( BAD_PXLS_FRACTION_STR, 0 );
585
writeErrors( BAD_PXLS_FRACTION_STR, badPxlsFractions, &fs );
586
fs.writeComment( ROI_STR, 0 );
587
writeROI( calcROI, &fs );
588
fs << "}"; // datasetName
589
}
590
else // compare
591
{
592
ts->printf( cvtest::TS::LOG, "\nquality of case named %s\n", caseNames[caseIdx].c_str() );
593
ts->printf( cvtest::TS::LOG, "%s\n", RMS_STR.c_str() );
594
writeErrors( RMS_STR, rmss );
595
ts->printf( cvtest::TS::LOG, "%s\n", BAD_PXLS_FRACTION_STR.c_str() );
596
writeErrors( BAD_PXLS_FRACTION_STR, badPxlsFractions );
597
ts->printf( cvtest::TS::LOG, "%s\n", ROI_STR.c_str() );
598
writeROI( calcROI );
599
600
FileNode fn = fs.getFirstTopLevelNode()[caseNames[caseIdx]];
601
vector<float> validRmss, validBadPxlsFractions;
602
Rect validROI;
603
604
readErrors( fn, RMS_STR, validRmss );
605
readErrors( fn, BAD_PXLS_FRACTION_STR, validBadPxlsFractions );
606
readROI( fn, validROI );
607
int tempCode = compareErrors( rmss, validRmss, rmsEps, RMS_STR );
608
code = tempCode==cvtest::TS::OK ? code : tempCode;
609
tempCode = compareErrors( badPxlsFractions, validBadPxlsFractions, fracEps, BAD_PXLS_FRACTION_STR );
610
code = tempCode==cvtest::TS::OK ? code : tempCode;
611
tempCode = compareROI( calcROI, validROI );
612
code = tempCode==cvtest::TS::OK ? code : tempCode;
613
}
614
return code;
615
}
616
617
int CV_StereoMatchingTest::readDatasetsParams( FileStorage& fs )
618
{
619
if( !fs.isOpened() )
620
{
621
ts->printf( cvtest::TS::LOG, "datasetsParams can not be read " );
622
return cvtest::TS::FAIL_INVALID_TEST_DATA;
623
}
624
datasetsParams.clear();
625
FileNode fn = fs.getFirstTopLevelNode();
626
assert(fn.isSeq());
627
for( int i = 0; i < (int)fn.size(); i+=3 )
628
{
629
String _name = fn[i];
630
DatasetParams params;
631
String sf = fn[i+1]; params.dispScaleFactor = atoi(sf.c_str());
632
String uv = fn[i+2]; params.dispUnknVal = atoi(uv.c_str());
633
datasetsParams[_name] = params;
634
}
635
return cvtest::TS::OK;
636
}
637
638
int CV_StereoMatchingTest::readRunParams( FileStorage& fs )
639
{
640
if( !fs.isOpened() )
641
{
642
ts->printf( cvtest::TS::LOG, "runParams can not be read " );
643
return cvtest::TS::FAIL_INVALID_TEST_DATA;
644
}
645
caseNames.clear();;
646
caseDatasets.clear();
647
return cvtest::TS::OK;
648
}
649
650
void CV_StereoMatchingTest::writeErrors( const string& errName, const vector<float>& errors, FileStorage* fs )
651
{
652
assert( (int)errors.size() == ERROR_KINDS_COUNT );
653
vector<float>::const_iterator it = errors.begin();
654
if( fs )
655
for( int i = 0; i < ERROR_KINDS_COUNT; i++, ++it )
656
*fs << ERROR_PREFIXES[i] + errName << *it;
657
else
658
for( int i = 0; i < ERROR_KINDS_COUNT; i++, ++it )
659
ts->printf( cvtest::TS::LOG, "%s = %f\n", string(ERROR_PREFIXES[i]+errName).c_str(), *it );
660
}
661
662
void CV_StereoMatchingTest::writeROI( const Rect& calcROI, FileStorage* fs )
663
{
664
if( fs )
665
{
666
*fs << ROI_PREFIXES[0] << calcROI.x;
667
*fs << ROI_PREFIXES[1] << calcROI.y;
668
*fs << ROI_PREFIXES[2] << calcROI.width;
669
*fs << ROI_PREFIXES[3] << calcROI.height;
670
}
671
else
672
{
673
ts->printf( cvtest::TS::LOG, "%s = %d\n", ROI_PREFIXES[0].c_str(), calcROI.x );
674
ts->printf( cvtest::TS::LOG, "%s = %d\n", ROI_PREFIXES[1].c_str(), calcROI.y );
675
ts->printf( cvtest::TS::LOG, "%s = %d\n", ROI_PREFIXES[2].c_str(), calcROI.width );
676
ts->printf( cvtest::TS::LOG, "%s = %d\n", ROI_PREFIXES[3].c_str(), calcROI.height );
677
}
678
}
679
680
void CV_StereoMatchingTest::readErrors( FileNode& fn, const string& errName, vector<float>& errors )
681
{
682
errors.resize( ERROR_KINDS_COUNT );
683
vector<float>::iterator it = errors.begin();
684
for( int i = 0; i < ERROR_KINDS_COUNT; i++, ++it )
685
fn[ERROR_PREFIXES[i]+errName] >> *it;
686
}
687
688
void CV_StereoMatchingTest::readROI( FileNode& fn, Rect& validROI )
689
{
690
fn[ROI_PREFIXES[0]] >> validROI.x;
691
fn[ROI_PREFIXES[1]] >> validROI.y;
692
fn[ROI_PREFIXES[2]] >> validROI.width;
693
fn[ROI_PREFIXES[3]] >> validROI.height;
694
}
695
696
int CV_StereoMatchingTest::compareErrors( const vector<float>& calcErrors, const vector<float>& validErrors,
697
const vector<float>& eps, const string& errName )
698
{
699
assert( (int)calcErrors.size() == ERROR_KINDS_COUNT );
700
assert( (int)validErrors.size() == ERROR_KINDS_COUNT );
701
assert( (int)eps.size() == ERROR_KINDS_COUNT );
702
vector<float>::const_iterator calcIt = calcErrors.begin(),
703
validIt = validErrors.begin(),
704
epsIt = eps.begin();
705
bool ok = true;
706
for( int i = 0; i < ERROR_KINDS_COUNT; i++, ++calcIt, ++validIt, ++epsIt )
707
if( *calcIt - *validIt > *epsIt )
708
{
709
ts->printf( cvtest::TS::LOG, "bad accuracy of %s (valid=%f; calc=%f)\n", string(ERROR_PREFIXES[i]+errName).c_str(), *validIt, *calcIt );
710
ok = false;
711
}
712
return ok ? cvtest::TS::OK : cvtest::TS::FAIL_BAD_ACCURACY;
713
}
714
715
int CV_StereoMatchingTest::compareROI( const Rect& calcROI, const Rect& validROI )
716
{
717
int compare[4][2] = {
718
{ calcROI.x, validROI.x },
719
{ calcROI.y, validROI.y },
720
{ calcROI.width, validROI.width },
721
{ calcROI.height, validROI.height },
722
};
723
bool ok = true;
724
for (int i = 0; i < 4; i++)
725
{
726
if (compare[i][0] != compare[i][1])
727
{
728
ts->printf( cvtest::TS::LOG, "bad accuracy of %s (valid=%d; calc=%d)\n", ROI_PREFIXES[i].c_str(), compare[i][1], compare[i][0] );
729
ok = false;
730
}
731
}
732
return ok ? cvtest::TS::OK : cvtest::TS::FAIL_BAD_ACCURACY;
733
}
734
735
//----------------------------------- StereoBM test -----------------------------------------------------
736
737
class CV_StereoBMTest : public CV_StereoMatchingTest
738
{
739
public:
740
CV_StereoBMTest()
741
{
742
name = "stereobm";
743
fill(rmsEps.begin(), rmsEps.end(), 0.4f);
744
fill(fracEps.begin(), fracEps.end(), 0.022f);
745
}
746
747
protected:
748
struct RunParams
749
{
750
int ndisp;
751
int mindisp;
752
int winSize;
753
};
754
vector<RunParams> caseRunParams;
755
756
virtual int readRunParams( FileStorage& fs )
757
{
758
int code = CV_StereoMatchingTest::readRunParams( fs );
759
FileNode fn = fs.getFirstTopLevelNode();
760
assert(fn.isSeq());
761
for( int i = 0; i < (int)fn.size(); i+=5 )
762
{
763
String caseName = fn[i], datasetName = fn[i+1];
764
RunParams params;
765
String ndisp = fn[i+2]; params.ndisp = atoi(ndisp.c_str());
766
String mindisp = fn[i+3]; params.mindisp = atoi(mindisp.c_str());
767
String winSize = fn[i+4]; params.winSize = atoi(winSize.c_str());
768
caseNames.push_back( caseName );
769
caseDatasets.push_back( datasetName );
770
caseRunParams.push_back( params );
771
}
772
return code;
773
}
774
775
virtual int runStereoMatchingAlgorithm( const Mat& _leftImg, const Mat& _rightImg,
776
Rect& calcROI, Mat& leftDisp, Mat& /*rightDisp*/, int caseIdx )
777
{
778
RunParams params = caseRunParams[caseIdx];
779
assert( params.ndisp%16 == 0 );
780
assert( _leftImg.type() == CV_8UC3 && _rightImg.type() == CV_8UC3 );
781
Mat leftImg; cvtColor( _leftImg, leftImg, COLOR_BGR2GRAY );
782
Mat rightImg; cvtColor( _rightImg, rightImg, COLOR_BGR2GRAY );
783
784
Ptr<StereoBM> bm = StereoBM::create( params.ndisp, params.winSize );
785
Mat tempDisp;
786
bm->setMinDisparity(params.mindisp);
787
788
Rect cROI(0, 0, _leftImg.cols, _leftImg.rows);
789
calcROI = getValidDisparityROI(cROI, cROI, params.mindisp, params.ndisp, params.winSize);
790
791
bm->compute( leftImg, rightImg, tempDisp );
792
tempDisp.convertTo(leftDisp, CV_32F, 1./StereoMatcher::DISP_SCALE);
793
794
//check for fixed-type disparity data type
795
Mat_<float> fixedFloatDisp;
796
bm->compute( leftImg, rightImg, fixedFloatDisp );
797
EXPECT_LT(cvtest::norm(fixedFloatDisp, leftDisp, cv::NORM_L2 | cv::NORM_RELATIVE),
798
0.005 + DBL_EPSILON);
799
800
if (params.mindisp != 0)
801
for (int y = 0; y < leftDisp.rows; y++)
802
for (int x = 0; x < leftDisp.cols; x++)
803
{
804
if (leftDisp.at<float>(y, x) < params.mindisp)
805
leftDisp.at<float>(y, x) = -1./StereoMatcher::DISP_SCALE; // treat disparity < mindisp as no disparity
806
}
807
808
return params.winSize/2;
809
}
810
};
811
812
//----------------------------------- StereoSGBM test -----------------------------------------------------
813
814
class CV_StereoSGBMTest : public CV_StereoMatchingTest
815
{
816
public:
817
CV_StereoSGBMTest()
818
{
819
name = "stereosgbm";
820
fill(rmsEps.begin(), rmsEps.end(), 0.25f);
821
fill(fracEps.begin(), fracEps.end(), 0.01f);
822
}
823
824
protected:
825
struct RunParams
826
{
827
int ndisp;
828
int winSize;
829
int mode;
830
};
831
vector<RunParams> caseRunParams;
832
833
virtual int readRunParams( FileStorage& fs )
834
{
835
int code = CV_StereoMatchingTest::readRunParams(fs);
836
FileNode fn = fs.getFirstTopLevelNode();
837
assert(fn.isSeq());
838
for( int i = 0; i < (int)fn.size(); i+=5 )
839
{
840
String caseName = fn[i], datasetName = fn[i+1];
841
RunParams params;
842
String ndisp = fn[i+2]; params.ndisp = atoi(ndisp.c_str());
843
String winSize = fn[i+3]; params.winSize = atoi(winSize.c_str());
844
String mode = fn[i+4]; params.mode = atoi(mode.c_str());
845
caseNames.push_back( caseName );
846
caseDatasets.push_back( datasetName );
847
caseRunParams.push_back( params );
848
}
849
return code;
850
}
851
852
virtual int runStereoMatchingAlgorithm( const Mat& leftImg, const Mat& rightImg,
853
Rect& calcROI, Mat& leftDisp, Mat& /*rightDisp*/, int caseIdx )
854
{
855
RunParams params = caseRunParams[caseIdx];
856
assert( params.ndisp%16 == 0 );
857
Ptr<StereoSGBM> sgbm = StereoSGBM::create( 0, params.ndisp, params.winSize,
858
10*params.winSize*params.winSize,
859
40*params.winSize*params.winSize,
860
1, 63, 10, 100, 32, params.mode );
861
862
Rect cROI(0, 0, leftImg.cols, leftImg.rows);
863
calcROI = getValidDisparityROI(cROI, cROI, 0, params.ndisp, params.winSize);
864
865
sgbm->compute( leftImg, rightImg, leftDisp );
866
CV_Assert( leftDisp.type() == CV_16SC1 );
867
leftDisp/=16;
868
return 0;
869
}
870
};
871
872
873
TEST(Calib3d_StereoBM, regression) { CV_StereoBMTest test; test.safe_run(); }
874
TEST(Calib3d_StereoSGBM, regression) { CV_StereoSGBMTest test; test.safe_run(); }
875
876
TEST(Calib3d_StereoSGBM_HH4, regression)
877
{
878
String path = cvtest::TS::ptr()->get_data_path() + "cv/stereomatching/datasets/teddy/";
879
Mat leftImg = imread(path + "im2.png", 0);
880
ASSERT_FALSE(leftImg.empty());
881
Mat rightImg = imread(path + "im6.png", 0);
882
ASSERT_FALSE(rightImg.empty());
883
Mat testData = imread(path + "disp2_hh4.png",-1);
884
ASSERT_FALSE(testData.empty());
885
Mat leftDisp;
886
Mat toCheck;
887
{
888
Ptr<StereoSGBM> sgbm = StereoSGBM::create( 0, 48, 3, 90, 360, 1, 63, 10, 100, 32, StereoSGBM::MODE_HH4);
889
sgbm->compute( leftImg, rightImg, leftDisp);
890
CV_Assert( leftDisp.type() == CV_16SC1 );
891
leftDisp.convertTo(toCheck, CV_16UC1,1,16);
892
}
893
Mat diff;
894
absdiff(toCheck, testData,diff);
895
CV_Assert( countNonZero(diff)==0);
896
}
897
898
}} // namespace
899
900