Skip to content

improved version of HoughCircles (HOUGH_GRADIENT_ALT method)#16561

Merged
alalek merged 6 commits intoopencv:masterfrom
vpisarev:better_hough_circles
Feb 14, 2020
Merged

improved version of HoughCircles (HOUGH_GRADIENT_ALT method)#16561
alalek merged 6 commits intoopencv:masterfrom
vpisarev:better_hough_circles

Conversation

@vpisarev
Copy link
Copy Markdown
Contributor

@vpisarev vpisarev commented Feb 12, 2020

relates #16187 (on one image the eye is not detected with param2=0.9, need to lower it down to 0.85). Overall, the quality is much better than the existing HOUGH_GRADIENT method.

Usage:

Mat smoothed;
GaussianBlur(grayscale_image, smoothed, Size(7, 7), 1.5, 1.5);
HoughModes method = HOUGH_GRADIENT_ALT;
double dp = 1.5;
double minDist = 10;
double cannyThreshold = 300;
double minCos2 = 0.9; // 0.85 for one of the eye images
int minRadius = 7;
int maxRadius = 500;
std::vector<Vec3f> circles;
HoughCircles(smoothed, circles, method, dp, minDist,
           cannyThreshold, minCos2, minRadius, maxRadius);

this code detects circles on most of the images that I have (~100 images; the proper regression test will be added later). When it does not, try to narrow down the radius search range [minRadius, maxRadius] and decrease a bit minCos2 from 0.9 down to 0.85..0.75.

  • I agree to contribute to the project under OpenCV (BSD) License.
  • To the best of my knowledge, the proposed patch is not based on a code under GPL or other license that is incompatible with OpenCV
  • The PR is proposed to proper branch
  • There is reference to original bug report and related work
  • (need to add more tests in the future) There is accuracy test, performance test and test data in opencv_extra repository, if applicable
    Patch to opencv_extra has the same branch name.
  • The feature is well documented and sample code can be built with the project CMake

* 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 :)
Copy link
Copy Markdown
Member

@alalek alalek left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

New algorithm is interesting, but linked issue doesn't ask for that.
The issue blames on regression(changes) in implementation of existed algorithm between OpenCV versions.

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
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

documentation

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

