vendor: OpenCV 5.0.0 snapshot at 40738fb16ceddb5fb3fea747585f7ce6abb0605b

This commit is contained in:
Gitea Mirror Bot
2026-08-22 00:10:33 +08:00
commit f7f077da11
6933 changed files with 2335208 additions and 0 deletions
@@ -0,0 +1,97 @@
///////////////////////////////////////////////////////////////////////////////////////
//
// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING.
//
// By downloading, copying, installing or using the software you agree to this license.
// If you do not agree to this license, do not download, install,
// copy or use the software.
//
//
// License Agreement
// For Open Source Computer Vision Library
//
// Copyright (C) 2010-2012, Institute Of Software Chinese Academy Of Science, all rights reserved.
// Copyright (C) 2010-2012, Advanced Micro Devices, Inc., all rights reserved.
// Third party copyrights are property of their respective owners.
//
// Redistribution and use in source and binary forms, with or without modification,
// are permitted provided that the following conditions are met:
//
// * Redistribution's of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// * Redistribution's in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// * The name of the copyright holders may not be used to endorse or promote products
// derived from this software without specific prior written permission.
//
// This software is provided by the copyright holders and contributors "as is" and
// any express or implied warranties, including, but not limited to, the implied
// warranties of merchantability and fitness for a particular purpose are disclaimed.
// In no event shall the Intel Corporation or contributors be liable for any direct,
// indirect, incidental, special, exemplary, or consequential damages
// (including, but not limited to, procurement of substitute goods or services;
// loss of use, data, or profits; or business interruption) however caused
// and on any theory of liability, whether in contract, strict liability,
// or tort (including negligence or otherwise) arising in any way out of
// the use of this software, even if advised of the possibility of such damage.
//
//M*/
#include "../test_precomp.hpp"
#include "cvconfig.h"
#include "opencv2/ts/ocl_test.hpp"
#ifdef HAVE_OPENCL
namespace opencv_test {
namespace ocl {
PARAM_TEST_CASE(StereoBMFixture, int, int)
{
int n_disp;
int winSize;
Mat left, right, disp;
UMat uleft, uright, udisp;
virtual void SetUp()
{
n_disp = GET_PARAM(0);
winSize = GET_PARAM(1);
left = readImage("gpu/stereobm/aloe-L.png", IMREAD_GRAYSCALE);
right = readImage("gpu/stereobm/aloe-R.png", IMREAD_GRAYSCALE);
ASSERT_FALSE(left.empty());
ASSERT_FALSE(right.empty());
left.copyTo(uleft);
right.copyTo(uright);
}
void Near(double eps = 0.0)
{
EXPECT_MAT_NEAR_RELATIVE(disp, udisp, eps);
}
};
OCL_TEST_P(StereoBMFixture, StereoBM)
{
Ptr<StereoBM> bm = StereoBM::create( n_disp, winSize);
bm->setPreFilterType(bm->PREFILTER_XSOBEL);
bm->setTextureThreshold(0);
OCL_OFF(bm->compute(left, right, disp));
OCL_ON(bm->compute(uleft, uright, udisp));
Near(1e-3);
}
OCL_INSTANTIATE_TEST_CASE_P(StereoMatcher, StereoBMFixture, testing::Combine(testing::Values(32, 64, 128),
testing::Values(11, 21)));
}//ocl
}//cvtest
#endif //HAVE_OPENCL
+395
View File
@@ -0,0 +1,395 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html.
#include "test_precomp.hpp"
#include <opencv2/ts/cuda_test.hpp> // EXPECT_MAT_NEAR
#include "opencv2/geometry.hpp"
#include <opencv2/core/utils/logger.hpp>
namespace opencv_test { namespace {
static bool checkPandROI(const Matx33d& M, const Matx<double, 5, 1>& D,
const Mat& R, const Mat& P, Size imgsize, Rect roi)
{
const double eps = 0.05;
const int N = 21;
int x, y, k;
vector<Point2f> pts, upts;
// step 1. check that all the original points belong to the destination image
for( y = 0; y < N; y++ )
for( x = 0; x < N; x++ )
pts.push_back(Point2f((float)x*imgsize.width/(N-1), (float)y*imgsize.height/(N-1)));
undistortPoints(pts, upts, M, D, R, P );
for( k = 0; k < N*N; k++ )
if( upts[k].x < -imgsize.width*eps || upts[k].x > imgsize.width*(1+eps) ||
upts[k].y < -imgsize.height*eps || upts[k].y > imgsize.height*(1+eps) )
{
CV_LOG_ERROR(NULL, cv::format("The point (%g, %g) was mapped to (%g, %g) which is out of image\n",
pts[k].x, pts[k].y, upts[k].x, upts[k].y));
return false;
}
// step 2. check that all the points inside ROI belong to the original source image
Mat temp(imgsize, CV_8U), utemp, map1, map2;
temp = Scalar::all(1);
initUndistortRectifyMap(M, D, R, P, imgsize, CV_16SC2, map1, map2);
remap(temp, utemp, map1, map2, INTER_LINEAR);
if(roi.x < 0 || roi.y < 0 || roi.x + roi.width > imgsize.width || roi.y + roi.height > imgsize.height)
{
CV_LOG_ERROR(NULL, cv::format("The ROI=(%d, %d, %d, %d) is outside of the imge rectangle\n",
roi.x, roi.y, roi.width, roi.height));
return false;
}
double s = sum(utemp(roi))[0];
if( s > roi.area() || roi.area() - s > roi.area()*(1-eps) )
{
CV_LOG_ERROR(NULL, cv::format("The ratio of black pixels inside the valid ROI (~%g%%) is too large\n",
s*100./roi.area()));
return false;
}
return true;
}
TEST(StereoGeometry, stereoRectify)
{
// camera parameters are extracted from the original calib3d test CV_StereoCalibrationTest::run
const Matx33d M1(
530.4643719672913, 0, 319.5,
0, 529.7477570329314, 239.5,
0, 0, 1);
const Matx<double, 5, 1> D1(-0.2982901576925627, 0.1134645765152131, 0, 0, 0);
const Matx33d M2(
530.4643719672913, 0, 319.5,
0, 529.7477570329314, 239.5,
0, 0, 1);
const Matx<double, 5, 1> D2(-0.2833068597502156, 0.0944810713984697, 0, 0, 0);
const Matx33d R(0.9996903750450727, 0.005330951201286465, -0.02430504066096785,
-0.004837810799471072, 0.9997821583334892, 0.02030348405319902,
0.02440798289310936, -0.02017961439967296, 0.9994983909610711);
const Matx31d T(-3.328706469151101, 0.05621025406095936, -0.02956576727262086);
const Size imageSize(640, 480);
Mat R1, R2, P1, P2, Q;
Rect roi1, roi2;
stereoRectify( M1, D1, M2, D2, imageSize, R, T, R1, R2, P1, P2, Q, 0, 1, imageSize, &roi1, &roi2 );
Mat eye33 = Mat::eye(3,3,CV_64F);
Mat R1t = R1.t(), R2t = R2.t();
EXPECT_LE(cvtest::norm(R1t*R1 - eye33, NORM_L2), 0.01) << "R1 is not orthogonal!";
EXPECT_LE(cvtest::norm(R2t*R2 - eye33, NORM_L2), 0.01) << "R2 is not orthogonal!";
//check that Tx after rectification is equal to distance between cameras
double tx = fabs(P2.at<double>(0, 3) / P2.at<double>(0, 0));
EXPECT_LE(fabs(tx - cvtest::norm(T, NORM_L2)), 1e-5);
EXPECT_TRUE(checkPandROI(M1, D1, R1, P1, imageSize, roi1));
EXPECT_TRUE(checkPandROI(M2, D2, R2, P2, imageSize, roi2));
//check that Q reprojects points before the camera
double testPoint[4] = {0.0, 0.0, 100.0, 1.0};
Mat reprojectedTestPoint = Q * Mat_<double>(4, 1, testPoint);
CV_Assert(reprojectedTestPoint.type() == CV_64FC1);
EXPECT_GT( reprojectedTestPoint.at<double>(2) / reprojectedTestPoint.at<double>(3), 0 ) << \
"A point after rectification is reprojected behind the camera";
}
TEST(StereoGeometry, regression_10791)
{
const Matx33d M1(
853.1387981631528, 0, 704.154907802121,
0, 853.6445089162528, 520.3600712930319,
0, 0, 1
);
const Matx33d M2(
848.6090216909176, 0, 701.6162856852185,
0, 849.7040162357157, 509.1864036137,
0, 0, 1
);
const Matx<double, 14, 1> D1(-6.463598629567206, 79.00104930508179, -0.0001006144444464403, -0.0005437499822299972,
12.56900616588467, -6.056719942752855, 76.3842481414836, 45.57460250612659,
0, 0, 0, 0, 0, 0);
const Matx<double, 14, 1> D2(0.6123436439798265, -0.4671756923224087, -0.0001261947899033442, -0.000597334584036978,
-0.05660119809538371, 1.037075740629769, -0.3076042835831711, -0.2502169324283623,
0, 0, 0, 0, 0, 0);
const Matx33d R(
0.9999926627018476, -0.0001095586963765905, 0.003829169539302921,
0.0001021735876758584, 0.9999981346680941, 0.0019287874145156,
-0.003829373712065528, -0.001928382022437616, 0.9999908085776333
);
const Matx31d T(-58.9161771697128, -0.01581306249996402, -0.8492960216760961);
const Size imageSize(1280, 960);
Mat R1, R2, P1, P2, Q;
Rect roi1, roi2;
stereoRectify(M1, D1, M2, D2, imageSize, R, T,
R1, R2, P1, P2, Q,
STEREO_ZERO_DISPARITY, 1, imageSize, &roi1, &roi2);
EXPECT_GE(roi1.area(), 400*300) << roi1;
EXPECT_GE(roi2.area(), 400*300) << roi2;
}
TEST(StereoGeometry, regression_11131)
{
const Matx33d M1(
1457.572438721727, 0, 1212.945694211622,
0, 1457.522226502963, 1007.32058848921,
0, 0, 1
);
const Matx33d M2(
1460.868570835972, 0, 1215.024068023046,
0, 1460.791367088, 1011.107202932225,
0, 0, 1
);
const Matx<double, 5, 1> D1(0, 0, 0, 0, 0);
const Matx<double, 5, 1> D2(0, 0, 0, 0, 0);
const Matx33d R(
0.9985404059825475, 0.02963547172078553, -0.04515303352041626,
-0.03103795276460111, 0.9990471552537432, -0.03068268351343364,
0.04420071389006859, 0.03203935697372317, 0.9985087763742083
);
const Matx31d T(0.9995500167379527, 0.0116311595111068, 0.02764923448462666);
const Size imageSize(2456, 2058);
Mat R1, R2, P1, P2, Q;
Rect roi1, roi2;
stereoRectify(M1, D1, M2, D2, imageSize, R, T,
R1, R2, P1, P2, Q,
STEREO_ZERO_DISPARITY, 1, imageSize, &roi1, &roi2);
EXPECT_GT(P1.at<double>(0, 0), 0);
EXPECT_GT(P2.at<double>(0, 0), 0);
EXPECT_GT(R1.at<double>(0, 0), 0);
EXPECT_GT(R2.at<double>(0, 0), 0);
EXPECT_GE(roi1.area(), 400*300) << roi1;
EXPECT_GE(roi2.area(), 400*300) << roi2;
}
TEST(StereoGeometry, regression_23305)
{
const Matx33d M1(
850, 0, 640,
0, 850, 640,
0, 0, 1
);
const Matx34d P1_gold(
850, 0, 640, 0,
0, 850, 640, 0,
0, 0, 1, 0
);
const Matx33d M2(
850, 0, 640,
0, 850, 640,
0, 0, 1
);
const Matx34d P2_gold(
850, 0, 640, -2*850, // correcponds to T(-2., 0., 0.)
0, 850, 640, 0,
0, 0, 1, 0
);
const Matx<double, 5, 1> D1(0, 0, 0, 0, 0);
const Matx<double, 5, 1> D2(0, 0, 0, 0, 0);
const Matx33d R(
1., 0., 0.,
0., 1., 0.,
0., 0., 1.
);
const Matx31d T(-2., 0., 0.);
const Size imageSize(1280, 1280);
Mat R1, R2, P1, P2, Q;
Rect roi1, roi2;
stereoRectify(M1, D1, M2, D2, imageSize, R, T,
R1, R2, P1, P2, Q,
STEREO_ZERO_DISPARITY, 0, imageSize, &roi1, &roi2);
EXPECT_EQ(cv::norm(P1, P1_gold), 0.);
EXPECT_EQ(cv::norm(P2, P2_gold), 0.);
}
class fisheyeTest : public ::testing::Test {
protected:
const static cv::Size imageSize;
const static cv::Matx33d K;
const static cv::Vec4d D;
const static cv::Matx33d R;
const static cv::Vec3d T;
std::string datasets_repository_path;
virtual void SetUp() {
datasets_repository_path = combine(cvtest::TS::ptr()->get_data_path(), "cv/cameracalibration/fisheye");
}
protected:
std::string combine(const std::string& _item1, const std::string& _item2);
static void merge4(const cv::Mat& tl, const cv::Mat& tr, const cv::Mat& bl, const cv::Mat& br, cv::Mat& merged);
};
const cv::Size fisheyeTest::imageSize(1280, 800);
const cv::Matx33d fisheyeTest::K(558.478087865323, 0, 620.458515360843,
0, 560.506767351568, 381.939424848348,
0, 0, 1);
const cv::Vec4d fisheyeTest::D(-0.0014613319981768, -0.00329861110580401, 0.00605760088590183, -0.00374209380722371);
const cv::Matx33d fisheyeTest::R ( 9.9756700084424932e-01, 6.9698277640183867e-02, 1.4929569991321144e-03,
-6.9711825162322980e-02, 9.9748249845531767e-01, 1.2997180766418455e-02,
-5.8331736398316541e-04,-1.3069635393884985e-02, 9.9991441852366736e-01);
const cv::Vec3d fisheyeTest::T(-9.9217369356044638e-02, 3.1741831972356663e-03, 1.8551007952921010e-04);
std::string fisheyeTest::combine(const std::string& _item1, const std::string& _item2)
{
std::string item1 = _item1, item2 = _item2;
std::replace(item1.begin(), item1.end(), '\\', '/');
std::replace(item2.begin(), item2.end(), '\\', '/');
if (item1.empty())
return item2;
if (item2.empty())
return item1;
char last = item1[item1.size()-1];
return item1 + (last != '/' ? "/" : "") + item2;
}
void fisheyeTest::merge4(const cv::Mat& tl, const cv::Mat& tr, const cv::Mat& bl, const cv::Mat& br, cv::Mat& merged)
{
int type = tl.type();
cv::Size sz = tl.size();
ASSERT_EQ(type, tr.type()); ASSERT_EQ(type, bl.type()); ASSERT_EQ(type, br.type());
ASSERT_EQ(sz.width, tr.cols); ASSERT_EQ(sz.width, bl.cols); ASSERT_EQ(sz.width, br.cols);
ASSERT_EQ(sz.height, tr.rows); ASSERT_EQ(sz.height, bl.rows); ASSERT_EQ(sz.height, br.rows);
merged.create(cv::Size(sz.width * 2, sz.height * 2), type);
tl.copyTo(merged(cv::Rect(0, 0, sz.width, sz.height)));
tr.copyTo(merged(cv::Rect(sz.width, 0, sz.width, sz.height)));
bl.copyTo(merged(cv::Rect(0, sz.height, sz.width, sz.height)));
br.copyTo(merged(cv::Rect(sz.width, sz.height, sz.width, sz.height)));
}
TEST_F(fisheyeTest, stereoRectify)
{
const std::string folder = combine(datasets_repository_path, "calib-3_stereo_from_JY");
cv::Size calibration_size = this->imageSize, requested_size = calibration_size;
cv::Matx33d K1 = this->K, K2 = K1;
cv::Mat D1 = cv::Mat(this->D), D2 = D1;
cv::Vec3d theT = this->T;
cv::Matx33d theR = this->R;
double balance = 0.0, fov_scale = 1.1;
cv::Mat R1, R2, P1, P2, Q;
cv::fisheye::stereoRectify(K1, D1, K2, D2, calibration_size, theR, theT, R1, R2, P1, P2, Q,
cv::STEREO_ZERO_DISPARITY, requested_size, balance, fov_scale);
// Collected with these CMake flags: -DWITH_IPP=OFF -DCV_ENABLE_INTRINSICS=OFF -DCV_DISABLE_OPTIMIZATION=ON -DCMAKE_BUILD_TYPE=Debug
cv::Matx33d R1_ref(
0.9992853269091279, 0.03779164101000276, -0.0007920188690205426,
-0.03778569762983931, 0.9992646472015868, 0.006511981857667881,
0.001037534936357442, -0.006477400933964018, 0.9999784831677112
);
cv::Matx33d R2_ref(
0.9994868963898833, -0.03197579751378937, -0.001868774538573449,
0.03196298186616116, 0.9994677442608699, -0.0065265589947392,
0.002076471801477729, 0.006463478587068991, 0.9999769555891836
);
cv::Matx34d P1_ref(
420.9684016542647, 0, 586.3059567784627, 0,
0, 420.9684016542647, 374.8571836462291, 0,
0, 0, 1, 0
);
cv::Matx34d P2_ref(
420.9684016542647, 0, 586.3059567784627, -41.78881938824554,
0, 420.9684016542647, 374.8571836462291, 0,
0, 0, 1, 0
);
cv::Matx44d Q_ref(
1, 0, 0, -586.3059567784627,
0, 1, 0, -374.8571836462291,
0, 0, 0, 420.9684016542647,
0, 0, 10.07370889670733, -0
);
const double eps = 1e-10;
EXPECT_MAT_NEAR(R1_ref, R1, eps);
EXPECT_MAT_NEAR(R2_ref, R2, eps);
EXPECT_MAT_NEAR(P1_ref, P1, eps);
EXPECT_MAT_NEAR(P2_ref, P2, eps);
EXPECT_MAT_NEAR(Q_ref, Q, eps);
if (::testing::Test::HasFailure())
{
std::cout << "Actual values are:" << std::endl
<< "R1 =" << std::endl << R1 << std::endl
<< "R2 =" << std::endl << R2 << std::endl
<< "P1 =" << std::endl << P1 << std::endl
<< "P2 =" << std::endl << P2 << std::endl
<< "Q =" << std::endl << Q << std::endl;
}
if (cvtest::debugLevel == 0)
return;
// DEBUG code is below
cv::Mat lmapx, lmapy, rmapx, rmapy;
//rewrite for fisheye
cv::fisheye::initUndistortRectifyMap(K1, D1, R1, P1, requested_size, CV_32F, lmapx, lmapy);
cv::fisheye::initUndistortRectifyMap(K2, D2, R2, P2, requested_size, CV_32F, rmapx, rmapy);
cv::Mat l, r, lundist, rundist;
for (int i = 0; i < 34; ++i)
{
SCOPED_TRACE(cv::format("image %d", i));
l = imread(combine(folder, cv::format("left/stereo_pair_%03d.jpg", i)), cv::IMREAD_COLOR);
r = imread(combine(folder, cv::format("right/stereo_pair_%03d.jpg", i)), cv::IMREAD_COLOR);
ASSERT_FALSE(l.empty());
ASSERT_FALSE(r.empty());
int ndisp = 128;
cv::rectangle(l, cv::Rect(255, 0, 829, l.rows-1), cv::Scalar(0, 0, 255));
cv::rectangle(r, cv::Rect(255, 0, 829, l.rows-1), cv::Scalar(0, 0, 255));
cv::rectangle(r, cv::Rect(255-ndisp, 0, 829+ndisp ,l.rows-1), cv::Scalar(0, 0, 255));
cv::remap(l, lundist, lmapx, lmapy, cv::INTER_LINEAR);
cv::remap(r, rundist, rmapx, rmapy, cv::INTER_LINEAR);
for (int ii = 0; ii < lundist.rows; ii += 20)
{
cv::line(lundist, cv::Point(0, ii), cv::Point(lundist.cols, ii), cv::Scalar(0, 255, 0));
cv::line(rundist, cv::Point(0, ii), cv::Point(lundist.cols, ii), cv::Scalar(0, 255, 0));
}
cv::Mat rectification;
merge4(l, r, lundist, rundist, rectification);
// Add the "--test_debug" to arguments for file output
if (cvtest::debugLevel > 0)
cv::imwrite(cv::format("fisheye_rectification_AB_%03d.png", i), rectification);
}
}
}}
+10
View File
@@ -0,0 +1,10 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html.
#include "test_precomp.hpp"
#if defined(HAVE_HPX)
#include <hpx/hpx_main.hpp>
#endif
CV_TEST_MAIN("")
+13
View File
@@ -0,0 +1,13 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html.
#ifndef __OPENCV_TEST_PRECOMP_HPP__
#define __OPENCV_TEST_PRECOMP_HPP__
#include <functional>
#include <numeric>
#include "opencv2/ts.hpp"
#include "opencv2/stereo.hpp"
#endif
@@ -0,0 +1,173 @@
/*M///////////////////////////////////////////////////////////////////////////////////////
//
// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING.
//
// By downloading, copying, installing or using the software you agree to this license.
// If you do not agree to this license, do not download, install,
// copy or use the software.
//
//
// License Agreement
// For Open Source Computer Vision Library
//
// Copyright (C) 2000-2008, Intel Corporation, all rights reserved.
// Copyright (C) 2009, Willow Garage Inc., all rights reserved.
// Third party copyrights are property of their respective owners.
//
// Redistribution and use in source and binary forms, with or without modification,
// are permitted provided that the following conditions are met:
//
// * Redistribution's of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// * Redistribution's in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// * The name of the copyright holders may not be used to endorse or promote products
// derived from this software without specific prior written permission.
//
// This software is provided by the copyright holders and contributors "as is" and
// any express or implied warranties, including, but not limited to, the implied
// warranties of merchantability and fitness for a particular purpose are disclaimed.
// In no event shall the Intel Corporation or contributors be liable for any direct,
// indirect, incidental, special, exemplary, or consequential damages
// (including, but not limited to, procurement of substitute goods or services;
// loss of use, data, or profits; or business interruption) however caused
// and on any theory of liability, whether in contract, strict liability,
// or tort (including negligence or otherwise) arising in any way out of
// the use of this software, even if advised of the possibility of such damage.
//
//M*/
#include "test_precomp.hpp"
namespace opencv_test { namespace {
template<class T> double thres() { return 1.0; }
template<> double thres<float>() { return 1e-5; }
class CV_ReprojectImageTo3DTest : public cvtest::BaseTest
{
public:
CV_ReprojectImageTo3DTest() {}
~CV_ReprojectImageTo3DTest() {}
protected:
void run(int)
{
ts->set_failed_test_info(cvtest::TS::OK);
int progress = 0;
int caseId = 0;
progress = update_progress( progress, 1, 14, 0 );
runCase<float, float>(++caseId, -100.f, 100.f);
progress = update_progress( progress, 2, 14, 0 );
runCase<int, float>(++caseId, -100, 100);
progress = update_progress( progress, 3, 14, 0 );
runCase<short, float>(++caseId, -100, 100);
progress = update_progress( progress, 4, 14, 0 );
runCase<unsigned char, float>(++caseId, 10, 100);
progress = update_progress( progress, 5, 14, 0 );
runCase<float, int>(++caseId, -100.f, 100.f);
progress = update_progress( progress, 6, 14, 0 );
runCase<int, int>(++caseId, -100, 100);
progress = update_progress( progress, 7, 14, 0 );
runCase<short, int>(++caseId, -100, 100);
progress = update_progress( progress, 8, 14, 0 );
runCase<unsigned char, int>(++caseId, 10, 100);
progress = update_progress( progress, 10, 14, 0 );
runCase<float, short>(++caseId, -100.f, 100.f);
progress = update_progress( progress, 11, 14, 0 );
runCase<int, short>(++caseId, -100, 100);
progress = update_progress( progress, 12, 14, 0 );
runCase<short, short>(++caseId, -100, 100);
progress = update_progress( progress, 13, 14, 0 );
runCase<unsigned char, short>(++caseId, 10, 100);
progress = update_progress( progress, 14, 14, 0 );
}
template<class U, class V> double error(const Vec<U, 3>& v1, const Vec<V, 3>& v2) const
{
double tmp, sum = 0;
double nsum = 0;
for(int i = 0; i < 3; ++i)
{
tmp = v1[i];
nsum += tmp * tmp;
tmp = tmp - v2[i];
sum += tmp * tmp;
}
return sqrt(sum)/(sqrt(nsum)+1.);
}
template<class InT, class OutT> void runCase(int caseId, InT min, InT max)
{
typedef Vec<OutT, 3> out3d_t;
bool handleMissingValues = (unsigned)theRNG() % 2 == 0;
Mat_<InT> disp(Size(320, 240));
randu(disp, Scalar(min), Scalar(max));
if (handleMissingValues)
disp(disp.rows/2, disp.cols/2) = min - 1;
Mat_<double> Q(4, 4);
randu(Q, Scalar(-5), Scalar(5));
Mat_<out3d_t> _3dImg(disp.size());
reprojectImageTo3D(disp, _3dImg, Q, handleMissingValues);
for(int y = 0; y < disp.rows; ++y)
for(int x = 0; x < disp.cols; ++x)
{
InT d = disp(y, x);
double from[4] = {
static_cast<double>(x),
static_cast<double>(y),
static_cast<double>(d),
1.0,
};
Mat_<double> res = Q * Mat_<double>(4, 1, from);
res /= res(3, 0);
out3d_t pixel_exp = *res.ptr<Vec3d>();
out3d_t pixel_out = _3dImg(y, x);
const int largeZValue = 10000; /* see documentation */
if (handleMissingValues && y == disp.rows/2 && x == disp.cols/2)
{
if (pixel_out[2] == largeZValue)
continue;
ts->printf(cvtest::TS::LOG, "Missing values are handled improperly\n");
ts->set_failed_test_info( cvtest::TS::FAIL_BAD_ACCURACY );
return;
}
else
{
double err = error(pixel_out, pixel_exp), t = thres<OutT>();
if ( err > t )
{
ts->printf(cvtest::TS::LOG, "case %d. too big error at (%d, %d): %g vs expected %g: res = (%g, %g, %g, w=%g) vs pixel_out = (%g, %g, %g)\n",
caseId, x, y, err, t, res(0,0), res(1,0), res(2,0), res(3,0),
(double)pixel_out[0], (double)pixel_out[1], (double)pixel_out[2]);
ts->set_failed_test_info( cvtest::TS::FAIL_BAD_ACCURACY );
return;
}
}
}
}
};
TEST(Calib3d_ReprojectImageTo3D, accuracy) { CV_ReprojectImageTo3DTest test; test.safe_run(); }
}} // namespace
+977
View File
@@ -0,0 +1,977 @@
/*M///////////////////////////////////////////////////////////////////////////////////////
//
// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING.
//
// By downloading, copying, installing or using the software you agree to this license.
// If you do not agree to this license, do not download, install,
// copy or use the software.
//
//
// Intel License Agreement
// For Open Source Computer Vision Library
//
// Copyright (C) 2000, Intel Corporation, all rights reserved.
// Third party copyrights are property of their respective owners.
//
// Redistribution and use in source and binary forms, with or without modification,
// are permitted provided that the following conditions are met:
//
// * Redistribution's of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// * Redistribution's in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// * The name of Intel Corporation may not be used to endorse or promote products
// derived from this software without specific prior written permission.
//
// This software is provided by the copyright holders and contributors "as is" and
// any express or implied warranties, including, but not limited to, the implied
// warranties of merchantability and fitness for a particular purpose are disclaimed.
// In no event shall the Intel Corporation or contributors be liable for any direct,
// indirect, incidental, special, exemplary, or consequential damages
// (including, but not limited to, procurement of substitute goods or services;
// loss of use, data, or profits; or business interruption) however caused
// and on any theory of liability, whether in contract, strict liability,
// or tort (including negligence or otherwise) arising in any way out of
// the use of this software, even if advised of the possibility of such damage.
//
//M*/
/*
This is a regression test for stereo matching algorithms. This test gets some quality metrics
described in "A Taxonomy and Evaluation of Dense Two-Frame Stereo Correspondence Algorithms".
Daniel Scharstein, Richard Szeliski
*/
#include "test_precomp.hpp"
namespace opencv_test { namespace {
const float EVAL_BAD_THRESH = 1.f;
const int EVAL_TEXTURELESS_WIDTH = 3;
const float EVAL_TEXTURELESS_THRESH = 4.f;
const float EVAL_DISP_THRESH = 1.f;
const float EVAL_DISP_GAP = 2.f;
const int EVAL_DISCONT_WIDTH = 9;
const int EVAL_IGNORE_BORDER = 10;
const int ERROR_KINDS_COUNT = 6;
//============================== quality measuring functions =================================================
/*
Calculate textureless regions of image (regions where the squared horizontal intensity gradient averaged over
a square window of size=evalTexturelessWidth is below a threshold=evalTexturelessThresh) and textured regions.
*/
void computeTextureBasedMasks( const Mat& _img, Mat* texturelessMask, Mat* texturedMask,
int texturelessWidth = EVAL_TEXTURELESS_WIDTH, float texturelessThresh = EVAL_TEXTURELESS_THRESH )
{
if( !texturelessMask && !texturedMask )
return;
if( _img.empty() )
CV_Error( Error::StsBadArg, "img is empty" );
Mat img = _img;
if( _img.channels() > 1)
{
Mat tmp; cvtColor( _img, tmp, COLOR_BGR2GRAY ); img = tmp;
}
Mat dxI; Sobel( img, dxI, CV_32FC1, 1, 0, 3 );
Mat dxI2; pow( dxI / 8.f/*normalize*/, 2, dxI2 );
Mat avgDxI2; boxFilter( dxI2, avgDxI2, CV_32FC1, Size(texturelessWidth,texturelessWidth) );
if( texturelessMask )
*texturelessMask = avgDxI2 < texturelessThresh;
if( texturedMask )
*texturedMask = avgDxI2 >= texturelessThresh;
}
void checkTypeAndSizeOfDisp( const Mat& dispMap, const Size* sz )
{
if( dispMap.empty() )
CV_Error( Error::StsBadArg, "dispMap is empty" );
if( dispMap.type() != CV_32FC1 )
CV_Error( Error::StsBadArg, "dispMap must have CV_32FC1 type" );
if( sz && (dispMap.rows != sz->height || dispMap.cols != sz->width) )
CV_Error( Error::StsBadArg, "dispMap has incorrect size" );
}
void checkTypeAndSizeOfMask( const Mat& mask, Size sz )
{
if( mask.empty() )
CV_Error( Error::StsBadArg, "mask is empty" );
if( mask.type() != CV_8UC1 )
CV_Error( Error::StsBadArg, "mask must have CV_8UC1 type" );
if( mask.rows != sz.height || mask.cols != sz.width )
CV_Error( Error::StsBadArg, "mask has incorrect size" );
}
void checkDispMapsAndUnknDispMasks( const Mat& leftDispMap, const Mat& rightDispMap,
const Mat& leftUnknDispMask, const Mat& rightUnknDispMask )
{
// check type and size of disparity maps
checkTypeAndSizeOfDisp( leftDispMap, 0 );
if( !rightDispMap.empty() )
{
Size sz = leftDispMap.size();
checkTypeAndSizeOfDisp( rightDispMap, &sz );
}
// check size and type of unknown disparity maps
if( !leftUnknDispMask.empty() )
checkTypeAndSizeOfMask( leftUnknDispMask, leftDispMap.size() );
if( !rightUnknDispMask.empty() )
checkTypeAndSizeOfMask( rightUnknDispMask, rightDispMap.size() );
// check values of disparity maps (known disparity values musy be positive)
double leftMinVal = 0, rightMinVal = 0;
if( leftUnknDispMask.empty() )
minMaxLoc( leftDispMap, &leftMinVal );
else
minMaxLoc( leftDispMap, &leftMinVal, 0, 0, 0, ~leftUnknDispMask );
if( !rightDispMap.empty() )
{
if( rightUnknDispMask.empty() )
minMaxLoc( rightDispMap, &rightMinVal );
else
minMaxLoc( rightDispMap, &rightMinVal, 0, 0, 0, ~rightUnknDispMask );
}
if( leftMinVal < 0 || rightMinVal < 0)
CV_Error( Error::StsBadArg, "known disparity values must be positive" );
}
/*
Calculate occluded regions of reference image (left image) (regions that are occluded in the matching image (right image),
i.e., where the forward-mapped disparity lands at a location with a larger (nearer) disparity) and non occluded regions.
*/
void computeOcclusionBasedMasks( const Mat& leftDisp, const Mat& _rightDisp,
Mat* occludedMask, Mat* nonOccludedMask,
const Mat& leftUnknDispMask = Mat(), const Mat& rightUnknDispMask = Mat(),
float dispThresh = EVAL_DISP_THRESH )
{
if( !occludedMask && !nonOccludedMask )
return;
checkDispMapsAndUnknDispMasks( leftDisp, _rightDisp, leftUnknDispMask, rightUnknDispMask );
Mat rightDisp;
if( _rightDisp.empty() )
{
if( !rightUnknDispMask.empty() )
CV_Error( Error::StsBadArg, "rightUnknDispMask must be empty if _rightDisp is empty" );
rightDisp.create(leftDisp.size(), CV_32FC1);
rightDisp.setTo(Scalar::all(0) );
for( int leftY = 0; leftY < leftDisp.rows; leftY++ )
{
for( int leftX = 0; leftX < leftDisp.cols; leftX++ )
{
if( !leftUnknDispMask.empty() && leftUnknDispMask.at<uchar>(leftY,leftX) )
continue;
float leftDispVal = leftDisp.at<float>(leftY, leftX);
int rightX = leftX - cvRound(leftDispVal), rightY = leftY;
if( rightX >= 0)
rightDisp.at<float>(rightY,rightX) = max(rightDisp.at<float>(rightY,rightX), leftDispVal);
}
}
}
else
_rightDisp.copyTo(rightDisp);
if( occludedMask )
{
occludedMask->create(leftDisp.size(), CV_8UC1);
occludedMask->setTo(Scalar::all(0) );
}
if( nonOccludedMask )
{
nonOccludedMask->create(leftDisp.size(), CV_8UC1);
nonOccludedMask->setTo(Scalar::all(0) );
}
for( int leftY = 0; leftY < leftDisp.rows; leftY++ )
{
for( int leftX = 0; leftX < leftDisp.cols; leftX++ )
{
if( !leftUnknDispMask.empty() && leftUnknDispMask.at<uchar>(leftY,leftX) )
continue;
float leftDispVal = leftDisp.at<float>(leftY, leftX);
int rightX = leftX - cvRound(leftDispVal), rightY = leftY;
if( rightX < 0 && occludedMask )
occludedMask->at<uchar>(leftY, leftX) = 255;
else
{
if( !rightUnknDispMask.empty() && rightUnknDispMask.at<uchar>(rightY,rightX) )
continue;
float rightDispVal = rightDisp.at<float>(rightY, rightX);
if( rightDispVal > leftDispVal + dispThresh )
{
if( occludedMask )
occludedMask->at<uchar>(leftY, leftX) = 255;
}
else
{
if( nonOccludedMask )
nonOccludedMask->at<uchar>(leftY, leftX) = 255;
}
}
}
}
}
/*
Calculate depth discontinuity regions: pixels whose neighboring disparities differ by more than
dispGap, dilated by window of width discontWidth.
*/
void computeDepthDiscontMask( const Mat& disp, Mat& depthDiscontMask, const Mat& unknDispMask = Mat(),
float dispGap = EVAL_DISP_GAP, int discontWidth = EVAL_DISCONT_WIDTH )
{
if( disp.empty() )
CV_Error( Error::StsBadArg, "disp is empty" );
if( disp.type() != CV_32FC1 )
CV_Error( Error::StsBadArg, "disp must have CV_32FC1 type" );
if( !unknDispMask.empty() )
checkTypeAndSizeOfMask( unknDispMask, disp.size() );
Mat curDisp; disp.copyTo( curDisp );
if( !unknDispMask.empty() )
curDisp.setTo( Scalar(std::numeric_limits<float>::min()), unknDispMask );
Mat maxNeighbDisp; dilate( curDisp, maxNeighbDisp, Mat(3, 3, CV_8UC1, Scalar(1)) );
if( !unknDispMask.empty() )
curDisp.setTo( Scalar(std::numeric_limits<float>::max()), unknDispMask );
Mat minNeighbDisp; erode( curDisp, minNeighbDisp, Mat(3, 3, CV_8UC1, Scalar(1)) );
depthDiscontMask = max( (Mat)(maxNeighbDisp-disp), (Mat)(disp-minNeighbDisp) ) > dispGap;
if( !unknDispMask.empty() )
depthDiscontMask &= ~unknDispMask;
dilate( depthDiscontMask, depthDiscontMask, Mat(discontWidth, discontWidth, CV_8UC1, Scalar(1)) );
}
/*
Get evaluation masks excluding a border.
*/
Mat getBorderedMask( Size maskSize, int border = EVAL_IGNORE_BORDER )
{
CV_Assert( border >= 0 );
Mat mask(maskSize, CV_8UC1, Scalar(0));
int w = maskSize.width - 2*border, h = maskSize.height - 2*border;
if( w < 0 || h < 0 )
mask.setTo(Scalar(0));
else
mask( Rect(Point(border,border),Size(w,h)) ).setTo(Scalar(255));
return mask;
}
/*
Calculate root-mean-squared error between the computed disparity map (computedDisp) and ground truth map (groundTruthDisp).
*/
float dispRMS( const Mat& computedDisp, const Mat& groundTruthDisp, const Mat& mask )
{
checkTypeAndSizeOfDisp( groundTruthDisp, 0 );
Size sz = groundTruthDisp.size();
checkTypeAndSizeOfDisp( computedDisp, &sz );
int pointsCount = sz.height*sz.width;
if( !mask.empty() )
{
checkTypeAndSizeOfMask( mask, sz );
pointsCount = countNonZero(mask);
}
return 1.f/sqrt((float)pointsCount) * (float)cvtest::norm(computedDisp, groundTruthDisp, NORM_L2, mask);
}
/*
Calculate fraction of bad matching pixels.
*/
float badMatchPxlsFraction( const Mat& computedDisp, const Mat& groundTruthDisp, const Mat& mask,
float _badThresh = EVAL_BAD_THRESH )
{
int badThresh = cvRound(_badThresh);
checkTypeAndSizeOfDisp( groundTruthDisp, 0 );
Size sz = groundTruthDisp.size();
checkTypeAndSizeOfDisp( computedDisp, &sz );
Mat badPxlsMap;
absdiff( computedDisp, groundTruthDisp, badPxlsMap );
badPxlsMap = badPxlsMap > badThresh;
int pointsCount = sz.height*sz.width;
if( !mask.empty() )
{
checkTypeAndSizeOfMask( mask, sz );
badPxlsMap = badPxlsMap & mask;
pointsCount = countNonZero(mask);
}
return 1.f/pointsCount * countNonZero(badPxlsMap);
}
//===================== regression test for stereo matching algorithms ==============================
const string ALGORITHMS_DIR = "stereomatching/algorithms/";
const string DATASETS_DIR = "stereomatching/datasets/";
const string DATASETS_FILE = "datasets.xml";
const string RUN_PARAMS_FILE = "_params.xml";
const string RESULT_FILE = "_res.xml";
const string LEFT_IMG_NAME = "im2.png";
const string RIGHT_IMG_NAME = "im6.png";
const string TRUE_LEFT_DISP_NAME = "disp2.png";
const string TRUE_RIGHT_DISP_NAME = "disp6.png";
string ERROR_PREFIXES[] = { "borderedAll",
"borderedNoOccl",
"borderedOccl",
"borderedTextured",
"borderedTextureless",
"borderedDepthDiscont" }; // size of ERROR_KINDS_COUNT
string ROI_PREFIXES[] = { "roiX",
"roiY",
"roiWidth",
"roiHeight" };
const string RMS_STR = "RMS";
const string BAD_PXLS_FRACTION_STR = "BadPxlsFraction";
const string ROI_STR = "ValidDisparityROI";
class QualityEvalParams
{
public:
QualityEvalParams() { setDefaults(); }
QualityEvalParams( int _ignoreBorder )
{
setDefaults();
ignoreBorder = _ignoreBorder;
}
void setDefaults()
{
badThresh = EVAL_BAD_THRESH;
texturelessWidth = EVAL_TEXTURELESS_WIDTH;
texturelessThresh = EVAL_TEXTURELESS_THRESH;
dispThresh = EVAL_DISP_THRESH;
dispGap = EVAL_DISP_GAP;
discontWidth = EVAL_DISCONT_WIDTH;
ignoreBorder = EVAL_IGNORE_BORDER;
}
float badThresh;
int texturelessWidth;
float texturelessThresh;
float dispThresh;
float dispGap;
int discontWidth;
int ignoreBorder;
};
class CV_StereoMatchingTest : public cvtest::BaseTest
{
public:
CV_StereoMatchingTest()
{ rmsEps.resize( ERROR_KINDS_COUNT, 0.01f ); fracEps.resize( ERROR_KINDS_COUNT, 1.e-6f ); }
protected:
// assumed that left image is a reference image
virtual int runStereoMatchingAlgorithm( const Mat& leftImg, const Mat& rightImg,
Rect& calcROI, Mat& leftDisp, Mat& rightDisp, int caseIdx ) = 0; // return ignored border width
int readDatasetsParams( FileStorage& fs );
virtual int readRunParams( FileStorage& fs );
void writeErrors( const string& errName, const vector<float>& errors, FileStorage* fs = 0 );
void writeROI( const Rect& calcROI, FileStorage* fs = 0 );
void readErrors( FileNode& fn, const string& errName, vector<float>& errors );
void readROI( FileNode& fn, Rect& trueROI );
int compareErrors( const vector<float>& calcErrors, const vector<float>& validErrors,
const vector<float>& eps, const string& errName );
int compareROI( const Rect& calcROI, const Rect& validROI );
int processStereoMatchingResults( FileStorage& fs, int caseIdx, bool isWrite,
const Mat& leftImg, const Mat& rightImg,
const Rect& calcROI,
const Mat& trueLeftDisp, const Mat& trueRightDisp,
const Mat& leftDisp, const Mat& rightDisp,
const QualityEvalParams& qualityEvalParams );
void run( int );
vector<float> rmsEps;
vector<float> fracEps;
struct DatasetParams
{
int dispScaleFactor;
int dispUnknVal;
};
map<string, DatasetParams> datasetsParams;
vector<string> caseNames;
vector<string> caseDatasets;
};
void CV_StereoMatchingTest::run(int)
{
string dataPath = ts->get_data_path() + "cv/";
string algorithmName = name;
CV_Assert( !algorithmName.empty() );
if( dataPath.empty() )
{
ts->printf( cvtest::TS::LOG, "dataPath is empty" );
ts->set_failed_test_info( cvtest::TS::FAIL_BAD_ARG_CHECK );
return;
}
FileStorage datasetsFS( dataPath + DATASETS_DIR + DATASETS_FILE, FileStorage::READ );
int code = readDatasetsParams( datasetsFS );
if( code != cvtest::TS::OK )
{
ts->set_failed_test_info( code );
return;
}
FileStorage runParamsFS( dataPath + ALGORITHMS_DIR + algorithmName + RUN_PARAMS_FILE, FileStorage::READ );
code = readRunParams( runParamsFS );
if( code != cvtest::TS::OK )
{
ts->set_failed_test_info( code );
return;
}
string fullResultFilename = dataPath + ALGORITHMS_DIR + algorithmName + RESULT_FILE;
FileStorage resFS( fullResultFilename, FileStorage::READ );
bool isWrite = true; // write or compare results
if( resFS.isOpened() )
isWrite = false;
else
{
resFS.open( fullResultFilename, FileStorage::WRITE );
if( !resFS.isOpened() )
{
ts->printf( cvtest::TS::LOG, "file %s can not be read or written\n", fullResultFilename.c_str() );
ts->set_failed_test_info( cvtest::TS::FAIL_BAD_ARG_CHECK );
return;
}
resFS << "stereo_matching" << "{";
}
int progress = 0, caseCount = (int)caseNames.size();
for( int ci = 0; ci < caseCount; ci++)
{
progress = update_progress( progress, ci, caseCount, 0 );
printf("progress: %d%%\n", progress);
fflush(stdout);
string datasetName = caseDatasets[ci];
string datasetFullDirName = dataPath + DATASETS_DIR + datasetName + "/";
Mat leftImg = imread(datasetFullDirName + LEFT_IMG_NAME);
Mat rightImg = imread(datasetFullDirName + RIGHT_IMG_NAME);
Mat trueLeftDisp = imread(datasetFullDirName + TRUE_LEFT_DISP_NAME, IMREAD_GRAYSCALE);
Mat trueRightDisp = imread(datasetFullDirName + TRUE_RIGHT_DISP_NAME, IMREAD_GRAYSCALE);
Rect calcROI;
if( leftImg.empty() || rightImg.empty() || trueLeftDisp.empty() )
{
ts->printf( cvtest::TS::LOG, "images or left ground-truth disparities of dataset %s can not be read", datasetName.c_str() );
code = cvtest::TS::FAIL_INVALID_TEST_DATA;
continue;
}
int dispScaleFactor = datasetsParams[datasetName].dispScaleFactor;
Mat tmp;
trueLeftDisp.convertTo( tmp, CV_32FC1, 1.f/dispScaleFactor );
trueLeftDisp = tmp;
tmp.release();
if( !trueRightDisp.empty() )
{
trueRightDisp.convertTo( tmp, CV_32FC1, 1.f/dispScaleFactor );
trueRightDisp = tmp;
tmp.release();
}
Mat leftDisp, rightDisp;
int ignBorder = max(runStereoMatchingAlgorithm(leftImg, rightImg, calcROI, leftDisp, rightDisp, ci), EVAL_IGNORE_BORDER);
leftDisp.convertTo( tmp, CV_32FC1 );
leftDisp = tmp;
tmp.release();
rightDisp.convertTo( tmp, CV_32FC1 );
rightDisp = tmp;
tmp.release();
int tempCode = processStereoMatchingResults( resFS, ci, isWrite,
leftImg, rightImg, calcROI, trueLeftDisp, trueRightDisp, leftDisp, rightDisp, QualityEvalParams(ignBorder));
code = tempCode==cvtest::TS::OK ? code : tempCode;
}
if( isWrite )
resFS << "}"; // "stereo_matching"
ts->set_failed_test_info( code );
}
void calcErrors( const Mat& leftImg, const Mat& /*rightImg*/,
const Mat& trueLeftDisp, const Mat& trueRightDisp,
const Mat& trueLeftUnknDispMask, const Mat& trueRightUnknDispMask,
const Mat& calcLeftDisp, const Mat& /*calcRightDisp*/,
vector<float>& rms, vector<float>& badPxlsFractions,
const QualityEvalParams& qualityEvalParams )
{
Mat texturelessMask, texturedMask;
computeTextureBasedMasks( leftImg, &texturelessMask, &texturedMask,
qualityEvalParams.texturelessWidth, qualityEvalParams.texturelessThresh );
Mat occludedMask, nonOccludedMask;
computeOcclusionBasedMasks( trueLeftDisp, trueRightDisp, &occludedMask, &nonOccludedMask,
trueLeftUnknDispMask, trueRightUnknDispMask, qualityEvalParams.dispThresh);
Mat depthDiscontMask;
computeDepthDiscontMask( trueLeftDisp, depthDiscontMask, trueLeftUnknDispMask,
qualityEvalParams.dispGap, qualityEvalParams.discontWidth);
Mat borderedKnownMask = getBorderedMask( leftImg.size(), qualityEvalParams.ignoreBorder ) & ~trueLeftUnknDispMask;
nonOccludedMask &= borderedKnownMask;
occludedMask &= borderedKnownMask;
texturedMask &= nonOccludedMask; // & borderedKnownMask
texturelessMask &= nonOccludedMask; // & borderedKnownMask
depthDiscontMask &= nonOccludedMask; // & borderedKnownMask
rms.resize(ERROR_KINDS_COUNT);
rms[0] = dispRMS( calcLeftDisp, trueLeftDisp, borderedKnownMask );
rms[1] = dispRMS( calcLeftDisp, trueLeftDisp, nonOccludedMask );
rms[2] = dispRMS( calcLeftDisp, trueLeftDisp, occludedMask );
rms[3] = dispRMS( calcLeftDisp, trueLeftDisp, texturedMask );
rms[4] = dispRMS( calcLeftDisp, trueLeftDisp, texturelessMask );
rms[5] = dispRMS( calcLeftDisp, trueLeftDisp, depthDiscontMask );
badPxlsFractions.resize(ERROR_KINDS_COUNT);
badPxlsFractions[0] = badMatchPxlsFraction( calcLeftDisp, trueLeftDisp, borderedKnownMask, qualityEvalParams.badThresh );
badPxlsFractions[1] = badMatchPxlsFraction( calcLeftDisp, trueLeftDisp, nonOccludedMask, qualityEvalParams.badThresh );
badPxlsFractions[2] = badMatchPxlsFraction( calcLeftDisp, trueLeftDisp, occludedMask, qualityEvalParams.badThresh );
badPxlsFractions[3] = badMatchPxlsFraction( calcLeftDisp, trueLeftDisp, texturedMask, qualityEvalParams.badThresh );
badPxlsFractions[4] = badMatchPxlsFraction( calcLeftDisp, trueLeftDisp, texturelessMask, qualityEvalParams.badThresh );
badPxlsFractions[5] = badMatchPxlsFraction( calcLeftDisp, trueLeftDisp, depthDiscontMask, qualityEvalParams.badThresh );
}
int CV_StereoMatchingTest::processStereoMatchingResults( FileStorage& fs, int caseIdx, bool isWrite,
const Mat& leftImg, const Mat& rightImg,
const Rect& calcROI,
const Mat& trueLeftDisp, const Mat& trueRightDisp,
const Mat& leftDisp, const Mat& rightDisp,
const QualityEvalParams& qualityEvalParams )
{
// rightDisp is not used in current test virsion
int code = cvtest::TS::OK;
CV_Assert( fs.isOpened() );
CV_Assert( trueLeftDisp.type() == CV_32FC1 );
CV_Assert( trueRightDisp.empty() || trueRightDisp.type() == CV_32FC1 );
CV_Assert( leftDisp.type() == CV_32FC1 && (rightDisp.empty() || rightDisp.type() == CV_32FC1) );
// get masks for unknown ground truth disparity values
Mat leftUnknMask, rightUnknMask;
DatasetParams params = datasetsParams[caseDatasets[caseIdx]];
absdiff( trueLeftDisp, Scalar(params.dispUnknVal), leftUnknMask );
leftUnknMask = leftUnknMask < std::numeric_limits<float>::epsilon();
CV_Assert(leftUnknMask.type() == CV_8UC1);
if( !trueRightDisp.empty() )
{
absdiff( trueRightDisp, Scalar(params.dispUnknVal), rightUnknMask );
rightUnknMask = rightUnknMask < std::numeric_limits<float>::epsilon();
CV_Assert(rightUnknMask.type() == CV_8UC1);
}
// calculate errors
vector<float> rmss, badPxlsFractions;
calcErrors( leftImg, rightImg, trueLeftDisp, trueRightDisp, leftUnknMask, rightUnknMask,
leftDisp, rightDisp, rmss, badPxlsFractions, qualityEvalParams );
if( isWrite )
{
fs << caseNames[caseIdx] << "{";
fs.writeComment( RMS_STR, 0 );
writeErrors( RMS_STR, rmss, &fs );
fs.writeComment( BAD_PXLS_FRACTION_STR, 0 );
writeErrors( BAD_PXLS_FRACTION_STR, badPxlsFractions, &fs );
fs.writeComment( ROI_STR, 0 );
writeROI( calcROI, &fs );
fs << "}"; // datasetName
}
else // compare
{
ts->printf( cvtest::TS::LOG, "\nquality of case named %s\n", caseNames[caseIdx].c_str() );
ts->printf( cvtest::TS::LOG, "%s\n", RMS_STR.c_str() );
writeErrors( RMS_STR, rmss );
ts->printf( cvtest::TS::LOG, "%s\n", BAD_PXLS_FRACTION_STR.c_str() );
writeErrors( BAD_PXLS_FRACTION_STR, badPxlsFractions );
ts->printf( cvtest::TS::LOG, "%s\n", ROI_STR.c_str() );
writeROI( calcROI );
FileNode fn = fs.getFirstTopLevelNode()[caseNames[caseIdx]];
vector<float> validRmss, validBadPxlsFractions;
Rect validROI;
readErrors( fn, RMS_STR, validRmss );
readErrors( fn, BAD_PXLS_FRACTION_STR, validBadPxlsFractions );
readROI( fn, validROI );
int tempCode = compareErrors( rmss, validRmss, rmsEps, RMS_STR );
code = tempCode==cvtest::TS::OK ? code : tempCode;
tempCode = compareErrors( badPxlsFractions, validBadPxlsFractions, fracEps, BAD_PXLS_FRACTION_STR );
code = tempCode==cvtest::TS::OK ? code : tempCode;
tempCode = compareROI( calcROI, validROI );
code = tempCode==cvtest::TS::OK ? code : tempCode;
}
return code;
}
int CV_StereoMatchingTest::readDatasetsParams( FileStorage& fs )
{
if( !fs.isOpened() )
{
ts->printf( cvtest::TS::LOG, "datasetsParams can not be read " );
return cvtest::TS::FAIL_INVALID_TEST_DATA;
}
datasetsParams.clear();
FileNode fn = fs.getFirstTopLevelNode();
CV_Assert(fn.isSeq());
for( int i = 0; i < (int)fn.size(); i+=3 )
{
String _name = fn[i];
DatasetParams params;
String sf = fn[i+1]; params.dispScaleFactor = atoi(sf.c_str());
String uv = fn[i+2]; params.dispUnknVal = atoi(uv.c_str());
datasetsParams[_name] = params;
}
return cvtest::TS::OK;
}
int CV_StereoMatchingTest::readRunParams( FileStorage& fs )
{
if( !fs.isOpened() )
{
ts->printf( cvtest::TS::LOG, "runParams can not be read " );
return cvtest::TS::FAIL_INVALID_TEST_DATA;
}
caseNames.clear();;
caseDatasets.clear();
return cvtest::TS::OK;
}
void CV_StereoMatchingTest::writeErrors( const string& errName, const vector<float>& errors, FileStorage* fs )
{
CV_Assert( (int)errors.size() == ERROR_KINDS_COUNT );
vector<float>::const_iterator it = errors.begin();
if( fs )
for( int i = 0; i < ERROR_KINDS_COUNT; i++, ++it )
*fs << ERROR_PREFIXES[i] + errName << *it;
else
for( int i = 0; i < ERROR_KINDS_COUNT; i++, ++it )
ts->printf( cvtest::TS::LOG, "%s = %f\n", string(ERROR_PREFIXES[i]+errName).c_str(), *it );
}
void CV_StereoMatchingTest::writeROI( const Rect& calcROI, FileStorage* fs )
{
if( fs )
{
*fs << ROI_PREFIXES[0] << calcROI.x;
*fs << ROI_PREFIXES[1] << calcROI.y;
*fs << ROI_PREFIXES[2] << calcROI.width;
*fs << ROI_PREFIXES[3] << calcROI.height;
}
else
{
ts->printf( cvtest::TS::LOG, "%s = %d\n", ROI_PREFIXES[0].c_str(), calcROI.x );
ts->printf( cvtest::TS::LOG, "%s = %d\n", ROI_PREFIXES[1].c_str(), calcROI.y );
ts->printf( cvtest::TS::LOG, "%s = %d\n", ROI_PREFIXES[2].c_str(), calcROI.width );
ts->printf( cvtest::TS::LOG, "%s = %d\n", ROI_PREFIXES[3].c_str(), calcROI.height );
}
}
void CV_StereoMatchingTest::readErrors( FileNode& fn, const string& errName, vector<float>& errors )
{
errors.resize( ERROR_KINDS_COUNT );
vector<float>::iterator it = errors.begin();
for( int i = 0; i < ERROR_KINDS_COUNT; i++, ++it )
fn[ERROR_PREFIXES[i]+errName] >> *it;
}
void CV_StereoMatchingTest::readROI( FileNode& fn, Rect& validROI )
{
fn[ROI_PREFIXES[0]] >> validROI.x;
fn[ROI_PREFIXES[1]] >> validROI.y;
fn[ROI_PREFIXES[2]] >> validROI.width;
fn[ROI_PREFIXES[3]] >> validROI.height;
}
int CV_StereoMatchingTest::compareErrors( const vector<float>& calcErrors, const vector<float>& validErrors,
const vector<float>& eps, const string& errName )
{
CV_Assert( (int)calcErrors.size() == ERROR_KINDS_COUNT );
CV_Assert( (int)validErrors.size() == ERROR_KINDS_COUNT );
CV_Assert( (int)eps.size() == ERROR_KINDS_COUNT );
vector<float>::const_iterator calcIt = calcErrors.begin(),
validIt = validErrors.begin(),
epsIt = eps.begin();
bool ok = true;
for( int i = 0; i < ERROR_KINDS_COUNT; i++, ++calcIt, ++validIt, ++epsIt )
if( *calcIt - *validIt > *epsIt )
{
ts->printf( cvtest::TS::LOG, "bad accuracy of %s (valid=%f; calc=%f)\n", string(ERROR_PREFIXES[i]+errName).c_str(), *validIt, *calcIt );
ok = false;
}
return ok ? cvtest::TS::OK : cvtest::TS::FAIL_BAD_ACCURACY;
}
int CV_StereoMatchingTest::compareROI( const Rect& calcROI, const Rect& validROI )
{
int compare[4][2] = {
{ calcROI.x, validROI.x },
{ calcROI.y, validROI.y },
{ calcROI.width, validROI.width },
{ calcROI.height, validROI.height },
};
bool ok = true;
for (int i = 0; i < 4; i++)
{
if (compare[i][0] != compare[i][1])
{
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] );
ok = false;
}
}
return ok ? cvtest::TS::OK : cvtest::TS::FAIL_BAD_ACCURACY;
}
//----------------------------------- StereoBM test -----------------------------------------------------
class CV_StereoBMTest : public CV_StereoMatchingTest
{
public:
CV_StereoBMTest()
{
name = "stereobm";
std::fill(rmsEps.begin(), rmsEps.end(), 0.4f);
std::fill(fracEps.begin(), fracEps.end(), 0.022f);
}
protected:
struct RunParams
{
int ndisp;
int mindisp;
int winSize;
};
vector<RunParams> caseRunParams;
virtual int readRunParams( FileStorage& fs )
{
int code = CV_StereoMatchingTest::readRunParams( fs );
FileNode fn = fs.getFirstTopLevelNode();
CV_Assert(fn.isSeq());
for( int i = 0; i < (int)fn.size(); i+=5 )
{
String caseName = fn[i], datasetName = fn[i+1];
RunParams params;
String ndisp = fn[i+2]; params.ndisp = atoi(ndisp.c_str());
String mindisp = fn[i+3]; params.mindisp = atoi(mindisp.c_str());
String winSize = fn[i+4]; params.winSize = atoi(winSize.c_str());
caseNames.push_back( caseName );
caseDatasets.push_back( datasetName );
caseRunParams.push_back( params );
}
return code;
}
virtual int runStereoMatchingAlgorithm( const Mat& _leftImg, const Mat& _rightImg,
Rect& calcROI, Mat& leftDisp, Mat& /*rightDisp*/, int caseIdx )
{
RunParams params = caseRunParams[caseIdx];
CV_Assert( params.ndisp%16 == 0 );
CV_Assert( _leftImg.type() == CV_8UC3 && _rightImg.type() == CV_8UC3 );
Mat leftImg; cvtColor( _leftImg, leftImg, COLOR_BGR2GRAY );
Mat rightImg; cvtColor( _rightImg, rightImg, COLOR_BGR2GRAY );
Ptr<StereoBM> bm = StereoBM::create( params.ndisp, params.winSize );
Mat tempDisp;
bm->setMinDisparity(params.mindisp);
Rect cROI(0, 0, _leftImg.cols, _leftImg.rows);
calcROI = getValidDisparityROI(cROI, cROI, params.mindisp, params.ndisp, params.winSize);
bm->compute( leftImg, rightImg, tempDisp );
tempDisp.convertTo(leftDisp, CV_32F, 1./static_cast<double>(StereoMatcher::DISP_SCALE));
//check for fixed-type disparity data type
Mat_<float> fixedFloatDisp;
bm->compute( leftImg, rightImg, fixedFloatDisp );
EXPECT_LT(cvtest::norm(fixedFloatDisp, leftDisp, cv::NORM_L2 | cv::NORM_RELATIVE),
0.005 + DBL_EPSILON);
if (params.mindisp != 0)
for (int y = 0; y < leftDisp.rows; y++)
for (int x = 0; x < leftDisp.cols; x++)
{
if (leftDisp.at<float>(y, x) < params.mindisp)
leftDisp.at<float>(y, x) = -1./static_cast<double>(StereoMatcher::DISP_SCALE); // treat disparity < mindisp as no disparity
}
return params.winSize/2;
}
};
TEST(Calib3d_StereoBM, regression) { CV_StereoBMTest test; test.safe_run(); }
/* < preFilter, < preFilterCap, SADWindowSize > >*/
typedef tuple < int, tuple < int, int > > BufferBM_Params_t;
typedef testing::TestWithParam< BufferBM_Params_t > Calib3d_StereoBM_BufferBM;
const int preFilters[] =
{
StereoBM::PREFILTER_NORMALIZED_RESPONSE,
StereoBM::PREFILTER_XSOBEL
};
const tuple < int, int > useShortsConditions[] =
{
make_tuple(30, 19),
make_tuple(32, 23)
};
TEST_P(Calib3d_StereoBM_BufferBM, memAllocsTest)
{
const int preFilter = get<0>(GetParam());
const int preFilterCap = get<0>(get<1>(GetParam()));
const int SADWindowSize = get<1>(get<1>(GetParam()));
String path = cvtest::TS::ptr()->get_data_path() + "cv/stereomatching/datasets/teddy/";
Mat leftImg = imread(path + "im2.png", IMREAD_GRAYSCALE);
ASSERT_FALSE(leftImg.empty());
Mat rightImg = imread(path + "im6.png", IMREAD_GRAYSCALE);
ASSERT_FALSE(rightImg.empty());
Mat leftDisp;
{
Ptr<StereoBM> bm = StereoBM::create(16,9);
bm->setPreFilterType(preFilter);
bm->setPreFilterCap(preFilterCap);
bm->setBlockSize(SADWindowSize);
bm->compute( leftImg, rightImg, leftDisp);
ASSERT_FALSE(leftDisp.empty());
}
}
INSTANTIATE_TEST_CASE_P(/*nothing*/, Calib3d_StereoBM_BufferBM,
testing::Combine(
testing::ValuesIn(preFilters),
testing::ValuesIn(useShortsConditions)
)
);
//----------------------------------- StereoSGBM test -----------------------------------------------------
class CV_StereoSGBMTest : public CV_StereoMatchingTest
{
public:
CV_StereoSGBMTest()
{
name = "stereosgbm";
std::fill(rmsEps.begin(), rmsEps.end(), 0.25f);
std::fill(fracEps.begin(), fracEps.end(), 0.01f);
}
protected:
struct RunParams
{
int ndisp;
int winSize;
int mode;
};
vector<RunParams> caseRunParams;
virtual int readRunParams( FileStorage& fs )
{
int code = CV_StereoMatchingTest::readRunParams(fs);
FileNode fn = fs.getFirstTopLevelNode();
CV_Assert(fn.isSeq());
for( int i = 0; i < (int)fn.size(); i+=5 )
{
String caseName = fn[i], datasetName = fn[i+1];
RunParams params;
String ndisp = fn[i+2]; params.ndisp = atoi(ndisp.c_str());
String winSize = fn[i+3]; params.winSize = atoi(winSize.c_str());
String mode = fn[i+4]; params.mode = atoi(mode.c_str());
caseNames.push_back( caseName );
caseDatasets.push_back( datasetName );
caseRunParams.push_back( params );
}
return code;
}
virtual int runStereoMatchingAlgorithm( const Mat& leftImg, const Mat& rightImg,
Rect& calcROI, Mat& leftDisp, Mat& /*rightDisp*/, int caseIdx )
{
RunParams params = caseRunParams[caseIdx];
CV_Assert( params.ndisp%16 == 0 );
Ptr<StereoSGBM> sgbm = StereoSGBM::create( 0, params.ndisp, params.winSize,
10*params.winSize*params.winSize,
40*params.winSize*params.winSize,
1, 63, 10, 100, 32, params.mode );
Rect cROI(0, 0, leftImg.cols, leftImg.rows);
calcROI = getValidDisparityROI(cROI, cROI, 0, params.ndisp, params.winSize);
sgbm->compute( leftImg, rightImg, leftDisp );
CV_Assert( leftDisp.type() == CV_16SC1 );
leftDisp/=16;
return 0;
}
};
TEST(Calib3d_StereoSGBM, regression) { CV_StereoSGBMTest test; test.safe_run(); }
TEST(Calib3d_StereoSGBM, deterministic) {
cv::Ptr<cv::StereoSGBM> matcher = cv::StereoSGBM::create(16, 11);
// Expect throw error (non-determinism case)
int widthNarrow = 28;
int height = 15;
cv::Mat leftNarrow(height, widthNarrow, CV_8UC1);
cv::Mat rightNarrow(height, widthNarrow, CV_8UC1);
randu(leftNarrow, cv::Scalar(0), cv::Scalar(255));
randu(rightNarrow, cv::Scalar(0), cv::Scalar(255));
cv::Mat disp;
EXPECT_THROW(matcher->compute(leftNarrow, rightNarrow, disp), cv::Exception);
// Deterministic case, image is sufficiently large for StereSGBM parameters
int widthWide = 40;
cv::Mat leftWide(height, widthWide, CV_8UC1);
cv::Mat rightWide(height, widthWide, CV_8UC1);
randu(leftWide, cv::Scalar(0), cv::Scalar(255));
randu(rightWide, cv::Scalar(0), cv::Scalar(255));
cv::Mat disp1, disp2;
for (int i = 0; i < 10; i++) {
matcher->compute(leftWide, rightWide, disp1);
matcher->compute(leftWide, rightWide, disp2);
cv::Mat dst;
cv::bitwise_xor(disp1, disp2, dst);
EXPECT_EQ(cv::countNonZero(dst), 0);
}
}
TEST(Calib3d_StereoSGBM_HH4, regression)
{
String path = cvtest::TS::ptr()->get_data_path() + "cv/stereomatching/datasets/teddy/";
Mat leftImg = imread(path + "im2.png", IMREAD_GRAYSCALE);
ASSERT_FALSE(leftImg.empty());
Mat rightImg = imread(path + "im6.png", IMREAD_GRAYSCALE);
ASSERT_FALSE(rightImg.empty());
Mat testData = imread(path + "disp2_hh4.png",-1);
ASSERT_FALSE(testData.empty());
Mat leftDisp;
Mat toCheck;
{
Ptr<StereoSGBM> sgbm = StereoSGBM::create( 0, 48, 3, 90, 360, 1, 63, 10, 100, 32, StereoSGBM::MODE_HH4);
sgbm->compute( leftImg, rightImg, leftDisp);
CV_Assert( leftDisp.type() == CV_16SC1 );
leftDisp.convertTo(toCheck, CV_16UC1,1,16);
}
Mat diff;
absdiff(toCheck, testData,diff);
CV_Assert( countNonZero(diff)==0);
}
}} // namespace