diff --git a/modules/imgproc/include/opencv2/imgproc.hpp b/modules/imgproc/include/opencv2/imgproc.hpp index 1d94e748d5..a58b5b799b 100644 --- a/modules/imgproc/include/opencv2/imgproc.hpp +++ b/modules/imgproc/include/opencv2/imgproc.hpp @@ -1977,10 +1977,11 @@ detection. See 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 diff --git a/modules/imgproc/perf/perf_houghcircles.cpp b/modules/imgproc/perf/perf_houghcircles.cpp index fb6340831c..ba4fab8f99 100644 --- a/modules/imgproc/perf/perf_houghcircles.cpp +++ b/modules/imgproc/perf/perf_houghcircles.cpp @@ -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 circles; + declare.in(img); + + TEST_CYCLE() + { + HoughCircles(img, circles, CV_HOUGH_GRADIENT, dp, minDist, edgeThreshold, accumThreshold, minRadius, maxRadius); + } + + SANITY_CHECK_NOTHING(); +} + } // namespace diff --git a/modules/imgproc/perf/perf_houghlines.cpp b/modules/imgproc/perf/perf_houghlines.cpp index 4d5aedd9c7..eaadaf1d97 100644 --- a/modules/imgproc/perf/perf_houghlines.cpp +++ b/modules/imgproc/perf/perf_houghlines.cpp @@ -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 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 diff --git a/modules/imgproc/src/hough.cpp b/modules/imgproc/src/hough.cpp index 68dfabef5b..ec05edf888 100644 --- a/modules/imgproc/src/hough.cpp +++ b/modules/imgproc/src/hough.cpp @@ -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& 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 _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(min_theta) + n * theta; - lines.push_back(Vec2f(line.rho, line.angle)); + if (type == CV_32FC2) + { + _lines.at(i) = Vec2f(line.rho, line.angle); + } + else + { + CV_DbgAssert(type == CV_32FC3); + _lines.at(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& 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((int)idx) = Vec2f(lst[idx].rho, lst[idx].theta); + } + else + { + CV_DbgAssert(type == CV_32FC3); + _lines.at((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 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 { private: @@ -1264,12 +1302,13 @@ private: Mutex& _lock; }; -static bool CheckDistance(const std::vector &circles, size_t endIdx, const Vec3f& circle, float minDist2) +template +static bool CheckDistance(const std::vector &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 ¢ers, std::vector } } -static void RemoveOverlaps(std::vector& circles, float minDist) +static void GetCircleCenters(const std::vector ¢ers, std::vector &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 +static void RemoveOverlaps(std::vector& 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& circles, float minDist) circles.resize(endIdx); } +static void CreateCircles(const std::vector& circlesEst, std::vector& circles) +{ + std::transform(circlesEst.begin(), circlesEst.end(), std::back_inserter(circles), GetCircle); +} + +static void CreateCircles(const std::vector& circlesEst, std::vector& circles) +{ + std::transform(circlesEst.begin(), circlesEst.end(), std::back_inserter(circles), GetCircle4f); +} + template class HoughCircleEstimateRadiusInvoker : public ParallelLoopBody { @@ -1556,11 +1623,14 @@ inline int HoughCircleEstimateRadiusInvoker::filterCircles(const Poi return nzCount; } -static void HoughCirclesGradient(InputArray _image, OutputArray _circles, float dp, float minDist, +template +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())); - std::vector circles; + std::vector 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::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(_image, _circles, (float)dp, (float)minDist, + minRadius, maxRadius, cannyThresh, + accThresh, maxCircles, kernelSize, centersOnly); + else if (type == CV_32FC4) + HoughCirclesGradient(_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, diff --git a/modules/imgproc/test/test_houghcircles.cpp b/modules/imgproc/test/test_houghcircles.cpp index 26213d3487..dded1b3ea8 100644 --- a/modules/imgproc/test/test_houghcircles.cpp +++ b/modules/imgproc/test/test_houghcircles.cpp @@ -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 + 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 circles; + vector 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 exp_circles; - read(fs[test_case_name], exp_circles, vector()); +#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 exp_circles; + read(fs[test_case_name], exp_circles, vector()); fs.release(); - EXPECT_EQ(exp_circles.size(), circles.size()); +#endif } }; TEST_P(HoughCirclesTestFixture, regression) { - run_test(); + run_test("HoughCircles.xml"); +} + +TEST_P(HoughCirclesTestFixture, regression4f) +{ + run_test("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 circles; + vector 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 circles; + vector 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 circles; + vector 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 diff --git a/modules/imgproc/test/test_houghlines.cpp b/modules/imgproc/test/test_houghlines.cpp index 916b9223a4..fca0449b91 100644 --- a/modules/imgproc/test/test_houghlines.cpp +++ b/modules/imgproc/test/test_houghlines.cpp @@ -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 @@ -52,30 +54,36 @@ struct SimilarWith float theta_eps; float rho_eps; SimilarWith(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::operator()(Vec2f other) +bool SimilarWith::operator()(const Vec2f& other) +{ + return std::abs(other[0] - value[0]) < rho_eps && std::abs(other[1] - value[1]) < theta_eps; +} + +template<> +bool SimilarWith::operator()(const Vec3f& other) { return std::abs(other[0] - value[0]) < rho_eps && std::abs(other[1] - value[1]) < theta_eps; } template<> -bool SimilarWith::operator()(Vec4i other) +bool SimilarWith::operator()(const Vec4i& other) { return cv::norm(value, other) < theta_eps; } template -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_ it=expect.begin(); it!=expect.end(); it++) + for (MatConstIterator_ it=expect.begin(); it!=expect.end(); it++) { - MatIterator_ f = std::find_if(actual.begin(), actual.end(), SimilarWith(*it, eps, rho_eps)); + MatConstIterator_ f = std::find_if(actual.begin(), actual.end(), SimilarWith(*it, eps, rho_eps)); if (f != actual.end()) count++; } @@ -99,7 +107,8 @@ class BaseHoughLineTest public: enum {STANDART = 0, PROBABILISTIC}; protected: - void run_test(int type); + template + 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 +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(exp_lines, lines, (float) thetaStep + FLT_EPSILON, (float) rhoStep + FLT_EPSILON); + count = countMatIntersection(Mat(exp_lines), Mat(lines), (float) thetaStep + FLT_EPSILON, (float) rhoStep + FLT_EPSILON); else if (type == PROBABILISTIC) - count = countMatIntersection(exp_lines, lines, 1e-4f, 0.f); + count = countMatIntersection(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(STANDART, "HoughLines.xml"); } TEST_P(ProbabilisticHoughLinesTest, regression) { - run_test(PROBABILISTIC); + run_test(PROBABILISTIC, "HoughLinesP.xml"); +} + +TEST_P(StandartHoughLinesTest, regression_Vec2f) +{ + run_test, Vec2f>(STANDART, "HoughLines2f.xml"); +} + +TEST_P(StandartHoughLinesTest, regression_Vec3f) +{ + run_test, Vec3f>(STANDART, "HoughLines3f.xml"); } TEST_P(HoughLinesPointSetTest, regression)