Skip to content

Identify whether image is a circle #237

Closed

Description

@marckohlbrugge

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?

Activity

  1. Nakilon commented on Jun 15, 2020

    @Nakilon
    Contributor

    Can we see input examples?

  2. Nakilon commented on Jun 15, 2020

    @Nakilon
    Contributor

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

  3. jcupitt commented on Jun 15, 2020

    @jcupitt
    Member

    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.

  4. added a commit that references this issue on Jun 15, 2020
  5. jcupitt commented on Jun 15, 2020

    @jcupitt
    Member

    ... 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.

  6. marckohlbrugge commented on Jun 16, 2020

    @marckohlbrugge
    Author

    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.

  7. jcupitt commented on Jun 16, 2020

    @jcupitt
    Member

    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.

  8. marckohlbrugge commented on Jun 16, 2020

    @marckohlbrugge
    Author

    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?

  9. jcupitt commented on Jun 16, 2020

    @jcupitt
    Member

    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.

  10. locked and limited conversation to collaborators on Jan 31, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions