Search code examples
pythonimageopencvimage-processingcrop

What's the most simple way to crop a circle thumbnail from an image?


I am trying to crop a centered (or not centered) circle from this image:

enter image description here

I stole this code from the existing questions regarding this topic on stack overflow, something goes wrong though:

import cv2

file = 'dog.png'

img = cv2.imread(file)
img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
circle = cv2.HoughCircles(img,
                          3,
                          dp=1.5,
                          minDist=10,
                          minRadius=1,
                          maxRadius=10)
x = circle[0][0][0]
y = circle[0][0][1]
r = circle[0][0][2]

rectX = (x - r) 
rectY = (y - r)
crop_img = img[rectY:(rectY+2*r), rectX:(rectX+2*r)]
cv2.imwrite('dog_circle.png', crop_img)

Output:

Traceback (most recent call last):
  File "C:\Users\Artur\Desktop\crop_circle - Kopie\crop_circle.py", line 14, in <module>
    x = circle[0][0][0]
TypeError: 'NoneType' object is not subscriptable

cv2.HoughCircles() seems to produce None instead of a cropped circle array. How do I fix this?


Solution

  • first: HoughCircles is used to detect circles on image, not to crop it.


    You can't have circle image. Image is always rectangle but some of pixels can be transparent (alpha channel in RGBA) and programs will not display them.

    So you can first crop image to have square and later add alpha channel with information which pixels should be visible. And here you can use mask with white circle on black background. At the end you have to save it as png or tiff because jpg can't keep alpha channel.


    I use module PIL/pillow for this.

    I crop square region in center of image but you can use different coordinates for this.

    Next I create grayscale image with the same size and black background and draw white circle/ellipse.

    Finally I add this image as alpha channel to cropped image and save it as png.

    from PIL import Image, ImageDraw
    
    filename = 'dog.jpg'
    
    # load image
    img = Image.open(filename)
    
    # crop image 
    width, height = img.size
    x = (width - height)//2
    img_cropped = img.crop((x, 0, x+height, height))
    
    # create grayscale image with white circle (255) on black background (0)
    mask = Image.new('L', img_cropped.size)
    mask_draw = ImageDraw.Draw(mask)
    width, height = img_cropped.size
    mask_draw.ellipse((0, 0, width, height), fill=255)
    #mask.show()
    
    # add mask as alpha channel
    img_cropped.putalpha(mask)
    
    # save as png which keeps alpha channel 
    img_cropped.save('dog_circle.png')
    
    img_cropped.show()
    

    Result

    enter image description here


    BTW:

    In mask you can use values from 0 to 255 and different pixels may have different transparency - some of them can be half-transparent to make smooth border.

    If you want to use it in HTML on own page then you don't have to create circle image because web browser can round corners of image and display it as circle. You have to use CSS for this.


    EDIT: Example with more circles on mask.

    enter image description here

    from PIL import Image, ImageDraw
    
    filename = 'dog.jpg'
    
    # load image
    img = Image.open(filename)
    
    # crop image 
    width, height = img.size
    x = (width - height)//2
    img_cropped = img.crop((x, 0, x+height, height))
    
    # create grayscale image with white circle (255) on black background (0)
    mask = Image.new('L', img_cropped.size)
    mask_draw = ImageDraw.Draw(mask)
    width, height = img_cropped.size
    mask_draw.ellipse((50, 50, width-50, height-50), fill=255)
    mask_draw.ellipse((0, 0, 250, 250), fill=255)
    mask_draw.ellipse((width-250, 0, width, 250), fill=255)
    
    # add mask as alpha channel
    img_cropped.putalpha(mask)
    
    # save as png which keeps alpha channel 
    img_cropped.save('dog_2.png')
    
    img_cropped.show()