diff --git a/modules/features2d/src/gftt.cpp b/modules/features2d/src/gftt.cpp index 11ed29f39d..bc97fc1677 100644 --- a/modules/features2d/src/gftt.cpp +++ b/modules/features2d/src/gftt.cpp @@ -87,6 +87,7 @@ public: } std::vector corners; + std::vector cornersQuality; if (_image.isUMat()) { @@ -97,7 +98,7 @@ public: ugrayImage = _image.getUMat(); goodFeaturesToTrack( ugrayImage, corners, nfeatures, qualityLevel, minDistance, _mask, - blockSize, gradSize, useHarrisDetector, k ); + cornersQuality, blockSize, gradSize, useHarrisDetector, k ); } else { @@ -106,14 +107,14 @@ public: cvtColor( image, grayImage, COLOR_BGR2GRAY ); goodFeaturesToTrack( grayImage, corners, nfeatures, qualityLevel, minDistance, _mask, - blockSize, gradSize, useHarrisDetector, k ); + cornersQuality, blockSize, gradSize, useHarrisDetector, k ); } + CV_Assert(corners.size() == cornersQuality.size()); + keypoints.resize(corners.size()); - std::vector::const_iterator corner_it = corners.begin(); - std::vector::iterator keypoint_it = keypoints.begin(); - for( ; corner_it != corners.end() && keypoint_it != keypoints.end(); ++corner_it, ++keypoint_it ) - *keypoint_it = KeyPoint( *corner_it, (float)blockSize ); + for (size_t i = 0; i < corners.size(); i++) + keypoints[i] = KeyPoint(corners[i], (float)blockSize, -1, cornersQuality[i]); } diff --git a/modules/imgproc/include/opencv2/imgproc.hpp b/modules/imgproc/include/opencv2/imgproc.hpp index 337c12826d..26363f4bcf 100644 --- a/modules/imgproc/include/opencv2/imgproc.hpp +++ b/modules/imgproc/include/opencv2/imgproc.hpp @@ -1999,6 +1999,38 @@ CV_EXPORTS_W void goodFeaturesToTrack( InputArray image, OutputArray corners, InputArray mask, int blockSize, int gradientSize, bool useHarrisDetector = false, double k = 0.04 ); + +/** @brief Same as above, but returns also quality measure of the detected corners. + +@param image Input 8-bit or floating-point 32-bit, single-channel image. +@param corners Output vector of detected corners. +@param maxCorners Maximum number of corners to return. If there are more corners than are found, +the strongest of them is returned. `maxCorners <= 0` implies that no limit on the maximum is set +and all detected corners are returned. +@param qualityLevel Parameter characterizing the minimal accepted quality of image corners. The +parameter value is multiplied by the best corner quality measure, which is the minimal eigenvalue +(see #cornerMinEigenVal ) or the Harris function response (see #cornerHarris ). The corners with the +quality measure less than the product are rejected. For example, if the best corner has the +quality measure = 1500, and the qualityLevel=0.01 , then all the corners with the quality measure +less than 15 are rejected. +@param minDistance Minimum possible Euclidean distance between the returned corners. +@param mask Region of interest. If the image is not empty (it needs to have the type +CV_8UC1 and the same size as image ), it specifies the region in which the corners are detected. +@param cornersQuality Output vector of quality measure of the detected corners. +@param blockSize Size of an average block for computing a derivative covariation matrix over each +pixel neighborhood. See cornerEigenValsAndVecs . +@param gradientSize Aperture parameter for the Sobel operator used for derivatives computation. +See cornerEigenValsAndVecs . +@param useHarrisDetector Parameter indicating whether to use a Harris detector (see #cornerHarris) +or #cornerMinEigenVal. +@param k Free parameter of the Harris detector. + */ +CV_EXPORTS CV_WRAP_AS(goodFeaturesToTrackWithQuality) void goodFeaturesToTrack( + InputArray image, OutputArray corners, + int maxCorners, double qualityLevel, double minDistance, + InputArray mask, OutputArray cornersQuality, int blockSize = 3, + int gradientSize = 3, bool useHarrisDetector = false, double k = 0.04); + /** @example samples/cpp/tutorial_code/ImgTrans/houghlines.cpp An example using the Hough line detector ![Sample input image](Hough_Lines_Tutorial_Original_Image.jpg) ![Output image](Hough_Lines_Tutorial_Result.jpg) diff --git a/modules/imgproc/perf/opencl/perf_gftt.cpp b/modules/imgproc/perf/opencl/perf_gftt.cpp index a352e9933f..6d4e3e5913 100644 --- a/modules/imgproc/perf/opencl/perf_gftt.cpp +++ b/modules/imgproc/perf/opencl/perf_gftt.cpp @@ -82,6 +82,35 @@ OCL_PERF_TEST_P(GoodFeaturesToTrackFixture, GoodFeaturesToTrack, SANITY_CHECK(dst); } +OCL_PERF_TEST_P(GoodFeaturesToTrackFixture, GoodFeaturesToTrackWithQuality, + ::testing::Combine(OCL_PERF_ENUM(String("gpu/opticalflow/rubberwhale1.png")), + OCL_PERF_ENUM(3.0), Bool())) +{ + GoodFeaturesToTrackParams params = GetParam(); + const String fileName = get<0>(params); + const double minDistance = get<1>(params), qualityLevel = 0.01; + const bool harrisDetector = get<2>(params); + const int maxCorners = 1000; + + Mat img = imread(getDataPath(fileName), cv::IMREAD_GRAYSCALE); + ASSERT_FALSE(img.empty()) << "could not load " << fileName; + + checkDeviceMaxMemoryAllocSize(img.size(), img.type()); + + UMat src(img.size(), img.type()), dst(1, maxCorners, CV_32FC2); + img.copyTo(src); + + std::vector cornersQuality; + + declare.in(src, WARMUP_READ).out(dst); + + OCL_TEST_CYCLE() cv::goodFeaturesToTrack(src, dst, maxCorners, qualityLevel, minDistance, + noArray(), cornersQuality, 3, 3, harrisDetector, 0.04); + + SANITY_CHECK(dst); + SANITY_CHECK(cornersQuality, 1e-6); +} + } } // namespace opencv_test::ocl #endif diff --git a/modules/imgproc/perf/perf_goodFeaturesToTrack.cpp b/modules/imgproc/perf/perf_goodFeaturesToTrack.cpp index a344ff1176..9aa7b6e269 100644 --- a/modules/imgproc/perf/perf_goodFeaturesToTrack.cpp +++ b/modules/imgproc/perf/perf_goodFeaturesToTrack.cpp @@ -41,4 +41,37 @@ PERF_TEST_P(Image_MaxCorners_QualityLevel_MinDistance_BlockSize_gradientSize_Use SANITY_CHECK(corners); } +PERF_TEST_P(Image_MaxCorners_QualityLevel_MinDistance_BlockSize_gradientSize_UseHarris, goodFeaturesToTrackWithQuality, + testing::Combine( + testing::Values( "stitching/a1.png", "cv/shared/pic5.png"), + testing::Values( 50 ), + testing::Values( 0.01 ), + testing::Values( 3 ), + testing::Values( 3 ), + testing::Bool() + ) +) +{ + string filename = getDataPath(get<0>(GetParam())); + int maxCorners = get<1>(GetParam()); + double qualityLevel = get<2>(GetParam()); + int blockSize = get<3>(GetParam()); + int gradientSize = get<4>(GetParam()); + bool useHarrisDetector = get<5>(GetParam()); + double minDistance = 1; + + Mat image = imread(filename, IMREAD_GRAYSCALE); + if (image.empty()) + FAIL() << "Unable to load source image" << filename; + + std::vector corners; + std::vector cornersQuality; + + TEST_CYCLE() goodFeaturesToTrack(image, corners, maxCorners, qualityLevel, minDistance, noArray(), + cornersQuality, blockSize, gradientSize, useHarrisDetector); + + SANITY_CHECK(corners); + SANITY_CHECK(cornersQuality, 1e-6); +} + } // namespace diff --git a/modules/imgproc/src/featureselect.cpp b/modules/imgproc/src/featureselect.cpp index 26c20ea832..b6fa12f35b 100644 --- a/modules/imgproc/src/featureselect.cpp +++ b/modules/imgproc/src/featureselect.cpp @@ -74,8 +74,8 @@ struct Corner static bool ocl_goodFeaturesToTrack( InputArray _image, OutputArray _corners, int maxCorners, double qualityLevel, double minDistance, - InputArray _mask, int blockSize, int gradientSize, - bool useHarrisDetector, double harrisK ) + InputArray _mask, OutputArray _cornersQuality, int blockSize, int gradientSize, + bool useHarrisDetector, double harrisK) { UMat eig, maxEigenValue; if( useHarrisDetector ) @@ -176,7 +176,9 @@ static bool ocl_goodFeaturesToTrack( InputArray _image, OutputArray _corners, std::sort(corner_ptr, corner_ptr + total); std::vector corners; + std::vector cornersQuality; corners.reserve(total); + cornersQuality.reserve(total); if (minDistance >= 1) { @@ -237,6 +239,7 @@ static bool ocl_goodFeaturesToTrack( InputArray _image, OutputArray _corners, grid[y_cell*grid_width + x_cell].push_back(Point2f((float)c.x, (float)c.y)); corners.push_back(Point2f((float)c.x, (float)c.y)); + cornersQuality.push_back(c.val); ++ncorners; if( maxCorners > 0 && (int)ncorners == maxCorners ) @@ -251,13 +254,19 @@ static bool ocl_goodFeaturesToTrack( InputArray _image, OutputArray _corners, const Corner & c = corner_ptr[i]; corners.push_back(Point2f((float)c.x, (float)c.y)); + cornersQuality.push_back(c.val); ++ncorners; + if( maxCorners > 0 && (int)ncorners == maxCorners ) break; } } Mat(corners).convertTo(_corners, _corners.fixedType() ? _corners.type() : CV_32F); + if (_cornersQuality.needed()) { + Mat(cornersQuality).convertTo(_cornersQuality, _cornersQuality.fixedType() ? _cornersQuality.type() : CV_32F); + } + return true; } @@ -354,9 +363,25 @@ static bool openvx_harris(Mat image, OutputArray _corners, } +void cv::goodFeaturesToTrack( InputArray image, OutputArray corners, + int maxCorners, double qualityLevel, double minDistance, + InputArray mask, int blockSize, bool useHarrisDetector, double k ) +{ + return goodFeaturesToTrack(image, corners, maxCorners, qualityLevel, minDistance, + mask, noArray(), blockSize, 3, useHarrisDetector, k); +} + +void cv::goodFeaturesToTrack( InputArray image, OutputArray corners, + int maxCorners, double qualityLevel, double minDistance, + InputArray mask, int blockSize, int gradientSize, bool useHarrisDetector, double k ) +{ + return goodFeaturesToTrack( image, corners, maxCorners, qualityLevel, minDistance, + mask, noArray(), blockSize, gradientSize, useHarrisDetector, k ); +} + void cv::goodFeaturesToTrack( InputArray _image, OutputArray _corners, int maxCorners, double qualityLevel, double minDistance, - InputArray _mask, int blockSize, int gradientSize, + InputArray _mask, OutputArray _cornersQuality, int blockSize, int gradientSize, bool useHarrisDetector, double harrisK ) { CV_INSTRUMENT_REGION(); @@ -366,12 +391,13 @@ void cv::goodFeaturesToTrack( InputArray _image, OutputArray _corners, CV_OCL_RUN(_image.dims() <= 2 && _image.isUMat(), ocl_goodFeaturesToTrack(_image, _corners, maxCorners, qualityLevel, minDistance, - _mask, blockSize, gradientSize, useHarrisDetector, harrisK)) + _mask, _cornersQuality, blockSize, gradientSize, useHarrisDetector, harrisK)) Mat image = _image.getMat(), eig, tmp; if (image.empty()) { _corners.release(); + _cornersQuality.release(); return; } @@ -410,11 +436,13 @@ void cv::goodFeaturesToTrack( InputArray _image, OutputArray _corners, } std::vector corners; + std::vector cornersQuality; size_t i, j, total = tmpCorners.size(), ncorners = 0; if (total == 0) { _corners.release(); + _cornersQuality.release(); return; } @@ -485,6 +513,8 @@ void cv::goodFeaturesToTrack( InputArray _image, OutputArray _corners, { grid[y_cell*grid_width + x_cell].push_back(Point2f((float)x, (float)y)); + cornersQuality.push_back(*tmpCorners[i]); + corners.push_back(Point2f((float)x, (float)y)); ++ncorners; @@ -497,18 +527,24 @@ void cv::goodFeaturesToTrack( InputArray _image, OutputArray _corners, { for( i = 0; i < total; i++ ) { + cornersQuality.push_back(*tmpCorners[i]); + int ofs = (int)((const uchar*)tmpCorners[i] - eig.ptr()); int y = (int)(ofs / eig.step); int x = (int)((ofs - y*eig.step)/sizeof(float)); corners.push_back(Point2f((float)x, (float)y)); ++ncorners; + if( maxCorners > 0 && (int)ncorners == maxCorners ) break; } } Mat(corners).convertTo(_corners, _corners.fixedType() ? _corners.type() : CV_32F); + if (_cornersQuality.needed()) { + Mat(cornersQuality).convertTo(_cornersQuality, _cornersQuality.fixedType() ? _cornersQuality.type() : CV_32F); + } } CV_IMPL void @@ -534,12 +570,4 @@ cvGoodFeaturesToTrack( const void* _image, void*, void*, *_corner_count = (int)ncorners; } -void cv::goodFeaturesToTrack( InputArray _image, OutputArray _corners, - int maxCorners, double qualityLevel, double minDistance, - InputArray _mask, int blockSize, - bool useHarrisDetector, double harrisK ) -{ - cv::goodFeaturesToTrack(_image, _corners, maxCorners, qualityLevel, minDistance, - _mask, blockSize, 3, useHarrisDetector, harrisK ); -} /* End of file. */ diff --git a/modules/imgproc/test/ocl/test_gftt.cpp b/modules/imgproc/test/ocl/test_gftt.cpp index 1e47cd250f..a05be2bccf 100644 --- a/modules/imgproc/test/ocl/test_gftt.cpp +++ b/modules/imgproc/test/ocl/test_gftt.cpp @@ -62,6 +62,7 @@ PARAM_TEST_CASE(GoodFeaturesToTrack, double, bool) TEST_DECLARE_INPUT_PARAMETER(src); UMat points, upoints; + std::vector quality, uquality; virtual void SetUp() { @@ -100,14 +101,16 @@ OCL_TEST_P(GoodFeaturesToTrack, Accuracy) std::vector upts, pts; - OCL_OFF(cv::goodFeaturesToTrack(src_roi, points, maxCorners, qualityLevel, minDistance, noArray())); + OCL_OFF(cv::goodFeaturesToTrack(src_roi, points, maxCorners, qualityLevel, minDistance, noArray(), quality)); ASSERT_FALSE(points.empty()); UMatToVector(points, pts); - OCL_ON(cv::goodFeaturesToTrack(usrc_roi, upoints, maxCorners, qualityLevel, minDistance)); + OCL_ON(cv::goodFeaturesToTrack(usrc_roi, upoints, maxCorners, qualityLevel, minDistance, noArray(), uquality)); ASSERT_FALSE(upoints.empty()); UMatToVector(upoints, upts); + ASSERT_EQ(pts.size(), quality.size()); + ASSERT_EQ(upts.size(), uquality.size()); ASSERT_EQ(upts.size(), pts.size()); int mistmatch = 0; @@ -115,7 +118,8 @@ OCL_TEST_P(GoodFeaturesToTrack, Accuracy) { Point2i a = upts[i], b = pts[i]; - bool eq = std::abs(a.x - b.x) < 1 && std::abs(a.y - b.y) < 1; + bool eq = std::abs(a.x - b.x) < 1 && std::abs(a.y - b.y) < 1 && + std::abs(quality[i] - uquality[i]) <= 3.f * FLT_EPSILON * std::max(quality[i], uquality[i]); if (!eq) ++mistmatch; @@ -131,9 +135,10 @@ OCL_TEST_P(GoodFeaturesToTrack, EmptyCorners) generateTestData(); usrc_roi.setTo(Scalar::all(0)); - OCL_ON(cv::goodFeaturesToTrack(usrc_roi, upoints, maxCorners, qualityLevel, minDistance)); + OCL_ON(cv::goodFeaturesToTrack(usrc_roi, upoints, maxCorners, qualityLevel, minDistance, noArray(), uquality)); ASSERT_TRUE(upoints.empty()); + ASSERT_TRUE(uquality.empty()); } OCL_INSTANTIATE_TEST_CASE_P(Imgproc, GoodFeaturesToTrack, diff --git a/modules/imgproc/test/test_goodfeaturetotrack.cpp b/modules/imgproc/test/test_goodfeaturetotrack.cpp index 0ffee1e55c..d6204c0404 100644 --- a/modules/imgproc/test/test_goodfeaturetotrack.cpp +++ b/modules/imgproc/test/test_goodfeaturetotrack.cpp @@ -88,14 +88,13 @@ test_cornerEigenValsVecs( const Mat& src, Mat& eigenv, int block_size, cvtest::filter2D( src, dy2, ftype, kernel*kernel_scale, anchor, 0, borderType,borderValue ); double denom = (1 << (aperture_size-1))*block_size; - denom = denom * denom; if( _aperture_size < 0 ) - denom *= 4; + denom *= 2.; if(type != ftype ) denom *= 255.; - denom = 1./denom; + denom = 1. / (denom * denom); for( i = 0; i < src.rows; i++ ) { @@ -159,8 +158,8 @@ test_cornerEigenValsVecs( const Mat& src, Mat& eigenv, int block_size, static void test_goodFeaturesToTrack( InputArray _image, OutputArray _corners, int maxCorners, double qualityLevel, double minDistance, - InputArray _mask, int blockSize, int gradientSize, - bool useHarrisDetector, double harrisK ) + InputArray _mask, OutputArray _cornersQuality, + int blockSize, int gradientSize, bool useHarrisDetector, double harrisK) { CV_Assert( qualityLevel > 0 && minDistance >= 0 && maxCorners >= 0 ); @@ -208,6 +207,7 @@ test_goodFeaturesToTrack( InputArray _image, OutputArray _corners, } vector corners; + vector cornersQuality; size_t i, j, total = tmpCorners.size(), ncorners = 0; std::sort( tmpCorners.begin(), tmpCorners.end(), greaterThanPtr() ); @@ -277,6 +277,8 @@ test_goodFeaturesToTrack( InputArray _image, OutputArray _corners, { grid[y_cell*grid_width + x_cell].push_back(Point2f((float)x, (float)y)); + cornersQuality.push_back(*tmpCorners[i]); + corners.push_back(Point2f((float)x, (float)y)); ++ncorners; @@ -289,18 +291,24 @@ test_goodFeaturesToTrack( InputArray _image, OutputArray _corners, { for( i = 0; i < total; i++ ) { + cornersQuality.push_back(*tmpCorners[i]); + int ofs = (int)((const uchar*)tmpCorners[i] - eig.data); int y = (int)(ofs / eig.step); int x = (int)((ofs - y*eig.step)/sizeof(float)); corners.push_back(Point2f((float)x, (float)y)); ++ncorners; + if( maxCorners > 0 && (int)ncorners == maxCorners ) break; } } Mat(corners).convertTo(_corners, _corners.fixedType() ? _corners.type() : CV_32F); + if (_cornersQuality.needed()) { + Mat(cornersQuality).convertTo(_cornersQuality, _cornersQuality.fixedType() ? _cornersQuality.type() : CV_32F); + } } @@ -325,6 +333,8 @@ protected: int maxCorners; vector corners; vector Refcorners; + vector cornersQuality; + vector RefcornersQuality; double qualityLevel; double minDistance; int blockSize; @@ -396,6 +406,7 @@ void CV_GoodFeatureToTTest::run_func() qualityLevel, minDistance, Mat(), + cornersQuality, blockSize, gradientSize, useHarrisDetector, @@ -414,6 +425,7 @@ void CV_GoodFeatureToTTest::run_func() qualityLevel, minDistance, Mat(), + cornersQuality, blockSize, gradientSize, useHarrisDetector, @@ -439,6 +451,7 @@ int CV_GoodFeatureToTTest::validate_test_results( int test_case_idx ) qualityLevel, minDistance, Mat(), + RefcornersQuality, blockSize, gradientSize, useHarrisDetector, @@ -457,6 +470,7 @@ int CV_GoodFeatureToTTest::validate_test_results( int test_case_idx ) qualityLevel, minDistance, Mat(), + RefcornersQuality, blockSize, gradientSize, useHarrisDetector, @@ -471,7 +485,7 @@ int CV_GoodFeatureToTTest::validate_test_results( int test_case_idx ) TEST_MESSAGEL (" TestCorners = ", corners.size()) TEST_MESSAGE ("\n") - ts->printf(cvtest::TS::CONSOLE, "actual error: %g, expected: %g", e, eps); + EXPECT_LE(e, eps); // never true ts->set_failed_test_info(cvtest::TS::FAIL_BAD_ACCURACY); for(int i = 0; i < (int)std::min((unsigned int)(corners.size()), (unsigned int)(Refcorners.size())); i++){ @@ -488,6 +502,19 @@ int CV_GoodFeatureToTTest::validate_test_results( int test_case_idx ) ts->set_failed_test_info(cvtest::TS::OK); } + e = cv::norm(cornersQuality, RefcornersQuality, NORM_RELATIVE | NORM_INF); + + if (e > eps) + { + EXPECT_LE(e, eps); // never true + ts->set_failed_test_info(cvtest::TS::FAIL_BAD_ACCURACY); + + for(int i = 0; i < (int)std::min((unsigned int)(cornersQuality.size()), (unsigned int)(cornersQuality.size())); i++) { + if (std::abs(cornersQuality[i] - RefcornersQuality[i]) > eps * std::max(cornersQuality[i], RefcornersQuality[i])) + printf("i = %i Quality %2.6f Quality ref %2.6f\n", i, cornersQuality[i], RefcornersQuality[i]); + } + } + return BaseTest::validate_test_results(test_case_idx); }