Unverified Commit cc259e49 authored by Vadim Pisarevsky's avatar Vadim Pisarevsky Committed by GitHub

Merge pull request #16561 from vpisarev:better_hough_circles

* improved version of HoughCircles (HOUGH_GRADIENT_ALT method)

* trying to fix build problems on Windows

* fixed typo

* * fixed warnings on Windows
* make use of param2. make it minCos2 (minimal value of squared cosine between the gradient at the pixel edge and the vector connecting it with circle center). with minCos2=0.85 we can detect some more eyes :)

* * added description of HOUGH_GRADIENT_ALT
* cleaned up the implementation; added comments, replaced built-in numeic constants with symbolic constants
* rewrote circle_popcount() to use built-in popcount() if possible
* modified some of HoughCircles tests to use method parameter instead of the built-in loop

* fixed warnings on Windows
parent 3efa7831
......@@ -473,7 +473,8 @@ enum HoughModes {
/** multi-scale variant of the classical Hough transform. The lines are encoded the same way as
HOUGH_STANDARD. */
HOUGH_MULTI_SCALE = 2,
HOUGH_GRADIENT = 3 //!< basically *21HT*, described in @cite Yuen90
HOUGH_GRADIENT = 3, //!< basically *21HT*, described in @cite Yuen90
HOUGH_GRADIENT_ALT = 4, //!< variation of HOUGH_GRADIENT to get better accuracy
};
//! Variants of Line Segment %Detector
......@@ -2096,28 +2097,37 @@ Example: :
@note Usually the function detects the centers of circles well. However, it may fail to find correct
radii. You can assist to the function by specifying the radius range ( minRadius and maxRadius ) if
you know it. Or, you may set maxRadius to a negative number to return centers only without radius
search, and find the correct radius using an additional procedure.
you know it. Or, in the case of #HOUGH_GRADIENT method you may set maxRadius to a negative number
to return centers only without radius search, and find the correct radius using an additional procedure.
It also helps to smooth image a bit unless it's already soft. For example,
GaussianBlur() with 7x7 kernel and 1.5x1.5 sigma or similar blurring may help.
@param image 8-bit, single-channel, grayscale input image.
@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 method Detection method, see #HoughModes. The available methods are #HOUGH_GRADIENT and #HOUGH_GRADIENT_ALT.
@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
half as big width and height.
half as big width and height. For #HOUGH_GRADIENT_ALT the recommended value is dp=1.5,
unless some small very circles need to be detected.
@param minDist Minimum distance between the centers of the detected circles. If the parameter is
too small, multiple neighbor circles may be falsely detected in addition to a true one. If it is
too large, some circles may be missed.
@param param1 First method-specific parameter. In case of #HOUGH_GRADIENT , it is the higher
threshold of the two passed to the Canny edge detector (the lower one is twice smaller).
@param param2 Second method-specific parameter. In case of #HOUGH_GRADIENT , it is the
@param param1 First method-specific parameter. In case of #HOUGH_GRADIENT and #HOUGH_GRADIENT_ALT,
it is the higher threshold of the two passed to the Canny edge detector (the lower one is twice smaller).
Note that #HOUGH_GRADIENT_ALT uses #Scharr algorithm to compute image derivatives, so the threshold value
shough normally be higher, such as 300 or normally exposed and contrasty images.
@param param2 Second method-specific parameter. In case of #HOUGH_GRADIENT, it is the
accumulator threshold for the circle centers at the detection stage. The smaller it is, the more
false circles may be detected. Circles, corresponding to the larger accumulator values, will be
returned first.
returned first. In the case of #HOUGH_GRADIENT_ALT algorithm, this is the circle "perfectness" measure.
The closer it to 1, the better shaped circles algorithm selects. In most cases 0.9 should be fine.
If you want get better detection of small circles, you may decrease it to 0.85, 0.8 or even less.
But then also try to limit the search range [minRadius, maxRadius] to avoid many false circles.
@param minRadius Minimum circle radius.
@param maxRadius Maximum circle radius. If <= 0, uses the maximum image dimension. If < 0, returns
centers without finding the radius.
@param maxRadius Maximum circle radius. If <= 0, uses the maximum image dimension. If < 0, #HOUGH_GRADIENT returns
centers without finding the radius. #HOUGH_GRADIENT_ALT always computes circle radiuses.
@sa fitEllipse, minEnclosingCircle
*/
......
This diff is collapsed.
......@@ -178,26 +178,34 @@ INSTANTIATE_TEST_CASE_P(ImgProc, HoughCirclesTestFixture, testing::Combine(
testing::Values(200)
));
TEST(HoughCirclesTest, DefaultMaxRadius)
class HoughCirclesTest : public testing::TestWithParam<HoughModes>
{
string picture_name = "imgproc/stuff.jpg";
const double dp = 1.0;
double minDist = 20;
double edgeThreshold = 20;
double accumThreshold = 30;
int minRadius = 20;
int maxRadius = 0;
protected:
HoughModes method;
public:
HoughCirclesTest() { method = GetParam(); }
};
TEST_P(HoughCirclesTest, DefaultMaxRadius)
{
string picture_name = "imgproc/stuff.jpg";
string filename = cvtest::TS::ptr()->get_data_path() + picture_name;
Mat src = imread(filename, IMREAD_GRAYSCALE);
EXPECT_FALSE(src.empty()) << "Invalid test image: " << filename;
GaussianBlur(src, src, Size(9, 9), 2, 2);
double dp = 1.0;
double minDist = 20.0;
double edgeThreshold = 20.0;
double param2 = method == HOUGH_GRADIENT_ALT ? 0.9 : 30.;
int minRadius = method == HOUGH_GRADIENT_ALT ? 10 : 20;
int maxRadius = 0;
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);
HoughCircles(src, circles, method, dp, minDist, edgeThreshold, param2, minRadius, maxRadius);
HoughCircles(src, circles4f, method, dp, minDist, edgeThreshold, param2, minRadius, maxRadius);
#if DEBUG_IMAGES
string imgProc = string(cvtest::TS::ptr()->get_data_path()) + "imgproc/";
......@@ -206,7 +214,15 @@ TEST(HoughCirclesTest, DefaultMaxRadius)
int maxDimension = std::max(src.rows, src.cols);
EXPECT_GT(circles.size(), size_t(0)) << "Should find at least some circles";
if(method == HOUGH_GRADIENT_ALT)
{
EXPECT_EQ(circles.size(), size_t(3)) << "Should find 3 circles";
}
else
{
EXPECT_GT(circles.size(), size_t(0)) << "Should find at least some circles";
}
for (size_t i = 0; i < circles.size(); ++i)
{
EXPECT_GE(circles[i][2], minRadius) << "Radius should be >= minRadius";
......@@ -214,60 +230,80 @@ TEST(HoughCirclesTest, DefaultMaxRadius)
}
}
TEST(HoughCirclesTest, CentersOnly)
TEST_P(HoughCirclesTest, CentersOnly)
{
string picture_name = "imgproc/stuff.jpg";
const double dp = 1.0;
double minDist = 20;
double edgeThreshold = 20;
double accumThreshold = 30;
int minRadius = 20;
int maxRadius = -1;
string filename = cvtest::TS::ptr()->get_data_path() + picture_name;
Mat src = imread(filename, IMREAD_GRAYSCALE);
EXPECT_FALSE(src.empty()) << "Invalid test image: " << filename;
GaussianBlur(src, src, Size(9, 9), 2, 2);
double dp = 1.0;
double minDist = 20.0;
double edgeThreshold = 20.0;
double param2 = method == HOUGH_GRADIENT_ALT ? 0.9 : 30.;
int minRadius = method == HOUGH_GRADIENT_ALT ? 10 : 20;
int maxRadius = -1;
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);
HoughCircles(src, circles, method, dp, minDist, edgeThreshold, param2, minRadius, maxRadius);
HoughCircles(src, circles4f, method, dp, minDist, edgeThreshold, param2, minRadius, maxRadius);
#if DEBUG_IMAGES
string imgProc = string(cvtest::TS::ptr()->get_data_path()) + "imgproc/";
highlightCircles(filename, circles, imgProc + "HoughCirclesTest_CentersOnly.png");
highlightCircles(filename, circles, imgProc + "HoughCirclesTest_DefaultMaxRadius.png");
#endif
EXPECT_GT(circles.size(), size_t(0)) << "Should find at least some circles";
if(method == HOUGH_GRADIENT_ALT)
{
EXPECT_EQ(circles.size(), size_t(3)) << "Should find 3 circles";
}
else
{
EXPECT_GT(circles.size(), size_t(0)) << "Should find at least some circles";
}
for (size_t i = 0; i < circles.size(); ++i)
{
EXPECT_EQ(circles[i][2], 0.0f) << "Did not ask for radius";
if( method == HOUGH_GRADIENT )
{
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]);
}
}
TEST(HoughCirclesTest, ManySmallCircles)
TEST_P(HoughCirclesTest, ManySmallCircles)
{
string picture_name = "imgproc/beads.jpg";
const double dp = 1.0;
string filename = cvtest::TS::ptr()->get_data_path() + picture_name;
Mat src = imread(filename, IMREAD_GRAYSCALE);
EXPECT_FALSE(src.empty()) << "Invalid test image: " << filename;
const double dp = method == HOUGH_GRADIENT_ALT ? 1.5 : 1.0;
double minDist = 10;
double edgeThreshold = 90;
double accumThreshold = 11;
double minCos2 = 0.85;
double param2 = method == HOUGH_GRADIENT_ALT ? minCos2 : accumThreshold;
int minRadius = 7;
int maxRadius = 18;
int ncircles_min = method == HOUGH_GRADIENT_ALT ? 2000 : 3000;
string filename = cvtest::TS::ptr()->get_data_path() + picture_name;
Mat src = imread(filename, IMREAD_GRAYSCALE);
EXPECT_FALSE(src.empty()) << "Invalid test image: " << filename;
Mat src_smooth;
if( method == HOUGH_GRADIENT_ALT )
GaussianBlur(src, src_smooth, Size(7, 7), 1.5, 1.5);
else
src.copyTo(src_smooth);
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);
HoughCircles(src_smooth, circles, method, dp, minDist, edgeThreshold, param2, minRadius, maxRadius);
HoughCircles(src_smooth, circles4f, method, dp, minDist, edgeThreshold, param2, minRadius, maxRadius);
#if DEBUG_IMAGES
string imgProc = string(cvtest::TS::ptr()->get_data_path()) + "imgproc/";
......@@ -275,8 +311,11 @@ TEST(HoughCirclesTest, ManySmallCircles)
highlightCircles(filename, circles, imgProc + test_case_name + ".png");
#endif
EXPECT_GT(circles.size(), size_t(3000)) << "Should find a lot of circles";
EXPECT_GT(circles.size(), size_t(ncircles_min)) << "Should find a lot of circles";
EXPECT_EQ(circles.size(), circles4f.size());
}
INSTANTIATE_TEST_CASE_P(HoughGradient, HoughCirclesTest, testing::Values(HOUGH_GRADIENT));
INSTANTIATE_TEST_CASE_P(HoughGradientAlt, HoughCirclesTest, testing::Values(HOUGH_GRADIENT_ALT));
}} // namespace
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