Commit ed207d79 authored by yuki takehara's avatar yuki takehara Committed by Alexander Alekhin

Merge pull request #11108 from take1014:hough_4303

* 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
parent 2d5d98ec
...@@ -1977,10 +1977,11 @@ detection. See <http://homepages.inf.ed.ac.uk/rbf/HIPR2/hough.htm> for a good ex ...@@ -1977,10 +1977,11 @@ detection. See <http://homepages.inf.ed.ac.uk/rbf/HIPR2/hough.htm> for a good ex
transform. transform.
@param image 8-bit, single-channel binary source image. The image may be modified by the function. @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 @param lines Output vector of lines. Each line is represented by a 2 or 3 element vector
\f$(\rho, \theta)\f$ . \f$\rho\f$ is the distance from the coordinate origin \f$(0,0)\f$ (top-left corner of \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 ( 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$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 rho Distance resolution of the accumulator in pixels.
@param theta Angle resolution of the accumulator in radians. @param theta Angle resolution of the accumulator in radians.
@param threshold Accumulator threshold parameter. Only those lines are returned that get enough @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 ...@@ -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. search, and find the correct radius using an additional procedure.
@param image 8-bit, single-channel, grayscale input image. @param image 8-bit, single-channel, grayscale input image.
@param circles Output vector of found circles. Each vector is encoded as a 3-element @param circles Output vector of found circles. Each vector is encoded as 3 or 4 element
floating-point vector \f$(x, y, radius)\f$ . 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 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 @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 dp=1 , the accumulator has the same resolution as the input image. If dp=2 , the accumulator has
......
...@@ -56,4 +56,30 @@ PERF_TEST(PerfHoughCircles2, ManySmallCircles) ...@@ -56,4 +56,30 @@ PERF_TEST(PerfHoughCircles2, ManySmallCircles)
SANITY_CHECK_NOTHING(); 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 } // namespace
...@@ -69,4 +69,47 @@ PERF_TEST_P(Image_RhoStep_ThetaStep_Threshold, HoughLines, ...@@ -69,4 +69,47 @@ PERF_TEST_P(Image_RhoStep_ThetaStep_Threshold, HoughLines,
SANITY_CHECK_NOTHING(); 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 } // namespace
...@@ -105,48 +105,56 @@ array of (rho, theta) pairs. linesMax is the buffer size (number of pairs). ...@@ -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. Functions return the actual number of found lines.
*/ */
static void static void
HoughLinesStandard( const Mat& img, float rho, float theta, HoughLinesStandard( InputArray src, OutputArray lines, int type,
int threshold, std::vector<Vec2f>& lines, int linesMax, float rho, float theta,
int threshold, int linesMax,
double min_theta, double max_theta ) double min_theta, double max_theta )
{ {
CV_CheckType(type, type == CV_32FC2 || type == CV_32FC3, "Internal error");
Mat img = src.getMat();
int i, j; int i, j;
float irho = 1 / rho; float irho = 1 / rho;
CV_Assert( img.type() == CV_8UC1 ); CV_Assert( img.type() == CV_8UC1 );
CV_Assert( linesMax > 0 );
const uchar* image = img.ptr(); const uchar* image = img.ptr();
int step = (int)img.step; int step = (int)img.step;
int width = img.cols; int width = img.cols;
int height = img.rows; int height = img.rows;
if (max_theta < min_theta ) { int max_rho = width + height;
CV_Error( CV_StsBadArg, "max_theta must be greater than min_theta" ); 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 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 #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 }; IppiSize srcSize = { width, height };
IppPointPolar delta = { rho, theta }; 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 bufferSize;
int nz = countNonZero(img); int nz = countNonZero(img);
int ipp_linesMax = std::min(linesMax, nz*numangle/threshold); int ipp_linesMax = std::min(linesMax, nz*numangle/threshold);
int linesCount = 0; int linesCount = 0;
lines.resize(ipp_linesMax); std::vector<Vec2f> _lines(ipp_linesMax);
IppStatus ok = ippiHoughLineGetSize_8u_C1R(srcSize, delta, ipp_linesMax, &bufferSize); IppStatus ok = ippiHoughLineGetSize_8u_C1R(srcSize, delta, ipp_linesMax, &bufferSize);
Ipp8u* buffer = ippsMalloc_8u_L(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); ippsFree(buffer);
if (ok >= 0) 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); CV_IMPL_ADD(CV_IMPL_IPP);
return; return;
} }
lines.clear();
setIppErrorStatus(); setIppErrorStatus();
} }
#endif #endif
...@@ -185,6 +193,9 @@ HoughLinesStandard( const Mat& img, float rho, float theta, ...@@ -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 // stage 4. store the first min(total,linesMax) lines to the output buffer
linesMax = std::min(linesMax, (int)_sort_buf.size()); linesMax = std::min(linesMax, (int)_sort_buf.size());
double scale = 1./(numrho+2); double scale = 1./(numrho+2);
lines.create(linesMax, 1, type);
Mat _lines = lines.getMat();
for( i = 0; i < linesMax; i++ ) for( i = 0; i < linesMax; i++ )
{ {
LinePolar line; LinePolar line;
...@@ -193,7 +204,15 @@ HoughLinesStandard( const Mat& img, float rho, float theta, ...@@ -193,7 +204,15 @@ HoughLinesStandard( const Mat& img, float rho, float theta,
int r = idx - (n+1)*(numrho+2) - 1; int r = idx - (n+1)*(numrho+2) - 1;
line.rho = (r - (numrho - 1)*0.5f) * rho; line.rho = (r - (numrho - 1)*0.5f) * rho;
line.angle = static_cast<float>(min_theta) + n * theta; 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 ...@@ -212,15 +231,17 @@ struct hough_index
static void static void
HoughLinesSDiv( const Mat& img, HoughLinesSDiv( InputArray image, OutputArray lines, int type,
float rho, float theta, int threshold, float rho, float theta, int threshold,
int srn, int stn, int srn, int stn, int linesMax,
std::vector<Vec2f>& lines, int linesMax,
double min_theta, double max_theta ) double min_theta, double max_theta )
{ {
CV_CheckType(type, type == CV_32FC2 || type == CV_32FC3, "Internal error");
#define _POINT(row, column)\ #define _POINT(row, column)\
(image_src[(row)*step+(column)]) (image_src[(row)*step+(column)])
Mat img = image.getMat();
int index, i; int index, i;
int ri, ti, ti1, ti0; int ri, ti, ti1, ti0;
int row, col; int row, col;
...@@ -343,7 +364,7 @@ HoughLinesSDiv( const Mat& img, ...@@ -343,7 +364,7 @@ HoughLinesSDiv( const Mat& img,
if( count * 100 > rn * tn ) 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; return;
} }
...@@ -415,11 +436,21 @@ HoughLinesSDiv( const Mat& img, ...@@ -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++ ) for( size_t idx = 0; idx < lst.size(); idx++ )
{ {
if( lst[idx].rho < 0 ) if( lst[idx].rho < 0 )
continue; 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 ...@@ -861,24 +892,26 @@ static bool ocl_HoughLinesP(InputArray _src, OutputArray _lines, double rho, dou
#endif /* HAVE_OPENCL */ #endif /* HAVE_OPENCL */
void HoughLines( InputArray _image, OutputArray _lines, void HoughLines( InputArray _image, OutputArray lines,
double rho, double theta, int threshold, double rho, double theta, int threshold,
double srn, double stn, double min_theta, double max_theta ) double srn, double stn, double min_theta, double max_theta )
{ {
CV_INSTRUMENT_REGION() CV_INSTRUMENT_REGION()
CV_OCL_RUN(srn == 0 && stn == 0 && _image.isUMat() && _lines.isUMat(), int type = CV_32FC2;
ocl_HoughLines(_image, _lines, rho, theta, threshold, min_theta, max_theta)); if (lines.fixedType())
{
type = lines.type();
CV_CheckType(type, type == CV_32FC2 || type == CV_32FC3, "Wrong type of output lines");
}
Mat image = _image.getMat(); CV_OCL_RUN(srn == 0 && stn == 0 && _image.isUMat() && lines.isUMat() && type == CV_32FC2,
std::vector<Vec2f> lines; ocl_HoughLines(_image, lines, rho, theta, threshold, min_theta, max_theta));
if( srn == 0 && stn == 0 ) 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 else
HoughLinesSDiv(image, (float)rho, (float)theta, threshold, cvRound(srn), cvRound(stn), lines, INT_MAX, min_theta, max_theta); HoughLinesSDiv(_image, lines, type, (float)rho, (float)theta, threshold, cvRound(srn), cvRound(stn), INT_MAX, min_theta, max_theta);
Mat(lines).copyTo(_lines);
} }
...@@ -1007,11 +1040,16 @@ static bool cmpAccum(const EstimatedCircle& left, const EstimatedCircle& right) ...@@ -1007,11 +1040,16 @@ static bool cmpAccum(const EstimatedCircle& left, const EstimatedCircle& right)
return false; return false;
} }
inline Vec3f GetCircle(const EstimatedCircle& est) static inline Vec3f GetCircle(const EstimatedCircle& est)
{ {
return est.c; 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> class NZPointList : public std::vector<Point>
{ {
private: private:
...@@ -1264,12 +1302,13 @@ private: ...@@ -1264,12 +1302,13 @@ private:
Mutex& _lock; 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; bool goodPoint = true;
for (uint j = 0; j < endIdx; ++j) 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]; float distX = circle[0] - pt[0], distY = circle[1] - pt[1];
if (distX * distX + distY * distY < minDist2) if (distX * distX + distY * distY < minDist2)
{ {
...@@ -1297,13 +1336,31 @@ static void GetCircleCenters(const std::vector<int> &centers, std::vector<Vec3f> ...@@ -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; float minDist2 = minDist * minDist;
size_t endIdx = 1; size_t endIdx = 1;
for (size_t i = 1; i < circles.size(); ++i) for (size_t i = 1; i < circles.size(); ++i)
{ {
Vec3f circle = circles[i]; T circle = circles[i];
if (CheckDistance(circles, endIdx, circle, minDist2)) if (CheckDistance(circles, endIdx, circle, minDist2))
{ {
circles[endIdx] = circle; circles[endIdx] = circle;
...@@ -1313,6 +1370,16 @@ static void RemoveOverlaps(std::vector<Vec3f>& circles, float minDist) ...@@ -1313,6 +1370,16 @@ static void RemoveOverlaps(std::vector<Vec3f>& circles, float minDist)
circles.resize(endIdx); 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> template<class NZPoints>
class HoughCircleEstimateRadiusInvoker : public ParallelLoopBody class HoughCircleEstimateRadiusInvoker : public ParallelLoopBody
{ {
...@@ -1556,11 +1623,14 @@ inline int HoughCircleEstimateRadiusInvoker<NZPointSet>::filterCircles(const Poi ...@@ -1556,11 +1623,14 @@ inline int HoughCircleEstimateRadiusInvoker<NZPointSet>::filterCircles(const Poi
return nzCount; 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 minRadius, int maxRadius, int cannyThreshold,
int accThreshold, int maxCircles, int kernelSize, bool centersOnly) int accThreshold, int maxCircles, int kernelSize, bool centersOnly)
{ {
CV_Assert(kernelSize == -1 || kernelSize == 3 || kernelSize == 5 || kernelSize == 7); CV_Assert(kernelSize == -1 || kernelSize == 3 || kernelSize == 5 || kernelSize == 7);
dp = max(dp, 1.f); dp = max(dp, 1.f);
float idp = 1.f/dp; float idp = 1.f/dp;
...@@ -1602,7 +1672,7 @@ static void HoughCirclesGradient(InputArray _image, OutputArray _circles, float ...@@ -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::sort(centers.begin(), centers.end(), hough_cmp_gt(accum.ptr<int>()));
std::vector<Vec3f> circles; std::vector<CircleType> circles;
circles.reserve(256); circles.reserve(256);
if (centersOnly) if (centersOnly)
{ {
...@@ -1635,15 +1705,16 @@ static void HoughCirclesGradient(InputArray _image, OutputArray _circles, float ...@@ -1635,15 +1705,16 @@ static void HoughCirclesGradient(InputArray _image, OutputArray _circles, float
// Sort by accumulator value // Sort by accumulator value
std::sort(circlesEst.begin(), circlesEst.end(), cmpAccum); 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); RemoveOverlaps(circles, minDist);
} }
if(circles.size() > 0) if (circles.size() > 0)
{ {
int numCircles = std::min(maxCircles, int(circles.size())); int numCircles = std::min(maxCircles, int(circles.size()));
_circles.create(1, numCircles, CV_32FC3); Mat(1, numCircles, cv::traits::Type<CircleType>::value, &circles[0]).copyTo(_circles);
Mat(1, numCircles, CV_32FC3, &circles[0]).copyTo(_circles.getMat());
return; return;
} }
} }
...@@ -1656,6 +1727,13 @@ static void HoughCircles( InputArray _image, OutputArray _circles, ...@@ -1656,6 +1727,13 @@ static void HoughCircles( InputArray _image, OutputArray _circles,
{ {
CV_INSTRUMENT_REGION() 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(!_image.empty() && _image.type() == CV_8UC1 && (_image.isMat() || _image.isUMat()));
CV_Assert(_circles.isMat() || _circles.isVector()); CV_Assert(_circles.isMat() || _circles.isVector());
...@@ -1679,9 +1757,16 @@ static void HoughCircles( InputArray _image, OutputArray _circles, ...@@ -1679,9 +1757,16 @@ static void HoughCircles( InputArray _image, OutputArray _circles,
switch( method ) switch( method )
{ {
case CV_HOUGH_GRADIENT: case CV_HOUGH_GRADIENT:
HoughCirclesGradient(_image, _circles, (float)dp, (float)minDist, if (type == CV_32FC3)
HoughCirclesGradient<Vec3f>(_image, _circles, (float)dp, (float)minDist,
minRadius, maxRadius, cannyThresh, minRadius, maxRadius, cannyThresh,
accThresh, maxCircles, kernelSize, centersOnly); 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; break;
default: default:
CV_Error( Error::StsBadArg, "Unrecognized method id. Actually only CV_HOUGH_GRADIENT is supported." ); 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, ...@@ -1764,12 +1849,12 @@ cvHoughLines2( CvArr* src_image, void* lineStorage, int method,
switch( method ) switch( method )
{ {
case CV_HOUGH_STANDARD: case CV_HOUGH_STANDARD:
HoughLinesStandard( image, (float)rho, HoughLinesStandard( image, l2, CV_32FC2, (float)rho,
(float)theta, threshold, l2, linesMax, min_theta, max_theta ); (float)theta, threshold, linesMax, min_theta, max_theta );
break; break;
case CV_HOUGH_MULTI_SCALE: case CV_HOUGH_MULTI_SCALE:
HoughLinesSDiv( image, (float)rho, (float)theta, HoughLinesSDiv( image, l2, CV_32FC2, (float)rho, (float)theta,
threshold, iparam1, iparam2, l2, linesMax, min_theta, max_theta ); threshold, iparam1, iparam2, linesMax, min_theta, max_theta );
break; break;
case CV_HOUGH_PROBABILISTIC: case CV_HOUGH_PROBABILISTIC:
HoughLinesProbabilistic( image, (float)rho, (float)theta, HoughLinesProbabilistic( image, (float)rho, (float)theta,
......
...@@ -49,6 +49,8 @@ namespace opencv_test { namespace { ...@@ -49,6 +49,8 @@ namespace opencv_test { namespace {
#define DEBUG_IMAGES 0 #define DEBUG_IMAGES 0
#endif #endif
//#define GENERATE_DATA // generate data in debug mode via CPU code path (without IPP / OpenCL and other accelerators)
using namespace cv; using namespace cv;
using namespace std; using namespace std;
...@@ -109,7 +111,8 @@ public: ...@@ -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 test_case_name = getTestCaseName(picture_name, minDist, edgeThreshold, accumThreshold, minRadius, maxRadius);
string filename = cvtest::TS::ptr()->get_data_path() + picture_name; string filename = cvtest::TS::ptr()->get_data_path() + picture_name;
...@@ -118,7 +121,7 @@ public: ...@@ -118,7 +121,7 @@ public:
GaussianBlur(src, src, Size(9, 9), 2, 2); GaussianBlur(src, src, Size(9, 9), 2, 2);
vector<Vec3f> circles; vector<CircleType> circles;
const double dp = 1.0; const double dp = 1.0;
HoughCircles(src, circles, CV_HOUGH_GRADIENT, dp, minDist, edgeThreshold, accumThreshold, minRadius, maxRadius); HoughCircles(src, circles, CV_HOUGH_GRADIENT, dp, minDist, edgeThreshold, accumThreshold, minRadius, maxRadius);
...@@ -127,31 +130,37 @@ public: ...@@ -127,31 +130,37 @@ public:
highlightCircles(filename, circles, imgProc + test_case_name + ".png"); highlightCircles(filename, circles, imgProc + test_case_name + ".png");
#endif #endif
string xml = imgProc + "HoughCircles.xml"; string xml = imgProc + xml_name;
#ifdef GENERATE_DATA
{
FileStorage fs(xml, FileStorage::READ); FileStorage fs(xml, FileStorage::READ);
FileNode node = fs[test_case_name]; ASSERT_TRUE(!fs.isOpened() || fs[test_case_name].empty());
if (node.empty()) }
{ {
fs.release(); FileStorage fs(xml, FileStorage::APPEND);
fs.open(xml, FileStorage::APPEND);
EXPECT_TRUE(fs.isOpened()) << "Cannot open sanity data file: " << xml; EXPECT_TRUE(fs.isOpened()) << "Cannot open sanity data file: " << xml;
fs << test_case_name << circles; fs << test_case_name << circles;
fs.release();
fs.open(xml, FileStorage::READ);
EXPECT_TRUE(fs.isOpened()) << "Cannot open sanity data file: " << xml;
} }
#else
vector<Vec3f> exp_circles; FileStorage fs(xml, FileStorage::READ);
read(fs[test_case_name], exp_circles, vector<Vec3f>()); 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(); fs.release();
EXPECT_EQ(exp_circles.size(), circles.size()); EXPECT_EQ(exp_circles.size(), circles.size());
#endif
} }
}; };
TEST_P(HoughCirclesTestFixture, regression) 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( INSTANTIATE_TEST_CASE_P(ImgProc, HoughCirclesTestFixture, testing::Combine(
...@@ -186,7 +195,9 @@ TEST(HoughCirclesTest, DefaultMaxRadius) ...@@ -186,7 +195,9 @@ TEST(HoughCirclesTest, DefaultMaxRadius)
GaussianBlur(src, src, Size(9, 9), 2, 2); GaussianBlur(src, src, Size(9, 9), 2, 2);
vector<Vec3f> circles; vector<Vec3f> circles;
vector<Vec4f> circles4f;
HoughCircles(src, circles, CV_HOUGH_GRADIENT, dp, minDist, edgeThreshold, accumThreshold, minRadius, maxRadius); 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 #if DEBUG_IMAGES
string imgProc = string(cvtest::TS::ptr()->get_data_path()) + "imgproc/"; string imgProc = string(cvtest::TS::ptr()->get_data_path()) + "imgproc/";
...@@ -220,7 +231,9 @@ TEST(HoughCirclesTest, CentersOnly) ...@@ -220,7 +231,9 @@ TEST(HoughCirclesTest, CentersOnly)
GaussianBlur(src, src, Size(9, 9), 2, 2); GaussianBlur(src, src, Size(9, 9), 2, 2);
vector<Vec3f> circles; vector<Vec3f> circles;
vector<Vec4f> circles4f;
HoughCircles(src, circles, CV_HOUGH_GRADIENT, dp, minDist, edgeThreshold, accumThreshold, minRadius, maxRadius); 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 #if DEBUG_IMAGES
string imgProc = string(cvtest::TS::ptr()->get_data_path()) + "imgproc/"; string imgProc = string(cvtest::TS::ptr()->get_data_path()) + "imgproc/";
...@@ -231,6 +244,9 @@ TEST(HoughCirclesTest, CentersOnly) ...@@ -231,6 +244,9 @@ TEST(HoughCirclesTest, CentersOnly)
for (size_t i = 0; i < circles.size(); ++i) 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][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) ...@@ -249,7 +265,9 @@ TEST(HoughCirclesTest, ManySmallCircles)
EXPECT_FALSE(src.empty()) << "Invalid test image: " << filename; EXPECT_FALSE(src.empty()) << "Invalid test image: " << filename;
vector<Vec3f> circles; vector<Vec3f> circles;
vector<Vec4f> circles4f;
HoughCircles(src, circles, CV_HOUGH_GRADIENT, dp, minDist, edgeThreshold, accumThreshold, minRadius, maxRadius); 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 #if DEBUG_IMAGES
string imgProc = string(cvtest::TS::ptr()->get_data_path()) + "imgproc/"; string imgProc = string(cvtest::TS::ptr()->get_data_path()) + "imgproc/";
...@@ -258,6 +276,7 @@ TEST(HoughCirclesTest, ManySmallCircles) ...@@ -258,6 +276,7 @@ TEST(HoughCirclesTest, ManySmallCircles)
#endif #endif
EXPECT_GT(circles.size(), size_t(3000)) << "Should find a lot of circles"; EXPECT_GT(circles.size(), size_t(3000)) << "Should find a lot of circles";
EXPECT_EQ(circles.size(), circles4f.size());
} }
}} // namespace }} // namespace
...@@ -43,6 +43,8 @@ ...@@ -43,6 +43,8 @@
#include "test_precomp.hpp" #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 { namespace opencv_test { namespace {
template<typename T> template<typename T>
...@@ -52,30 +54,36 @@ struct SimilarWith ...@@ -52,30 +54,36 @@ struct SimilarWith
float theta_eps; float theta_eps;
float rho_eps; float rho_eps;
SimilarWith<T>(T val, float e, float r_e): value(val), theta_eps(e), rho_eps(r_e) { }; 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<> 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; return std::abs(other[0] - value[0]) < rho_eps && std::abs(other[1] - value[1]) < theta_eps;
} }
template<> template<>
bool SimilarWith<Vec4i>::operator()(Vec4i other) bool SimilarWith<Vec4i>::operator()(const Vec4i& other)
{ {
return cv::norm(value, other) < theta_eps; return cv::norm(value, other) < theta_eps;
} }
template <typename T> 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; int count = 0;
if (!expect.empty() && !actual.empty()) 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>()) if (f != actual.end<T>())
count++; count++;
} }
...@@ -99,7 +107,8 @@ class BaseHoughLineTest ...@@ -99,7 +107,8 @@ class BaseHoughLineTest
public: public:
enum {STANDART = 0, PROBABILISTIC}; enum {STANDART = 0, PROBABILISTIC};
protected: protected:
void run_test(int type); template<typename LinesType, typename LineType>
void run_test(int type, const char* xml_name);
string picture_name; string picture_name;
double rhoStep; double rhoStep;
...@@ -162,23 +171,20 @@ public: ...@@ -162,23 +171,20 @@ 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; string filename = cvtest::TS::ptr()->get_data_path() + picture_name;
Mat src = imread(filename, IMREAD_GRAYSCALE); Mat src = imread(filename, IMREAD_GRAYSCALE);
EXPECT_FALSE(src.empty()) << "Invalid test image: " << filename; ASSERT_FALSE(src.empty()) << "Invalid test image: " << filename;
string xml; string xml = string(cvtest::TS::ptr()->get_data_path()) + "imgproc/" + xml_name;
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";
Mat dst; Mat dst;
Canny(src, dst, 100, 150, 3); 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) if (type == STANDART)
HoughLines(dst, lines, rhoStep, thetaStep, threshold, 0, 0); HoughLines(dst, lines, rhoStep, thetaStep, threshold, 0, 0);
else if (type == PROBABILISTIC) else if (type == PROBABILISTIC)
...@@ -188,34 +194,40 @@ void BaseHoughLineTest::run_test(int type) ...@@ -188,34 +194,40 @@ void BaseHoughLineTest::run_test(int type)
threshold, minLineLength, maxGap); threshold, minLineLength, maxGap);
test_case_name = getTestCaseName(test_case_name); test_case_name = getTestCaseName(test_case_name);
#ifdef GENERATE_DATA
{
FileStorage fs(xml, FileStorage::READ); FileStorage fs(xml, FileStorage::READ);
FileNode node = fs[test_case_name]; ASSERT_TRUE(!fs.isOpened() || fs[test_case_name].empty());
if (node.empty()) }
{ {
fs.release(); FileStorage fs(xml, FileStorage::APPEND);
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);
EXPECT_TRUE(fs.isOpened()) << "Cannot open sanity data file: " << xml; 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; Mat exp_lines_;
read( fs[test_case_name], exp_lines, Mat() ); read(fs[test_case_name], exp_lines_, Mat());
fs.release(); fs.release();
LinesType exp_lines;
exp_lines_.copyTo(exp_lines);
int count = -1; int count = -1;
if (type == STANDART) 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) 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 #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 #else
EXPECT_EQ( count, (int) exp_lines.total()); EXPECT_EQ(count, (int)Mat(exp_lines).total());
#endif #endif
#endif // GENERATE_DATA
} }
void HoughLinesPointSetTest::run_test(void) void HoughLinesPointSetTest::run_test(void)
...@@ -264,12 +276,22 @@ void HoughLinesPointSetTest::run_test(void) ...@@ -264,12 +276,22 @@ void HoughLinesPointSetTest::run_test(void)
TEST_P(StandartHoughLinesTest, regression) TEST_P(StandartHoughLinesTest, regression)
{ {
run_test(STANDART); run_test<Mat, Vec2f>(STANDART, "HoughLines.xml");
} }
TEST_P(ProbabilisticHoughLinesTest, regression) 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) TEST_P(HoughLinesPointSetTest, regression)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment