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