
Use min/max bounds instead of local value to calculate acceptance threshold. Threshold based on local values somethimes does not work because cancellation of big values may produce error bigger than local value.
1372 lines
46 KiB
C++
1372 lines
46 KiB
C++
#include "precomp.hpp"
|
|
|
|
#ifdef HAVE_CUDA
|
|
#include "opencv2/core/gpumat.hpp"
|
|
#endif
|
|
|
|
#ifdef ANDROID
|
|
# include <sys/time.h>
|
|
#endif
|
|
|
|
using namespace perf;
|
|
|
|
int64 TestBase::timeLimitDefault = 0;
|
|
unsigned int TestBase::iterationsLimitDefault = (unsigned int)(-1);
|
|
int64 TestBase::_timeadjustment = 0;
|
|
|
|
const std::string command_line_keys =
|
|
"{ |perf_max_outliers |8 |percent of allowed outliers}"
|
|
"{ |perf_min_samples |10 |minimal required numer of samples}"
|
|
"{ |perf_force_samples |100 |force set maximum number of samples for all tests}"
|
|
"{ |perf_seed |809564 |seed for random numbers generator}"
|
|
"{ |perf_threads |-1 |the number of worker threads, if parallel execution is enabled}"
|
|
"{ |perf_write_sanity |false |create new records for sanity checks}"
|
|
"{ |perf_verify_sanity |false |fail tests having no regression data for sanity checks}"
|
|
#ifdef ANDROID
|
|
"{ |perf_time_limit |6.0 |default time limit for a single test (in seconds)}"
|
|
"{ |perf_affinity_mask |0 |set affinity mask for the main thread}"
|
|
"{ |perf_log_power_checkpoints | |additional xml logging for power measurement}"
|
|
#else
|
|
"{ |perf_time_limit |3.0 |default time limit for a single test (in seconds)}"
|
|
#endif
|
|
"{ |perf_max_deviation |1.0 |}"
|
|
"{h |help |false |print help info}"
|
|
#ifdef HAVE_CUDA
|
|
"{ |perf_run_cpu |false |run GPU performance tests for analogical CPU functions}"
|
|
"{ |perf_cuda_device |0 |run GPU test suite onto specific CUDA capable device}"
|
|
"{ |perf_cuda_info_only |false |print an information about system and an available CUDA devices and then exit.}"
|
|
#endif
|
|
;
|
|
|
|
static double param_max_outliers;
|
|
static double param_max_deviation;
|
|
static unsigned int param_min_samples;
|
|
static unsigned int param_force_samples;
|
|
static uint64 param_seed;
|
|
static double param_time_limit;
|
|
static int param_threads;
|
|
static bool param_write_sanity;
|
|
static bool param_verify_sanity;
|
|
#ifdef HAVE_CUDA
|
|
static bool param_run_cpu;
|
|
static int param_cuda_device;
|
|
#endif
|
|
|
|
|
|
#ifdef ANDROID
|
|
static int param_affinity_mask;
|
|
static bool log_power_checkpoints;
|
|
|
|
#include <sys/syscall.h>
|
|
#include <pthread.h>
|
|
static void setCurrentThreadAffinityMask(int mask)
|
|
{
|
|
pid_t pid=gettid();
|
|
int syscallres=syscall(__NR_sched_setaffinity, pid, sizeof(mask), &mask);
|
|
if (syscallres)
|
|
{
|
|
int err=errno;
|
|
err=err;//to avoid warnings about unused variables
|
|
LOGE("Error in the syscall setaffinity: mask=%d=0x%x err=%d=0x%x", mask, mask, err, err);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
#ifdef HAVE_CUDA
|
|
# include <opencv2/core/gpumat.hpp>
|
|
#endif
|
|
|
|
namespace {
|
|
|
|
class PerfEnvironment: public ::testing::Environment
|
|
{
|
|
public:
|
|
void TearDown()
|
|
{
|
|
cv::setNumThreads(-1);
|
|
}
|
|
};
|
|
|
|
} // namespace
|
|
|
|
static void randu(cv::Mat& m)
|
|
{
|
|
const int bigValue = 0x00000FFF;
|
|
if (m.depth() < CV_32F)
|
|
{
|
|
int minmax[] = {0, 256};
|
|
cv::Mat mr = cv::Mat(m.rows, (int)(m.cols * m.elemSize()), CV_8U, m.ptr(), m.step[0]);
|
|
cv::randu(mr, cv::Mat(1, 1, CV_32S, minmax), cv::Mat(1, 1, CV_32S, minmax + 1));
|
|
}
|
|
else if (m.depth() == CV_32F)
|
|
{
|
|
//float minmax[] = {-FLT_MAX, FLT_MAX};
|
|
float minmax[] = {-bigValue, bigValue};
|
|
cv::Mat mr = m.reshape(1);
|
|
cv::randu(mr, cv::Mat(1, 1, CV_32F, minmax), cv::Mat(1, 1, CV_32F, minmax + 1));
|
|
}
|
|
else
|
|
{
|
|
//double minmax[] = {-DBL_MAX, DBL_MAX};
|
|
double minmax[] = {-bigValue, bigValue};
|
|
cv::Mat mr = m.reshape(1);
|
|
cv::randu(mr, cv::Mat(1, 1, CV_64F, minmax), cv::Mat(1, 1, CV_64F, minmax + 1));
|
|
}
|
|
}
|
|
|
|
/*****************************************************************************************\
|
|
* inner exception class for early termination
|
|
\*****************************************************************************************/
|
|
|
|
class PerfEarlyExitException: public cv::Exception {};
|
|
|
|
/*****************************************************************************************\
|
|
* ::perf::Regression
|
|
\*****************************************************************************************/
|
|
|
|
Regression& Regression::instance()
|
|
{
|
|
static Regression single;
|
|
return single;
|
|
}
|
|
|
|
Regression& Regression::add(TestBase* test, const std::string& name, cv::InputArray array, double eps, ERROR_TYPE err)
|
|
{
|
|
if(test) test->verified = true;
|
|
return instance()(name, array, eps, err);
|
|
}
|
|
|
|
Regression& Regression::addKeypoints(TestBase* test, const std::string& name, const std::vector<cv::KeyPoint>& array, double eps, ERROR_TYPE err)
|
|
{
|
|
int len = (int)array.size();
|
|
cv::Mat pt (len, 1, CV_32FC2, (void*)&array[0].pt, sizeof(cv::KeyPoint));
|
|
cv::Mat size (len, 1, CV_32FC1, (void*)&array[0].size, sizeof(cv::KeyPoint));
|
|
cv::Mat angle (len, 1, CV_32FC1, (void*)&array[0].angle, sizeof(cv::KeyPoint));
|
|
cv::Mat response(len, 1, CV_32FC1, (void*)&array[0].response, sizeof(cv::KeyPoint));
|
|
cv::Mat octave (len, 1, CV_32SC1, (void*)&array[0].octave, sizeof(cv::KeyPoint));
|
|
cv::Mat class_id(len, 1, CV_32SC1, (void*)&array[0].class_id, sizeof(cv::KeyPoint));
|
|
|
|
return Regression::add(test, name + "-pt", pt, eps, ERROR_ABSOLUTE)
|
|
(name + "-size", size, eps, ERROR_ABSOLUTE)
|
|
(name + "-angle", angle, eps, ERROR_ABSOLUTE)
|
|
(name + "-response", response, eps, err)
|
|
(name + "-octave", octave, eps, ERROR_ABSOLUTE)
|
|
(name + "-class_id", class_id, eps, ERROR_ABSOLUTE);
|
|
}
|
|
|
|
Regression& Regression::addMatches(TestBase* test, const std::string& name, const std::vector<cv::DMatch>& array, double eps, ERROR_TYPE err)
|
|
{
|
|
int len = (int)array.size();
|
|
cv::Mat queryIdx(len, 1, CV_32SC1, (void*)&array[0].queryIdx, sizeof(cv::DMatch));
|
|
cv::Mat trainIdx(len, 1, CV_32SC1, (void*)&array[0].trainIdx, sizeof(cv::DMatch));
|
|
cv::Mat imgIdx (len, 1, CV_32SC1, (void*)&array[0].imgIdx, sizeof(cv::DMatch));
|
|
cv::Mat distance(len, 1, CV_32FC1, (void*)&array[0].distance, sizeof(cv::DMatch));
|
|
|
|
return Regression::add(test, name + "-queryIdx", queryIdx, DBL_EPSILON, ERROR_ABSOLUTE)
|
|
(name + "-trainIdx", trainIdx, DBL_EPSILON, ERROR_ABSOLUTE)
|
|
(name + "-imgIdx", imgIdx, DBL_EPSILON, ERROR_ABSOLUTE)
|
|
(name + "-distance", distance, eps, err);
|
|
}
|
|
|
|
void Regression::Init(const std::string& testSuitName, const std::string& ext)
|
|
{
|
|
instance().init(testSuitName, ext);
|
|
}
|
|
|
|
void Regression::init(const std::string& testSuitName, const std::string& ext)
|
|
{
|
|
if (!storageInPath.empty())
|
|
{
|
|
LOGE("Subsequent initialisation of Regression utility is not allowed.");
|
|
return;
|
|
}
|
|
|
|
const char *data_path_dir = getenv("OPENCV_TEST_DATA_PATH");
|
|
const char *path_separator = "/";
|
|
|
|
if (data_path_dir)
|
|
{
|
|
int len = (int)strlen(data_path_dir)-1;
|
|
if (len < 0) len = 0;
|
|
std::string path_base = (data_path_dir[0] == 0 ? std::string(".") : std::string(data_path_dir))
|
|
+ (data_path_dir[len] == '/' || data_path_dir[len] == '\\' ? "" : path_separator)
|
|
+ "perf"
|
|
+ path_separator;
|
|
|
|
storageInPath = path_base + testSuitName + ext;
|
|
storageOutPath = path_base + testSuitName;
|
|
}
|
|
else
|
|
{
|
|
storageInPath = testSuitName + ext;
|
|
storageOutPath = testSuitName;
|
|
}
|
|
|
|
suiteName = testSuitName;
|
|
|
|
try
|
|
{
|
|
if (storageIn.open(storageInPath, cv::FileStorage::READ))
|
|
{
|
|
rootIn = storageIn.root();
|
|
if (storageInPath.length() > 3 && storageInPath.substr(storageInPath.length()-3) == ".gz")
|
|
storageOutPath += "_new";
|
|
storageOutPath += ext;
|
|
}
|
|
}
|
|
catch(cv::Exception&)
|
|
{
|
|
LOGE("Failed to open sanity data for reading: %s", storageInPath.c_str());
|
|
}
|
|
|
|
if(!storageIn.isOpened())
|
|
storageOutPath = storageInPath;
|
|
}
|
|
|
|
Regression::Regression() : regRNG(cv::getTickCount())//this rng should be really random
|
|
{
|
|
}
|
|
|
|
Regression::~Regression()
|
|
{
|
|
if (storageIn.isOpened())
|
|
storageIn.release();
|
|
if (storageOut.isOpened())
|
|
{
|
|
if (!currentTestNodeName.empty())
|
|
storageOut << "}";
|
|
storageOut.release();
|
|
}
|
|
}
|
|
|
|
cv::FileStorage& Regression::write()
|
|
{
|
|
if (!storageOut.isOpened() && !storageOutPath.empty())
|
|
{
|
|
int mode = (storageIn.isOpened() && storageInPath == storageOutPath)
|
|
? cv::FileStorage::APPEND : cv::FileStorage::WRITE;
|
|
storageOut.open(storageOutPath, mode);
|
|
if (!storageOut.isOpened())
|
|
{
|
|
LOGE("Could not open \"%s\" file for writing", storageOutPath.c_str());
|
|
storageOutPath.clear();
|
|
}
|
|
else if (mode == cv::FileStorage::WRITE && !rootIn.empty())
|
|
{
|
|
//TODO: write content of rootIn node into the storageOut
|
|
}
|
|
}
|
|
return storageOut;
|
|
}
|
|
|
|
std::string Regression::getCurrentTestNodeName()
|
|
{
|
|
const ::testing::TestInfo* const test_info =
|
|
::testing::UnitTest::GetInstance()->current_test_info();
|
|
|
|
if (test_info == 0)
|
|
return "undefined";
|
|
|
|
std::string nodename = std::string(test_info->test_case_name()) + "--" + test_info->name();
|
|
size_t idx = nodename.find_first_of('/');
|
|
if (idx != std::string::npos)
|
|
nodename.erase(idx);
|
|
|
|
const char* type_param = test_info->type_param();
|
|
if (type_param != 0)
|
|
(nodename += "--") += type_param;
|
|
|
|
const char* value_param = test_info->value_param();
|
|
if (value_param != 0)
|
|
(nodename += "--") += value_param;
|
|
|
|
for(size_t i = 0; i < nodename.length(); ++i)
|
|
if (!isalnum(nodename[i]) && '_' != nodename[i])
|
|
nodename[i] = '-';
|
|
|
|
return nodename;
|
|
}
|
|
|
|
bool Regression::isVector(cv::InputArray a)
|
|
{
|
|
return a.kind() == cv::_InputArray::STD_VECTOR_MAT || a.kind() == cv::_InputArray::STD_VECTOR_VECTOR;
|
|
}
|
|
|
|
double Regression::getElem(cv::Mat& m, int y, int x, int cn)
|
|
{
|
|
switch (m.depth())
|
|
{
|
|
case CV_8U: return *(m.ptr<unsigned char>(y, x) + cn);
|
|
case CV_8S: return *(m.ptr<signed char>(y, x) + cn);
|
|
case CV_16U: return *(m.ptr<unsigned short>(y, x) + cn);
|
|
case CV_16S: return *(m.ptr<signed short>(y, x) + cn);
|
|
case CV_32S: return *(m.ptr<signed int>(y, x) + cn);
|
|
case CV_32F: return *(m.ptr<float>(y, x) + cn);
|
|
case CV_64F: return *(m.ptr<double>(y, x) + cn);
|
|
default: return 0;
|
|
}
|
|
}
|
|
|
|
void Regression::write(cv::Mat m)
|
|
{
|
|
if (!m.empty() && m.dims < 2) return;
|
|
|
|
double min, max;
|
|
cv::minMaxIdx(m, &min, &max);
|
|
write() << "min" << min << "max" << max;
|
|
|
|
write() << "last" << "{" << "x" << m.size.p[1] - 1 << "y" << m.size.p[0] - 1
|
|
<< "val" << getElem(m, m.size.p[0] - 1, m.size.p[1] - 1, m.channels() - 1) << "}";
|
|
|
|
int x, y, cn;
|
|
x = regRNG.uniform(0, m.size.p[1]);
|
|
y = regRNG.uniform(0, m.size.p[0]);
|
|
cn = regRNG.uniform(0, m.channels());
|
|
write() << "rng1" << "{" << "x" << x << "y" << y;
|
|
if(cn > 0) write() << "cn" << cn;
|
|
write() << "val" << getElem(m, y, x, cn) << "}";
|
|
|
|
x = regRNG.uniform(0, m.size.p[1]);
|
|
y = regRNG.uniform(0, m.size.p[0]);
|
|
cn = regRNG.uniform(0, m.channels());
|
|
write() << "rng2" << "{" << "x" << x << "y" << y;
|
|
if (cn > 0) write() << "cn" << cn;
|
|
write() << "val" << getElem(m, y, x, cn) << "}";
|
|
}
|
|
|
|
void Regression::verify(cv::FileNode node, cv::Mat actual, double eps, std::string argname, ERROR_TYPE err)
|
|
{
|
|
if (!actual.empty() && actual.dims < 2) return;
|
|
|
|
double expect_min = (double)node["min"];
|
|
double expect_max = (double)node["max"];
|
|
|
|
if (err == ERROR_RELATIVE)
|
|
eps *= std::max(std::abs(expect_min), std::abs(expect_max));
|
|
|
|
double actual_min, actual_max;
|
|
cv::minMaxIdx(actual, &actual_min, &actual_max);
|
|
|
|
ASSERT_NEAR(expect_min, actual_min, eps)
|
|
<< argname << " has unexpected minimal value" << std::endl;
|
|
ASSERT_NEAR(expect_max, actual_max, eps)
|
|
<< argname << " has unexpected maximal value" << std::endl;
|
|
|
|
cv::FileNode last = node["last"];
|
|
double actual_last = getElem(actual, actual.size.p[0] - 1, actual.size.p[1] - 1, actual.channels() - 1);
|
|
int expect_cols = (int)last["x"] + 1;
|
|
int expect_rows = (int)last["y"] + 1;
|
|
ASSERT_EQ(expect_cols, actual.size.p[1])
|
|
<< argname << " has unexpected number of columns" << std::endl;
|
|
ASSERT_EQ(expect_rows, actual.size.p[0])
|
|
<< argname << " has unexpected number of rows" << std::endl;
|
|
|
|
double expect_last = (double)last["val"];
|
|
ASSERT_NEAR(expect_last, actual_last, eps)
|
|
<< argname << " has unexpected value of the last element" << std::endl;
|
|
|
|
cv::FileNode rng1 = node["rng1"];
|
|
int x1 = rng1["x"];
|
|
int y1 = rng1["y"];
|
|
int cn1 = rng1["cn"];
|
|
|
|
double expect_rng1 = (double)rng1["val"];
|
|
// it is safe to use x1 and y1 without checks here because we have already
|
|
// verified that mat size is the same as recorded
|
|
double actual_rng1 = getElem(actual, y1, x1, cn1);
|
|
|
|
ASSERT_NEAR(expect_rng1, actual_rng1, eps)
|
|
<< argname << " has unexpected value of the ["<< x1 << ":" << y1 << ":" << cn1 <<"] element" << std::endl;
|
|
|
|
cv::FileNode rng2 = node["rng2"];
|
|
int x2 = rng2["x"];
|
|
int y2 = rng2["y"];
|
|
int cn2 = rng2["cn"];
|
|
|
|
double expect_rng2 = (double)rng2["val"];
|
|
double actual_rng2 = getElem(actual, y2, x2, cn2);
|
|
|
|
ASSERT_NEAR(expect_rng2, actual_rng2, eps)
|
|
<< argname << " has unexpected value of the ["<< x2 << ":" << y2 << ":" << cn2 <<"] element" << std::endl;
|
|
}
|
|
|
|
void Regression::write(cv::InputArray array)
|
|
{
|
|
write() << "kind" << array.kind();
|
|
write() << "type" << array.type();
|
|
if (isVector(array))
|
|
{
|
|
int total = (int)array.total();
|
|
int idx = regRNG.uniform(0, total);
|
|
write() << "len" << total;
|
|
write() << "idx" << idx;
|
|
|
|
cv::Mat m = array.getMat(idx);
|
|
|
|
if (m.total() * m.channels() < 26) //5x5 or smaller
|
|
write() << "val" << m;
|
|
else
|
|
write(m);
|
|
}
|
|
else
|
|
{
|
|
if (array.total() * array.channels() < 26) //5x5 or smaller
|
|
write() << "val" << array.getMat();
|
|
else
|
|
write(array.getMat());
|
|
}
|
|
}
|
|
|
|
static int countViolations(const cv::Mat& expected, const cv::Mat& actual, const cv::Mat& diff, double eps, double* max_violation = 0, double* max_allowed = 0)
|
|
{
|
|
cv::Mat diff64f;
|
|
diff.reshape(1).convertTo(diff64f, CV_64F);
|
|
|
|
cv::Mat expected_abs = cv::abs(expected.reshape(1));
|
|
cv::Mat actual_abs = cv::abs(actual.reshape(1));
|
|
cv::Mat maximum, mask;
|
|
cv::max(expected_abs, actual_abs, maximum);
|
|
cv::multiply(maximum, cv::Vec<double, 1>(eps), maximum, CV_64F);
|
|
cv::compare(diff64f, maximum, mask, cv::CMP_GT);
|
|
|
|
int v = cv::countNonZero(mask);
|
|
|
|
if (v > 0 && max_violation != 0 && max_allowed != 0)
|
|
{
|
|
int loc[10];
|
|
cv::minMaxIdx(maximum, 0, max_allowed, 0, loc, mask);
|
|
*max_violation = diff64f.at<double>(loc[1], loc[0]);
|
|
}
|
|
|
|
return v;
|
|
}
|
|
|
|
void Regression::verify(cv::FileNode node, cv::InputArray array, double eps, ERROR_TYPE err)
|
|
{
|
|
int expected_kind = (int)node["kind"];
|
|
int expected_type = (int)node["type"];
|
|
ASSERT_EQ(expected_kind, array.kind()) << " Argument \"" << node.name() << "\" has unexpected kind";
|
|
ASSERT_EQ(expected_type, array.type()) << " Argument \"" << node.name() << "\" has unexpected type";
|
|
|
|
cv::FileNode valnode = node["val"];
|
|
if (isVector(array))
|
|
{
|
|
int expected_length = (int)node["len"];
|
|
ASSERT_EQ(expected_length, (int)array.total()) << " Vector \"" << node.name() << "\" has unexpected length";
|
|
int idx = node["idx"];
|
|
|
|
cv::Mat actual = array.getMat(idx);
|
|
|
|
if (valnode.isNone())
|
|
{
|
|
ASSERT_LE((size_t)26, actual.total() * (size_t)actual.channels())
|
|
<< " \"" << node.name() << "[" << idx << "]\" has unexpected number of elements";
|
|
verify(node, actual, eps, cv::format("%s[%d]", node.name().c_str(), idx), err);
|
|
}
|
|
else
|
|
{
|
|
cv::Mat expected;
|
|
valnode >> expected;
|
|
|
|
if(expected.empty())
|
|
{
|
|
ASSERT_TRUE(actual.empty())
|
|
<< " expected empty " << node.name() << "[" << idx<< "]";
|
|
}
|
|
else
|
|
{
|
|
ASSERT_EQ(expected.size(), actual.size())
|
|
<< " " << node.name() << "[" << idx<< "] has unexpected size";
|
|
|
|
cv::Mat diff;
|
|
cv::absdiff(expected, actual, diff);
|
|
|
|
if (err == ERROR_ABSOLUTE)
|
|
{
|
|
if (!cv::checkRange(diff, true, 0, 0, eps))
|
|
{
|
|
if(expected.total() * expected.channels() < 12)
|
|
std::cout << " Expected: " << std::endl << expected << std::endl << " Actual:" << std::endl << actual << std::endl;
|
|
|
|
double max;
|
|
cv::minMaxIdx(diff.reshape(1), 0, &max);
|
|
|
|
FAIL() << " Absolute difference (=" << max << ") between argument \""
|
|
<< node.name() << "[" << idx << "]\" and expected value is greater than " << eps;
|
|
}
|
|
}
|
|
else if (err == ERROR_RELATIVE)
|
|
{
|
|
double maxv, maxa;
|
|
int violations = countViolations(expected, actual, diff, eps, &maxv, &maxa);
|
|
if (violations > 0)
|
|
{
|
|
FAIL() << " Relative difference (" << maxv << " of " << maxa << " allowed) between argument \""
|
|
<< node.name() << "[" << idx << "]\" and expected value is greater than " << eps << " in " << violations << " points";
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (valnode.isNone())
|
|
{
|
|
ASSERT_LE((size_t)26, array.total() * (size_t)array.channels())
|
|
<< " Argument \"" << node.name() << "\" has unexpected number of elements";
|
|
verify(node, array.getMat(), eps, "Argument \"" + node.name() + "\"", err);
|
|
}
|
|
else
|
|
{
|
|
cv::Mat expected;
|
|
valnode >> expected;
|
|
cv::Mat actual = array.getMat();
|
|
|
|
if(expected.empty())
|
|
{
|
|
ASSERT_TRUE(actual.empty())
|
|
<< " expected empty " << node.name();
|
|
}
|
|
else
|
|
{
|
|
ASSERT_EQ(expected.size(), actual.size())
|
|
<< " Argument \"" << node.name() << "\" has unexpected size";
|
|
|
|
cv::Mat diff;
|
|
cv::absdiff(expected, actual, diff);
|
|
|
|
if (err == ERROR_ABSOLUTE)
|
|
{
|
|
if (!cv::checkRange(diff, true, 0, 0, eps))
|
|
{
|
|
if(expected.total() * expected.channels() < 12)
|
|
std::cout << " Expected: " << std::endl << expected << std::endl << " Actual:" << std::endl << actual << std::endl;
|
|
|
|
double max;
|
|
cv::minMaxIdx(diff.reshape(1), 0, &max);
|
|
|
|
FAIL() << " Difference (=" << max << ") between argument1 \"" << node.name()
|
|
<< "\" and expected value is greater than " << eps;
|
|
}
|
|
}
|
|
else if (err == ERROR_RELATIVE)
|
|
{
|
|
double maxv, maxa;
|
|
int violations = countViolations(expected, actual, diff, eps, &maxv, &maxa);
|
|
if (violations > 0)
|
|
{
|
|
FAIL() << " Relative difference (" << maxv << " of " << maxa << " allowed) between argument \"" << node.name()
|
|
<< "\" and expected value is greater than " << eps << " in " << violations << " points";
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Regression& Regression::operator() (const std::string& name, cv::InputArray array, double eps, ERROR_TYPE err)
|
|
{
|
|
// exit if current test is already failed
|
|
if(::testing::UnitTest::GetInstance()->current_test_info()->result()->Failed()) return *this;
|
|
|
|
if(!array.empty() && array.depth() == CV_USRTYPE1)
|
|
{
|
|
ADD_FAILURE() << " Can not check regression for CV_USRTYPE1 data type for " << name;
|
|
return *this;
|
|
}
|
|
|
|
std::string nodename = getCurrentTestNodeName();
|
|
|
|
#ifdef HAVE_CUDA
|
|
static const std::string prefix = (param_run_cpu)? "CPU_" : "GPU_";
|
|
if(suiteName == "gpu")
|
|
nodename = prefix + nodename;
|
|
#endif
|
|
|
|
cv::FileNode n = rootIn[nodename];
|
|
if(n.isNone())
|
|
{
|
|
if(param_write_sanity)
|
|
{
|
|
if (nodename != currentTestNodeName)
|
|
{
|
|
if (!currentTestNodeName.empty())
|
|
write() << "}";
|
|
currentTestNodeName = nodename;
|
|
|
|
write() << nodename << "{";
|
|
}
|
|
// TODO: verify that name is alphanumeric, current error message is useless
|
|
write() << name << "{";
|
|
write(array);
|
|
write() << "}";
|
|
}
|
|
else if(param_verify_sanity)
|
|
{
|
|
ADD_FAILURE() << " No regression data for " << name << " argument";
|
|
}
|
|
}
|
|
else
|
|
{
|
|
cv::FileNode this_arg = n[name];
|
|
if (!this_arg.isMap())
|
|
ADD_FAILURE() << " No regression data for " << name << " argument";
|
|
else
|
|
verify(this_arg, array, eps, err);
|
|
}
|
|
|
|
return *this;
|
|
}
|
|
|
|
|
|
/*****************************************************************************************\
|
|
* ::perf::performance_metrics
|
|
\*****************************************************************************************/
|
|
performance_metrics::performance_metrics()
|
|
{
|
|
bytesIn = 0;
|
|
bytesOut = 0;
|
|
samples = 0;
|
|
outliers = 0;
|
|
gmean = 0;
|
|
gstddev = 0;
|
|
mean = 0;
|
|
stddev = 0;
|
|
median = 0;
|
|
min = 0;
|
|
frequency = 0;
|
|
terminationReason = TERM_UNKNOWN;
|
|
}
|
|
|
|
|
|
/*****************************************************************************************\
|
|
* ::perf::TestBase
|
|
\*****************************************************************************************/
|
|
|
|
|
|
void TestBase::Init(int argc, const char* const argv[])
|
|
{
|
|
cv::CommandLineParser args(argc, argv, command_line_keys.c_str());
|
|
if (args.get<bool>("help"))
|
|
{
|
|
args.printParams();
|
|
printf("\n\n");
|
|
return;
|
|
}
|
|
|
|
::testing::AddGlobalTestEnvironment(new PerfEnvironment);
|
|
|
|
param_max_outliers = std::min(100., std::max(0., args.get<double>("perf_max_outliers")));
|
|
param_min_samples = std::max(1u, args.get<unsigned int>("perf_min_samples"));
|
|
param_max_deviation = std::max(0., args.get<double>("perf_max_deviation"));
|
|
param_seed = args.get<uint64>("perf_seed");
|
|
param_time_limit = std::max(0., args.get<double>("perf_time_limit"));
|
|
param_force_samples = args.get<unsigned int>("perf_force_samples");
|
|
param_write_sanity = args.get<bool>("perf_write_sanity");
|
|
param_verify_sanity = args.get<bool>("perf_verify_sanity");
|
|
param_threads = args.get<int>("perf_threads");
|
|
#ifdef ANDROID
|
|
param_affinity_mask = args.get<int>("perf_affinity_mask");
|
|
log_power_checkpoints = args.get<bool>("perf_log_power_checkpoints");
|
|
#endif
|
|
|
|
#ifdef HAVE_CUDA
|
|
|
|
bool printOnly = args.get<bool>("perf_cuda_info_only");
|
|
|
|
if (printOnly)
|
|
exit(0);
|
|
|
|
param_run_cpu = args.get<bool>("perf_run_cpu");
|
|
param_cuda_device = std::max(0, std::min(cv::gpu::getCudaEnabledDeviceCount(), args.get<int>("perf_cuda_device")));
|
|
|
|
if (param_run_cpu)
|
|
printf("[----------]\n[ GPU INFO ] \tRun test suite on CPU.\n[----------]\n"), fflush(stdout);
|
|
else
|
|
{
|
|
cv::gpu::DeviceInfo info(param_cuda_device);
|
|
if (!info.isCompatible())
|
|
{
|
|
printf("[----------]\n[ FAILURE ] \tDevice %s is NOT compatible with current GPU module build.\n[----------]\n", info.name().c_str()), fflush(stdout);
|
|
exit(-1);
|
|
}
|
|
|
|
cv::gpu::setDevice(param_cuda_device);
|
|
|
|
printf("[----------]\n[ GPU INFO ] \tRun test suite on %s GPU.\n[----------]\n", info.name().c_str()), fflush(stdout);
|
|
}
|
|
#endif
|
|
|
|
// if (!args.check())
|
|
// {
|
|
// args.printErrors();
|
|
// return;
|
|
// }
|
|
|
|
timeLimitDefault = param_time_limit == 0.0 ? 1 : (int64)(param_time_limit * cv::getTickFrequency());
|
|
iterationsLimitDefault = param_force_samples == 0 ? (unsigned)(-1) : param_force_samples;
|
|
_timeadjustment = _calibrate();
|
|
}
|
|
|
|
int64 TestBase::_calibrate()
|
|
{
|
|
class _helper : public ::perf::TestBase
|
|
{
|
|
public:
|
|
performance_metrics& getMetrics() { return calcMetrics(); }
|
|
virtual void TestBody() {}
|
|
virtual void PerfTestBody()
|
|
{
|
|
//the whole system warmup
|
|
SetUp();
|
|
cv::Mat a(2048, 2048, CV_32S, cv::Scalar(1));
|
|
cv::Mat b(2048, 2048, CV_32S, cv::Scalar(2));
|
|
declare.time(30);
|
|
double s = 0;
|
|
for(declare.iterations(20); startTimer(), next(); stopTimer())
|
|
s+=a.dot(b);
|
|
declare.time(s);
|
|
|
|
//self calibration
|
|
SetUp();
|
|
for(declare.iterations(1000); startTimer(), next(); stopTimer()){}
|
|
}
|
|
};
|
|
|
|
_timeadjustment = 0;
|
|
_helper h;
|
|
h.PerfTestBody();
|
|
double compensation = h.getMetrics().min;
|
|
LOGD("Time compensation is %.0f", compensation);
|
|
return (int64)compensation;
|
|
}
|
|
|
|
#ifdef _MSC_VER
|
|
# pragma warning(push)
|
|
# pragma warning(disable:4355) // 'this' : used in base member initializer list
|
|
#endif
|
|
TestBase::TestBase(): declare(this)
|
|
{
|
|
}
|
|
#ifdef _MSC_VER
|
|
# pragma warning(pop)
|
|
#endif
|
|
|
|
|
|
void TestBase::declareArray(SizeVector& sizes, cv::InputOutputArray a, int wtype)
|
|
{
|
|
if (!a.empty())
|
|
{
|
|
sizes.push_back(std::pair<int, cv::Size>(getSizeInBytes(a), getSize(a)));
|
|
warmup(a, wtype);
|
|
}
|
|
else if (a.kind() != cv::_InputArray::NONE)
|
|
ADD_FAILURE() << " Uninitialized input/output parameters are not allowed for performance tests";
|
|
}
|
|
|
|
void TestBase::warmup(cv::InputOutputArray a, int wtype)
|
|
{
|
|
if (a.empty()) return;
|
|
if (a.kind() != cv::_InputArray::STD_VECTOR_MAT && a.kind() != cv::_InputArray::STD_VECTOR_VECTOR)
|
|
warmup_impl(a.getMat(), wtype);
|
|
else
|
|
{
|
|
size_t total = a.total();
|
|
for (size_t i = 0; i < total; ++i)
|
|
warmup_impl(a.getMat((int)i), wtype);
|
|
}
|
|
}
|
|
|
|
int TestBase::getSizeInBytes(cv::InputArray a)
|
|
{
|
|
if (a.empty()) return 0;
|
|
int total = (int)a.total();
|
|
if (a.kind() != cv::_InputArray::STD_VECTOR_MAT && a.kind() != cv::_InputArray::STD_VECTOR_VECTOR)
|
|
return total * CV_ELEM_SIZE(a.type());
|
|
|
|
int size = 0;
|
|
for (int i = 0; i < total; ++i)
|
|
size += (int)a.total(i) * CV_ELEM_SIZE(a.type(i));
|
|
|
|
return size;
|
|
}
|
|
|
|
cv::Size TestBase::getSize(cv::InputArray a)
|
|
{
|
|
if (a.kind() != cv::_InputArray::STD_VECTOR_MAT && a.kind() != cv::_InputArray::STD_VECTOR_VECTOR)
|
|
return a.size();
|
|
return cv::Size();
|
|
}
|
|
|
|
bool TestBase::next()
|
|
{
|
|
bool has_next = ++currentIter < nIters && totalTime < timeLimit;
|
|
cv::theRNG().state = param_seed; //this rng should generate same numbers for each run
|
|
|
|
#ifdef ANDROID
|
|
if (log_power_checkpoints)
|
|
{
|
|
timeval tim;
|
|
gettimeofday(&tim, NULL);
|
|
unsigned long long t1 = tim.tv_sec * 1000LLU + (unsigned long long)(tim.tv_usec / 1000.f);
|
|
|
|
if (currentIter == 1) RecordProperty("test_start", cv::format("%llu",t1).c_str());
|
|
if (!has_next) RecordProperty("test_complete", cv::format("%llu",t1).c_str());
|
|
}
|
|
#endif
|
|
return has_next;
|
|
}
|
|
|
|
void TestBase::warmup_impl(cv::Mat m, int wtype)
|
|
{
|
|
switch(wtype)
|
|
{
|
|
case WARMUP_READ:
|
|
cv::sum(m.reshape(1));
|
|
return;
|
|
case WARMUP_WRITE:
|
|
m.reshape(1).setTo(cv::Scalar::all(0));
|
|
return;
|
|
case WARMUP_RNG:
|
|
randu(m);
|
|
return;
|
|
default:
|
|
return;
|
|
}
|
|
}
|
|
|
|
unsigned int TestBase::getTotalInputSize() const
|
|
{
|
|
unsigned int res = 0;
|
|
for (SizeVector::const_iterator i = inputData.begin(); i != inputData.end(); ++i)
|
|
res += i->first;
|
|
return res;
|
|
}
|
|
|
|
unsigned int TestBase::getTotalOutputSize() const
|
|
{
|
|
unsigned int res = 0;
|
|
for (SizeVector::const_iterator i = outputData.begin(); i != outputData.end(); ++i)
|
|
res += i->first;
|
|
return res;
|
|
}
|
|
|
|
void TestBase::startTimer()
|
|
{
|
|
lastTime = cv::getTickCount();
|
|
}
|
|
|
|
void TestBase::stopTimer()
|
|
{
|
|
int64 time = cv::getTickCount();
|
|
if (lastTime == 0)
|
|
ADD_FAILURE() << " stopTimer() is called before startTimer()";
|
|
lastTime = time - lastTime;
|
|
totalTime += lastTime;
|
|
lastTime -= _timeadjustment;
|
|
if (lastTime < 0) lastTime = 0;
|
|
times.push_back(lastTime);
|
|
lastTime = 0;
|
|
}
|
|
|
|
performance_metrics& TestBase::calcMetrics()
|
|
{
|
|
if ((metrics.samples == (unsigned int)currentIter) || times.size() == 0)
|
|
return metrics;
|
|
|
|
metrics.bytesIn = getTotalInputSize();
|
|
metrics.bytesOut = getTotalOutputSize();
|
|
metrics.frequency = cv::getTickFrequency();
|
|
metrics.samples = (unsigned int)times.size();
|
|
metrics.outliers = 0;
|
|
|
|
if (metrics.terminationReason != performance_metrics::TERM_INTERRUPT && metrics.terminationReason != performance_metrics::TERM_EXCEPTION)
|
|
{
|
|
if (currentIter == nIters)
|
|
metrics.terminationReason = performance_metrics::TERM_ITERATIONS;
|
|
else if (totalTime >= timeLimit)
|
|
metrics.terminationReason = performance_metrics::TERM_TIME;
|
|
else
|
|
metrics.terminationReason = performance_metrics::TERM_UNKNOWN;
|
|
}
|
|
|
|
std::sort(times.begin(), times.end());
|
|
|
|
//estimate mean and stddev for log(time)
|
|
double gmean = 0;
|
|
double gstddev = 0;
|
|
int n = 0;
|
|
for(TimeVector::const_iterator i = times.begin(); i != times.end(); ++i)
|
|
{
|
|
double x = static_cast<double>(*i)/runsPerIteration;
|
|
if (x < DBL_EPSILON) continue;
|
|
double lx = log(x);
|
|
|
|
++n;
|
|
double delta = lx - gmean;
|
|
gmean += delta / n;
|
|
gstddev += delta * (lx - gmean);
|
|
}
|
|
|
|
gstddev = n > 1 ? sqrt(gstddev / (n - 1)) : 0;
|
|
|
|
TimeVector::const_iterator start = times.begin();
|
|
TimeVector::const_iterator end = times.end();
|
|
|
|
//filter outliers assuming log-normal distribution
|
|
//http://stackoverflow.com/questions/1867426/modeling-distribution-of-performance-measurements
|
|
int offset = 0;
|
|
if (gstddev > DBL_EPSILON)
|
|
{
|
|
double minout = exp(gmean - 3 * gstddev) * runsPerIteration;
|
|
double maxout = exp(gmean + 3 * gstddev) * runsPerIteration;
|
|
while(*start < minout) ++start, ++metrics.outliers, ++offset;
|
|
do --end, ++metrics.outliers; while(*end > maxout);
|
|
++end, --metrics.outliers;
|
|
}
|
|
|
|
metrics.min = static_cast<double>(*start)/runsPerIteration;
|
|
//calc final metrics
|
|
n = 0;
|
|
gmean = 0;
|
|
gstddev = 0;
|
|
double mean = 0;
|
|
double stddev = 0;
|
|
int m = 0;
|
|
for(; start != end; ++start)
|
|
{
|
|
double x = static_cast<double>(*start)/runsPerIteration;
|
|
if (x > DBL_EPSILON)
|
|
{
|
|
double lx = log(x);
|
|
++m;
|
|
double gdelta = lx - gmean;
|
|
gmean += gdelta / m;
|
|
gstddev += gdelta * (lx - gmean);
|
|
}
|
|
++n;
|
|
double delta = x - mean;
|
|
mean += delta / n;
|
|
stddev += delta * (x - mean);
|
|
}
|
|
|
|
metrics.mean = mean;
|
|
metrics.gmean = exp(gmean);
|
|
metrics.gstddev = m > 1 ? sqrt(gstddev / (m - 1)) : 0;
|
|
metrics.stddev = n > 1 ? sqrt(stddev / (n - 1)) : 0;
|
|
metrics.median = n % 2
|
|
? (double)times[offset + n / 2]
|
|
: 0.5 * (times[offset + n / 2] + times[offset + n / 2 - 1]);
|
|
|
|
metrics.median /= runsPerIteration;
|
|
|
|
return metrics;
|
|
}
|
|
|
|
void TestBase::validateMetrics()
|
|
{
|
|
performance_metrics& m = calcMetrics();
|
|
|
|
if (HasFailure()) return;
|
|
|
|
ASSERT_GE(m.samples, 1u)
|
|
<< " No time measurements was performed.\nstartTimer() and stopTimer() commands are required for performance tests.";
|
|
|
|
EXPECT_GE(m.samples, param_min_samples)
|
|
<< " Only a few samples are collected.\nPlease increase number of iterations or/and time limit to get reliable performance measurements.";
|
|
|
|
if (m.gstddev > DBL_EPSILON)
|
|
{
|
|
EXPECT_GT(/*m.gmean * */1., /*m.gmean * */ 2 * sinh(m.gstddev * param_max_deviation))
|
|
<< " Test results are not reliable ((mean-sigma,mean+sigma) deviation interval is greater than measured time interval).";
|
|
}
|
|
|
|
EXPECT_LE(m.outliers, std::max((unsigned int)cvCeil(m.samples * param_max_outliers / 100.), 1u))
|
|
<< " Test results are not reliable (too many outliers).";
|
|
}
|
|
|
|
void TestBase::reportMetrics(bool toJUnitXML)
|
|
{
|
|
performance_metrics& m = calcMetrics();
|
|
|
|
if (toJUnitXML)
|
|
{
|
|
RecordProperty("bytesIn", (int)m.bytesIn);
|
|
RecordProperty("bytesOut", (int)m.bytesOut);
|
|
RecordProperty("term", m.terminationReason);
|
|
RecordProperty("samples", (int)m.samples);
|
|
RecordProperty("outliers", (int)m.outliers);
|
|
RecordProperty("frequency", cv::format("%.0f", m.frequency).c_str());
|
|
RecordProperty("min", cv::format("%.0f", m.min).c_str());
|
|
RecordProperty("median", cv::format("%.0f", m.median).c_str());
|
|
RecordProperty("gmean", cv::format("%.0f", m.gmean).c_str());
|
|
RecordProperty("gstddev", cv::format("%.6f", m.gstddev).c_str());
|
|
RecordProperty("mean", cv::format("%.0f", m.mean).c_str());
|
|
RecordProperty("stddev", cv::format("%.0f", m.stddev).c_str());
|
|
}
|
|
else
|
|
{
|
|
const ::testing::TestInfo* const test_info = ::testing::UnitTest::GetInstance()->current_test_info();
|
|
const char* type_param = test_info->type_param();
|
|
const char* value_param = test_info->value_param();
|
|
|
|
#if defined(ANDROID) && defined(USE_ANDROID_LOGGING)
|
|
LOGD("[ FAILED ] %s.%s", test_info->test_case_name(), test_info->name());
|
|
#endif
|
|
|
|
if (type_param) LOGD("type = %11s", type_param);
|
|
if (value_param) LOGD("params = %11s", value_param);
|
|
|
|
switch (m.terminationReason)
|
|
{
|
|
case performance_metrics::TERM_ITERATIONS:
|
|
LOGD("termination reason: reached maximum number of iterations");
|
|
break;
|
|
case performance_metrics::TERM_TIME:
|
|
LOGD("termination reason: reached time limit");
|
|
break;
|
|
case performance_metrics::TERM_INTERRUPT:
|
|
LOGD("termination reason: aborted by the performance testing framework");
|
|
break;
|
|
case performance_metrics::TERM_EXCEPTION:
|
|
LOGD("termination reason: unhandled exception");
|
|
break;
|
|
case performance_metrics::TERM_UNKNOWN:
|
|
default:
|
|
LOGD("termination reason: unknown");
|
|
break;
|
|
};
|
|
|
|
LOGD("bytesIn =%11lu", (unsigned long)m.bytesIn);
|
|
LOGD("bytesOut =%11lu", (unsigned long)m.bytesOut);
|
|
if (nIters == (unsigned int)-1 || m.terminationReason == performance_metrics::TERM_ITERATIONS)
|
|
LOGD("samples =%11u", m.samples);
|
|
else
|
|
LOGD("samples =%11u of %u", m.samples, nIters);
|
|
LOGD("outliers =%11u", m.outliers);
|
|
LOGD("frequency =%11.0f", m.frequency);
|
|
if (m.samples > 0)
|
|
{
|
|
LOGD("min =%11.0f = %.2fms", m.min, m.min * 1e3 / m.frequency);
|
|
LOGD("median =%11.0f = %.2fms", m.median, m.median * 1e3 / m.frequency);
|
|
LOGD("gmean =%11.0f = %.2fms", m.gmean, m.gmean * 1e3 / m.frequency);
|
|
LOGD("gstddev =%11.8f = %.2fms for 97%% dispersion interval", m.gstddev, m.gmean * 2 * sinh(m.gstddev * 3) * 1e3 / m.frequency);
|
|
LOGD("mean =%11.0f = %.2fms", m.mean, m.mean * 1e3 / m.frequency);
|
|
LOGD("stddev =%11.0f = %.2fms", m.stddev, m.stddev * 1e3 / m.frequency);
|
|
}
|
|
}
|
|
}
|
|
|
|
void TestBase::SetUp()
|
|
{
|
|
cv::theRNG().state = param_seed; // this rng should generate same numbers for each run
|
|
|
|
if (param_threads >= 0)
|
|
cv::setNumThreads(param_threads);
|
|
|
|
#ifdef ANDROID
|
|
if (param_affinity_mask)
|
|
setCurrentThreadAffinityMask(param_affinity_mask);
|
|
#endif
|
|
|
|
verified = false;
|
|
lastTime = 0;
|
|
totalTime = 0;
|
|
runsPerIteration = 1;
|
|
nIters = iterationsLimitDefault;
|
|
currentIter = (unsigned int)-1;
|
|
timeLimit = timeLimitDefault;
|
|
times.clear();
|
|
}
|
|
|
|
void TestBase::TearDown()
|
|
{
|
|
if (!HasFailure() && !verified)
|
|
ADD_FAILURE() << "The test has no sanity checks. There should be at least one check at the end of performance test.";
|
|
|
|
validateMetrics();
|
|
if (HasFailure())
|
|
reportMetrics(false);
|
|
else
|
|
{
|
|
const ::testing::TestInfo* const test_info = ::testing::UnitTest::GetInstance()->current_test_info();
|
|
const char* type_param = test_info->type_param();
|
|
const char* value_param = test_info->value_param();
|
|
if (value_param) printf("[ VALUE ] \t%s\n", value_param), fflush(stdout);
|
|
if (type_param) printf("[ TYPE ] \t%s\n", type_param), fflush(stdout);
|
|
reportMetrics(true);
|
|
}
|
|
}
|
|
|
|
std::string TestBase::getDataPath(const std::string& relativePath)
|
|
{
|
|
if (relativePath.empty())
|
|
{
|
|
ADD_FAILURE() << " Bad path to test resource";
|
|
throw PerfEarlyExitException();
|
|
}
|
|
|
|
const char *data_path_dir = getenv("OPENCV_TEST_DATA_PATH");
|
|
const char *path_separator = "/";
|
|
|
|
std::string path;
|
|
if (data_path_dir)
|
|
{
|
|
int len = (int)strlen(data_path_dir) - 1;
|
|
if (len < 0) len = 0;
|
|
path = (data_path_dir[0] == 0 ? std::string(".") : std::string(data_path_dir))
|
|
+ (data_path_dir[len] == '/' || data_path_dir[len] == '\\' ? "" : path_separator);
|
|
}
|
|
else
|
|
{
|
|
path = ".";
|
|
path += path_separator;
|
|
}
|
|
|
|
if (relativePath[0] == '/' || relativePath[0] == '\\')
|
|
path += relativePath.substr(1);
|
|
else
|
|
path += relativePath;
|
|
|
|
FILE* fp = fopen(path.c_str(), "r");
|
|
if (fp)
|
|
fclose(fp);
|
|
else
|
|
{
|
|
ADD_FAILURE() << " Requested file \"" << path << "\" does not exist.";
|
|
throw PerfEarlyExitException();
|
|
}
|
|
return path;
|
|
}
|
|
|
|
void TestBase::RunPerfTestBody()
|
|
{
|
|
try
|
|
{
|
|
this->PerfTestBody();
|
|
}
|
|
catch(PerfEarlyExitException)
|
|
{
|
|
metrics.terminationReason = performance_metrics::TERM_INTERRUPT;
|
|
return;//no additional failure logging
|
|
}
|
|
catch(cv::Exception e)
|
|
{
|
|
metrics.terminationReason = performance_metrics::TERM_EXCEPTION;
|
|
#ifdef HAVE_CUDA
|
|
if (e.code == CV_GpuApiCallError)
|
|
cv::gpu::resetDevice();
|
|
#endif
|
|
FAIL() << "Expected: PerfTestBody() doesn't throw an exception.\n Actual: it throws cv::Exception:\n " << e.what();
|
|
}
|
|
catch(std::exception e)
|
|
{
|
|
metrics.terminationReason = performance_metrics::TERM_EXCEPTION;
|
|
FAIL() << "Expected: PerfTestBody() doesn't throw an exception.\n Actual: it throws std::exception:\n " << e.what();
|
|
}
|
|
catch(...)
|
|
{
|
|
metrics.terminationReason = performance_metrics::TERM_EXCEPTION;
|
|
FAIL() << "Expected: PerfTestBody() doesn't throw an exception.\n Actual: it throws...";
|
|
}
|
|
}
|
|
|
|
/*****************************************************************************************\
|
|
* ::perf::TestBase::_declareHelper
|
|
\*****************************************************************************************/
|
|
TestBase::_declareHelper& TestBase::_declareHelper::iterations(unsigned int n)
|
|
{
|
|
test->times.clear();
|
|
test->times.reserve(n);
|
|
test->nIters = std::min(n, TestBase::iterationsLimitDefault);
|
|
test->currentIter = (unsigned int)-1;
|
|
return *this;
|
|
}
|
|
|
|
TestBase::_declareHelper& TestBase::_declareHelper::time(double timeLimitSecs)
|
|
{
|
|
test->times.clear();
|
|
test->currentIter = (unsigned int)-1;
|
|
test->timeLimit = (int64)(timeLimitSecs * cv::getTickFrequency());
|
|
return *this;
|
|
}
|
|
|
|
TestBase::_declareHelper& TestBase::_declareHelper::tbb_threads(int n)
|
|
{
|
|
cv::setNumThreads(n);
|
|
return *this;
|
|
}
|
|
|
|
TestBase::_declareHelper& TestBase::_declareHelper::runs(unsigned int runsNumber)
|
|
{
|
|
test->runsPerIteration = runsNumber;
|
|
return *this;
|
|
}
|
|
|
|
TestBase::_declareHelper& TestBase::_declareHelper::in(cv::InputOutputArray a1, int wtype)
|
|
{
|
|
if (!test->times.empty()) return *this;
|
|
TestBase::declareArray(test->inputData, a1, wtype);
|
|
return *this;
|
|
}
|
|
|
|
TestBase::_declareHelper& TestBase::_declareHelper::in(cv::InputOutputArray a1, cv::InputOutputArray a2, int wtype)
|
|
{
|
|
if (!test->times.empty()) return *this;
|
|
TestBase::declareArray(test->inputData, a1, wtype);
|
|
TestBase::declareArray(test->inputData, a2, wtype);
|
|
return *this;
|
|
}
|
|
|
|
TestBase::_declareHelper& TestBase::_declareHelper::in(cv::InputOutputArray a1, cv::InputOutputArray a2, cv::InputOutputArray a3, int wtype)
|
|
{
|
|
if (!test->times.empty()) return *this;
|
|
TestBase::declareArray(test->inputData, a1, wtype);
|
|
TestBase::declareArray(test->inputData, a2, wtype);
|
|
TestBase::declareArray(test->inputData, a3, wtype);
|
|
return *this;
|
|
}
|
|
|
|
TestBase::_declareHelper& TestBase::_declareHelper::in(cv::InputOutputArray a1, cv::InputOutputArray a2, cv::InputOutputArray a3, cv::InputOutputArray a4, int wtype)
|
|
{
|
|
if (!test->times.empty()) return *this;
|
|
TestBase::declareArray(test->inputData, a1, wtype);
|
|
TestBase::declareArray(test->inputData, a2, wtype);
|
|
TestBase::declareArray(test->inputData, a3, wtype);
|
|
TestBase::declareArray(test->inputData, a4, wtype);
|
|
return *this;
|
|
}
|
|
|
|
TestBase::_declareHelper& TestBase::_declareHelper::out(cv::InputOutputArray a1, int wtype)
|
|
{
|
|
if (!test->times.empty()) return *this;
|
|
TestBase::declareArray(test->outputData, a1, wtype);
|
|
return *this;
|
|
}
|
|
|
|
TestBase::_declareHelper& TestBase::_declareHelper::out(cv::InputOutputArray a1, cv::InputOutputArray a2, int wtype)
|
|
{
|
|
if (!test->times.empty()) return *this;
|
|
TestBase::declareArray(test->outputData, a1, wtype);
|
|
TestBase::declareArray(test->outputData, a2, wtype);
|
|
return *this;
|
|
}
|
|
|
|
TestBase::_declareHelper& TestBase::_declareHelper::out(cv::InputOutputArray a1, cv::InputOutputArray a2, cv::InputOutputArray a3, int wtype)
|
|
{
|
|
if (!test->times.empty()) return *this;
|
|
TestBase::declareArray(test->outputData, a1, wtype);
|
|
TestBase::declareArray(test->outputData, a2, wtype);
|
|
TestBase::declareArray(test->outputData, a3, wtype);
|
|
return *this;
|
|
}
|
|
|
|
TestBase::_declareHelper& TestBase::_declareHelper::out(cv::InputOutputArray a1, cv::InputOutputArray a2, cv::InputOutputArray a3, cv::InputOutputArray a4, int wtype)
|
|
{
|
|
if (!test->times.empty()) return *this;
|
|
TestBase::declareArray(test->outputData, a1, wtype);
|
|
TestBase::declareArray(test->outputData, a2, wtype);
|
|
TestBase::declareArray(test->outputData, a3, wtype);
|
|
TestBase::declareArray(test->outputData, a4, wtype);
|
|
return *this;
|
|
}
|
|
|
|
TestBase::_declareHelper::_declareHelper(TestBase* t) : test(t)
|
|
{
|
|
}
|
|
|
|
/*****************************************************************************************\
|
|
* miscellaneous
|
|
\*****************************************************************************************/
|
|
|
|
namespace {
|
|
struct KeypointComparator
|
|
{
|
|
std::vector<cv::KeyPoint>& pts_;
|
|
comparators::KeypointGreater cmp;
|
|
|
|
KeypointComparator(std::vector<cv::KeyPoint>& pts) : pts_(pts), cmp() {}
|
|
|
|
bool operator()(int idx1, int idx2) const
|
|
{
|
|
return cmp(pts_[idx1], pts_[idx2]);
|
|
}
|
|
private:
|
|
const KeypointComparator& operator=(const KeypointComparator&); // quiet MSVC
|
|
};
|
|
}//namespace
|
|
|
|
void perf::sort(std::vector<cv::KeyPoint>& pts, cv::InputOutputArray descriptors)
|
|
{
|
|
cv::Mat desc = descriptors.getMat();
|
|
|
|
CV_Assert(pts.size() == (size_t)desc.rows);
|
|
cv::AutoBuffer<int> idxs(desc.rows);
|
|
|
|
for (int i = 0; i < desc.rows; ++i)
|
|
idxs[i] = i;
|
|
|
|
std::sort((int*)idxs, (int*)idxs + desc.rows, KeypointComparator(pts));
|
|
|
|
std::vector<cv::KeyPoint> spts(pts.size());
|
|
cv::Mat sdesc(desc.size(), desc.type());
|
|
|
|
for(int j = 0; j < desc.rows; ++j)
|
|
{
|
|
spts[j] = pts[idxs[j]];
|
|
cv::Mat row = sdesc.row(j);
|
|
desc.row(idxs[j]).copyTo(row);
|
|
}
|
|
|
|
spts.swap(pts);
|
|
sdesc.copyTo(desc);
|
|
}
|
|
|
|
/*****************************************************************************************\
|
|
* ::perf::GpuPerf
|
|
\*****************************************************************************************/
|
|
#ifdef HAVE_CUDA
|
|
bool perf::GpuPerf::targetDevice()
|
|
{
|
|
return !param_run_cpu;
|
|
}
|
|
#endif
|
|
|
|
/*****************************************************************************************\
|
|
* ::perf::PrintTo
|
|
\*****************************************************************************************/
|
|
namespace perf
|
|
{
|
|
|
|
void PrintTo(const MatType& t, ::std::ostream* os)
|
|
{
|
|
switch( CV_MAT_DEPTH((int)t) )
|
|
{
|
|
case CV_8U: *os << "8U"; break;
|
|
case CV_8S: *os << "8S"; break;
|
|
case CV_16U: *os << "16U"; break;
|
|
case CV_16S: *os << "16S"; break;
|
|
case CV_32S: *os << "32S"; break;
|
|
case CV_32F: *os << "32F"; break;
|
|
case CV_64F: *os << "64F"; break;
|
|
case CV_USRTYPE1: *os << "USRTYPE1"; break;
|
|
default: *os << "INVALID_TYPE"; break;
|
|
}
|
|
*os << 'C' << CV_MAT_CN((int)t);
|
|
}
|
|
|
|
} //namespace perf
|
|
|
|
/*****************************************************************************************\
|
|
* ::cv::PrintTo
|
|
\*****************************************************************************************/
|
|
namespace cv {
|
|
|
|
void PrintTo(const Size& sz, ::std::ostream* os)
|
|
{
|
|
*os << /*"Size:" << */sz.width << "x" << sz.height;
|
|
}
|
|
|
|
} // namespace cv
|
|
|