Comment on lines +1722 to +1726
static int circle_popcnt(uint64 mask)
{
int count = 0;
for(int b = 0; b < 64; b++, mask >>= 1)
count += (mask & 1) != 0;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

reuse existed popcnt implementations.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have not found simple inline function that takes uint64(_t) on input, just universal intrinsic that I do not need. I improved the implementation to use CV_POPCOUNT_U64 if possible. Someone (maybe me) in some subsequent patch can move it to some of core headers and rename cv::circle_popcnt() to cv::popcount() or cvPopCount64() or something like that.

Comment on lines +1741 to +1742
if( maxRadius <= 0 ) maxRadius = std::min(img.cols, img.rows)*0.5;
if( minRadius > maxRadius ) std::swap(minRadius, maxRadius);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Avoid merging of if's conditions and if's body.
Let make code coverage reports more useful.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

Mat Dx, Dy, edges;
Scharr(img, Dx, CV_16S, 1, 0);
Scharr(img, Dy, CV_16S, 0, 1);
Canny(Dx, Dy, edges, cannyThreshold/2, cannyThreshold, true);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe such pre-processing should be optional.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's not preprocessing. Canny edge detector is the essential part of HoughCircles that takes grayscale image on input. HOUGH_GRADIENT method does the same.

{
for( int x = 0; x < edges.cols; x++ )
{
if(!edgeData[y*estep + x] || mdata[y*mstep + x])
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Avoid manual address arithmetic.
There is still efficient .at(y, x) method for release builds and there are extra checks in debug builds.

Copy link
Copy Markdown
Contributor Author

@vpisarev vpisarev Feb 12, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the inner loop where performance matters. Address arithmetic is faster here and will not be replaced with .at

circles.clear();
std::vector<Vec4f> nz;

std::vector<Point> stack;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is std::stack for that.

Copy link
Copy Markdown
Contributor Author

@vpisarev vpisarev Feb 12, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

std::stack is an adapter on top of std::vector (or list or deque). Why use this adapter instead of plain simple std::vector?

Comment on lines +1806 to +1810
int sx = cvRound(vx * 1024 / mag);
int sy = cvRound(vy * 1024 / mag);

int x0 = cvRound((p.x * idp) * 1024);
int y0 = cvRound((p.y * idp) * 1024);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is 1024 magic number?
Consider declaring this constant separately with descriptive name.

Same notes are about 64, 7, 3 below.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

Comment on lines +1857 to +1858
}
while(!stack.empty());
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't add line break before while in do while statements.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

int prev = prev_idx[j];
prev_idx[j] = i;

if( std::abs(rij - r_arc) < (r_arc + 80)*0.03 && prev+1 == i && !stop_marker )
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

80
0.03

and below

4000
0.06

"magic numbers"

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

Comment on lines +238 to +240
for( int k = 1; k < 2; k++ )
{
int method = k == 0 ? HOUGH_GRADIENT : HOUGH_GRADIENT_ALT;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is better to split/parametrize test itself instead of internal loops.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

* 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
@vpisarev
Copy link
Copy Markdown
Contributor Author

vpisarev commented Feb 12, 2020

@alalek, the original issue complains about HoughCircles quality on some particular images, but the problem is much deeper. For a long time I knew that cv::HoughCircles() does not work well. But I was not sure about the effects of the latest modifications, if they improved or degraded quality and if there is some good revision of this implementation. So, for confirmation I took several revisions of OpenCV and ran them on a dataset of ~100 images (mostly from here: https://github.com/AlanLuSun/Circle-detection, but also my little collection of circles images and photos from Internet). The results are really bad. The existing implementation is very advanced in terms of speed optimization efforts, but it's fundamentally wrong. The newly proposed algorithm, on which I spent the whole week, uses the similar first phase with 2D accumulator update, but the center selection and the radius estimation are completely different. It's not compatible with the existing implementation. It needs somewhat higher cannyThreshold (param1). It treats minRadius a bit differently. It treats param2 completely differently, because accumThreshold is very difficult to set to some "universally good" value, you need to vary it from image to image and too bad if you have circles of very different size in the same image - there is no good accumThreshold value in such case. It does not have "center only" mode, because any Hough-based algorithm for circle detection, be it HOUGH_GRADIENT or HOUGH_GRADIENT_ALT, produce a lot of fake centers. So I decided to make a separate algorithm without having to keep compatibility with the previous version. In OpenCV 5.0, perhaps, HOUGH_GRADIENT can be removed completely.

@asmorkalov asmorkalov added Hackathon https://opencv.org/opencv-hackathon-starts-next-week/ category: imgproc labels Feb 14, 2020
@catree
Copy link
Copy Markdown
Contributor

catree commented Feb 14, 2020

If HOUGH_GRADIENT_ALT performs significantly better than HOUGH_GRADIENT with comparable speed performance, add somewhere in the documentation that HOUGH_GRADIENT_ALT method should be preferred?
Otherwise, a note that HOUGH_GRADIENT_ALT is more accurate while HOUGH_GRADIENT is faster?

Also, it would be great to have some numbers to evaluate the performance of the new method on a ground truth dataset.

@vpisarev
Copy link
Copy Markdown
Contributor Author

@catree, thanks! More detailed test that evaluates performance over a real dataset is being developed.

@alalek alalek merged commit cc259e4 into opencv:master Feb 14, 2020
@vpisarev vpisarev deleted the better_hough_circles branch May 28, 2020 18:21
@feature-engineer
Copy link
Copy Markdown

feature-engineer commented Jun 14, 2020

@vpisarev When will a test evaluating the performance of the Hough circles implementations be published?

@nadavv
Copy link
Copy Markdown

nadavv commented Jun 25, 2020

Hi,
As I am new here, could you please explain what it would take for this version to be available in python?
Muchas gracias

@alalek alalek mentioned this pull request Aug 22, 2021
6 tasks
a-sajjad72 pushed a commit to a-sajjad72/opencv that referenced this pull request Mar 30, 2023
* 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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

category: imgproc Hackathon https://opencv.org/opencv-hackathon-starts-next-week/

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants