1 /*M///////////////////////////////////////////////////////////////////////////////////////
3 // IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING.
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.
10 // Intel License Agreement
11 // For Open Source Computer Vision Library
13 // Copyright (C) 2000, Intel Corporation, all rights reserved.
14 // Third party copyrights are property of their respective owners.
16 // Redistribution and use in source and binary forms, with or without modification,
17 // are permitted provided that the following conditions are met:
19 // * Redistribution's of source code must retain the above copyright notice,
20 // this list of conditions and the following disclaimer.
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.
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.
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.
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
53 const float EVAL_BAD_THRESH = 1.f;
54 const int EVAL_TEXTURELESS_WIDTH = 9;
55 const float EVAL_TEXTURELESS_THRESH = 2.f;
56 const float EVAL_DISP_THRESH = 1.f;
57 const float EVAL_DISP_GAP = 2.f;
58 const int EVAL_DISCONT_WIDTH = 9;
59 const int EVAL_IGNORE_BORDER = 10;
61 const int ERROR_KINDS_COUNT = 6;
63 //============================== quality measuring functions =================================================
66 Calculate textureless regions of image: regions where the squared horizontal intensity gradient averaged over
67 a square window of size=evalTexturelessWidth is below a threshold=evalTexturelessThresh.
69 void computeTexturelessRegions( const Mat& img, Mat& texturelessMask,
70 int texturelessWidth = EVAL_TEXTURELESS_WIDTH, float texturelessThresh = EVAL_TEXTURELESS_THRESH )
73 CV_Error( CV_StsBadArg, "img is empty" );
74 if( img.type() != CV_8UC3 )
75 CV_Error( CV_StsBadArg, "img.type() must be CV_8UC1" );
76 Mat dxI; Sobel( img, dxI, CV_32F, 1, 0, 3 );
77 Mat dxI2; pow( dxI / 8.f, 2, dxI2 );
78 Mat tmp; cvtColor(dxI2, tmp, CV_BGR2GRAY); dxI2 = tmp;
79 Mat avgDxI2; boxFilter( dxI2, avgDxI2, CV_32FC1, Size(texturelessWidth,texturelessWidth) );
80 texturelessMask = avgDxI2 < texturelessThresh;
83 void checkDispMaps( const Mat& disp1, const Mat& disp2 )
85 if( disp1.empty() || disp2.empty() )
86 CV_Error( CV_StsBadArg, "disp1 or disp2 is empty" );
87 if( disp1.depth() != CV_8U || disp2.depth() != CV_8U )
88 CV_Error( CV_StsBadArg, "disp1.depth() and disp2.depth() must be CV_8U" );
89 Size sz = disp1.size();
90 if( disp1.cols != sz.width || disp2.rows != sz.height )
91 CV_Error( CV_StsBadArg, "disp1 and disp2 must have the same size" );
95 Calculate occluded regions of reference image (left image): regions that are occluded in the matching image (right image),
96 i.e., where the forward-mapped disparity lands at a location with a larger (nearer) disparity.
98 void computeOccludedRegions( const Mat& leftDisp, const Mat& rightDisp, Mat& occludedMask,
99 float dispThresh = EVAL_DISP_THRESH )
101 checkDispMaps(leftDisp, rightDisp);
102 Mat grayLeftDisp; cvtColor(leftDisp, grayLeftDisp, CV_BGR2GRAY);
103 Mat grayRightDisp; cvtColor(rightDisp, grayRightDisp, CV_BGR2GRAY);
105 occludedMask.create(leftDisp.size(), CV_8UC1); occludedMask.setTo(Scalar::all(255));
106 for( int leftY = 0; leftY < leftDisp.rows; leftY++ )
107 for( int leftX = 0; leftX < leftDisp.cols; leftX++ )
109 int leftDispVal = grayLeftDisp.at<uchar>(leftY, leftX);
110 int rightX = leftX - leftDispVal, rightY = leftY;
112 occludedMask.at<uchar>(leftY, leftX) = 0;
115 int rightDispVal = grayRightDisp.at<uchar>(rightY, rightX);
116 if( abs(rightDispVal - leftDispVal) > dispThresh )
117 occludedMask.at<uchar>(leftY, leftX) = 0;
123 Calculate depth discontinuty regions: pixels whose neiboring disparities differ by more than
124 dispGap, dilated by window of width discontWidth.
126 void computeDepthDiscontRegions( const Mat& disp, Mat& depthDiscontMask,
127 float dispGap = EVAL_DISP_GAP, int discontWidth = EVAL_DISCONT_WIDTH )
130 CV_Error( CV_StsBadArg, "disp is empty" );
131 if( disp.depth() != CV_8U )
132 CV_Error( CV_StsBadArg, "disp.depth() must be CV_8U" );
133 Mat grayDisp; cvtColor(disp, grayDisp, CV_BGR2GRAY);
134 Mat maxNeighbDisp; dilate(grayDisp, maxNeighbDisp, Mat(3, 3, CV_8UC1, Scalar(1)));
135 Mat minNeighbDisp; erode(grayDisp, minNeighbDisp, Mat(3, 3, CV_8UC1, Scalar(1)));
136 depthDiscontMask = max((Mat)(maxNeighbDisp-grayDisp), (Mat)(grayDisp-minNeighbDisp)) > dispGap;
137 dilate(depthDiscontMask, depthDiscontMask, Mat(discontWidth, discontWidth, CV_8UC1, Scalar(1)));
141 Following functions are for getting evaluation masks excluding a border.
143 Mat borderedAllMask( Size maskSize, int border = EVAL_IGNORE_BORDER )
145 CV_Assert( border >= 0 );
146 Mat mask(maskSize, CV_8UC1, Scalar(0));
147 int w = maskSize.width - 2*border, h = maskSize.height - 2*border;
149 mask.setTo(Scalar(0));
151 mask( Rect(Point(border,border),Size(w,h)) ).setTo(Scalar(255));
155 void checkMask( const Mat& mask )
158 CV_Error( CV_StsBadArg, "mask is empty" );
159 if( mask.type() != CV_8UC1 )
160 CV_Error( CV_StsBadArg, "mask must have CV_8UC1 type" );
163 void checkMasks( const Mat& mask1, const Mat& mask2 )
167 if( mask1.cols != mask2.cols || mask1.rows != mask2.rows )
168 CV_Error( CV_StsBadArg, "mask1 and mask2 must have the same size" );
171 Mat borderedNoOcclMask( const Mat& occludedMask, int border = EVAL_IGNORE_BORDER )
173 checkMask(occludedMask);
174 return ~occludedMask & borderedAllMask(occludedMask.size(), border);
177 Mat borderedOcclMask( const Mat& occludedMask, int border = EVAL_IGNORE_BORDER )
179 checkMask(occludedMask);
180 return occludedMask & borderedAllMask(occludedMask.size(), border);
183 Mat borderedTexturedMask( const Mat& texturelessMask, const Mat& occludedMask, int border = EVAL_IGNORE_BORDER )
185 checkMasks(texturelessMask, occludedMask);
186 return ~texturelessMask & borderedAllMask(occludedMask.size(), border);
189 Mat borderedTexturelessMask( const Mat& texturelessMask, const Mat& occludedMask, int border = EVAL_IGNORE_BORDER )
191 checkMasks(texturelessMask, occludedMask);
192 return texturelessMask & borderedAllMask(occludedMask.size(), border);
195 Mat borderedDepthDiscontMask( const Mat& depthDiscontMask, const Mat& occludedMask, int border = EVAL_IGNORE_BORDER )
197 checkMasks(depthDiscontMask, occludedMask);
198 return depthDiscontMask & borderedAllMask(occludedMask.size(), border);
202 Calculate root-mean-squared error between the computed disparity map (computedDisp) and ground truth map (groundTruthDisp).
204 float dispRMS( const Mat& computedDisp, const Mat& groundTruthDisp, const Mat& mask )
206 checkDispMaps(computedDisp, groundTruthDisp);
207 Mat grayComputedDisp; cvtColor(computedDisp, grayComputedDisp, CV_BGR2GRAY);
208 Mat grayGroundTruthDisp; cvtColor(groundTruthDisp, grayGroundTruthDisp, CV_BGR2GRAY);
210 int pointsCount = mask.empty() ? computedDisp.cols*computedDisp.rows : countNonZero(mask);
211 return 1.f/sqrt((float)pointsCount) * norm(grayComputedDisp, grayGroundTruthDisp, NORM_L2, mask);
215 Calculate percentage of bad matching pixels.
217 float badMatchPxlsPercentage( const Mat& computedDisp, const Mat& groundTruthDisp, const Mat& mask,
218 int badThresh = EVAL_BAD_THRESH )
220 checkDispMaps(computedDisp, groundTruthDisp);
221 Mat grayComputedDisp; cvtColor(computedDisp, grayComputedDisp, CV_BGR2GRAY);
222 Mat grayGroundTruthDisp; cvtColor(groundTruthDisp, grayGroundTruthDisp, CV_BGR2GRAY);
225 absdiff(computedDisp, groundTruthDisp, badPxlsMap);
226 badPxlsMap = badPxlsMap > badThresh;
227 int pointsCount = computedDisp.cols*computedDisp.rows;
230 badPxlsMap = badPxlsMap & mask;
231 pointsCount = countNonZero(mask);
233 return 1.f/pointsCount * countNonZero(badPxlsMap);
237 Calculate root-mean-squared errors for six kinds regions:
238 bordered region, bordered non occluded region, bordered occluded region, bordered textured region,
239 bordered textureless region, bordered depth discontinuty region.
241 void calcRMSs( const Mat& computedDisp, const Mat& groundTruthDisp,
242 const Mat& texturelessMask, const Mat& occludedMask, const Mat& depthDiscontMask,
243 vector<float>& errors )
245 errors.resize(ERROR_KINDS_COUNT);
246 errors[0] = dispRMS( computedDisp, groundTruthDisp, borderedAllMask(computedDisp.size()) );
247 errors[1] = dispRMS( computedDisp, groundTruthDisp, borderedNoOcclMask(occludedMask) );
248 errors[2] = dispRMS( computedDisp, groundTruthDisp, borderedOcclMask(occludedMask) ),
249 errors[3] = dispRMS( computedDisp, groundTruthDisp, borderedTexturedMask(texturelessMask, occludedMask) ),
250 errors[4] = dispRMS( computedDisp, groundTruthDisp, borderedTexturelessMask(texturelessMask, occludedMask) ),
251 errors[5] = dispRMS( computedDisp, groundTruthDisp, borderedDepthDiscontMask(depthDiscontMask, occludedMask) );
255 Calculate percentages of bad matching pixels for six kinds regions:
256 bordered region, bordered non occluded region, bordered occluded region, bordered textured region,
257 bordered textureless region, bordered depth discontinuty region.
259 void calcBadMatchPxlsPercentages( const Mat& computedDisp, const Mat& groundTruthDisp,
260 const Mat& texturelessMask, const Mat& occludedMask, const Mat& depthDiscontMask,
261 vector<float>& errors, int badThresh = EVAL_BAD_THRESH )
263 errors.resize(ERROR_KINDS_COUNT);
264 errors[0] = badMatchPxlsPercentage( computedDisp, groundTruthDisp, borderedAllMask(computedDisp.size()), badThresh ),
265 errors[1] = badMatchPxlsPercentage( computedDisp, groundTruthDisp, borderedNoOcclMask(occludedMask), badThresh ),
266 errors[2] = badMatchPxlsPercentage( computedDisp, groundTruthDisp, borderedOcclMask(occludedMask), badThresh ),
267 errors[3] = badMatchPxlsPercentage( computedDisp, groundTruthDisp, borderedTexturedMask(texturelessMask, occludedMask), badThresh ),
268 errors[4] = badMatchPxlsPercentage( computedDisp, groundTruthDisp, borderedTexturelessMask(texturelessMask, occludedMask), badThresh ),
269 errors[5] = badMatchPxlsPercentage( computedDisp, groundTruthDisp, borderedDepthDiscontMask(depthDiscontMask, occludedMask), badThresh );
272 //===================== regression test for stereo matching algorithms ==============================
274 const string RESULT_DIR = "test_results";
275 const string LEFT_IMG_NAME = "im2.ppm";
276 const string RIGHT_IMG_NAME = "im6.ppm";
277 const string TRUE_LEFT_DISP_NAME = "disp2.pgm";
278 const string TRUE_RIGHT_DISP_NAME = "disp6.pgm";
280 string DATASETS_NAMES[] = { "barn2", "bull", "cones", "poster", "sawtooth", "teddy", "venus" };
281 string ERROR_PREFIXES[] = { "borderedAll",
285 "borderedTextureless",
286 "borderedDepthDiscont" }; // size of ERROR_KINDS_COUNT
288 const string RMS_STR = "RMS";
289 const string BAD_PXLS_PERCENTAGE_STR = "badPxlsPercentage";
291 class CV_StereoMatchingTest : public CvTest
294 CV_StereoMatchingTest(const char* testName, const char* testFuncs) :
295 CvTest( testName, testFuncs ) {}
297 // assumed that left image is a reference image
298 virtual void runStereoMatchingFunc( const Mat& leftImg, const Mat& rigthImg,
299 Mat& leftDisp, Mat& rightDisp ) = 0;
301 void writeErrors( FileStorage fs, const string& errName, const vector<float>& errors );
302 void readErrors( FileNode fn, const string& errName, vector<float>& errors );
303 int compareErrors( const vector<float>& calcErrors, const vector<float>& validErrors,
304 const vector<float>& eps, const string& errName, const string& datasetName );
305 int processStereoMatchingResults( FileStorage& fs, const string& datasetName, bool isWrite,
306 const Mat& leftImg, const Mat& rightImg,
307 const Mat& trueLeftDisp, const Mat& trueRightDisp,
308 const Mat& leftDisp, const Mat& rightDisp );
311 string resultFilename;
314 void CV_StereoMatchingTest::writeErrors( FileStorage fs, const string& errName, const vector<float>& errors )
316 assert( (int)errors.size() == ERROR_KINDS_COUNT );
317 vector<float>::const_iterator it = errors.begin();
318 for( int i = 0; i < ERROR_KINDS_COUNT; i++, ++it )
319 fs << ERROR_PREFIXES[i] + errName << *it;
322 void CV_StereoMatchingTest::readErrors( FileNode fn, const string& errName, vector<float>& errors )
324 errors.resize( ERROR_KINDS_COUNT );
325 vector<float>::iterator it = errors.begin();
326 for( int i = 0; i < ERROR_KINDS_COUNT; i++, ++it )
327 fn[ERROR_PREFIXES[i]+errName] >> *it;
330 int CV_StereoMatchingTest::compareErrors( const vector<float>& calcErrors, const vector<float>& validErrors,
331 const vector<float>& eps, const string& errName, const string& datasetName )
333 assert( (int)calcErrors.size() == ERROR_KINDS_COUNT );
334 assert( (int)validErrors.size() == ERROR_KINDS_COUNT );
335 assert( (int)eps.size() == ERROR_KINDS_COUNT );
336 vector<float>::const_iterator calcIt = calcErrors.begin(),
337 validIt = validErrors.begin(),
339 for( int i = 0; i < ERROR_KINDS_COUNT; i++, ++calcIt, ++validIt, ++epsIt )
340 if( fabs(*calcIt - *validIt) > *epsIt )
342 ts->printf( CvTS::LOG, "bad accuracy of %s on dataset %s",
343 string(ERROR_PREFIXES[i]+errName).c_str(), datasetName.c_str() );
344 return CvTS::FAIL_BAD_ACCURACY;
349 int CV_StereoMatchingTest::processStereoMatchingResults( FileStorage& fs, const string& datasetName, bool isWrite,
350 const Mat& leftImg, const Mat& rightImg,
351 const Mat& trueLeftDisp, const Mat& trueRightDisp,
352 const Mat& leftDisp, const Mat& rightDisp )
354 // rightDisp is not used in current test virsion
356 assert( fs.isOpened() );
357 assert( !datasetName.empty() );
359 // commpute three kinds masks
360 Mat texturelessMask; computeTexturelessRegions( leftImg, texturelessMask );
361 Mat occludedMask; computeOccludedRegions( trueLeftDisp, trueRightDisp, occludedMask );
362 Mat depthDiscontMask; computeDepthDiscontRegions( trueLeftDisp, depthDiscontMask );
363 assert( !texturelessMask.empty() && !occludedMask.empty() && !depthDiscontMask.empty() );
366 vector<float> rmss, badPxlsPercentages;
367 calcRMSs( leftDisp, trueLeftDisp, texturelessMask, occludedMask, depthDiscontMask, rmss );
368 calcBadMatchPxlsPercentages( leftDisp, trueLeftDisp,
369 texturelessMask, occludedMask, depthDiscontMask, badPxlsPercentages );
373 fs << datasetName << "{";
374 writeErrors( fs, RMS_STR, rmss );
375 writeErrors( fs, BAD_PXLS_PERCENTAGE_STR, badPxlsPercentages );
376 fs << "}"; // datasetName
380 FileNode fn = fs.getFirstTopLevelNode()[datasetName];
381 vector<float> validRmss, validBadPxlsPercentages;
383 readErrors( fn, RMS_STR, validRmss );
384 int tempCode = compareErrors( rmss, validRmss,
385 vector<float>(ERROR_KINDS_COUNT, 0.01f), RMS_STR, datasetName );
386 code = tempCode==CvTS::OK ? code : tempCode;
388 readErrors( fn, BAD_PXLS_PERCENTAGE_STR, validBadPxlsPercentages );
389 tempCode = compareErrors( badPxlsPercentages, validBadPxlsPercentages,
390 vector<float>(ERROR_KINDS_COUNT, 0.01f), BAD_PXLS_PERCENTAGE_STR, datasetName );
391 code = tempCode==CvTS::OK ? code : tempCode;
396 void CV_StereoMatchingTest::run(int)
399 if( resultFilename.empty() )
401 ts->printf( CvTS::LOG, "resultFilename is empty" );
402 ts->set_failed_test_info( CvTS::FAIL_BAD_ARG_CHECK );
406 string fullResultFilename = string(ts->get_data_path()) + "stereomatching/" + RESULT_DIR + "/" + resultFilename;
407 bool isWrite = true; // write or compare results
408 FileStorage fs( fullResultFilename, FileStorage::READ );
413 fs.open( fullResultFilename, FileStorage::WRITE );
416 ts->printf( CvTS::LOG, "file named %s can not be read or written", fullResultFilename.c_str() );
417 code = CvTS::FAIL_BAD_ARG_CHECK;
420 fs << "stereo_matching" << "{";
423 int datasetsCount = sizeof(DATASETS_NAMES)/sizeof(DATASETS_NAMES[0]);
424 for( int dsi = 0; dsi < datasetsCount; dsi++)
426 string dataPath = string(ts->get_data_path()) + "stereomatching/datasets/" + DATASETS_NAMES[dsi] + "/";
427 Mat leftImg = imread(dataPath + LEFT_IMG_NAME);
428 Mat rightImg = imread(dataPath + RIGHT_IMG_NAME);
429 Mat trueLeftDisp = imread(dataPath + TRUE_LEFT_DISP_NAME);
430 Mat trueRightDisp = imread(dataPath + TRUE_RIGHT_DISP_NAME);
431 if( leftImg.empty() || rightImg.empty() || trueLeftDisp.empty() || trueRightDisp.empty() )
433 ts->printf( CvTS::LOG, "images or true disparities of dataset %s can not be read", DATASETS_NAMES[dsi].c_str() );
434 code = CvTS::FAIL_INVALID_TEST_DATA;
438 Mat leftDisp, rightDisp;
439 runStereoMatchingFunc( leftImg, rightImg, leftDisp, rightDisp );
441 int tempCode = processStereoMatchingResults( fs, DATASETS_NAMES[dsi], isWrite,
442 leftImg, rightImg, trueLeftDisp, trueRightDisp, leftDisp, rightDisp);
443 code = tempCode==CvTS::OK ? code : tempCode;
447 fs << "}"; // "stereo_matching"
449 ts->set_failed_test_info( code );
452 //CV_StereoMatchingTest stereoMatchingTest;