diff --git a/modules/video/include/opencv2/video/tracking.hpp b/modules/video/include/opencv2/video/tracking.hpp index 86749f72b0..fb70abbc74 100644 --- a/modules/video/include/opencv2/video/tracking.hpp +++ b/modules/video/include/opencv2/video/tracking.hpp @@ -268,17 +268,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. +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 @@ -287,8 +313,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: @@ -305,7 +331,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..387b593936 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,39 @@ 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); + + // TODO: Update baseline for new test + SANITY_CHECK_NOTHING(); } -} // 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