-
Notifications
You must be signed in to change notification settings - Fork 62
Identify whether image is a circle #276
-
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?
Beta Was this translation helpful? Give feedback.
All reactions
Replies: 9 comments 2 replies
-
Can we see input examples?
Beta Was this translation helpful? Give feedback.
All reactions
-
Sure!
Should match
- https://wip.imgix.net/store/product/26/logo/677eb5e0b6aaa6c3013af05a87a4049f.png?ixlib=rb-3.2.1&auto=format&fit=crop&w=100&h=100&dpr=2&q=40&s=32e561134a0e47d39a66a7dad07acd80
- https://wip.imgix.net/store/product/927/logo/e21b057fe38c928b9f4e083cf89783cf.png?ixlib=rb-3.2.1&auto=format&fit=crop&w=100&h=100&dpr=2&q=40&s=593d82f9594507eac1715377afb71582
- https://wip.imgix.net/store/product/3567/logo/ab4f5c3cbd04b5d82c750c2ece413c4a.png?ixlib=rb-3.2.1&auto=format&fit=crop&w=100&h=100&dpr=2&q=40&s=fc6de7211539cb6e309bc86b06faad05
Should not match
- https://wip.imgix.net/store/product/15/logo/d9e83530166c2d8bf8fa0039b3dc5276.png?ixlib=rb-3.2.1&auto=format&fit=crop&w=100&h=100&dpr=2&q=40&s=c86e47c1a017bf119ab549d0f11276ef
- https://wip.imgix.net/store/product/252/logo/e4eb12a475bacd21d33f6bc6668ed446.png?ixlib=rb-3.2.1&auto=format&fit=crop&w=100&h=100&dpr=2&q=40&s=88d7c19b276d5f650eed4ed8e4c68f63
- https://wip.imgix.net/store/product/2233/logo/0b62e893a6e152a9fbada9a4e16b983d.png?ixlib=rb-3.2.1&auto=format&fit=crop&w=100&h=100&dpr=2&q=40&s=ad31c891a7d6010bc9073eeb88242b89
Beta Was this translation helpful? Give feedback.
All reactions
-
The last one is tricky. Is it supposed to not match because the hexagon is outside of the circle?
Beta Was this translation helpful? Give feedback.
All reactions
-
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:
$ ./detect_circle.rb ~/pics/logo3.png
strength = 559.0, x = 100, y = 100, radius = 89
But for this one:
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.
Beta Was this translation helpful? Give feedback.
All reactions
-
❤️ 2
-
May I ask about normalization again? How do I preview the results of canny?
.canny(precision: :integer)
.canny(precision: :integer).falsecolour
.canny(precision: :integer).hist_equal
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
Beta Was this translation helpful? Give feedback.
All reactions
-
(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:
Beta Was this translation helpful? Give feedback.
All reactions
-
... 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.
Beta Was this translation helpful? Give feedback.
All reactions
-
👍 1
-
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.
Beta Was this translation helpful? Give feedback.
All reactions
-
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.
Beta Was this translation helpful? Give feedback.
All reactions
-
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?
Beta Was this translation helpful? Give feedback.
All reactions
-
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.
Beta Was this translation helpful? Give feedback.
All reactions
-
👍 1