diff --git a/modules/3d/doc/3d.bib b/modules/3d/doc/3d.bib index bd3e874a16..d9c00e7722 100644 --- a/modules/3d/doc/3d.bib +++ b/modules/3d/doc/3d.bib @@ -77,3 +77,13 @@ editor={H.G. Maas and D. Schneider}, booktitle={ISPRS 2006 : Proceedings of the ISPRS commission V symposium Vol. 35, part 6 : image engineering and vision metrology, Dresden, Germany 25-27 September 2006} } + +@inproceedings{Terzakis2020SQPnP, + title={A Consistently Fast and Globally Optimal Solution to the Perspective-n-Point Problem}, + author={George Terzakis and Manolis Lourakis}, + booktitle={European Conference on Computer Vision}, + pages={478--494}, + year={2020}, + publisher={Springer International Publishing}, + url={https://www.ecva.net/papers/eccv_2020/papers_ECCV/papers/123460460.pdf} +} diff --git a/modules/3d/src/undistort.dispatch.cpp b/modules/3d/src/undistort.dispatch.cpp index 493144c40c..38a2dc6923 100644 --- a/modules/3d/src/undistort.dispatch.cpp +++ b/modules/3d/src/undistort.dispatch.cpp @@ -417,19 +417,27 @@ static void undistortPointsInternal( const Mat& _src, Mat& _dst, const Mat& _cam x = srcd[i*sstep].x; y = srcd[i*sstep].y; } + // [u, v]^T = [fx * x''' + cx, fy * y''' + cy]^T => + // [x''', y''']^T = [(u - cx) / fx, (v - cy) / fy]^T u = x; v = y; x = (x - cx)*ifx; y = (y - cy)*ify; if( haveDistCoeffs ) { // compensate tilt distortion + // s * [x''', y''', 1]^T = matTilt * [x'', y'', 1]^T => + // s * matTilt^{-1} * [x''', y''', 1]^T = [x'', y'', 1]^T => + // (invMatTilt := matTilt^{-1}, vecUntilt := invMatTilt * [x''', y''', 1]^T) + // s * vecUntilt = [x'', y'', 1]^T => + // s * vecUntilt_1 = x'', s * vecUntilt_2 = y'', s * vecUntilt_3 = 1 => + // invProj := s = 1 / vecUntilt_3, x'' = invProj * vecUntilt_1, y'' = invProj * vecUntilt_2 cv::Vec3d vecUntilt = invMatTilt * cv::Vec3d(x, y, 1); double invProj = vecUntilt(2) ? 1./vecUntilt(2) : 1; x0 = x = invProj * vecUntilt(0); y0 = y = invProj * vecUntilt(1); double error = std::numeric_limits::max(); - // compensate distortion iteratively + // compensate distortion iteratively using fixed-point iteration for( int j = 0; ; j++ ) { @@ -437,7 +445,9 @@ static void undistortPointsInternal( const Mat& _src, Mat& _dst, const Mat& _cam break; if ((criteria.type & TermCriteria::EPS) && error < criteria.epsilon) break; + // r^2 = x'^2 + y'^2 double r2 = x*x + y*y; + // icdist := (1 + k4 * r^2 + k5 * r^4 + k6 * r^6) / (1 + k1 * r^2 + k2 * r^4 + k3 * r^6) double icdist = (1 + ((k[7]*r2 + k[6])*r2 + k[5])*r2)/(1 + ((k[4]*r2 + k[1])*r2 + k[0])*r2); if (icdist < 0) // test: undistortPoints.regression_14583 { @@ -445,8 +455,15 @@ static void undistortPointsInternal( const Mat& _src, Mat& _dst, const Mat& _cam y = (v - cy)*ify; break; } + // deltaX := 2 * p1 * x' * y' + p2 * (r^2 + 2 * x'^2) + s1 * r^2 + s2 * r^4 + // deltaY := p1 * (r^2 + 2 * y'^2) + 2 * p2 * x' * y' + s3 * r^2 + s4 * r^4 double deltaX = 2*k[2]*x*y + k[3]*(r2 + 2*x*x)+ k[8]*r2+k[9]*r2*r2; double deltaY = k[2]*(r2 + 2*y*y) + 2*k[3]*x*y+ k[10]*r2+k[11]*r2*r2; + // [x'', y'']^T = [x' / icdist + deltaX, y' / icdist + deltaY]^T => + // [x', y']^T = [(x'' - deltaX) * icdist, (y'' - deltaY) * icdist]^T => + // x' = f1(x') := (x'' - deltaX) * icdist, y' = f2(y') := (y'' - deltaY) * icdist + // Fixed-point iteration: + // new_x' = f1(x') = (x'' - deltaX) * icdist, new_y' = f2(y') = (y'' - deltaY) * icdist x = (x0 - deltaX)*icdist; y = (y0 - deltaY)*icdist; @@ -456,22 +473,33 @@ static void undistortPointsInternal( const Mat& _src, Mat& _dst, const Mat& _cam double xd, yd, xd0, yd0; Vec3d vecTilt; + // r^2 = x'^2 + y'^2 r2 = x*x + y*y; r4 = r2*r2; r6 = r4*r2; a1 = 2*x*y; a2 = r2 + 2*x*x; a3 = r2 + 2*y*y; + // cdist := 1 + k1 * r^2 + k2 * r^4 + k3 * r^6 cdist = 1 + k[0]*r2 + k[1]*r4 + k[4]*r6; + // icdist2 := 1 / (1 + k4 * r^2 + k5 * r^4 + k6 * r^6) icdist2 = 1./(1 + k[5]*r2 + k[6]*r4 + k[7]*r6); + // x'' = x' * cdist * icdist2 + 2 * p1 * x' * y' + p2 * (r^2 + 2 * x'^2) + s1 * r^2 + s2 * r^4 + // y'' = y' * cdist * icdist2 + p1 * (r^2 + 2 * y'^2) + 2 * p2 * x' * y' + s3 * r^2 + s4 * r^4 xd0 = x*cdist*icdist2 + k[2]*a1 + k[3]*a2 + k[8]*r2+k[9]*r4; yd0 = y*cdist*icdist2 + k[2]*a3 + k[3]*a1 + k[10]*r2+k[11]*r4; + // s * [x''', y''', 1]^T = matTilt * [x'', y'', 1]^T => + // (vecTilt := matTilt * [x'', y'', 1]^T) + // s * [x''', y''', 1]^T = vecTilt => + // s * x''' = vecTilt_1, s * y''' = vecTilt_2, s = vecTilt_3 => + // invProj := 1 / s = 1 / vecTilt_3, x''' = invProj * vecTilt_1, y''' = invProj * vecTilt_2 vecTilt = matTilt*cv::Vec3d(xd0, yd0, 1); invProj = vecTilt(2) ? 1./vecTilt(2) : 1; xd = invProj * vecTilt(0); yd = invProj * vecTilt(1); + // [u, v]^T = [fx * x''' + cx, fy * y''' + cy]^T double x_proj = xd*fx + cx; double y_proj = yd*fy + cy; diff --git a/modules/calib3d/include/opencv2/calib3d.hpp b/modules/calib3d/include/opencv2/calib3d.hpp index 8a0edb0c69..60b2c07c12 100644 --- a/modules/calib3d/include/opencv2/calib3d.hpp +++ b/modules/calib3d/include/opencv2/calib3d.hpp @@ -4096,7 +4096,7 @@ namespace fisheye may additionally scale and shift the result by using a different matrix. @param new_size the new size - The function transforms an image to compensate radial and tangential lens distortion. + The function transforms an image to compensate radial lens distortion. The function is simply a combination of #fisheye::initUndistortRectifyMap (with unity R ) and #remap (with bilinear interpolation). See the former function for details of the transformation being diff --git a/modules/core/CMakeLists.txt b/modules/core/CMakeLists.txt index a1d66f1142..e7b83358be 100644 --- a/modules/core/CMakeLists.txt +++ b/modules/core/CMakeLists.txt @@ -167,7 +167,7 @@ if(HAVE_CUDA) message(FATAL_ERROR "CUDA: OpenCV requires enabled 'cudev' module from 'opencv_contrib' repository: https://github.com/opencv/opencv_contrib") endif() if(ENABLE_CUDA_FIRST_CLASS_LANGUAGE) - ocv_module_include_directories(${CUDAToolkit_INCLUDE_DIRS}) + list(APPEND extra_libs CUDA::cudart${CUDA_LIB_EXT}) endif() ocv_warnings_disable(CMAKE_CXX_FLAGS -Wundef -Wenum-compare -Wunused-function -Wshadow) endif() @@ -207,7 +207,11 @@ if(HAVE_OPENMP AND DEFINED OpenMP_CXX_LIBRARIES AND OpenMP_CXX_LIBRARIES) ocv_target_link_libraries(${the_module} LINK_PRIVATE "${OpenMP_CXX_LIBRARIES}") endif() -ocv_add_accuracy_tests() +set(test_libs "") +if(HAVE_CUDA AND ENABLE_CUDA_FIRST_CLASS_LANGUAGE) + list(APPEND test_libs CUDA::cudart${CUDA_LIB_EXT}) +endif() +ocv_add_accuracy_tests(${test_libs}) ocv_add_perf_tests() if (TARGET opencv_test_core AND HAVE_CUDA) diff --git a/modules/core/include/opencv2/core.hpp b/modules/core/include/opencv2/core.hpp index 4f6497e87c..f9c575cc20 100644 --- a/modules/core/include/opencv2/core.hpp +++ b/modules/core/include/opencv2/core.hpp @@ -1663,9 +1663,13 @@ elements. CV_EXPORTS_W bool checkRange(InputArray a, bool quiet = true, CV_OUT Point* pos = 0, double minVal = -DBL_MAX, double maxVal = DBL_MAX); -/** @brief Replaces NaNs by given number -@param a input/output matrix (CV_32F or CV_64F type) -@param val value to convert the NaNs +/** @brief Replaces NaNs (Not-a-Number values) in a matrix with the specified value. + +This function modifies the input matrix in-place. +The input matrix must be of type `CV_32F` or `CV_64F`; other types are not supported. + +@param a Input/output matrix (CV_32F or CV_64F type). +@param val Value used to replace NaNs (defaults to 0). */ CV_EXPORTS_W void patchNaNs(InputOutputArray a, double val = 0); diff --git a/modules/dnn/CMakeLists.txt b/modules/dnn/CMakeLists.txt index 437e7afd14..fcb2e6374e 100644 --- a/modules/dnn/CMakeLists.txt +++ b/modules/dnn/CMakeLists.txt @@ -179,7 +179,7 @@ if(OPENCV_DNN_CUDA AND HAVE_CUDA AND HAVE_CUBLAS AND HAVE_CUDNN) endforeach() unset(CC_LIST) if(ENABLE_CUDA_FIRST_CLASS_LANGUAGE) - list(APPEND libs ${CUDNN_LIBRARIES} CUDA::cublas${CUDA_LIB_EXT}) + list(APPEND libs CUDA::cudart${CUDA_LIB_EXT} ${CUDNN_LIBRARIES} CUDA::cublas${CUDA_LIB_EXT}) if(NOT CUDA_VERSION VERSION_LESS 10.1) list(APPEND libs CUDA::cublasLt${CUDA_LIB_EXT}) endif() diff --git a/modules/dnn/src/layers/cpu_kernels/convolution.cpp b/modules/dnn/src/layers/cpu_kernels/convolution.cpp index 49dcc7fdcc..ddd6d2bec0 100644 --- a/modules/dnn/src/layers/cpu_kernels/convolution.cpp +++ b/modules/dnn/src/layers/cpu_kernels/convolution.cpp @@ -2047,6 +2047,85 @@ static inline void convBlock4x4(int np, const float* a, const float* b, float* c } #endif +#if defined(__EMSCRIPTEN__) +template +static inline void conv2xNR( + int np, + const float* a, + const float* b, + float* c, int ldc, + bool init_c) +{ + static_assert(NR % 8 == 0, "NR must be a multiple of 8"); + float* d0 = c + (RowBase + 0) * ldc; + float* d1 = c + (RowBase + 1) * ldc; + + // p = 0 + { + const float* bp = b; + const float a0 = a[RowBase + 0]; + const float a1 = a[RowBase + 1]; + + if (init_c) + { + for (int j = 0; j < NR; j += 8) + { + const float b0=bp[j+0], b1=bp[j+1], b2=bp[j+2], b3=bp[j+3]; + const float b4=bp[j+4], b5=bp[j+5], b6=bp[j+6], b7=bp[j+7]; + d0[j+0] = b0*a0; d0[j+1] = b1*a0; d0[j+2] = b2*a0; d0[j+3] = b3*a0; + d0[j+4] = b4*a0; d0[j+5] = b5*a0; d0[j+6] = b6*a0; d0[j+7] = b7*a0; + d1[j+0] = b0*a1; d1[j+1] = b1*a1; d1[j+2] = b2*a1; d1[j+3] = b3*a1; + d1[j+4] = b4*a1; d1[j+5] = b5*a1; d1[j+6] = b6*a1; d1[j+7] = b7*a1; + } + } else + { + for (int j = 0; j < NR; j += 8) + { + const float b0=bp[j+0], b1=bp[j+1], b2=bp[j+2], b3=bp[j+3]; + const float b4=bp[j+4], b5=bp[j+5], b6=bp[j+6], b7=bp[j+7]; + d0[j+0] += b0*a0; d0[j+1] += b1*a0; d0[j+2] += b2*a0; d0[j+3] += b3*a0; + d0[j+4] += b4*a0; d0[j+5] += b5*a0; d0[j+6] += b6*a0; d0[j+7] += b7*a0; + d1[j+0] += b0*a1; d1[j+1] += b1*a1; d1[j+2] += b2*a1; d1[j+3] += b3*a1; + d1[j+4] += b4*a1; d1[j+5] += b5*a1; d1[j+6] += b6*a1; d1[j+7] += b7*a1; + } + } + } + + // p = 1..np-1 + for (int p = 1; p < np; ++p) + { + const float* bp = b + p * NR; + const int aoff = p * 4 + RowBase; + const float a0 = a[aoff + 0]; + const float a1 = a[aoff + 1]; + + for (int j = 0; j < NR; j += 8) + { + const float b0=bp[j+0], b1=bp[j+1], b2=bp[j+2], b3=bp[j+3]; + const float b4=bp[j+4], b5=bp[j+5], b6=bp[j+6], b7=bp[j+7]; + d0[j+0] += b0*a0; d0[j+1] += b1*a0; d0[j+2] += b2*a0; d0[j+3] += b3*a0; + d0[j+4] += b4*a0; d0[j+5] += b5*a0; d0[j+6] += b6*a0; d0[j+7] += b7*a0; + d1[j+0] += b0*a1; d1[j+1] += b1*a1; d1[j+2] += b2*a1; d1[j+3] += b3*a1; + d1[j+4] += b4*a1; d1[j+5] += b5*a1; d1[j+6] += b6*a1; d1[j+7] += b7*a1; + } + } +} + +// MR == 4, outLen == 24, scalar (no SIMD128) +static inline void convBlockNoSIMD4x24( + int np, + const float* a, + const float* b, + float* c, int ldc, + bool init_c, + int convNR) +{ + CV_Assert(np > 0 && convNR == 24); + conv2xNR<0, 24>(np, a, b, c, ldc, init_c); // rows 0 & 1 + conv2xNR<2, 24>(np, a, b, c, ldc, init_c); // rows 2 & 3 +} +#endif + static inline void convBlockNoSIMD(int np, const float* a, const float* b, float* c, int ldc, bool init_c, const int outLen, const int convMR, const int convNR) { @@ -2106,8 +2185,17 @@ void convBlock_F32(int np, const float* a, const float* b, float* c, int ldc, bo return; } convBlockNoSIMD(np, a, b, c, ldc, init_c, outLen, convMR, convNR); +#elif defined(__EMSCRIPTEN__) + CV_Assert(convMR == 4); + if (outLen == 24 && convNR == 24) + { + convBlockNoSIMD4x24(np, a, b, c, ldc, init_c, convNR); + return; + } + convBlockNoSIMD(np, a, b, c, ldc, init_c, outLen, convMR, convNR); #else convBlockNoSIMD(np, a, b, c, ldc, init_c, outLen, convMR, convNR); + return; #endif } diff --git a/modules/imgcodecs/src/grfmt_tiff.cpp b/modules/imgcodecs/src/grfmt_tiff.cpp index 3d8d6a4603..a9364b59c6 100644 --- a/modules/imgcodecs/src/grfmt_tiff.cpp +++ b/modules/imgcodecs/src/grfmt_tiff.cpp @@ -52,11 +52,18 @@ #include "grfmt_tiff.hpp" #include +#include +#include #include "tiff.h" #include "tiffio.h" #include "tiffvers.h" // For TIFFLIB_VERSION +#ifdef TIFFLIB_AT_LEAST +#if TIFFLIB_AT_LEAST(4, 5, 0) +#define OCV_HAVE_TIFF_OPEN_OPTIONS +#endif +#endif namespace cv { @@ -79,30 +86,116 @@ static void cv_tiffCloseHandle(void* handle) TIFFClose((TIFF*)handle); } +static std::string vformat(const char* fmt, va_list ap) +{ + if (!fmt) + return {}; + va_list ap_copy; + va_copy(ap_copy, ap); + const int len = std::vsnprintf(nullptr, 0, fmt, ap_copy); + va_end(ap_copy); + if (len < 0) + return fmt; + + std::string buf(static_cast(len) + 1, '\0'); + std::vsnprintf(&buf[0], buf.size(), fmt, ap); + buf.pop_back(); + return buf; +} + +static std::string formatTiffMessage(const char* module, const char* fmt, va_list ap) +{ + std::stringstream ss; + if (module && module[0] != '\0') + ss << module << ": "; + ss << cv::vformat(fmt, ap); + return ss.str(); +} + +static int TIFF_Error(TIFF *, void *, const char* module, const char* fmt, va_list ap) +{ + CV_LOG_ERROR(NULL, formatTiffMessage(module, fmt, ap)); + return 1; +} + +static int TIFF_Warning(TIFF *, void *, const char* module, const char* fmt, va_list ap) +{ + CV_LOG_WARNING(NULL, formatTiffMessage(module, fmt, ap)); + return 1; +} + +#ifdef OCV_HAVE_TIFF_OPEN_OPTIONS +static TIFFOpenOptions* cv_tiffCreateOptions() +{ + auto opts = TIFFOpenOptionsAlloc(); + TIFFOpenOptionsSetErrorHandlerExtR(opts, &TIFF_Error, nullptr); + TIFFOpenOptionsSetWarningHandlerExtR(opts, &TIFF_Warning, nullptr); +#if TIFFLIB_AT_LEAST(4, 7, 1) + TIFFOpenOptionsSetWarnAboutUnknownTags(opts, 1); +#endif + return opts; +} +#endif + +static TIFF* cv_tiffOpen(const char* filename, const char* mode) +{ +#ifdef OCV_HAVE_TIFF_OPEN_OPTIONS + auto opts = cv_tiffCreateOptions(); + auto tiff = TIFFOpenExt(filename, mode, opts); + TIFFOpenOptionsFree(opts); + return tiff; +#else + return TIFFOpen(filename, mode); +#endif +} + +static TIFF* cv_tiffClientOpen(const char* name, const char* mode, thandle_t clientdata, + TIFFReadWriteProc readproc, TIFFReadWriteProc writeproc, + TIFFSeekProc seekproc, TIFFCloseProc closeproc, + TIFFSizeProc sizeproc, TIFFMapFileProc mapproc, + TIFFUnmapFileProc unmapproc) +{ +#ifdef OCV_HAVE_TIFF_OPEN_OPTIONS + auto opts = cv_tiffCreateOptions(); + auto tiff = TIFFClientOpenExt(name, mode, clientdata, readproc, writeproc, + seekproc, closeproc, sizeproc, mapproc, unmapproc, opts); + TIFFOpenOptionsFree(opts); + return tiff; +#else + return TIFFClientOpen(name, mode, clientdata, readproc, writeproc, + seekproc, closeproc, sizeproc, mapproc, unmapproc); +#endif +} + +#ifndef OCV_HAVE_TIFF_OPEN_OPTIONS + static void cv_tiffErrorHandler(const char* module, const char* fmt, va_list ap) { - if (cv::utils::logging::getLogLevel() < cv::utils::logging::LOG_LEVEL_DEBUG) - return; - // TODO cv::vformat() with va_list parameter - fprintf(stderr, "OpenCV TIFF: "); - if (module != NULL) - fprintf(stderr, "%s: ", module); - fprintf(stderr, "Warning, "); - vfprintf(stderr, fmt, ap); - fprintf(stderr, ".\n"); + (void) TIFF_Error(nullptr, nullptr, module, fmt, ap); +} + +static void cv_tiffWarningHandler(const char* module, const char* fmt, va_list ap) +{ + (void) TIFF_Warning(nullptr, nullptr, module, fmt, ap); } static bool cv_tiffSetErrorHandler_() { TIFFSetErrorHandler(cv_tiffErrorHandler); - TIFFSetWarningHandler(cv_tiffErrorHandler); + TIFFSetWarningHandler(cv_tiffWarningHandler); return true; } +#endif + static bool cv_tiffSetErrorHandler() { +#ifndef OCV_HAVE_TIFF_OPEN_OPTIONS static bool v = cv_tiffSetErrorHandler_(); return v; +#else + return true; +#endif } static const char fmtSignTiffII[] = "II\x2a\x00"; @@ -242,7 +335,7 @@ bool TiffDecoder::readHeader() { m_buf_pos = 0; TiffDecoderBufHelper* buf_helper = new TiffDecoderBufHelper(this->m_buf, this->m_buf_pos); - tif = TIFFClientOpen( "", "r", reinterpret_cast(buf_helper), &TiffDecoderBufHelper::read, + tif = cv_tiffClientOpen( "", "r", reinterpret_cast(buf_helper), &TiffDecoderBufHelper::read, &TiffDecoderBufHelper::write, &TiffDecoderBufHelper::seek, &TiffDecoderBufHelper::close, &TiffDecoderBufHelper::size, &TiffDecoderBufHelper::map, /*unmap=*/0 ); @@ -251,7 +344,7 @@ bool TiffDecoder::readHeader() } else { - tif = TIFFOpen(m_filename.c_str(), "r"); + tif = cv_tiffOpen(m_filename.c_str(), "r"); } if (tif) m_tif.reset(tif, cv_tiffCloseHandle); @@ -1124,7 +1217,7 @@ public: { // do NOT put "wb" as the mode, because the b means "big endian" mode, not "binary" mode. // http://www.simplesystems.org/libtiff/functions/TIFFOpen.html - return TIFFClientOpen( "", "w", reinterpret_cast(this), &TiffEncoderBufHelper::read, + return cv_tiffClientOpen( "", "w", reinterpret_cast(this), &TiffEncoderBufHelper::read, &TiffEncoderBufHelper::write, &TiffEncoderBufHelper::seek, &TiffEncoderBufHelper::close, &TiffEncoderBufHelper::size, /*map=*/0, /*unmap=*/0 ); @@ -1221,7 +1314,7 @@ bool TiffEncoder::writeLibTiff( const std::vector& img_vec, const std::vect } else { - tif = TIFFOpen(m_filename.c_str(), "w"); + tif = cv_tiffOpen(m_filename.c_str(), "w"); } if (!tif) { diff --git a/modules/imgcodecs/test/test_tiff.cpp b/modules/imgcodecs/test/test_tiff.cpp index aae7ecf684..4850a22dfa 100644 --- a/modules/imgcodecs/test/test_tiff.cpp +++ b/modules/imgcodecs/test/test_tiff.cpp @@ -1285,6 +1285,17 @@ TEST(Imgcodecs_Tiff, read_bigtiff_images) } } +TEST(Imgcodecs_Tiff, read_junk) { + // Test exercises the tiff error handler integration. + // Error messages can be seen with OPENCV_LOG_LEVEL=DEBUG + const char junk[] = "II\x2a\x00\x08\x00\x00\x00\x00\x00\x00\x00"; + cv::Mat junkInputArray(1, sizeof(junk) - 1, CV_8UC1, (void*)junk); + + cv::Mat img; + ASSERT_NO_THROW(img = cv::imdecode(junkInputArray, IMREAD_UNCHANGED)); + ASSERT_TRUE(img.empty()); +} + #endif }} // namespace diff --git a/modules/js/generator/embindgen.py b/modules/js/generator/embindgen.py index e81ceb837a..430baf99ef 100644 --- a/modules/js/generator/embindgen.py +++ b/modules/js/generator/embindgen.py @@ -849,6 +849,10 @@ class JSWrapperGenerator(object): # Register the smart pointer base_class_name = variant.rettype base_class_name = base_class_name.replace("Ptr<","").replace(">","").strip() + if base_class_name not in self.classes: + new_base_class_name = '%s_%s' % (ns_id, base_class_name) + print(base_class_name, ' not found in classes for registering smart pointer using ', new_base_class_name, 'instead') + base_class_name = new_base_class_name self.classes[base_class_name].has_smart_ptr = True # Adds the external constructor diff --git a/modules/photo/CMakeLists.txt b/modules/photo/CMakeLists.txt index f65cf9e678..1d970f28f4 100644 --- a/modules/photo/CMakeLists.txt +++ b/modules/photo/CMakeLists.txt @@ -6,6 +6,6 @@ endif() ocv_define_module(photo opencv_imgproc OPTIONAL opencv_cudaarithm opencv_cudaimgproc WRAP java objc python js) -if(HAVE_CUDA AND ENABLE_CUDA_FIRST_CLASS_LANGUAGE AND HAVE_OPENCV_CUDAARITHM AND HAVE_OPENCV_CUDAIMGPROC) - ocv_target_link_libraries(${the_module} PUBLIC "CUDA::cudart${CUDA_LIB_EXT}") +if(HAVE_CUDA AND ENABLE_CUDA_FIRST_CLASS_LANGUAGE AND HAVE_opencv_cudaarithm AND HAVE_opencv_cudaimgproc) + ocv_target_link_libraries(${the_module} PRIVATE CUDA::cudart${CUDA_LIB_EXT}) endif() diff --git a/modules/photo/src/denoising.cuda.cpp b/modules/photo/src/denoising.cuda.cpp index d9e265cda9..12735dfbf6 100644 --- a/modules/photo/src/denoising.cuda.cpp +++ b/modules/photo/src/denoising.cuda.cpp @@ -46,6 +46,12 @@ #include "opencv2/opencv_modules.hpp" +#if !defined (HAVE_CUDA) || !defined(HAVE_OPENCV_CUDAARITHM) || !defined(HAVE_OPENCV_CUDAIMGPROC) +#include "opencv2/core/private/cuda_stubs.hpp" +#else +#include "opencv2/core/private.cuda.hpp" +#endif + #ifdef HAVE_OPENCV_CUDAARITHM # include "opencv2/cudaarithm.hpp" #endif @@ -59,15 +65,12 @@ using namespace cv::cuda; #if !defined (HAVE_CUDA) || !defined(HAVE_OPENCV_CUDAARITHM) || !defined(HAVE_OPENCV_CUDAIMGPROC) -#include "opencv2/core/private/cuda_stubs.hpp" void cv::cuda::nonLocalMeans(InputArray, OutputArray, float, int, int, int, Stream&) { throw_no_cuda(); } void cv::cuda::fastNlMeansDenoising(InputArray, OutputArray, float, int, int, Stream&) { throw_no_cuda(); } void cv::cuda::fastNlMeansDenoisingColored(InputArray, OutputArray, float, float, int, int, Stream&) { throw_no_cuda(); } #else -#include "opencv2/core/private.cuda.hpp" - ////////////////////////////////////////////////////////////////////////////////// //// Non Local Means Denosing (brute force) diff --git a/modules/video/include/opencv2/video/tracking.hpp b/modules/video/include/opencv2/video/tracking.hpp index 2c76ab7046..553cef7c6c 100644 --- a/modules/video/include/opencv2/video/tracking.hpp +++ b/modules/video/include/opencv2/video/tracking.hpp @@ -277,17 +277,43 @@ enum MOTION_HOMOGRAPHY = 3 }; -/** @brief Computes the Enhanced Correlation Coefficient value between two images @cite EP08 . +/** +@brief Computes the Enhanced Correlation Coefficient (ECC) value between two images -@param templateImage single-channel template image; CV_8U or CV_32F array. -@param inputImage single-channel input image to be warped to provide an image similar to - templateImage, same type as templateImage. -@param inputMask An optional mask to indicate valid values of inputImage. +The Enhanced Correlation Coefficient (ECC) is a normalized measure of similarity between two images @cite EP08. +The result lies in the range [-1, 1], where 1 corresponds to perfect similarity (modulo affine shift and scale), +0 indicates no correlation, and -1 indicates perfect negative correlation. -@sa -findTransformECC - */ +For single-channel images, the ECC is defined as: +\f[ +\mathrm{ECC}(I, T) = \frac{\sum_{x} (I(x) - \mu_I)(T(x) - \mu_T)} +{\sqrt{\sum_{x} (I(x) - \mu_I)^2} \cdot \sqrt{\sum_{x} (T(x) - \mu_T)^2}} +\f] + +For multi-channel images (e.g., 3-channel RGB), the formula generalizes to: + +\f[ +\mathrm{ECC}(I, T) = +\frac{\sum_{x} \sum_{c=1}^{C} (I_c(x) - \mu_{I_c})(T_c(x) - \mu_{T_c})} +{\sqrt{\sum_{x} \sum_{c=1}^{C} (I_c(x) - \mu_{I_c})^2} \cdot + \sqrt{\sum_{x} \sum_{c=1}^{C} (T_c(x) - \mu_{T_c})^2}} +\f] + +Where: +- \f$I_c(x), T_c(x)\f$ are the values of channel \f$c\f$ at spatial location \f$x\f$, +- \f$\mu_{I_c}, \mu_{T_c}\f$ are the mean values of channel \f$c\f$ over the masked region (if provided), +- \f$C\f$ is the number of channels (only 1 and 3 are currently supported), +- The sums run over all pixels \f$x\f$ in the image domain (optionally restricted by mask). + +@param templateImage Input template image; must have either 1 or 3 channels and be of type CV_8U, CV_16U, CV_32F, or CV_64F. +@param inputImage Input image to be compared with the template; must have the same type and number of channels as templateImage. +@param inputMask Optional single-channel mask to specify the valid region of interest in inputImage and templateImage. + +@return The ECC similarity coefficient in the range [-1, 1]. + +@sa findTransformECC +*/ CV_EXPORTS_W double computeECC(InputArray templateImage, InputArray inputImage, InputArray inputMask = noArray()); /** @example samples/cpp/image_alignment.cpp @@ -296,8 +322,8 @@ An example using the image alignment ECC algorithm /** @brief Finds the geometric transform (warp) between two images in terms of the ECC criterion @cite EP08 . -@param templateImage single-channel template image; CV_8U or CV_32F array. -@param inputImage single-channel input image which should be warped with the final warpMatrix in +@param templateImage 1 or 3 channel template image; CV_8U, CV_16U, CV_32F, CV_64F type. +@param inputImage input image which should be warped with the final warpMatrix in order to provide an image similar to templateImage, same type as templateImage. @param warpMatrix floating-point \f$2\times 3\f$ or \f$3\times 3\f$ mapping matrix (warp). @param motionType parameter, specifying the type of motion: @@ -314,7 +340,7 @@ order to provide an image similar to templateImage, same type as templateImage. criteria.epsilon defines the threshold of the increment in the correlation coefficient between two iterations (a negative criteria.epsilon makes criteria.maxcount the only termination criterion). Default values are shown in the declaration above. -@param inputMask An optional mask to indicate valid values of inputImage. +@param inputMask An optional single channel mask to indicate valid values of inputImage. @param gaussFiltSize An optional value indicating size of gaussian blur filter; (DEFAULT: 5) The function estimates the optimum transformation (warpMatrix) with respect to ECC criterion diff --git a/modules/video/perf/perf_ecc.cpp b/modules/video/perf/perf_ecc.cpp index ab38868c82..217b98fa0e 100644 --- a/modules/video/perf/perf_ecc.cpp +++ b/modules/video/perf/perf_ecc.cpp @@ -1,24 +1,22 @@ #include "perf_precomp.hpp" -namespace opencv_test -{ +namespace opencv_test { using namespace perf; CV_ENUM(MotionType, MOTION_TRANSLATION, MOTION_EUCLIDEAN, MOTION_AFFINE, MOTION_HOMOGRAPHY) +CV_ENUM(ReadFlag, IMREAD_GRAYSCALE, IMREAD_COLOR) -typedef tuple MotionType_t; -typedef perf::TestBaseWithParam TransformationType; - - -PERF_TEST_P(TransformationType, findTransformECC, /*testing::ValuesIn(MotionType::all())*/ - testing::Values((int) MOTION_TRANSLATION, (int) MOTION_EUCLIDEAN, - (int) MOTION_AFFINE, (int) MOTION_HOMOGRAPHY) - ) -{ - Mat img = imread(getDataPath("cv/shared/fruits_ecc.png"),0); - Mat templateImage; +typedef std::tuple TestParams; +typedef perf::TestBaseWithParam ECCPerfTest; +PERF_TEST_P(ECCPerfTest, findTransformECC, + testing::Combine(testing::Values(MOTION_TRANSLATION, MOTION_EUCLIDEAN, MOTION_AFFINE, MOTION_HOMOGRAPHY), + testing::Values(IMREAD_GRAYSCALE, IMREAD_COLOR))) { int transform_type = get<0>(GetParam()); + int readFlag = get<1>(GetParam()); + + Mat img = imread(getDataPath("cv/shared/fruits_ecc.png"), readFlag); + Mat templateImage; Mat warpMat; Mat warpGround; @@ -26,46 +24,38 @@ PERF_TEST_P(TransformationType, findTransformECC, /*testing::ValuesIn(MotionType double angle; switch (transform_type) { case MOTION_TRANSLATION: - warpGround = (Mat_(2,3) << 1.f, 0.f, 7.234f, - 0.f, 1.f, 11.839f); + warpGround = (Mat_(2, 3) << 1.f, 0.f, 7.234f, 0.f, 1.f, 11.839f); - warpAffine(img, templateImage, warpGround, - Size(200,200), INTER_LINEAR + WARP_INVERSE_MAP); + warpAffine(img, templateImage, warpGround, Size(200, 200), INTER_LINEAR + WARP_INVERSE_MAP); break; case MOTION_EUCLIDEAN: - angle = CV_PI/30; + angle = CV_PI / 30; - warpGround = (Mat_(2,3) << (float)cos(angle), (float)-sin(angle), 12.123f, - (float)sin(angle), (float)cos(angle), 14.789f); - warpAffine(img, templateImage, warpGround, - Size(200,200), INTER_LINEAR + WARP_INVERSE_MAP); + warpGround = (Mat_(2, 3) << (float)cos(angle), (float)-sin(angle), 12.123f, (float)sin(angle), + (float)cos(angle), 14.789f); + warpAffine(img, templateImage, warpGround, Size(200, 200), INTER_LINEAR + WARP_INVERSE_MAP); break; case MOTION_AFFINE: - warpGround = (Mat_(2,3) << 0.98f, 0.03f, 15.523f, - -0.02f, 0.95f, 10.456f); - warpAffine(img, templateImage, warpGround, - Size(200,200), INTER_LINEAR + WARP_INVERSE_MAP); + warpGround = (Mat_(2, 3) << 0.98f, 0.03f, 15.523f, -0.02f, 0.95f, 10.456f); + warpAffine(img, templateImage, warpGround, Size(200, 200), INTER_LINEAR + WARP_INVERSE_MAP); break; case MOTION_HOMOGRAPHY: - warpGround = (Mat_(3,3) << 0.98f, 0.03f, 15.523f, - -0.02f, 0.95f, 10.456f, - 0.0002f, 0.0003f, 1.f); - warpPerspective(img, templateImage, warpGround, - Size(200,200), INTER_LINEAR + WARP_INVERSE_MAP); + warpGround = (Mat_(3, 3) << 0.98f, 0.03f, 15.523f, -0.02f, 0.95f, 10.456f, 0.0002f, 0.0003f, 1.f); + warpPerspective(img, templateImage, warpGround, Size(200, 200), INTER_LINEAR + WARP_INVERSE_MAP); break; } - TEST_CYCLE() - { - if (transform_type<3) - warpMat = Mat::eye(2,3, CV_32F); + TEST_CYCLE() { + if (transform_type < 3) + warpMat = Mat::eye(2, 3, CV_32F); else - warpMat = Mat::eye(3,3, CV_32F); + warpMat = Mat::eye(3, 3, CV_32F); findTransformECC(templateImage, img, warpMat, transform_type, - TermCriteria(TermCriteria::COUNT+TermCriteria::EPS, 5, -1)); + TermCriteria(TermCriteria::COUNT + TermCriteria::EPS, 5, -1)); } + SANITY_CHECK(warpMat, 3e-3); } -} // namespace +} // namespace opencv_test diff --git a/modules/video/src/ecc.cpp b/modules/video/src/ecc.cpp index eede0e7071..4fa45a4b32 100644 --- a/modules/video/src/ecc.cpp +++ b/modules/video/src/ecc.cpp @@ -41,30 +41,24 @@ #include "precomp.hpp" - /****************************************************************************************\ * Image Alignment (ECC algorithm) * \****************************************************************************************/ using namespace cv; -static void image_jacobian_homo_ECC(const Mat& src1, const Mat& src2, - const Mat& src3, const Mat& src4, - const Mat& src5, Mat& dst) -{ - - +static void image_jacobian_homo_ECC(const Mat& src1, const Mat& src2, const Mat& src3, const Mat& src4, const Mat& src5, + Mat& dst) { CV_Assert(src1.size() == src2.size()); CV_Assert(src1.size() == src3.size()); CV_Assert(src1.size() == src4.size()); - CV_Assert( src1.rows == dst.rows); - CV_Assert(dst.cols == (src1.cols*8)); - CV_Assert(dst.type() == CV_32FC1); + CV_Assert(src1.rows == dst.rows); + CV_Assert(dst.cols == (src1.cols * 8)); + CV_Assert(dst.type() == CV_MAKETYPE(CV_32F, src1.channels())); CV_Assert(src5.isContinuous()); - const float* hptr = src5.ptr(0); const float h0_ = hptr[0]; @@ -78,19 +72,23 @@ static void image_jacobian_homo_ECC(const Mat& src1, const Mat& src2, const int w = src1.cols; + // create denominator for all points as a block + Mat den_; + addWeighted(src3, h2_, src4, h5_, 1.0, den_); - //create denominator for all points as a block - Mat den_ = src3*h2_ + src4*h5_ + 1.0;//check the time of this! otherwise use addWeighted + // create projected points + Mat hatX_, hatY_; + addWeighted(src3, h0_, src4, h3_, 0.0, hatX_); + hatX_ += h6_; - //create projected points - Mat hatX_ = -src3*h0_ - src4*h3_ - h6_; - divide(hatX_, den_, hatX_); - Mat hatY_ = -src3*h1_ - src4*h4_ - h7_; - divide(hatY_, den_, hatY_); + addWeighted(src3, h1_, src4, h4_, 0.0, hatY_); + hatY_ += h7_; + divide(-hatY_, den_, hatY_); + divide(-hatX_, den_, hatX_); - //instead of dividing each block with den, - //just pre-divide the block of gradients (it's more efficient) + // instead of dividing each block with den, + // just pre-divide the block of gradients (it's more efficient) Mat src1Divided_; Mat src2Divided_; @@ -98,114 +96,98 @@ static void image_jacobian_homo_ECC(const Mat& src1, const Mat& src2, divide(src1, den_, src1Divided_); divide(src2, den_, src2Divided_); + // compute Jacobian blocks (8 blocks) - //compute Jacobian blocks (8 blocks) + dst.colRange(0, w) = src1Divided_.mul(src3); // 1 - dst.colRange(0, w) = src1Divided_.mul(src3);//1 + dst.colRange(w, 2 * w) = src2Divided_.mul(src3); // 2 - dst.colRange(w,2*w) = src2Divided_.mul(src3);//2 - - Mat temp_ = (hatX_.mul(src1Divided_)+hatY_.mul(src2Divided_)); - dst.colRange(2*w,3*w) = temp_.mul(src3);//3 + Mat temp_ = (hatX_.mul(src1Divided_) + hatY_.mul(src2Divided_)); + dst.colRange(2 * w, 3 * w) = temp_.mul(src3); // 3 hatX_.release(); hatY_.release(); - dst.colRange(3*w, 4*w) = src1Divided_.mul(src4);//4 + dst.colRange(3 * w, 4 * w) = src1Divided_.mul(src4); // 4 - dst.colRange(4*w, 5*w) = src2Divided_.mul(src4);//5 + dst.colRange(4 * w, 5 * w) = src2Divided_.mul(src4); // 5 - dst.colRange(5*w, 6*w) = temp_.mul(src4);//6 + dst.colRange(5 * w, 6 * w) = temp_.mul(src4); // 6 - src1Divided_.copyTo(dst.colRange(6*w, 7*w));//7 + src1Divided_.copyTo(dst.colRange(6 * w, 7 * w)); // 7 - src2Divided_.copyTo(dst.colRange(7*w, 8*w));//8 + src2Divided_.copyTo(dst.colRange(7 * w, 8 * w)); // 8 } -static void image_jacobian_euclidean_ECC(const Mat& src1, const Mat& src2, - const Mat& src3, const Mat& src4, - const Mat& src5, Mat& dst) -{ - - CV_Assert( src1.size()==src2.size()); - CV_Assert( src1.size()==src3.size()); - CV_Assert( src1.size()==src4.size()); - - CV_Assert( src1.rows == dst.rows); - CV_Assert(dst.cols == (src1.cols*3)); - CV_Assert(dst.type() == CV_32FC1); - - CV_Assert(src5.isContinuous()); - - const float* hptr = src5.ptr(0); - - const float h0 = hptr[0];//cos(theta) - const float h1 = hptr[3];//sin(theta) - - const int w = src1.cols; - - //create -sin(theta)*X -cos(theta)*Y for all points as a block -> hatX - Mat hatX = -(src3*h1) - (src4*h0); - - //create cos(theta)*X -sin(theta)*Y for all points as a block -> hatY - Mat hatY = (src3*h0) - (src4*h1); - - - //compute Jacobian blocks (3 blocks) - dst.colRange(0, w) = (src1.mul(hatX))+(src2.mul(hatY));//1 - - src1.copyTo(dst.colRange(w, 2*w));//2 - src2.copyTo(dst.colRange(2*w, 3*w));//3 -} - - -static void image_jacobian_affine_ECC(const Mat& src1, const Mat& src2, - const Mat& src3, const Mat& src4, - Mat& dst) -{ - +static void image_jacobian_euclidean_ECC(const Mat& src1, const Mat& src2, const Mat& src3, const Mat& src4, + const Mat& src5, Mat& dst) { CV_Assert(src1.size() == src2.size()); CV_Assert(src1.size() == src3.size()); CV_Assert(src1.size() == src4.size()); CV_Assert(src1.rows == dst.rows); - CV_Assert(dst.cols == (6*src1.cols)); + CV_Assert(dst.cols == (src1.cols * 3)); + CV_Assert(dst.type() == CV_MAKETYPE(CV_32F, src1.channels())); - CV_Assert(dst.type() == CV_32FC1); + CV_Assert(src5.isContinuous()); + const float* hptr = src5.ptr(0); + + const float h0 = hptr[0]; // cos(theta) + const float h1 = hptr[3]; // sin(theta) const int w = src1.cols; - //compute Jacobian blocks (6 blocks) + // create -sin(theta)*X -cos(theta)*Y for all points as a block -> hatX + Mat hatX = -(src3 * h1) - (src4 * h0); - dst.colRange(0,w) = src1.mul(src3);//1 - dst.colRange(w,2*w) = src2.mul(src3);//2 - dst.colRange(2*w,3*w) = src1.mul(src4);//3 - dst.colRange(3*w,4*w) = src2.mul(src4);//4 - src1.copyTo(dst.colRange(4*w,5*w));//5 - src2.copyTo(dst.colRange(5*w,6*w));//6 + // create cos(theta)*X -sin(theta)*Y for all points as a block -> hatY + Mat hatY = (src3 * h0) - (src4 * h1); + + // compute Jacobian blocks (3 blocks) + dst.colRange(0, w) = (src1.mul(hatX)) + (src2.mul(hatY)); // 1 + + src1.copyTo(dst.colRange(w, 2 * w)); // 2 + src2.copyTo(dst.colRange(2 * w, 3 * w)); // 3 } +static void image_jacobian_affine_ECC(const Mat& src1, const Mat& src2, const Mat& src3, const Mat& src4, Mat& dst) { + CV_Assert(src1.size() == src2.size()); + CV_Assert(src1.size() == src3.size()); + CV_Assert(src1.size() == src4.size()); -static void image_jacobian_translation_ECC(const Mat& src1, const Mat& src2, Mat& dst) -{ + CV_Assert(src1.rows == dst.rows); + CV_Assert(dst.cols == (6 * src1.cols)); - CV_Assert( src1.size()==src2.size()); - - CV_Assert( src1.rows == dst.rows); - CV_Assert(dst.cols == (src1.cols*2)); - CV_Assert(dst.type() == CV_32FC1); + CV_Assert(dst.type() == CV_MAKETYPE(CV_32F, src1.channels())); const int w = src1.cols; - //compute Jacobian blocks (2 blocks) + // compute Jacobian blocks (6 blocks) + + dst.colRange(0, w) = src1.mul(src3); // 1 + dst.colRange(w, 2 * w) = src2.mul(src3); // 2 + dst.colRange(2 * w, 3 * w) = src1.mul(src4); // 3 + dst.colRange(3 * w, 4 * w) = src2.mul(src4); // 4 + src1.copyTo(dst.colRange(4 * w, 5 * w)); // 5 + src2.copyTo(dst.colRange(5 * w, 6 * w)); // 6 +} + +static void image_jacobian_translation_ECC(const Mat& src1, const Mat& src2, Mat& dst) { + CV_Assert(src1.size() == src2.size()); + + CV_Assert(src1.rows == dst.rows); + CV_Assert(dst.cols == (src1.cols * 2)); + CV_Assert(dst.type() == CV_MAKETYPE(CV_32F, src1.channels())); + + const int w = src1.cols; + + // compute Jacobian blocks (2 blocks) src1.copyTo(dst.colRange(0, w)); - src2.copyTo(dst.colRange(w, 2*w)); + src2.copyTo(dst.colRange(w, 2 * w)); } - -static void project_onto_jacobian_ECC(const Mat& src1, const Mat& src2, Mat& dst) -{ +static void project_onto_jacobian_ECC(const Mat& src1, const Mat& src2, Mat& dst) { /* this functions is used for two types of projections. If src1.cols ==src.cols it does a blockwise multiplication (like in the outer product of vectors) of the blocks in matrices src1 and src2 and dst @@ -222,59 +204,54 @@ static void project_onto_jacobian_ECC(const Mat& src1, const Mat& src2, Mat& dst float* dstPtr = dst.ptr(0); - if (src1.cols !=src2.cols){//dst.cols==1 - w = src2.cols; - for (int i=0; i(0); const float* updatePtr = update.ptr(0); - - if (motionType == MOTION_TRANSLATION){ + if (motionType == MOTION_TRANSLATION) { mapPtr[2] += updatePtr[0]; mapPtr[5] += updatePtr[1]; } @@ -302,23 +279,23 @@ static void update_warping_matrix_ECC (Mat& map_matrix, const Mat& update, const mapPtr[2] += updatePtr[1]; mapPtr[5] += updatePtr[2]; - mapPtr[0] = mapPtr[4] = (float) cos(new_theta); - mapPtr[3] = (float) sin(new_theta); + mapPtr[0] = mapPtr[4] = (float)cos(new_theta); + mapPtr[3] = (float)sin(new_theta); mapPtr[1] = -mapPtr[3]; } } - /** Function that computes enhanced corelation coefficient from Georgios et.al. 2008 -* See https://github.com/opencv/opencv/issues/12432 -*/ -double cv::computeECC(InputArray templateImage, InputArray inputImage, InputArray inputMask) -{ + * See https://github.com/opencv/opencv/issues/12432 + */ +double cv::computeECC(InputArray templateImage, InputArray inputImage, InputArray inputMask) { CV_Assert(!templateImage.empty()); CV_Assert(!inputImage.empty()); - if( ! (templateImage.type()==inputImage.type())) - CV_Error( Error::StsUnmatchedFormats, "Both input images must have the same data type" ); + CV_Assert(templateImage.channels() == 1 || templateImage.channels() == 3); + + if (!(templateImage.type() == inputImage.type())) + CV_Error(Error::StsUnmatchedFormats, "Both input images must have the same data type"); Scalar meanTemplate, sdTemplate; @@ -335,7 +312,7 @@ double cv::computeECC(InputArray templateImage, InputArray inputImage, InputArra * ultimately results in an incorrect ECC. To circumvent this problem, if unsigned ints are provided, * we convert them to a signed ints with larger resolution for the subtraction step. */ - if(type == CV_8U || type == CV_16U) { + if (type == CV_8U || type == CV_16U) { int newType = type == CV_8U ? CV_16S : CV_32S; Mat templateMatConverted, inputMatConverted; templateMat.convertTo(templateMatConverted, newType); @@ -344,40 +321,36 @@ double cv::computeECC(InputArray templateImage, InputArray inputImage, InputArra cv::swap(inputMat, inputMatConverted); } subtract(templateMat, meanTemplate, templateImage_zeromean, inputMask); - double templateImagenorm = std::sqrt(active_pixels*sdTemplate.val[0]*sdTemplate.val[0]); + double templateImagenorm = std::sqrt(active_pixels * cv::norm(sdTemplate, NORM_L2SQR)); Scalar meanInput, sdInput; Mat inputImage_zeromean = Mat::zeros(inputImage.size(), inputImage.type()); meanStdDev(inputImage, meanInput, sdInput, inputMask); subtract(inputMat, meanInput, inputImage_zeromean, inputMask); - double inputImagenorm = std::sqrt(active_pixels*sdInput.val[0]*sdInput.val[0]); + double inputImagenorm = std::sqrt(active_pixels * norm(sdInput, NORM_L2SQR)); - return templateImage_zeromean.dot(inputImage_zeromean)/(templateImagenorm*inputImagenorm); + return templateImage_zeromean.dot(inputImage_zeromean) / (templateImagenorm * inputImagenorm); } - -double cv::findTransformECC(InputArray templateImage, - InputArray inputImage, - InputOutputArray warpMatrix, - int motionType, - TermCriteria criteria, - InputArray inputMask, - int gaussFiltSize) -{ - - - Mat src = templateImage.getMat();//template image - Mat dst = inputImage.getMat(); //input image (to be warped) - Mat map = warpMatrix.getMat(); //warp (transformation) +double cv::findTransformECC(InputArray templateImage, InputArray inputImage, InputOutputArray warpMatrix, + int motionType, TermCriteria criteria, InputArray inputMask, int gaussFiltSize) { + Mat src = templateImage.getMat(); // template image + Mat dst = inputImage.getMat(); // input image (to be warped) + Mat map = warpMatrix.getMat(); // warp (transformation) CV_Assert(!src.empty()); CV_Assert(!dst.empty()); + CV_Assert(src.channels() == 1 || src.channels() == 3); + CV_Assert(src.channels() == dst.channels()); + CV_Assert(src.depth() == dst.depth()); + CV_Assert(src.depth() == CV_8U || src.depth() == CV_16U || src.depth() == CV_32F || src.depth() == CV_64F); + // If the user passed an un-initialized warpMatrix, initialize to identity - if(map.empty()) { + if (map.empty()) { int rowCount = 2; - if(motionType == MOTION_HOMOGRAPHY) + if (motionType == MOTION_HOMOGRAPHY) rowCount = 3; warpMatrix.create(rowCount, 3, CV_32FC1); @@ -385,44 +358,39 @@ double cv::findTransformECC(InputArray templateImage, map = Mat::eye(rowCount, 3, CV_32F); } - if( ! (src.type()==dst.type())) - CV_Error( Error::StsUnmatchedFormats, "Both input images must have the same data type" ); + if (!(src.type() == dst.type())) + CV_Error(Error::StsUnmatchedFormats, "Both input images must have the same data type"); - //accept only 1-channel images - if( src.type() != CV_8UC1 && src.type()!= CV_32FC1) - CV_Error( Error::StsUnsupportedFormat, "Images must have 8uC1 or 32fC1 type"); + if (map.type() != CV_32FC1) + CV_Error(Error::StsUnsupportedFormat, "warpMatrix must be single-channel floating-point matrix"); - if( map.type() != CV_32FC1) - CV_Error( Error::StsUnsupportedFormat, "warpMatrix must be single-channel floating-point matrix"); + CV_Assert(map.cols == 3); + CV_Assert(map.rows == 2 || map.rows == 3); - CV_Assert (map.cols == 3); - CV_Assert (map.rows == 2 || map.rows ==3); + CV_Assert(motionType == MOTION_AFFINE || motionType == MOTION_HOMOGRAPHY || motionType == MOTION_EUCLIDEAN || + motionType == MOTION_TRANSLATION); - CV_Assert (motionType == MOTION_AFFINE || motionType == MOTION_HOMOGRAPHY || - motionType == MOTION_EUCLIDEAN || motionType == MOTION_TRANSLATION); - - if (motionType == MOTION_HOMOGRAPHY){ - CV_Assert (map.rows ==3); + if (motionType == MOTION_HOMOGRAPHY) { + CV_Assert(map.rows == 3); } - CV_Assert (criteria.type & TermCriteria::COUNT || criteria.type & TermCriteria::EPS); - const int numberOfIterations = (criteria.type & TermCriteria::COUNT) ? criteria.maxCount : 200; - const double termination_eps = (criteria.type & TermCriteria::EPS) ? criteria.epsilon : -1; + CV_Assert(criteria.type & TermCriteria::COUNT || criteria.type & TermCriteria::EPS); + const int numberOfIterations = (criteria.type & TermCriteria::COUNT) ? criteria.maxCount : 200; + const double termination_eps = (criteria.type & TermCriteria::EPS) ? criteria.epsilon : -1; - int paramTemp = 6;//default: affine - switch (motionType){ - case MOTION_TRANSLATION: - paramTemp = 2; - break; - case MOTION_EUCLIDEAN: - paramTemp = 3; - break; - case MOTION_HOMOGRAPHY: - paramTemp = 8; - break; + int paramTemp = 6; // default: affine + switch (motionType) { + case MOTION_TRANSLATION: + paramTemp = 2; + break; + case MOTION_EUCLIDEAN: + paramTemp = 3; + break; + case MOTION_HOMOGRAPHY: + paramTemp = 8; + break; } - const int numberOfParameters = paramTemp; const int ws = src.cols; @@ -438,10 +406,8 @@ double cv::findTransformECC(InputArray templateImage, float* XcoPtr = Xcoord.ptr(0); float* YcoPtr = Ycoord.ptr(0); int j; - for (j=0; j XgridCh(channels, Xgrid); + cv::merge(XgridCh, Xgrid); + + std::vector YgridCh(channels, Ygrid); + cv::merge(YgridCh, Ygrid); + + Mat templateZM = Mat(hs, ws, type); // to store the (smoothed)zero-mean version of template + Mat templateFloat = Mat(hs, ws, type); // to store the (smoothed) template + Mat imageFloat = Mat(hd, wd, type); // to store the (smoothed) input image + Mat imageWarped = Mat(hs, ws, type); // to store the warped zero-mean input image + Mat imageMask = Mat(hs, ws, CV_8U); // to store the final mask Mat inputMaskMat = inputMask.getMat(); - //to use it for mask warping + // to use it for mask warping Mat preMask; - if(inputMask.empty()) + if (inputMask.empty()) preMask = Mat::ones(hd, wd, CV_8U); else threshold(inputMask, preMask, 0, 1, THRESH_BINARY); - //gaussian filtering is optional + // Gaussian filtering is optional src.convertTo(templateFloat, templateFloat.type()); GaussianBlur(templateFloat, templateFloat, Size(gaussFiltSize, gaussFiltSize), 0, 0); Mat preMaskFloat; - preMask.convertTo(preMaskFloat, CV_32F); + preMask.convertTo(preMaskFloat, type); GaussianBlur(preMaskFloat, preMaskFloat, Size(gaussFiltSize, gaussFiltSize), 0, 0); // Change threshold. - preMaskFloat *= (0.5/0.95); + preMaskFloat *= (0.5 / 0.95); // Rounding conversion. preMaskFloat.convertTo(preMask, preMask.type()); preMask.convertTo(preMaskFloat, preMaskFloat.type()); @@ -480,11 +455,10 @@ double cv::findTransformECC(InputArray templateImage, GaussianBlur(imageFloat, imageFloat, Size(gaussFiltSize, gaussFiltSize), 0, 0); // needed matrices for gradients and warped gradients - Mat gradientX = Mat::zeros(hd, wd, CV_32FC1); - Mat gradientY = Mat::zeros(hd, wd, CV_32FC1); - Mat gradientXWarped = Mat(hs, ws, CV_32FC1); - Mat gradientYWarped = Mat(hs, ws, CV_32FC1); - + Mat gradientX = Mat::zeros(hd, wd, type); + Mat gradientY = Mat::zeros(hd, wd, type); + Mat gradientXWarped = Mat(hs, ws, type); + Mat gradientYWarped = Mat(hs, ws, type); // calculate first order image derivatives Matx13f dx(-0.5f, 0.0f, 0.5f); @@ -492,60 +466,59 @@ double cv::findTransformECC(InputArray templateImage, filter2D(imageFloat, gradientX, -1, dx); filter2D(imageFloat, gradientY, -1, dx.t()); - gradientX = gradientX.mul(preMaskFloat); - gradientY = gradientY.mul(preMaskFloat); + cv::Mat preMaskFloatNCh; + std::vector maskChannels(gradientX.channels(), preMaskFloat); + cv::merge(maskChannels, preMaskFloatNCh); + + gradientX = gradientX.mul(preMaskFloatNCh); + gradientY = gradientY.mul(preMaskFloatNCh); // matrices needed for solving linear equation system for maximizing ECC - Mat jacobian = Mat(hs, ws*numberOfParameters, CV_32F); - Mat hessian = Mat(numberOfParameters, numberOfParameters, CV_32F); - Mat hessianInv = Mat(numberOfParameters, numberOfParameters, CV_32F); - Mat imageProjection = Mat(numberOfParameters, 1, CV_32F); - Mat templateProjection = Mat(numberOfParameters, 1, CV_32F); - Mat imageProjectionHessian = Mat(numberOfParameters, 1, CV_32F); - Mat errorProjection = Mat(numberOfParameters, 1, CV_32F); + Mat jacobian = Mat(hs, ws * numberOfParameters, type); + Mat hessian = Mat(numberOfParameters, numberOfParameters, CV_32F); + Mat hessianInv = Mat(numberOfParameters, numberOfParameters, CV_32F); + Mat imageProjection = Mat(numberOfParameters, 1, CV_32F); + Mat templateProjection = Mat(numberOfParameters, 1, CV_32F); + Mat imageProjectionHessian = Mat(numberOfParameters, 1, CV_32F); + Mat errorProjection = Mat(numberOfParameters, 1, CV_32F); - Mat deltaP = Mat(numberOfParameters, 1, CV_32F);//transformation parameter correction - Mat error = Mat(hs, ws, CV_32F);//error as 2D matrix - - const int imageFlags = INTER_LINEAR + WARP_INVERSE_MAP; - const int maskFlags = INTER_NEAREST + WARP_INVERSE_MAP; + Mat deltaP = Mat(numberOfParameters, 1, CV_32F); // transformation parameter correction + Mat error = Mat(hs, ws, CV_32F); // error as 2D matrix + const int imageFlags = INTER_LINEAR + WARP_INVERSE_MAP; + const int maskFlags = INTER_NEAREST + WARP_INVERSE_MAP; // iteratively update map_matrix - double rho = -1; - double last_rho = - termination_eps; - for (int i = 1; (i <= numberOfIterations) && (fabs(rho-last_rho)>= termination_eps); i++) - { - + double rho = -1; + double last_rho = -termination_eps; + for (int i = 1; (i <= numberOfIterations) && (fabs(rho - last_rho) >= termination_eps); i++) { // warp-back portion of the inputImage and gradients to the coordinate space of the templateImage - if (motionType != MOTION_HOMOGRAPHY) - { - warpAffine(imageFloat, imageWarped, map, imageWarped.size(), imageFlags); - warpAffine(gradientX, gradientXWarped, map, gradientXWarped.size(), imageFlags); - warpAffine(gradientY, gradientYWarped, map, gradientYWarped.size(), imageFlags); - warpAffine(preMask, imageMask, map, imageMask.size(), maskFlags); - } - else - { - warpPerspective(imageFloat, imageWarped, map, imageWarped.size(), imageFlags); - warpPerspective(gradientX, gradientXWarped, map, gradientXWarped.size(), imageFlags); - warpPerspective(gradientY, gradientYWarped, map, gradientYWarped.size(), imageFlags); - warpPerspective(preMask, imageMask, map, imageMask.size(), maskFlags); + if (motionType != MOTION_HOMOGRAPHY) { + warpAffine(imageFloat, imageWarped, map, imageWarped.size(), imageFlags); + warpAffine(gradientX, gradientXWarped, map, gradientXWarped.size(), imageFlags); + warpAffine(gradientY, gradientYWarped, map, gradientYWarped.size(), imageFlags); + warpAffine(preMask, imageMask, map, imageMask.size(), maskFlags); + } else { + warpPerspective(imageFloat, imageWarped, map, imageWarped.size(), imageFlags); + warpPerspective(gradientX, gradientXWarped, map, gradientXWarped.size(), imageFlags); + warpPerspective(gradientY, gradientYWarped, map, gradientYWarped.size(), imageFlags); + warpPerspective(preMask, imageMask, map, imageMask.size(), maskFlags); } Scalar imgMean, imgStd, tmpMean, tmpStd; - meanStdDev(imageWarped, imgMean, imgStd, imageMask); + meanStdDev(imageWarped, imgMean, imgStd, imageMask); meanStdDev(templateFloat, tmpMean, tmpStd, imageMask); - subtract(imageWarped, imgMean, imageWarped, imageMask);//zero-mean input + subtract(imageWarped, imgMean, imageWarped, imageMask); // zero-mean input templateZM = Mat::zeros(templateZM.rows, templateZM.cols, templateZM.type()); - subtract(templateFloat, tmpMean, templateZM, imageMask);//zero-mean template + subtract(templateFloat, tmpMean, templateZM, imageMask); // zero-mean template - const double tmpNorm = std::sqrt(countNonZero(imageMask)*(tmpStd.val[0])*(tmpStd.val[0])); - const double imgNorm = std::sqrt(countNonZero(imageMask)*(imgStd.val[0])*(imgStd.val[0])); + int validPixels = countNonZero(imageMask); + double tmpNorm = std::sqrt(validPixels * cv::norm(tmpStd, cv::NORM_L2SQR)); + double imgNorm = std::sqrt(validPixels * cv::norm(imgStd, cv::NORM_L2SQR)); // calculate jacobian of image wrt parameters - switch (motionType){ + switch (motionType) { case MOTION_AFFINE: image_jacobian_affine_ECC(gradientXWarped, gradientYWarped, Xgrid, Ygrid, jacobian); break; @@ -569,48 +542,42 @@ double cv::findTransformECC(InputArray templateImage, // calculate enhanced correlation coefficient (ECC)->rho last_rho = rho; - rho = correlation/(imgNorm*tmpNorm); + rho = correlation / (imgNorm * tmpNorm); if (cvIsNaN(rho)) { - CV_Error(Error::StsNoConv, "NaN encountered."); + CV_Error(Error::StsNoConv, "NaN encountered."); } // project images into jacobian - project_onto_jacobian_ECC( jacobian, imageWarped, imageProjection); + project_onto_jacobian_ECC(jacobian, imageWarped, imageProjection); project_onto_jacobian_ECC(jacobian, templateZM, templateProjection); - // calculate the parameter lambda to account for illumination variation - imageProjectionHessian = hessianInv*imageProjection; - const double lambda_n = (imgNorm*imgNorm) - imageProjection.dot(imageProjectionHessian); + imageProjectionHessian = hessianInv * imageProjection; + const double lambda_n = (imgNorm * imgNorm) - imageProjection.dot(imageProjectionHessian); const double lambda_d = correlation - templateProjection.dot(imageProjectionHessian); - if (lambda_d <= 0.0) - { + if (lambda_d <= 0.0) { rho = -1; - CV_Error(Error::StsNoConv, "The algorithm stopped before its convergence. The correlation is going to be minimized. Images may be uncorrelated or non-overlapped"); - + CV_Error(Error::StsNoConv, + "The algorithm stopped before its convergence. The correlation is going to be minimized. Images " + "may be uncorrelated or non-overlapped"); } - const double lambda = (lambda_n/lambda_d); + const double lambda = (lambda_n / lambda_d); // estimate the update step delta_p - error = lambda*templateZM - imageWarped; + error = lambda * templateZM - imageWarped; project_onto_jacobian_ECC(jacobian, error, errorProjection); deltaP = hessianInv * errorProjection; // update warping matrix - update_warping_matrix_ECC( map, deltaP, motionType); - - + update_warping_matrix_ECC(map, deltaP, motionType); } // return final correlation coefficient return rho; } -double cv::findTransformECC(InputArray templateImage, InputArray inputImage, - InputOutputArray warpMatrix, int motionType, - TermCriteria criteria, - InputArray inputMask) -{ +double cv::findTransformECC(InputArray templateImage, InputArray inputImage, InputOutputArray warpMatrix, + int motionType, TermCriteria criteria, InputArray inputMask) { // Use default value of 5 for gaussFiltSize to maintain backward compatibility. return findTransformECC(templateImage, inputImage, warpMatrix, motionType, criteria, inputMask, 5); } diff --git a/modules/video/test/test_ecc.cpp b/modules/video/test/test_ecc.cpp index 21a5ae3915..50cd5fed7f 100644 --- a/modules/video/test/test_ecc.cpp +++ b/modules/video/test/test_ecc.cpp @@ -42,41 +42,49 @@ #include "test_precomp.hpp" -namespace opencv_test { namespace { +namespace opencv_test { +namespace { -class CV_ECC_BaseTest : public cvtest::BaseTest -{ -public: +class CV_ECC_BaseTest : public cvtest::BaseTest { + public: CV_ECC_BaseTest(); + virtual ~CV_ECC_BaseTest(); -protected: - + protected: double computeRMS(const Mat& mat1, const Mat& mat2); bool isMapCorrect(const Mat& mat); + virtual bool test(const Mat) { return true; }; // single test + bool testAllTypes(const Mat img); // run test for all supported data types (U8, U16, F32, F64) + bool testAllChNum(const Mat img); // run test for all supported channels count (gray, RGB) - double MAX_RMS_ECC;//upper bound for RMS error - int ntests;//number of tests per motion type - int ECC_iterations;//number of iterations for ECC - double ECC_epsilon; //we choose a negative value, so that + void run(int); + + bool checkMap(const Mat& map, const Mat& ground); + + double MAX_RMS_ECC; // upper bound for RMS error + int ntests; // number of tests per motion type + int ECC_iterations; // number of iterations for ECC + double ECC_epsilon; // we choose a negative value, so that // ECC_iterations are always executed + TermCriteria criteria; }; -CV_ECC_BaseTest::CV_ECC_BaseTest() -{ - MAX_RMS_ECC=0.1; +CV_ECC_BaseTest::CV_ECC_BaseTest() { + MAX_RMS_ECC = 0.1; ntests = 3; ECC_iterations = 50; - ECC_epsilon = -1; //-> negative value means that ECC_Iterations will be executed + ECC_epsilon = -1; //-> negative value means that ECC_Iterations will be executed + criteria = TermCriteria(TermCriteria::COUNT + TermCriteria::EPS, ECC_iterations, ECC_epsilon); } +CV_ECC_BaseTest::~CV_ECC_BaseTest() {} -bool CV_ECC_BaseTest::isMapCorrect(const Mat& map) -{ +bool CV_ECC_BaseTest::isMapCorrect(const Mat& map) { bool tr = true; float mapVal; - for(int i =0; i(i, j); tr = tr & (!cvIsNaN(mapVal) && (fabs(mapVal) < 1e9)); } @@ -84,415 +92,326 @@ bool CV_ECC_BaseTest::isMapCorrect(const Mat& map) return tr; } -double CV_ECC_BaseTest::computeRMS(const Mat& mat1, const Mat& mat2){ - +double CV_ECC_BaseTest::computeRMS(const Mat& mat1, const Mat& mat2) { CV_Assert(mat1.rows == mat2.rows); CV_Assert(mat1.cols == mat2.cols); Mat errorMat; subtract(mat1, mat2, errorMat); - return sqrt(errorMat.dot(errorMat)/(mat1.rows*mat1.cols)); + return sqrt(errorMat.dot(errorMat) / (mat1.rows * mat1.cols * mat1.channels())); } -class CV_ECC_Test_Translation : public CV_ECC_BaseTest -{ -public: +bool CV_ECC_BaseTest::checkMap(const Mat& map, const Mat& ground) { + if (!isMapCorrect(map)) { + ts->set_failed_test_info(cvtest::TS::FAIL_INVALID_OUTPUT); + return false; + } + + if (computeRMS(map, ground) > MAX_RMS_ECC) { + ts->set_failed_test_info(cvtest::TS::FAIL_BAD_ACCURACY); + ts->printf(ts->LOG, "RMS = %f", computeRMS(map, ground)); + return false; + } + return true; +} + +bool CV_ECC_BaseTest::testAllTypes(const Mat img) { + auto types = {CV_8U, CV_16U, CV_32F, CV_64F}; + for (auto type : types) { + Mat timg; + img.convertTo(timg, type); + if (!test(timg)) + return false; + } + return true; +} + +bool CV_ECC_BaseTest::testAllChNum(const Mat img) { + if (!testAllTypes(img)) + return false; + + Mat gray; + cvtColor(img, gray, COLOR_RGB2GRAY); + if (!testAllTypes(gray)) + return false; + + return true; +} + +void CV_ECC_BaseTest::run(int) { + Mat img = imread(string(ts->get_data_path()) + "shared/fruits.png"); + if (img.empty()) { + ts->printf(ts->LOG, "test image can not be read"); + ts->set_failed_test_info(cvtest::TS::FAIL_INVALID_TEST_DATA); + return; + } + + Mat testImg; + resize(img, testImg, Size(216, 216), 0, 0, INTER_LINEAR_EXACT); + + testAllChNum(testImg); + + ts->set_failed_test_info(cvtest::TS::OK); +} + +class CV_ECC_Test_Translation : public CV_ECC_BaseTest { + public: CV_ECC_Test_Translation(); -protected: - void run(int); - bool testTranslation(int); + protected: + bool test(const Mat); }; -CV_ECC_Test_Translation::CV_ECC_Test_Translation(){} - -bool CV_ECC_Test_Translation::testTranslation(int from) -{ - Mat img = imread( string(ts->get_data_path()) + "shared/fruits.png", 0); - - - if (img.empty()) - { - ts->printf( ts->LOG, "test image can not be read"); - ts->set_failed_test_info(cvtest::TS::FAIL_INVALID_TEST_DATA); - return false; - } - Mat testImg; - resize(img, testImg, Size(216, 216), 0, 0, INTER_LINEAR_EXACT); +CV_ECC_Test_Translation::CV_ECC_Test_Translation() {} +bool CV_ECC_Test_Translation::test(const Mat testImg) { cv::RNG rng = ts->get_rng(); - int progress=0; + int progress = 0; - for (int k=from; kupdate_context( this, k, true ); + for (int k = 0; k < ntests; k++) { + ts->update_context(this, k, true); progress = update_progress(progress, k, ntests, 0); - Mat translationGround = (Mat_(2,3) << 1, 0, (rng.uniform(10.f, 20.f)), - 0, 1, (rng.uniform(10.f, 20.f))); + Mat translationGround = (Mat_(2, 3) << 1, 0, (rng.uniform(10.f, 20.f)), 0, 1, (rng.uniform(10.f, 20.f))); Mat warpedImage; - warpAffine(testImg, warpedImage, translationGround, - Size(200,200), INTER_LINEAR + WARP_INVERSE_MAP); + warpAffine(testImg, warpedImage, translationGround, Size(200, 200), INTER_LINEAR + WARP_INVERSE_MAP); - Mat mapTranslation = (Mat_(2,3) << 1, 0, 0, 0, 1, 0); + Mat mapTranslation = (Mat_(2, 3) << 1, 0, 0, 0, 1, 0); - findTransformECC(warpedImage, testImg, mapTranslation, 0, - TermCriteria(TermCriteria::COUNT+TermCriteria::EPS, ECC_iterations, ECC_epsilon)); + findTransformECC(warpedImage, testImg, mapTranslation, 0, criteria); - if (!isMapCorrect(mapTranslation)){ - ts->set_failed_test_info(cvtest::TS::FAIL_INVALID_OUTPUT); + if (!checkMap(mapTranslation, translationGround)) return false; - } - - if (computeRMS(mapTranslation, translationGround)>MAX_RMS_ECC){ - ts->set_failed_test_info(cvtest::TS::FAIL_BAD_ACCURACY); - ts->printf( ts->LOG, "RMS = %f", - computeRMS(mapTranslation, translationGround)); - return false; - } - } return true; } -void CV_ECC_Test_Translation::run(int from) -{ - - if (!testTranslation(from)) - return; - - ts->set_failed_test_info(cvtest::TS::OK); -} - - - -class CV_ECC_Test_Euclidean : public CV_ECC_BaseTest -{ -public: +class CV_ECC_Test_Euclidean : public CV_ECC_BaseTest { + public: CV_ECC_Test_Euclidean(); -protected: - void run(int); - bool testEuclidean(int); + protected: + bool test(const Mat); }; -CV_ECC_Test_Euclidean::CV_ECC_Test_Euclidean() { } - -bool CV_ECC_Test_Euclidean::testEuclidean(int from) -{ - Mat img = imread( string(ts->get_data_path()) + "shared/fruits.png", 0); - - - if (img.empty()) - { - ts->printf( ts->LOG, "test image can not be read"); - ts->set_failed_test_info(cvtest::TS::FAIL_INVALID_TEST_DATA); - return false; - } - Mat testImg; - resize(img, testImg, Size(216, 216), 0, 0, INTER_LINEAR_EXACT); +CV_ECC_Test_Euclidean::CV_ECC_Test_Euclidean() {} +bool CV_ECC_Test_Euclidean::test(const Mat testImg) { cv::RNG rng = ts->get_rng(); int progress = 0; - for (int k=from; kupdate_context( this, k, true ); + for (int k = 0; k < ntests; k++) { + ts->update_context(this, k, true); progress = update_progress(progress, k, ntests, 0); - double angle = CV_PI/30 + CV_PI*rng.uniform((double)-2.f, (double)2.f)/180; + double angle = CV_PI / 30 + CV_PI * rng.uniform((double)-2.f, (double)2.f) / 180; - Mat euclideanGround = (Mat_(2,3) << cos(angle), -sin(angle), (rng.uniform(10.f, 20.f)), - sin(angle), cos(angle), (rng.uniform(10.f, 20.f))); + Mat euclideanGround = (Mat_(2, 3) << cos(angle), -sin(angle), (rng.uniform(10.f, 20.f)), sin(angle), + cos(angle), (rng.uniform(10.f, 20.f))); Mat warpedImage; - warpAffine(testImg, warpedImage, euclideanGround, - Size(200,200), INTER_LINEAR + WARP_INVERSE_MAP); + warpAffine(testImg, warpedImage, euclideanGround, Size(200, 200), INTER_LINEAR + WARP_INVERSE_MAP); - Mat mapEuclidean = (Mat_(2,3) << 1, 0, 0, 0, 1, 0); + Mat mapEuclidean = (Mat_(2, 3) << 1, 0, 0, 0, 1, 0); - findTransformECC(warpedImage, testImg, mapEuclidean, 1, - TermCriteria(TermCriteria::COUNT+TermCriteria::EPS, ECC_iterations, ECC_epsilon)); + findTransformECC(warpedImage, testImg, mapEuclidean, 1, criteria); - if (!isMapCorrect(mapEuclidean)){ - ts->set_failed_test_info(cvtest::TS::FAIL_INVALID_OUTPUT); + if (!checkMap(mapEuclidean, euclideanGround)) return false; - } - - if (computeRMS(mapEuclidean, euclideanGround)>MAX_RMS_ECC){ - ts->set_failed_test_info(cvtest::TS::FAIL_BAD_ACCURACY); - ts->printf( ts->LOG, "RMS = %f", - computeRMS(mapEuclidean, euclideanGround)); - return false; - } - } return true; } - -void CV_ECC_Test_Euclidean::run(int from) -{ - - if (!testEuclidean(from)) - return; - - ts->set_failed_test_info(cvtest::TS::OK); -} - -class CV_ECC_Test_Affine : public CV_ECC_BaseTest -{ -public: +class CV_ECC_Test_Affine : public CV_ECC_BaseTest { + public: CV_ECC_Test_Affine(); -protected: - void run(int); - bool testAffine(int); + protected: + bool test(const Mat img); }; -CV_ECC_Test_Affine::CV_ECC_Test_Affine(){} - - -bool CV_ECC_Test_Affine::testAffine(int from) -{ - Mat img = imread( string(ts->get_data_path()) + "shared/fruits.png", 0); - - if (img.empty()) - { - ts->printf( ts->LOG, "test image can not be read"); - ts->set_failed_test_info(cvtest::TS::FAIL_INVALID_TEST_DATA); - return false; - } - Mat testImg; - resize(img, testImg, Size(216, 216), 0, 0, INTER_LINEAR_EXACT); +CV_ECC_Test_Affine::CV_ECC_Test_Affine() {} +bool CV_ECC_Test_Affine::test(const Mat testImg) { cv::RNG rng = ts->get_rng(); int progress = 0; - for (int k=from; kupdate_context( this, k, true ); + for (int k = 0; k < ntests; k++) { + ts->update_context(this, k, true); progress = update_progress(progress, k, ntests, 0); - - Mat affineGround = (Mat_(2,3) << (1-rng.uniform(-0.05f, 0.05f)), - (rng.uniform(-0.03f, 0.03f)), (rng.uniform(10.f, 20.f)), - (rng.uniform(-0.03f, 0.03f)), (1-rng.uniform(-0.05f, 0.05f)), - (rng.uniform(10.f, 20.f))); + Mat affineGround = (Mat_(2, 3) << (1 - rng.uniform(-0.05f, 0.05f)), (rng.uniform(-0.03f, 0.03f)), + (rng.uniform(10.f, 20.f)), (rng.uniform(-0.03f, 0.03f)), (1 - rng.uniform(-0.05f, 0.05f)), + (rng.uniform(10.f, 20.f))); Mat warpedImage; - warpAffine(testImg, warpedImage, affineGround, - Size(200,200), INTER_LINEAR + WARP_INVERSE_MAP); + warpAffine(testImg, warpedImage, affineGround, Size(200, 200), INTER_LINEAR + WARP_INVERSE_MAP); - Mat mapAffine = (Mat_(2,3) << 1, 0, 0, 0, 1, 0); + Mat mapAffine = (Mat_(2, 3) << 1, 0, 0, 0, 1, 0); - findTransformECC(warpedImage, testImg, mapAffine, 2, - TermCriteria(TermCriteria::COUNT+TermCriteria::EPS, ECC_iterations, ECC_epsilon)); + findTransformECC(warpedImage, testImg, mapAffine, 2, criteria); - if (!isMapCorrect(mapAffine)){ - ts->set_failed_test_info(cvtest::TS::FAIL_INVALID_OUTPUT); + if (!checkMap(mapAffine, affineGround)) return false; - } - - if (computeRMS(mapAffine, affineGround)>MAX_RMS_ECC){ - ts->set_failed_test_info(cvtest::TS::FAIL_BAD_ACCURACY); - ts->printf( ts->LOG, "RMS = %f", - computeRMS(mapAffine, affineGround)); - return false; - } - } return true; } - -void CV_ECC_Test_Affine::run(int from) -{ - - if (!testAffine(from)) - return; - - ts->set_failed_test_info(cvtest::TS::OK); -} - -class CV_ECC_Test_Homography : public CV_ECC_BaseTest -{ -public: +class CV_ECC_Test_Homography : public CV_ECC_BaseTest { + public: CV_ECC_Test_Homography(); -protected: - void run(int); - bool testHomography(int); + protected: + bool test(const Mat testImg); }; -CV_ECC_Test_Homography::CV_ECC_Test_Homography(){} - -bool CV_ECC_Test_Homography::testHomography(int from) -{ - Mat img = imread( string(ts->get_data_path()) + "shared/fruits.png", 0); - - - if (img.empty()) - { - ts->printf( ts->LOG, "test image can not be read"); - ts->set_failed_test_info(cvtest::TS::FAIL_INVALID_TEST_DATA); - return false; - } - Mat testImg; - resize(img, testImg, Size(216, 216), 0, 0, INTER_LINEAR_EXACT); +CV_ECC_Test_Homography::CV_ECC_Test_Homography() {} +bool CV_ECC_Test_Homography::test(const Mat testImg) { cv::RNG rng = ts->get_rng(); int progress = 0; - for (int k=from; kupdate_context( this, k, true ); + for (int k = 0; k < ntests; k++) { + ts->update_context(this, k, true); progress = update_progress(progress, k, ntests, 0); - Mat homoGround = (Mat_(3,3) << (1-rng.uniform(-0.05f, 0.05f)), - (rng.uniform(-0.03f, 0.03f)), (rng.uniform(10.f, 20.f)), - (rng.uniform(-0.03f, 0.03f)), (1-rng.uniform(-0.05f, 0.05f)),(rng.uniform(10.f, 20.f)), - (rng.uniform(0.0001f, 0.0003f)), (rng.uniform(0.0001f, 0.0003f)), 1.f); + Mat homoGround = + (Mat_(3, 3) << (1 - rng.uniform(-0.05f, 0.05f)), (rng.uniform(-0.03f, 0.03f)), + (rng.uniform(10.f, 20.f)), (rng.uniform(-0.03f, 0.03f)), (1 - rng.uniform(-0.05f, 0.05f)), + (rng.uniform(10.f, 20.f)), (rng.uniform(0.0001f, 0.0003f)), (rng.uniform(0.0001f, 0.0003f)), 1.f); Mat warpedImage; - warpPerspective(testImg, warpedImage, homoGround, - Size(200,200), INTER_LINEAR + WARP_INVERSE_MAP); + warpPerspective(testImg, warpedImage, homoGround, Size(200, 200), INTER_LINEAR + WARP_INVERSE_MAP); Mat mapHomography = Mat::eye(3, 3, CV_32F); - findTransformECC(warpedImage, testImg, mapHomography, 3, - TermCriteria(TermCriteria::COUNT+TermCriteria::EPS, ECC_iterations, ECC_epsilon)); + findTransformECC(warpedImage, testImg, mapHomography, 3, criteria); - if (!isMapCorrect(mapHomography)){ - ts->set_failed_test_info(cvtest::TS::FAIL_INVALID_OUTPUT); + if (!checkMap(mapHomography, homoGround)) return false; - } - - if (computeRMS(mapHomography, homoGround)>MAX_RMS_ECC){ - ts->set_failed_test_info(cvtest::TS::FAIL_BAD_ACCURACY); - ts->printf( ts->LOG, "RMS = %f", - computeRMS(mapHomography, homoGround)); - return false; - } - } return true; } -void CV_ECC_Test_Homography::run(int from) -{ - if (!testHomography(from)) - return; - - ts->set_failed_test_info(cvtest::TS::OK); -} - -class CV_ECC_Test_Mask : public CV_ECC_BaseTest -{ -public: +class CV_ECC_Test_Mask : public CV_ECC_BaseTest { + public: CV_ECC_Test_Mask(); -protected: - void run(int); - bool testMask(int); + protected: + bool test(const Mat); }; -CV_ECC_Test_Mask::CV_ECC_Test_Mask(){} - -bool CV_ECC_Test_Mask::testMask(int from) -{ - Mat img = imread( string(ts->get_data_path()) + "shared/fruits.png", 0); - - - if (img.empty()) - { - ts->printf( ts->LOG, "test image can not be read"); - ts->set_failed_test_info(cvtest::TS::FAIL_INVALID_TEST_DATA); - return false; - } - Mat scaledImage; - resize(img, scaledImage, Size(216, 216), 0, 0, INTER_LINEAR_EXACT ); - - Mat_ testImg; - scaledImage.convertTo(testImg, testImg.type()); +CV_ECC_Test_Mask::CV_ECC_Test_Mask() {} +bool CV_ECC_Test_Mask::test(const Mat testImg) { cv::RNG rng = ts->get_rng(); - int progress=0; + int progress = 0; - for (int k=from; kupdate_context( this, k, true ); + for (int k = 0; k < ntests; k++) { + ts->update_context(this, k, true); progress = update_progress(progress, k, ntests, 0); - Mat translationGround = (Mat_(2,3) << 1, 0, (rng.uniform(10.f, 20.f)), - 0, 1, (rng.uniform(10.f, 20.f))); + Mat translationGround = (Mat_(2, 3) << 1, 0, (rng.uniform(10.f, 20.f)), 0, 1, (rng.uniform(10.f, 20.f))); Mat warpedImage; - warpAffine(testImg, warpedImage, translationGround, - Size(200,200), INTER_LINEAR + WARP_INVERSE_MAP); + warpAffine(testImg, warpedImage, translationGround, Size(200, 200), INTER_LINEAR + WARP_INVERSE_MAP); - Mat mapTranslation = (Mat_(2,3) << 1, 0, 0, 0, 1, 0); + Mat mapTranslation = (Mat_(2, 3) << 1, 0, 0, 0, 1, 0); Mat_ mask = Mat_::ones(testImg.rows, testImg.cols); - for (int i=testImg.rows*2/3; iset_failed_test_info(cvtest::TS::FAIL_INVALID_OUTPUT); + findTransformECC(warpedImage, testImg, mapTranslation, 0, criteria, mask); + + if (!checkMap(mapTranslation, translationGround)) return false; - } - - if (computeRMS(mapTranslation, translationGround)>MAX_RMS_ECC){ - ts->set_failed_test_info(cvtest::TS::FAIL_BAD_ACCURACY); - ts->printf( ts->LOG, "RMS = %f", - computeRMS(mapTranslation, translationGround)); - return false; - } // Test with non-default gaussian blur. - findTransformECC(warpedImage, testImg, mapTranslation, 0, - TermCriteria(TermCriteria::COUNT+TermCriteria::EPS, ECC_iterations, ECC_epsilon), mask, 1); + findTransformECC(warpedImage, testImg, mapTranslation, 0, criteria, mask, 1); - if (!isMapCorrect(mapTranslation)){ - ts->set_failed_test_info(cvtest::TS::FAIL_INVALID_OUTPUT); + if (!checkMap(mapTranslation, translationGround)) return false; - } - - if (computeRMS(mapTranslation, translationGround)>MAX_RMS_ECC){ - ts->set_failed_test_info(cvtest::TS::FAIL_BAD_ACCURACY); - ts->printf( ts->LOG, "RMS = %f", - computeRMS(mapTranslation, translationGround)); - return false; - } - } return true; } -void CV_ECC_Test_Mask::run(int from) -{ - if (!testMask(from)) - return; +void testECCProperties(Mat x, float eps) { + // The channels are independent + Mat y = x.t(); + Mat Z = Mat::zeros(x.size(), y.type()); + Mat O = Mat::ones(x.size(), y.type()); - ts->set_failed_test_info(cvtest::TS::OK); + EXPECT_NEAR(computeECC(x, y), 0.0, eps); + if (x.type() != CV_8U && x.type() != CV_8U) { + EXPECT_NEAR(computeECC(x + y, x - y), 0.0, eps); + } + + EXPECT_NEAR(computeECC(x, x), 1.0, eps); + + Mat R, G, B, X, Y; + cv::merge(std::vector({O, Z, Z}), R); + cv::merge(std::vector({Z, O, Z}), G); + cv::merge(std::vector({Z, Z, O}), B); + cv::merge(std::vector({x, x, x}), X); + cv::merge(std::vector({y, y, y}), Y); + + // 1. The channels are orthogonal and independent + EXPECT_NEAR(computeECC(X.mul(R), X.mul(G)), 0, eps); + EXPECT_NEAR(computeECC(X.mul(R), X.mul(B)), 0, eps); + EXPECT_NEAR(computeECC(X.mul(B), X.mul(G)), 0, eps); + + EXPECT_NEAR(computeECC(X.mul(R) + Y.mul(B), X.mul(B) + Y.mul(R)), 0, eps); + + EXPECT_NEAR(computeECC(X.mul(R) + Y.mul(G) + (X + Y).mul(B), Y.mul(R) + X.mul(G) + (X - Y).mul(B)), 0, eps); + + // 2. Each channel contribute equally + EXPECT_NEAR(computeECC(X.mul(R) + Y.mul(G + B), X), 1.0 / 3, eps); + EXPECT_NEAR(computeECC(X.mul(G) + Y.mul(R + B), X), 1.0 / 3, eps); + EXPECT_NEAR(computeECC(X.mul(B) + Y.mul(G + R), X), 1.0 / 3, eps); + + // 3. The coefficient is invariant with respect to the offset of channels + EXPECT_NEAR(computeECC(X - R + 2 * G + B, X), 1.0, eps); + if (x.type() != CV_8U && x.type() != CV_8U) { + EXPECT_NEAR(computeECC(X + R - 2 * G + B, Y), 0.0, eps); + } + + // The channels are independent. Check orthogonal combinations + // full squares norm = sum of squared norms + EXPECT_NEAR(computeECC(X, Y + X), 1.0 / sqrt(2.0), eps); + EXPECT_NEAR(computeECC(X, 2 * Y + X), 1.0 / sqrt(5.0), eps); } -TEST(Video_ECC_Test_Compute, accuracy) -{ +TEST(Video_ECC_Test_Compute, properies) { + Mat xline(1, 100, CV_32F), x; + for (int i = 0; i < xline.cols; ++i) xline.at(0, i) = (float)i; + + repeat(xline, xline.cols, 1, x); + + Mat x_f64, x_u8, x_u16; + x.convertTo(x_f64, CV_64F); + x.convertTo(x_u8, CV_8U); + x.convertTo(x_u16, CV_16U); + + testECCProperties(x, 1e-5f); + testECCProperties(x_f64, 1e-5f); + testECCProperties(x_u8, 1); + testECCProperties(x_u16, 1); +} + +TEST(Video_ECC_Test_Compute, accuracy) { Mat testImg = (Mat_(3, 3) << 1, 0, 0, 1, 0, 0, 1, 0, 0); Mat warpedImage = (Mat_(3, 3) << 0, 1, 0, 0, 1, 0, 0, 1, 0); Mat_ mask = Mat_::ones(testImg.rows, testImg.cols); @@ -501,8 +420,7 @@ TEST(Video_ECC_Test_Compute, accuracy) EXPECT_NEAR(ecc, -0.5f, 1e-5f); } -TEST(Video_ECC_Test_Compute, bug_14657) -{ +TEST(Video_ECC_Test_Compute, bug_14657) { /* * Simple test case - a 2 x 2 matrix with 10, 10, 10, 6. When the mean (36 / 4 = 9) is subtracted, * it results in 1, 1, 1, 0 for the unsigned int case - compare to 1, 1, 1, -3 in the signed case. @@ -512,11 +430,26 @@ TEST(Video_ECC_Test_Compute, bug_14657) EXPECT_NEAR(computeECC(img, img), 1.0f, 1e-5f); } +TEST(Video_ECC_Translation, accuracy) { + CV_ECC_Test_Translation test; + test.safe_run(); +} +TEST(Video_ECC_Euclidean, accuracy) { + CV_ECC_Test_Euclidean test; + test.safe_run(); +} +TEST(Video_ECC_Affine, accuracy) { + CV_ECC_Test_Affine test; + test.safe_run(); +} +TEST(Video_ECC_Homography, accuracy) { + CV_ECC_Test_Homography test; + test.safe_run(); +} +TEST(Video_ECC_Mask, accuracy) { + CV_ECC_Test_Mask test; + test.safe_run(); +} -TEST(Video_ECC_Translation, accuracy) { CV_ECC_Test_Translation test; test.safe_run();} -TEST(Video_ECC_Euclidean, accuracy) { CV_ECC_Test_Euclidean test; test.safe_run(); } -TEST(Video_ECC_Affine, accuracy) { CV_ECC_Test_Affine test; test.safe_run(); } -TEST(Video_ECC_Homography, accuracy) { CV_ECC_Test_Homography test; test.safe_run(); } -TEST(Video_ECC_Mask, accuracy) { CV_ECC_Test_Mask test; test.safe_run(); } - -}} // namespace +} // namespace +} // namespace opencv_test diff --git a/modules/videoio/src/cap_ffmpeg_impl.hpp b/modules/videoio/src/cap_ffmpeg_impl.hpp index 7834c59111..69a355fb4a 100644 --- a/modules/videoio/src/cap_ffmpeg_impl.hpp +++ b/modules/videoio/src/cap_ffmpeg_impl.hpp @@ -1170,7 +1170,9 @@ bool CvCapture_FFMPEG::open(const char* _filename, int index, const Ptrvalue); } +#ifdef HAVE_FFMPEG_LIBAVDEVICE AVDeviceInfoList* device_list = nullptr; +#endif if (index >= 0) { #ifdef HAVE_FFMPEG_LIBAVDEVICE @@ -1267,13 +1269,13 @@ bool CvCapture_FFMPEG::open(const char* _filename, int index, const Ptrpb = avio_context; } int err = avformat_open_input(&ic, _filename, input_format, &dict); +#ifdef HAVE_FFMPEG_LIBAVDEVICE if (device_list) { -#ifdef HAVE_FFMPEG_LIBAVDEVICE avdevice_free_list_devices(&device_list); device_list = nullptr; -#endif } +#endif if (err < 0) {