Merge branch 4.x

This commit is contained in:
Alexander Smorkalov
2025-11-05 14:48:03 +03:00
17 changed files with 810 additions and 647 deletions

View File

@@ -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}
}

View File

@@ -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<double>::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;

View File

@@ -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

View File

@@ -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)

View File

@@ -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);

View File

@@ -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()

View File

@@ -2047,6 +2047,85 @@ static inline void convBlock4x4(int np, const float* a, const float* b, float* c
}
#endif
#if defined(__EMSCRIPTEN__)
template<int RowBase, int NR = 24>
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
}

View File

@@ -52,11 +52,18 @@
#include "grfmt_tiff.hpp"
#include <limits>
#include <string>
#include <cstdarg>
#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<size_t>(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<thandle_t>(buf_helper), &TiffDecoderBufHelper::read,
tif = cv_tiffClientOpen( "", "r", reinterpret_cast<thandle_t>(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<thandle_t>(this), &TiffEncoderBufHelper::read,
return cv_tiffClientOpen( "", "w", reinterpret_cast<thandle_t>(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<Mat>& img_vec, const std::vect
}
else
{
tif = TIFFOpen(m_filename.c_str(), "w");
tif = cv_tiffOpen(m_filename.c_str(), "w");
}
if (!tif)
{

View File

@@ -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

View File

@@ -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

View File

@@ -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()

View File

@@ -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)

View File

@@ -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

View File

@@ -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> MotionType_t;
typedef perf::TestBaseWithParam<MotionType_t> 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<MotionType, ReadFlag> TestParams;
typedef perf::TestBaseWithParam<TestParams> 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_<float>(2,3) << 1.f, 0.f, 7.234f,
0.f, 1.f, 11.839f);
warpGround = (Mat_<float>(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_<float>(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_<float>(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_<float>(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_<float>(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_<float>(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_<float>(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

View File

@@ -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<float>(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<float>(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<float>(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<float>(0);
if (src1.cols !=src2.cols){//dst.cols==1
w = src2.cols;
for (int i=0; i<dst.rows; i++){
dstPtr[i] = (float) src2.dot(src1.colRange(i*w,(i+1)*w));
if (src1.cols != src2.cols) { // dst.cols==1
w = src2.cols;
for (int i = 0; i < dst.rows; i++) {
dstPtr[i] = (float)src2.dot(src1.colRange(i * w, (i + 1) * w));
}
}
else {
CV_Assert(dst.cols == dst.rows); //dst is square (and symmetric)
w = src2.cols/dst.cols;
CV_Assert(dst.cols == dst.rows); // dst is square (and symmetric)
w = src2.cols / dst.cols;
Mat mat;
for (int i=0; i<dst.rows; i++){
for (int i = 0; i < dst.rows; i++) {
mat = Mat(src1.colRange(i * w, (i + 1) * w));
dstPtr[i * (dst.rows + 1)] = (float)pow(norm(mat), 2); // diagonal elements
mat = Mat(src1.colRange(i*w, (i+1)*w));
dstPtr[i*(dst.rows+1)] = (float) pow(norm(mat),2); //diagonal elements
for (int j=i+1; j<dst.cols; j++){ //j starts from i+1
dstPtr[i*dst.cols+j] = (float) mat.dot(src2.colRange(j*w, (j+1)*w));
dstPtr[j*dst.cols+i] = dstPtr[i*dst.cols+j]; //due to symmetry
for (int j = i + 1; j < dst.cols; j++) { // j starts from i+1
dstPtr[i * dst.cols + j] = (float)mat.dot(src2.colRange(j * w, (j + 1) * w));
dstPtr[j * dst.cols + i] = dstPtr[i * dst.cols + j]; // due to symmetry
}
}
}
}
static void update_warping_matrix_ECC(Mat& map_matrix, const Mat& update, const int motionType) {
CV_Assert(map_matrix.type() == CV_32FC1);
CV_Assert(update.type() == CV_32FC1);
static void update_warping_matrix_ECC (Mat& map_matrix, const Mat& update, const int motionType)
{
CV_Assert (map_matrix.type() == CV_32FC1);
CV_Assert (update.type() == CV_32FC1);
CV_Assert (motionType == MOTION_TRANSLATION || motionType == MOTION_EUCLIDEAN ||
motionType == MOTION_AFFINE || motionType == MOTION_HOMOGRAPHY);
CV_Assert(motionType == MOTION_TRANSLATION || motionType == MOTION_EUCLIDEAN || motionType == MOTION_AFFINE ||
motionType == MOTION_HOMOGRAPHY);
if (motionType == MOTION_HOMOGRAPHY)
CV_Assert (map_matrix.rows == 3 && update.rows == 8);
CV_Assert(map_matrix.rows == 3 && update.rows == 8);
else if (motionType == MOTION_AFFINE)
CV_Assert(map_matrix.rows == 2 && update.rows == 6);
else if (motionType == MOTION_EUCLIDEAN)
CV_Assert (map_matrix.rows == 2 && update.rows == 3);
CV_Assert(map_matrix.rows == 2 && update.rows == 3);
else
CV_Assert (map_matrix.rows == 2 && update.rows == 2);
CV_Assert(map_matrix.rows == 2 && update.rows == 2);
CV_Assert (update.cols == 1);
CV_Assert( map_matrix.isContinuous());
CV_Assert( update.isContinuous() );
CV_Assert(update.cols == 1);
CV_Assert(map_matrix.isContinuous());
CV_Assert(update.isContinuous());
float* mapPtr = map_matrix.ptr<float>(0);
const float* updatePtr = update.ptr<float>(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<float>(0);
float* YcoPtr = Ycoord.ptr<float>(0);
int j;
for (j=0; j<ws; j++)
XcoPtr[j] = (float) j;
for (j=0; j<hs; j++)
YcoPtr[j] = (float) j;
for (j = 0; j < ws; j++) XcoPtr[j] = (float)j;
for (j = 0; j < hs; j++) YcoPtr[j] = (float)j;
repeat(Xcoord, hs, 1, Xgrid);
repeat(Ycoord, 1, ws, Ygrid);
@@ -449,29 +415,38 @@ double cv::findTransformECC(InputArray templateImage,
Xcoord.release();
Ycoord.release();
Mat templateZM = Mat(hs, ws, CV_32F);// to store the (smoothed)zero-mean version of template
Mat templateFloat = Mat(hs, ws, CV_32F);// to store the (smoothed) template
Mat imageFloat = Mat(hd, wd, CV_32F);// to store the (smoothed) input image
Mat imageWarped = Mat(hs, ws, CV_32F);// to store the warped zero-mean input image
Mat imageMask = Mat(hs, ws, CV_8U); // to store the final mask
const int channels = src.channels();
int type = CV_MAKETYPE(CV_32F, channels); // используем отдельно, если нужно явно
std::vector<cv::Mat> XgridCh(channels, Xgrid);
cv::merge(XgridCh, Xgrid);
std::vector<cv::Mat> 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<cv::Mat> 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);
}

View File

@@ -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<map.rows; i++)
for(int j=0; j<map.cols; j++){
for (int i = 0; i < map.rows; i++)
for (int j = 0; j < map.cols; j++) {
mapVal = map.at<float>(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; k<ntests; k++){
ts->update_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_<float>(2,3) << 1, 0, (rng.uniform(10.f, 20.f)),
0, 1, (rng.uniform(10.f, 20.f)));
Mat translationGround = (Mat_<float>(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_<float>(2,3) << 1, 0, 0, 0, 1, 0);
Mat mapTranslation = (Mat_<float>(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; k<ntests; k++){
ts->update_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_<float>(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_<float>(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_<float>(2,3) << 1, 0, 0, 0, 1, 0);
Mat mapEuclidean = (Mat_<float>(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; k<ntests; k++){
ts->update_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_<float>(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_<float>(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_<float>(2,3) << 1, 0, 0, 0, 1, 0);
Mat mapAffine = (Mat_<float>(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; k<ntests; k++){
ts->update_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_<float>(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_<float>(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_<float> 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; k<ntests; k++){
ts->update_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_<float>(2,3) << 1, 0, (rng.uniform(10.f, 20.f)),
0, 1, (rng.uniform(10.f, 20.f)));
Mat translationGround = (Mat_<float>(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_<float>(2,3) << 1, 0, 0, 0, 1, 0);
Mat mapTranslation = (Mat_<float>(2, 3) << 1, 0, 0, 0, 1, 0);
Mat_<unsigned char> mask = Mat_<unsigned char>::ones(testImg.rows, testImg.cols);
for (int i=testImg.rows*2/3; i<testImg.rows; i++) {
for (int j=testImg.cols*2/3; j<testImg.cols; j++) {
testImg(i, j) = 0;
mask(i, j) = 0;
}
}
Rect region(testImg.cols * 2 / 3, testImg.rows * 2 / 3, testImg.cols / 3, testImg.rows / 3);
findTransformECC(warpedImage, testImg, mapTranslation, 0,
TermCriteria(TermCriteria::COUNT+TermCriteria::EPS, ECC_iterations, ECC_epsilon), mask);
rectangle(testImg, region, Scalar::all(0), FILLED);
rectangle(mask, region, Scalar(0), FILLED);
if (!isMapCorrect(mapTranslation)){
ts->set_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<cv::Mat>({O, Z, Z}), R);
cv::merge(std::vector<cv::Mat>({Z, O, Z}), G);
cv::merge(std::vector<cv::Mat>({Z, Z, O}), B);
cv::merge(std::vector<cv::Mat>({x, x, x}), X);
cv::merge(std::vector<cv::Mat>({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<float>(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_<float>(3, 3) << 1, 0, 0, 1, 0, 0, 1, 0, 0);
Mat warpedImage = (Mat_<float>(3, 3) << 0, 1, 0, 0, 1, 0, 0, 1, 0);
Mat_<unsigned char> mask = Mat_<unsigned char>::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

View File

@@ -1170,7 +1170,9 @@ bool CvCapture_FFMPEG::open(const char* _filename, int index, const Ptr<IStreamR
input_format = av_find_input_format(entry->value);
}
#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 Ptr<IStreamR
ic->pb = 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)
{