Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Identify whether image is a circle #276

Unanswered
marckohlbrugge asked this question in Q&A
Discussion options

For a web app I'm building, I'm trying to figure out whether a given logo image has the shape of a circle.

I've come up with two different strategies, but I'm stuck moving forward with both.

The first strategy is to use hough circle transform to detect all circles in the image. If there's a circle that's roughly the size of the whole image, that probably means the image is indeed of a circular logo.

Here's how far I've got so far:

bytes = open(logo_url, &:read)
im = Vips::Image.new_from_buffer bytes, ""
im.colourspace(:b_w).flatten.bandsplit.first.hough_circle(scale: 1, min_radius: 5, max_radius: 500).write_to_memory.unpack('I_*')

I'm not sure what values to use for scale, min_radius, and max_radius. I think I can somehow use them to get only big circles that are around the image size. The .write_to_memory.unpack('I_*') part I get from #138 where @jcupitt explains it returns a matrix of "votes" for each pixel.

Any pointers on how to move forward from here?

The second strategy I came up with is applying a circular mask. And then comparing that to the original image. If the images remain identical, that means the part 'outside' the circular mask are unused and there's a high likelihood the image is a circle (or has a lot of whitespace).

I'm not sure how to approach this, although I did find VIPS::Mask.


Curious to hear your ideas and whether there's any relevant documentation I can read?

You must be logged in to vote

Replies: 9 comments 2 replies

Comment options

Can we see input examples?

You must be logged in to vote
0 replies
Comment options

The last one is tricky. Is it supposed to not match because the hexagon is outside of the circle?

You must be logged in to vote
0 replies
Comment options

You're right, the docs for hough_circle don't explain what the axes represent. I've tried to clarify it.

Here's a circle detector for your logos:

#!/usr/bin/ruby
require 'vips'
image = Vips::Image.new_from_file(ARGV[0])
# we need a one-band image of just the edges for circle detection
edges = image.colourspace(:b_w).flatten.canny(precision: :integer) 
# search for circles roughly the size of the image, so radius of half the
# diameter of the largest circle
radius_target = [image.width, image.height].min / 2
radius_margin = 20 
detect = edges.hough_circle(min_radius: radius_target - radius_margin, 
 max_radius: radius_target + radius_margin)
# look for the (x, y) with the peak, then at that point, find the radius
strength, opts = detect.max(x: true, y: true)
x = opts["x"]
y = opts["y"]
bands = detect.getpoint(x, y)
radius_detected = bands.each_with_index.max[1] + radius_target - radius_margin
puts "strength = #{strength}, x = #{x}, y = #{y}, radius = #{radius_detected}"

For this PNG I see:

logo3

$ ./detect_circle.rb ~/pics/logo3.png
strength = 559.0, x = 100, y = 100, radius = 89

But for this one:

logo4

I see:

$ ./detect_circle.rb ~/pics/logo4.png
strength = 167.0, x = 104, y = 118, radius = 80

That's just picking the strongest circle, so a strong circle off centre could hide a weak centred circle. You'd probably want to crop the hough output before searching for a peak. You could blur or rank filter as well to reduce noise.

You must be logged in to vote
2 replies
Comment options

May I ask about normalization again? How do I preview the results of canny?

.canny(precision: :integer)

image

.canny(precision: :integer).falsecolour

image

.canny(precision: :integer).hist_equal

image

It's barely visible, I have to zoom.

UPD: I do workaround as:

 (img - img.min) * 256 / (img.max - img.min + 1)

UPD2: add .cast :uchar

Comment options

(img - img.min) * 256 / (img.max - img.min + 1)

You can use img.scale() for this --- it find max and min, then stretches them to 0-255 and casts tp uchar.

Yes, canny() makes rather a dark float image:

$ irb
irb(main):001:0> require 'vips'
=> true
irb(main):002:0> x = Vips::Image.new_from_file "k2.jpg"
=> #<Image 1450x2048 uchar, 3 bands, srgb>
irb(main):003:0> x.canny.min
=> 0.0
irb(main):004:0> x.canny.max
=> 32.18000411987305
irb(main):005:0> 

You'll need to rescale the output depending on your application.

vipsdisp is very handy for this -- you can make a test float image:

$ vips canny nina.jpg x.v

Then examine the float image visually to find a good way to extract the edges:

image

Comment options

... I meant to say, you need to edge-detect before hough transforms, and canny is probably the best one. Photographic images will need some noise filtering too (threshold, median, smooth, etc.). Hough output will often also need some filtering, perhaps a rank filter to find local peaks, or morphology to remove isolated peaks.

Your PNG logos are very clean, of course, so you can skip most of this extra processing.

You must be logged in to vote
0 replies
Comment options

Wow, thank you. This is extremely helpful.

It seems like the strength variable is related to the size of the image and/or radius, correct? So if I do something like score = strength / radius I can figure out a threshold for images to reach in order to be considered a circle.

You must be logged in to vote
0 replies
Comment options

strength is corrected for radius, so it should just be a confidence score. I'd test it on some typical images and pick a threshold, eg. 300. Test cx/cy for being near the centre too.

You must be logged in to vote
0 replies
Comment options

I ran the code on different sizes of the same image (the blue globe icon attached above) and got these results:

ruby circle.rb globe.png
=> strength = 559.0, x = 100, y = 100, radius = 89
ruby circle.rb globe100px.png
=> strength = 262.0, x = 50, y = 50, radius = 45
ruby circle.rb globe500px.png
=> strength = 209.0, x = 256, y = 256, radius = 231

As you can see the strength value for the first, original, image is significantly higher. It seems like they all pertain to the same circle though. (central and with a radius just under half the width)

Any idea why that might be the case?

You must be logged in to vote
0 replies
Comment options

I'd guess because the circles don't quite line up. libvips is using a fast int approximation of a circle for the hough transform, and perhaps the logo is using something different.

You could try doing a slight gaussblur after canny and again after hough_circle. Your detected strengths will drop, but the result should be more stable.

You must be logged in to vote
0 replies
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Category
Q&A
Labels
None yet
Converted from issue

This discussion was converted from issue #237 on January 31, 2021 13:12.

AltStyle によって変換されたページ (->オリジナル) /