Merge pull request #11108 from take1014:hough_4303
authoryuki takehara <y.takehara1014@gmail.com>
Wed, 23 May 2018 20:42:12 +0000 (05:42 +0900)
committerAlexander Alekhin <alexander.a.alekhin@gmail.com>
Wed, 23 May 2018 20:42:12 +0000 (20:42 +0000)
* Added accumulator value to the output of HoughLines and HoughCircles

* imgproc: refactor Hough patch

- eliminate code duplication
- fix type handling, fix OpenCL code
- fix test data generation
- re-generated test data in debug mode via plain CPU code path

modules/imgproc/include/opencv2/imgproc.hpp
modules/imgproc/perf/perf_houghcircles.cpp
modules/imgproc/perf/perf_houghlines.cpp
modules/imgproc/src/hough.cpp
modules/imgproc/test/test_houghcircles.cpp
modules/imgproc/test/test_houghlines.cpp

index 1d94e74..a58b5b7 100644 (file)
@@ -1977,10 +1977,11 @@ detection. See <http://homepages.inf.ed.ac.uk/rbf/HIPR2/hough.htm> for a good ex
 transform.
 
 @param image 8-bit, single-channel binary source image. The image may be modified by the function.
-@param lines Output vector of lines. Each line is represented by a two-element vector
-\f$(\rho, \theta)\f$ . \f$\rho\f$ is the distance from the coordinate origin \f$(0,0)\f$ (top-left corner of
+@param lines Output vector of lines. Each line is represented by a 2 or 3 element vector
+\f$(\rho, \theta)\f$ or \f$(\rho, \theta, \votes)\f$ . \f$\rho\f$ is the distance from the coordinate origin \f$(0,0)\f$ (top-left corner of
 the image). \f$\theta\f$ is the line rotation angle in radians (
 \f$0 \sim \textrm{vertical line}, \pi/2 \sim \textrm{horizontal line}\f$ ).
+\f$\votes\f$ is the value of accumulator.
 @param rho Distance resolution of the accumulator in pixels.
 @param theta Angle resolution of the accumulator in radians.
 @param threshold Accumulator threshold parameter. Only those lines are returned that get enough
@@ -2155,8 +2156,8 @@ you know it. Or, you may set maxRadius to a negative number to return centers on
 search, and find the correct radius using an additional procedure.
 
 @param image 8-bit, single-channel, grayscale input image.
-@param circles Output vector of found circles. Each vector is encoded as a 3-element
-floating-point vector \f$(x, y, radius)\f$ .
+@param circles Output vector of found circles. Each vector is encoded as  3 or 4 element
+floating-point vector \f$(x, y, radius)\f$ or \f$(x, y, radius, votes)\f$ .
 @param method Detection method, see #HoughModes. Currently, the only implemented method is #HOUGH_GRADIENT
 @param dp Inverse ratio of the accumulator resolution to the image resolution. For example, if
 dp=1 , the accumulator has the same resolution as the input image. If dp=2 , the accumulator has
index fb63408..ba4fab8 100644 (file)
@@ -56,4 +56,30 @@ PERF_TEST(PerfHoughCircles2, ManySmallCircles)
     SANITY_CHECK_NOTHING();
 }
 
+PERF_TEST(PerfHoughCircles4f, Basic)
+{
+    string filename = getDataPath("cv/imgproc/stuff.jpg");
+    const double dp = 1.0;
+    double minDist = 20;
+    double edgeThreshold = 20;
+    double accumThreshold = 30;
+    int minRadius = 20;
+    int maxRadius = 200;
+
+    Mat img = imread(filename, IMREAD_GRAYSCALE);
+    ASSERT_FALSE(img.empty()) << "Unable to load source image " << filename;
+
+    GaussianBlur(img, img, Size(9, 9), 2, 2);
+
+    vector<Vec4f> circles;
+    declare.in(img);
+
+    TEST_CYCLE()
+    {
+        HoughCircles(img, circles, CV_HOUGH_GRADIENT, dp, minDist, edgeThreshold, accumThreshold, minRadius, maxRadius);
+    }
+
+    SANITY_CHECK_NOTHING();
+}
+
 } // namespace
index 4d5aedd..eaadaf1 100644 (file)
@@ -69,4 +69,47 @@ PERF_TEST_P(Image_RhoStep_ThetaStep_Threshold, HoughLines,
     SANITY_CHECK_NOTHING();
 }
 
+PERF_TEST_P(Image_RhoStep_ThetaStep_Threshold, HoughLines3f,
+            testing::Combine(
+                testing::Values( "cv/shared/pic5.png", "stitching/a1.png" ),
+                testing::Values( 1, 10 ),
+                testing::Values( 0.01, 0.1 ),
+                testing::Values( 0.5, 1.1 )
+                )
+            )
+{
+    string filename = getDataPath(get<0>(GetParam()));
+    double rhoStep = get<1>(GetParam());
+    double thetaStep = get<2>(GetParam());
+    double threshold_ratio = get<3>(GetParam());
+
+    Mat image = imread(filename, IMREAD_GRAYSCALE);
+    if (image.empty())
+        FAIL() << "Unable to load source image" << filename;
+
+    Canny(image, image, 32, 128);
+
+    // add some syntetic lines:
+    line(image, Point(0, 0), Point(image.cols, image.rows), Scalar::all(255), 3);
+    line(image, Point(image.cols, 0), Point(image.cols/2, image.rows), Scalar::all(255), 3);
+
+    vector<Vec3f> lines;
+    declare.time(60);
+
+    int hough_threshold = (int)(std::min(image.cols, image.rows) * threshold_ratio);
+
+    TEST_CYCLE() HoughLines(image, lines, rhoStep, thetaStep, hough_threshold);
+
+    printf("%dx%d: %d lines\n", image.cols, image.rows, (int)lines.size());
+
+    if (threshold_ratio < 1.0)
+    {
+        EXPECT_GE(lines.size(), 2u);
+    }
+
+    EXPECT_LT(lines.size(), 3000u);
+
+    SANITY_CHECK_NOTHING();
+}
+
 } // namespace
index 68dfabe..ec05edf 100644 (file)
@@ -105,48 +105,56 @@ array of (rho, theta) pairs. linesMax is the buffer size (number of pairs).
 Functions return the actual number of found lines.
 */
 static void
-HoughLinesStandard( const Mat& img, float rho, float theta,
-                    int threshold, std::vector<Vec2f>& lines, int linesMax,
+HoughLinesStandard( InputArray src, OutputArray lines, int type,
+                    float rho, float theta,
+                    int threshold, int linesMax,
                     double min_theta, double max_theta )
 {
+    CV_CheckType(type, type == CV_32FC2 || type == CV_32FC3, "Internal error");
+
+    Mat img = src.getMat();
+
     int i, j;
     float irho = 1 / rho;
 
     CV_Assert( img.type() == CV_8UC1 );
+    CV_Assert( linesMax > 0 );
 
     const uchar* image = img.ptr();
     int step = (int)img.step;
     int width = img.cols;
     int height = img.rows;
 
-    if (max_theta < min_theta ) {
-        CV_Error( CV_StsBadArg, "max_theta must be greater than min_theta" );
-    }
+    int max_rho = width + height;
+    int min_rho = -max_rho;
+
+    CV_CheckGE(max_theta, min_theta, "max_theta must be greater than min_theta");
+
     int numangle = cvRound((max_theta - min_theta) / theta);
-    int numrho = cvRound(((width + height) * 2 + 1) / rho);
+    int numrho = cvRound(((max_rho - min_rho) + 1) / rho);
 
 #if defined HAVE_IPP && IPP_VERSION_X100 >= 810 && !IPP_DISABLE_HOUGH
-    CV_IPP_CHECK()
+    if (type == CV_32FC2 && CV_IPP_CHECK_COND)
     {
         IppiSize srcSize = { width, height };
         IppPointPolar delta = { rho, theta };
-        IppPointPolar dstRoi[2] = {{(Ipp32f) -(width + height), (Ipp32f) min_theta},{(Ipp32f) (width + height), (Ipp32f) max_theta}};
+        IppPointPolar dstRoi[2] = {{(Ipp32f) min_rho, (Ipp32f) min_theta},{(Ipp32f) max_rho, (Ipp32f) max_theta}};
         int bufferSize;
         int nz = countNonZero(img);
         int ipp_linesMax = std::min(linesMax, nz*numangle/threshold);
         int linesCount = 0;
-        lines.resize(ipp_linesMax);
+        std::vector<Vec2f> _lines(ipp_linesMax);
         IppStatus ok = ippiHoughLineGetSize_8u_C1R(srcSize, delta, ipp_linesMax, &bufferSize);
         Ipp8u* buffer = ippsMalloc_8u_L(bufferSize);
-        if (ok >= 0) {ok = CV_INSTRUMENT_FUN_IPP(ippiHoughLine_Region_8u32f_C1R, image, step, srcSize, (IppPointPolar*) &lines[0], dstRoi, ipp_linesMax, &linesCount, delta, threshold, buffer);};
+        if (ok >= 0) {ok = CV_INSTRUMENT_FUN_IPP(ippiHoughLine_Region_8u32f_C1R, image, step, srcSize, (IppPointPolar*) &_lines[0], dstRoi, ipp_linesMax, &linesCount, delta, threshold, buffer);};
         ippsFree(buffer);
         if (ok >= 0)
         {
-            lines.resize(linesCount);
+            lines.create(linesCount, 1, CV_32FC2);
+            Mat(linesCount, 1, CV_32FC2, &_lines[0]).copyTo(lines);
             CV_IMPL_ADD(CV_IMPL_IPP);
             return;
         }
-        lines.clear();
         setIppErrorStatus();
     }
 #endif
@@ -185,6 +193,9 @@ HoughLinesStandard( const Mat& img, float rho, float theta,
     // stage 4. store the first min(total,linesMax) lines to the output buffer
     linesMax = std::min(linesMax, (int)_sort_buf.size());
     double scale = 1./(numrho+2);
+
+    lines.create(linesMax, 1, type);
+    Mat _lines = lines.getMat();
     for( i = 0; i < linesMax; i++ )
     {
         LinePolar line;
@@ -193,7 +204,15 @@ HoughLinesStandard( const Mat& img, float rho, float theta,
         int r = idx - (n+1)*(numrho+2) - 1;
         line.rho = (r - (numrho - 1)*0.5f) * rho;
         line.angle = static_cast<float>(min_theta) + n * theta;
-        lines.push_back(Vec2f(line.rho, line.angle));
+        if (type == CV_32FC2)
+        {
+            _lines.at<Vec2f>(i) = Vec2f(line.rho, line.angle);
+        }
+        else
+        {
+            CV_DbgAssert(type == CV_32FC3);
+            _lines.at<Vec3f>(i) = Vec3f(line.rho, line.angle, (float)accum[idx]);
+        }
     }
 }
 
@@ -212,15 +231,17 @@ struct hough_index
 
 
 static void
-HoughLinesSDiv( const Mat& img,
+HoughLinesSDiv( InputArray image, OutputArray lines, int type,
                 float rho, float theta, int threshold,
-                int srn, int stn,
-                std::vector<Vec2f>& lines, int linesMax,
+                int srn, int stn, int linesMax,
                 double min_theta, double max_theta )
 {
+    CV_CheckType(type, type == CV_32FC2 || type == CV_32FC3, "Internal error");
+
     #define _POINT(row, column)\
         (image_src[(row)*step+(column)])
 
+    Mat img = image.getMat();
     int index, i;
     int ri, ti, ti1, ti0;
     int row, col;
@@ -343,7 +364,7 @@ HoughLinesSDiv( const Mat& img,
 
     if( count * 100 > rn * tn )
     {
-        HoughLinesStandard( img, rho, theta, threshold, lines, linesMax, min_theta, max_theta );
+        HoughLinesStandard( image, lines, type, rho, theta, threshold, linesMax, min_theta, max_theta );
         return;
     }
 
@@ -415,11 +436,21 @@ HoughLinesSDiv( const Mat& img,
         }
     }
 
+    lines.create((int)lst.size(), 1, type);
+    Mat _lines = lines.getMat();
     for( size_t idx = 0; idx < lst.size(); idx++ )
     {
         if( lst[idx].rho < 0 )
             continue;
-        lines.push_back(Vec2f(lst[idx].rho, lst[idx].theta));
+        if (type == CV_32FC2)
+        {
+            _lines.at<Vec2f>((int)idx) = Vec2f(lst[idx].rho, lst[idx].theta);
+        }
+        else
+        {
+            CV_DbgAssert(type == CV_32FC3);
+            _lines.at<Vec3f>((int)idx) = Vec3f(lst[idx].rho, lst[idx].theta, (float)lst[idx].value);
+        }
     }
 }
 
@@ -861,24 +892,26 @@ static bool ocl_HoughLinesP(InputArray _src, OutputArray _lines, double rho, dou
 
 #endif /* HAVE_OPENCL */
 
-void HoughLines( InputArray _image, OutputArray _lines,
+void HoughLines( InputArray _image, OutputArray lines,
                  double rho, double theta, int threshold,
                  double srn, double stn, double min_theta, double max_theta )
 {
     CV_INSTRUMENT_REGION()
 
-    CV_OCL_RUN(srn == 0 && stn == 0 && _image.isUMat() && _lines.isUMat(),
-               ocl_HoughLines(_image, _lines, rho, theta, threshold, min_theta, max_theta));
+    int type = CV_32FC2;
+    if (lines.fixedType())
+    {
+        type = lines.type();
+        CV_CheckType(type, type == CV_32FC2 || type == CV_32FC3, "Wrong type of output lines");
+    }
 
-    Mat image = _image.getMat();
-    std::vector<Vec2f> lines;
+    CV_OCL_RUN(srn == 0 && stn == 0 && _image.isUMat() && lines.isUMat() && type == CV_32FC2,
+               ocl_HoughLines(_image, lines, rho, theta, threshold, min_theta, max_theta));
 
     if( srn == 0 && stn == 0 )
-        HoughLinesStandard(image, (float)rho, (float)theta, threshold, lines, INT_MAX, min_theta, max_theta );
+        HoughLinesStandard(_image, lines, type, (float)rho, (float)theta, threshold, INT_MAX, min_theta, max_theta );
     else
-        HoughLinesSDiv(image, (float)rho, (float)theta, threshold, cvRound(srn), cvRound(stn), lines, INT_MAX, min_theta, max_theta);
-
-    Mat(lines).copyTo(_lines);
+        HoughLinesSDiv(_image, lines, type, (float)rho, (float)theta, threshold, cvRound(srn), cvRound(stn), INT_MAX, min_theta, max_theta);
 }
 
 
@@ -1007,11 +1040,16 @@ static bool cmpAccum(const EstimatedCircle& left, const EstimatedCircle& right)
         return false;
 }
 
-inline Vec3f GetCircle(const EstimatedCircle& est)
+static inline Vec3f GetCircle(const EstimatedCircle& est)
 {
     return est.c;
 }
 
+static inline Vec4f GetCircle4f(const EstimatedCircle& est)
+{
+    return Vec4f(est.c[0], est.c[1], est.c[2], (float)est.accum);
+}
+
 class NZPointList : public std::vector<Point>
 {
 private:
@@ -1264,12 +1302,13 @@ private:
     Mutex& _lock;
 };
 
-static bool CheckDistance(const std::vector<Vec3f> &circles, size_t endIdx, const Vec3f& circle, float minDist2)
+template<typename T>
+static bool CheckDistance(const std::vector<T> &circles, size_t endIdx, const T& circle, float minDist2)
 {
     bool goodPoint = true;
     for (uint j = 0; j < endIdx; ++j)
     {
-        Vec3f pt = circles[j];
+        T pt = circles[j];
         float distX = circle[0] - pt[0], distY = circle[1] - pt[1];
         if (distX * distX + distY * distY < minDist2)
         {
@@ -1297,13 +1336,31 @@ static void GetCircleCenters(const std::vector<int> &centers, std::vector<Vec3f>
     }
 }
 
-static void RemoveOverlaps(std::vector<Vec3f>& circles, float minDist)
+static void GetCircleCenters(const std::vector<int> &centers, std::vector<Vec4f> &circles, int acols, float minDist, float dr)
+{
+    size_t centerCnt = centers.size();
+    float minDist2 = minDist * minDist;
+    for (size_t i = 0; i < centerCnt; ++i)
+    {
+        int center = centers[i];
+        int y = center / acols;
+        int x = center - y * acols;
+        Vec4f circle = Vec4f((x + 0.5f) * dr, (y + 0.5f) * dr, 0, (float)center);
+
+        bool goodPoint = CheckDistance(circles, circles.size(), circle, minDist2);
+        if (goodPoint)
+            circles.push_back(circle);
+    }
+}
+
+template<typename T>
+static void RemoveOverlaps(std::vector<T>& circles, float minDist)
 {
     float minDist2 = minDist * minDist;
     size_t endIdx = 1;
     for (size_t i = 1; i < circles.size(); ++i)
     {
-        Vec3f circle = circles[i];
+        T circle = circles[i];
         if (CheckDistance(circles, endIdx, circle, minDist2))
         {
             circles[endIdx] = circle;
@@ -1313,6 +1370,16 @@ static void RemoveOverlaps(std::vector<Vec3f>& circles, float minDist)
     circles.resize(endIdx);
 }
 
+static void CreateCircles(const std::vector<EstimatedCircle>& circlesEst, std::vector<Vec3f>& circles)
+{
+    std::transform(circlesEst.begin(), circlesEst.end(), std::back_inserter(circles), GetCircle);
+}
+
+static void CreateCircles(const std::vector<EstimatedCircle>& circlesEst, std::vector<Vec4f>& circles)
+{
+    std::transform(circlesEst.begin(), circlesEst.end(), std::back_inserter(circles), GetCircle4f);
+}
+
 template<class NZPoints>
 class HoughCircleEstimateRadiusInvoker : public ParallelLoopBody
 {
@@ -1556,11 +1623,14 @@ inline int HoughCircleEstimateRadiusInvoker<NZPointSet>::filterCircles(const Poi
     return nzCount;
 }
 
-static void HoughCirclesGradient(InputArray _image, OutputArray _circles, float dp, float minDist,
+template <typename CircleType>
+static void HoughCirclesGradient(InputArray _image, OutputArray _circles,
+                                 float dp, float minDist,
                                  int minRadius, int maxRadius, int cannyThreshold,
                                  int accThreshold, int maxCircles, int kernelSize, bool centersOnly)
 {
     CV_Assert(kernelSize == -1 || kernelSize == 3 || kernelSize == 5 || kernelSize == 7);
+
     dp = max(dp, 1.f);
     float idp = 1.f/dp;
 
@@ -1602,7 +1672,7 @@ static void HoughCirclesGradient(InputArray _image, OutputArray _circles, float
 
     std::sort(centers.begin(), centers.end(), hough_cmp_gt(accum.ptr<int>()));
 
-    std::vector<Vec3f> circles;
+    std::vector<CircleType> circles;
     circles.reserve(256);
     if (centersOnly)
     {
@@ -1635,15 +1705,16 @@ static void HoughCirclesGradient(InputArray _image, OutputArray _circles, float
 
         // Sort by accumulator value
         std::sort(circlesEst.begin(), circlesEst.end(), cmpAccum);
-        std::transform(circlesEst.begin(), circlesEst.end(), std::back_inserter(circles), GetCircle);
+
+        // Create Circles
+        CreateCircles(circlesEst, circles);
         RemoveOverlaps(circles, minDist);
     }
 
-    if(circles.size() > 0)
+    if (circles.size() > 0)
     {
         int numCircles = std::min(maxCircles, int(circles.size()));
-        _circles.create(1, numCircles, CV_32FC3);
-        Mat(1, numCircles, CV_32FC3, &circles[0]).copyTo(_circles.getMat());
+        Mat(1, numCircles, cv::traits::Type<CircleType>::value, &circles[0]).copyTo(_circles);
         return;
     }
 }
@@ -1656,6 +1727,13 @@ static void HoughCircles( InputArray _image, OutputArray _circles,
 {
     CV_INSTRUMENT_REGION()
 
+    int type = CV_32FC3;
+    if( _circles.fixedType() )
+    {
+        type = _circles.type();
+        CV_CheckType(type, type == CV_32FC3 || type == CV_32FC4, "Wrong type of output circles");
+    }
+
     CV_Assert(!_image.empty() && _image.type() == CV_8UC1 && (_image.isMat() || _image.isUMat()));
     CV_Assert(_circles.isMat() || _circles.isVector());
 
@@ -1679,9 +1757,16 @@ static void HoughCircles( InputArray _image, OutputArray _circles,
     switch( method )
     {
     case CV_HOUGH_GRADIENT:
-        HoughCirclesGradient(_image, _circles, (float)dp, (float)minDist,
-                             minRadius, maxRadius, cannyThresh,
-                             accThresh, maxCircles, kernelSize, centersOnly);
+        if (type == CV_32FC3)
+            HoughCirclesGradient<Vec3f>(_image, _circles, (float)dp, (float)minDist,
+                                        minRadius, maxRadius, cannyThresh,
+                                        accThresh, maxCircles, kernelSize, centersOnly);
+        else if (type == CV_32FC4)
+            HoughCirclesGradient<Vec4f>(_image, _circles, (float)dp, (float)minDist,
+                                        minRadius, maxRadius, cannyThresh,
+                                        accThresh, maxCircles, kernelSize, centersOnly);
+        else
+            CV_Error(Error::StsError, "Internal error");
         break;
     default:
         CV_Error( Error::StsBadArg, "Unrecognized method id. Actually only CV_HOUGH_GRADIENT is supported." );
@@ -1764,12 +1849,12 @@ cvHoughLines2( CvArr* src_image, void* lineStorage, int method,
     switch( method )
     {
     case CV_HOUGH_STANDARD:
-        HoughLinesStandard( image, (float)rho,
-                (float)theta, threshold, l2, linesMax, min_theta, max_theta );
+        HoughLinesStandard( image, l2, CV_32FC2, (float)rho,
+                (float)theta, threshold, linesMax, min_theta, max_theta );
         break;
     case CV_HOUGH_MULTI_SCALE:
-        HoughLinesSDiv( image, (float)rho, (float)theta,
-                threshold, iparam1, iparam2, l2, linesMax, min_theta, max_theta );
+        HoughLinesSDiv( image, l2, CV_32FC2, (float)rho, (float)theta,
+                threshold, iparam1, iparam2, linesMax, min_theta, max_theta );
         break;
     case CV_HOUGH_PROBABILISTIC:
         HoughLinesProbabilistic( image, (float)rho, (float)theta,
index 26213d3..dded1b3 100644 (file)
@@ -49,6 +49,8 @@ namespace opencv_test { namespace {
 #define DEBUG_IMAGES 0
 #endif
 
+//#define GENERATE_DATA // generate data in debug mode via CPU code path (without IPP / OpenCL and other accelerators)
+
 using namespace cv;
 using namespace std;
 
@@ -109,7 +111,8 @@ public:
     {
     }
 
-    void run_test()
+    template <typename CircleType>
+    void run_test(const char* xml_name)
     {
         string test_case_name = getTestCaseName(picture_name, minDist, edgeThreshold, accumThreshold, minRadius, maxRadius);
         string filename = cvtest::TS::ptr()->get_data_path() + picture_name;
@@ -118,7 +121,7 @@ public:
 
         GaussianBlur(src, src, Size(9, 9), 2, 2);
 
-        vector<Vec3f> circles;
+        vector<CircleType> circles;
         const double dp = 1.0;
         HoughCircles(src, circles, CV_HOUGH_GRADIENT, dp, minDist, edgeThreshold, accumThreshold, minRadius, maxRadius);
 
@@ -127,31 +130,37 @@ public:
         highlightCircles(filename, circles, imgProc + test_case_name + ".png");
 #endif
 
-        string xml = imgProc + "HoughCircles.xml";
-        FileStorage fs(xml, FileStorage::READ);
-        FileNode node = fs[test_case_name];
-        if (node.empty())
+        string xml = imgProc + xml_name;
+#ifdef GENERATE_DATA
         {
-            fs.release();
-            fs.open(xml, FileStorage::APPEND);
+            FileStorage fs(xml, FileStorage::READ);
+            ASSERT_TRUE(!fs.isOpened() || fs[test_case_name].empty());
+        }
+        {
+            FileStorage fs(xml, FileStorage::APPEND);
             EXPECT_TRUE(fs.isOpened()) << "Cannot open sanity data file: " << xml;
             fs << test_case_name << circles;
-            fs.release();
-            fs.open(xml, FileStorage::READ);
-            EXPECT_TRUE(fs.isOpened()) << "Cannot open sanity data file: " << xml;
         }
-
-        vector<Vec3f> exp_circles;
-        read(fs[test_case_name], exp_circles, vector<Vec3f>());
+#else
+        FileStorage fs(xml, FileStorage::READ);
+        FileNode node = fs[test_case_name];
+        ASSERT_FALSE(node.empty()) << "Missing test data: " << test_case_name << std::endl << "XML: " << xml;
+        vector<CircleType> exp_circles;
+        read(fs[test_case_name], exp_circles, vector<CircleType>());
         fs.release();
-
         EXPECT_EQ(exp_circles.size(), circles.size());
+#endif
     }
 };
 
 TEST_P(HoughCirclesTestFixture, regression)
 {
-    run_test();
+    run_test<Vec3f>("HoughCircles.xml");
+}
+
+TEST_P(HoughCirclesTestFixture, regression4f)
+{
+    run_test<Vec4f>("HoughCircles4f.xml");
 }
 
 INSTANTIATE_TEST_CASE_P(ImgProc, HoughCirclesTestFixture, testing::Combine(
@@ -186,7 +195,9 @@ TEST(HoughCirclesTest, DefaultMaxRadius)
     GaussianBlur(src, src, Size(9, 9), 2, 2);
 
     vector<Vec3f> circles;
+    vector<Vec4f> circles4f;
     HoughCircles(src, circles, CV_HOUGH_GRADIENT, dp, minDist, edgeThreshold, accumThreshold, minRadius, maxRadius);
+    HoughCircles(src, circles4f, CV_HOUGH_GRADIENT, dp, minDist, edgeThreshold, accumThreshold, minRadius, maxRadius);
 
 #if DEBUG_IMAGES
     string imgProc = string(cvtest::TS::ptr()->get_data_path()) + "imgproc/";
@@ -220,7 +231,9 @@ TEST(HoughCirclesTest, CentersOnly)
     GaussianBlur(src, src, Size(9, 9), 2, 2);
 
     vector<Vec3f> circles;
+    vector<Vec4f> circles4f;
     HoughCircles(src, circles, CV_HOUGH_GRADIENT, dp, minDist, edgeThreshold, accumThreshold, minRadius, maxRadius);
+    HoughCircles(src, circles4f, CV_HOUGH_GRADIENT, dp, minDist, edgeThreshold, accumThreshold, minRadius, maxRadius);
 
 #if DEBUG_IMAGES
     string imgProc = string(cvtest::TS::ptr()->get_data_path()) + "imgproc/";
@@ -231,6 +244,9 @@ TEST(HoughCirclesTest, CentersOnly)
     for (size_t i = 0; i < circles.size(); ++i)
     {
         EXPECT_EQ(circles[i][2], 0.0f) << "Did not ask for radius";
+        EXPECT_EQ(circles[i][0], circles4f[i][0]);
+        EXPECT_EQ(circles[i][1], circles4f[i][1]);
+        EXPECT_EQ(circles[i][2], circles4f[i][2]);
     }
 }
 
@@ -249,7 +265,9 @@ TEST(HoughCirclesTest, ManySmallCircles)
     EXPECT_FALSE(src.empty()) << "Invalid test image: " << filename;
 
     vector<Vec3f> circles;
+    vector<Vec4f> circles4f;
     HoughCircles(src, circles, CV_HOUGH_GRADIENT, dp, minDist, edgeThreshold, accumThreshold, minRadius, maxRadius);
+    HoughCircles(src, circles4f, CV_HOUGH_GRADIENT, dp, minDist, edgeThreshold, accumThreshold, minRadius, maxRadius);
 
 #if DEBUG_IMAGES
     string imgProc = string(cvtest::TS::ptr()->get_data_path()) + "imgproc/";
@@ -258,6 +276,7 @@ TEST(HoughCirclesTest, ManySmallCircles)
 #endif
 
     EXPECT_GT(circles.size(), size_t(3000)) << "Should find a lot of circles";
+    EXPECT_EQ(circles.size(), circles4f.size());
 }
 
 }} // namespace
index 916b922..fca0449 100644 (file)
@@ -43,6 +43,8 @@
 
 #include "test_precomp.hpp"
 
+//#define GENERATE_DATA // generate data in debug mode via CPU code path (without IPP / OpenCL and other accelerators)
+
 namespace opencv_test { namespace {
 
 template<typename T>
@@ -52,30 +54,36 @@ struct SimilarWith
     float theta_eps;
     float rho_eps;
     SimilarWith<T>(T val, float e, float r_e): value(val), theta_eps(e), rho_eps(r_e) { };
-    bool operator()(T other);
+    bool operator()(const T& other);
 };
 
 template<>
-bool SimilarWith<Vec2f>::operator()(Vec2f other)
+bool SimilarWith<Vec2f>::operator()(const Vec2f& other)
+{
+    return std::abs(other[0] - value[0]) < rho_eps && std::abs(other[1] - value[1]) < theta_eps;
+}
+
+template<>
+bool SimilarWith<Vec3f>::operator()(const Vec3f& other)
 {
     return std::abs(other[0] - value[0]) < rho_eps && std::abs(other[1] - value[1]) < theta_eps;
 }
 
 template<>
-bool SimilarWith<Vec4i>::operator()(Vec4i other)
+bool SimilarWith<Vec4i>::operator()(const Vec4i& other)
 {
     return cv::norm(value, other) < theta_eps;
 }
 
 template <typename T>
-int countMatIntersection(Mat expect, Mat actual, float eps, float rho_eps)
+int countMatIntersection(const Mat& expect, const Mat& actual, float eps, float rho_eps)
 {
     int count = 0;
     if (!expect.empty() && !actual.empty())
     {
-        for (MatIterator_<T> it=expect.begin<T>(); it!=expect.end<T>(); it++)
+        for (MatConstIterator_<T> it=expect.begin<T>(); it!=expect.end<T>(); it++)
         {
-            MatIterator_<T> f = std::find_if(actual.begin<T>(), actual.end<T>(), SimilarWith<T>(*it, eps, rho_eps));
+            MatConstIterator_<T> f = std::find_if(actual.begin<T>(), actual.end<T>(), SimilarWith<T>(*it, eps, rho_eps));
             if (f != actual.end<T>())
                 count++;
         }
@@ -99,7 +107,8 @@ class BaseHoughLineTest
 public:
     enum {STANDART = 0, PROBABILISTIC};
 protected:
-    void run_test(int type);
+    template<typename LinesType, typename LineType>
+    void run_test(int type, const char* xml_name);
 
     string picture_name;
     double rhoStep;
@@ -162,60 +171,63 @@ public:
     }
 };
 
-void BaseHoughLineTest::run_test(int type)
+template<typename LinesType, typename LineType>
+void BaseHoughLineTest::run_test(int type, const char* xml_name)
 {
     string filename = cvtest::TS::ptr()->get_data_path() + picture_name;
     Mat src = imread(filename, IMREAD_GRAYSCALE);
-    EXPECT_FALSE(src.empty()) << "Invalid test image: " << filename;
+    ASSERT_FALSE(src.empty()) << "Invalid test image: " << filename;
 
-    string xml;
-    if (type == STANDART)
-        xml = string(cvtest::TS::ptr()->get_data_path()) + "imgproc/HoughLines.xml";
-    else if (type == PROBABILISTIC)
-        xml = string(cvtest::TS::ptr()->get_data_path()) + "imgproc/HoughLinesP.xml";
+    string xml = string(cvtest::TS::ptr()->get_data_path()) + "imgproc/" + xml_name;
 
     Mat dst;
     Canny(src, dst, 100, 150, 3);
-    EXPECT_FALSE(dst.empty()) << "Failed Canny edge detector";
+    ASSERT_FALSE(dst.empty()) << "Failed Canny edge detector";
 
-    Mat lines;
+    LinesType lines;
     if (type == STANDART)
         HoughLines(dst, lines, rhoStep, thetaStep, threshold, 0, 0);
     else if (type == PROBABILISTIC)
         HoughLinesP(dst, lines, rhoStep, thetaStep, threshold, minLineLength, maxGap);
 
     String test_case_name = format("lines_%s_%.0f_%.2f_%d_%d_%d", picture_name.c_str(), rhoStep, thetaStep,
-                                    threshold, minLineLength, maxGap);
+                                   threshold, minLineLength, maxGap);
     test_case_name = getTestCaseName(test_case_name);
 
-    FileStorage fs(xml, FileStorage::READ);
-    FileNode node = fs[test_case_name];
-    if (node.empty())
+#ifdef GENERATE_DATA
     {
-        fs.release();
-        fs.open(xml, FileStorage::APPEND);
-        EXPECT_TRUE(fs.isOpened()) << "Cannot open sanity data file: " << xml;
-        fs << test_case_name << lines;
-        fs.release();
-        fs.open(xml, FileStorage::READ);
+        FileStorage fs(xml, FileStorage::READ);
+        ASSERT_TRUE(!fs.isOpened() || fs[test_case_name].empty());
+    }
+    {
+        FileStorage fs(xml, FileStorage::APPEND);
         EXPECT_TRUE(fs.isOpened()) << "Cannot open sanity data file: " << xml;
+        fs << test_case_name << Mat(lines);
     }
+#else
+    FileStorage fs(xml, FileStorage::READ);
+    FileNode node = fs[test_case_name];
+    ASSERT_FALSE(node.empty()) << "Missing test data: " << test_case_name << std::endl << "XML: " << xml;
 
-    Mat exp_lines;
-    read( fs[test_case_name], exp_lines, Mat() );
+    Mat exp_lines_;
+    read(fs[test_case_name], exp_lines_, Mat());
     fs.release();
+    LinesType exp_lines;
+    exp_lines_.copyTo(exp_lines);
 
     int count = -1;
     if (type == STANDART)
-        count = countMatIntersection<Vec2f>(exp_lines, lines, (float) thetaStep + FLT_EPSILON, (float) rhoStep + FLT_EPSILON);
+        count = countMatIntersection<LineType>(Mat(exp_lines), Mat(lines), (float) thetaStep + FLT_EPSILON, (float) rhoStep + FLT_EPSILON);
     else if (type == PROBABILISTIC)
-        count = countMatIntersection<Vec4i>(exp_lines, lines, 1e-4f, 0.f);
+        count = countMatIntersection<LineType>(Mat(exp_lines), Mat(lines), 1e-4f, 0.f);
 
 #if defined HAVE_IPP && IPP_VERSION_X100 >= 810 && !IPP_DISABLE_HOUGH
-    EXPECT_GE( count, (int) (exp_lines.total() * 0.8) );
+    EXPECT_LE(std::abs((double)count - Mat(exp_lines).total()), Mat(exp_lines).total() * 0.25)
+        << "count=" << count << " expected=" << Mat(exp_lines).total();
 #else
-    EXPECT_EQ( count, (int) exp_lines.total());
+    EXPECT_EQ(count, (int)Mat(exp_lines).total());
 #endif
+#endif // GENERATE_DATA
 }
 
 void HoughLinesPointSetTest::run_test(void)
@@ -264,12 +276,22 @@ void HoughLinesPointSetTest::run_test(void)
 
 TEST_P(StandartHoughLinesTest, regression)
 {
-    run_test(STANDART);
+    run_test<Mat, Vec2f>(STANDART, "HoughLines.xml");
 }
 
 TEST_P(ProbabilisticHoughLinesTest, regression)
 {
-    run_test(PROBABILISTIC);
+    run_test<Mat, Vec4i>(PROBABILISTIC, "HoughLinesP.xml");
+}
+
+TEST_P(StandartHoughLinesTest, regression_Vec2f)
+{
+    run_test<std::vector<Vec2f>, Vec2f>(STANDART, "HoughLines2f.xml");
+}
+
+TEST_P(StandartHoughLinesTest, regression_Vec3f)
+{
+    run_test<std::vector<Vec3f>, Vec3f>(STANDART, "HoughLines3f.xml");
 }
 
 TEST_P(HoughLinesPointSetTest, regression)