Merge pull request #19392 from amirtu:OCV-165_finalize_goodFeaturesToTrack_returns_al...
authorAmir Tulegenov <amir.tulegen@gmail.com>
Mon, 15 Feb 2021 19:55:57 +0000 (01:55 +0600)
committerGitHub <noreply@github.com>
Mon, 15 Feb 2021 19:55:57 +0000 (19:55 +0000)
* goodFeaturesToTrack returns also corner value

(cherry picked from commit 4a8f06755cf93785a82a455a2035a2ff572cafae)

* Added response to GFTT Detector keypoints

(cherry picked from commit b88fb40c6ea037e5283e4fbcf0ffde160c65a035)

* Moved corner values to another optional variable to preserve backward compatibility

(cherry picked from commit 6137383d32859efad7b44dd8a798e7b69f68dec5)

* Removed corners valus from perf tests and better unit tests for corners values

(cherry picked from commit f3d0ef21a78b7d0dc8696c457a6fabecfbe5e8ff)

* Fixed detector gftt call

(cherry picked from commit be2975553ba01a7d2e63f549fadccec6d7d56797)

* Restored test_cornerEigenValsVecs

(cherry picked from commit ea3e11811faee63487449983c0b80ff8ee35bbac)

* scaling fixed;
mineigen calculation rolled back;
gftt function overload added (with quality parameter);
perf tests were added for the new api function;
external bindings were added for the function (with different alias);
fixed issues with composition of the output array of the new function (e.g. as requested in comments) ;
added sanity checks in the perf tests;
removed C API changes.

* minor change to GFTTDetector::detect

* substitute ts->printf with EXPECT_LE

* avoid re-allocations

Co-authored-by: Anas <anas.el.amraoui@live.com>
Co-authored-by: amir.tulegenov <amir.tulegenov@xperience.ai>
modules/features2d/src/gftt.cpp
modules/imgproc/include/opencv2/imgproc.hpp
modules/imgproc/perf/opencl/perf_gftt.cpp
modules/imgproc/perf/perf_goodFeaturesToTrack.cpp
modules/imgproc/src/featureselect.cpp
modules/imgproc/test/ocl/test_gftt.cpp
modules/imgproc/test/test_goodfeaturetotrack.cpp

index 11ed29f..bc97fc1 100644 (file)
@@ -87,6 +87,7 @@ public:
         }
 
         std::vector<Point2f> corners;
+        std::vector<float> 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<Point2f>::const_iterator corner_it = corners.begin();
-        std::vector<KeyPoint>::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]);
 
     }
 
index 337c128..26363f4 100644 (file)
@@ -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)
index a352e99..6d4e3e5 100644 (file)
@@ -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<float> 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
index a344ff1..9aa7b6e 100644 (file)
@@ -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<Point2f> corners;
+    std::vector<float> cornersQuality;
+
+    TEST_CYCLE() goodFeaturesToTrack(image, corners, maxCorners, qualityLevel, minDistance, noArray(),
+                                     cornersQuality, blockSize, gradientSize, useHarrisDetector);
+
+    SANITY_CHECK(corners);
+    SANITY_CHECK(cornersQuality, 1e-6);
+}
+
 } // namespace
index 26c20ea..b6fa12f 100644 (file)
@@ -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<Point2f> corners;
+    std::vector<float> 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<Point2f> corners;
+    std::vector<float> 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. */
index 1e47cd2..a05be2b 100644 (file)
@@ -62,6 +62,7 @@ PARAM_TEST_CASE(GoodFeaturesToTrack, double, bool)
 
     TEST_DECLARE_INPUT_PARAMETER(src);
     UMat points, upoints;
+    std::vector<float> quality, uquality;
 
     virtual void SetUp()
     {
@@ -100,14 +101,16 @@ OCL_TEST_P(GoodFeaturesToTrack, Accuracy)
 
         std::vector<Point2f> 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,
index 0ffee1e..d6204c0 100644 (file)
@@ -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<Point2f> corners;
+    vector<float> 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<Point2f> corners;
     vector<Point2f> Refcorners;
+    vector<float> cornersQuality;
+    vector<float> 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);
 
 }