4

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?

nathancy
  • 42,661
  • 14
  • 115
  • 137
Artur Müller Romanov
  • 4,417
  • 10
  • 73
  • 132
  • `HoughCircles` is used to detect circles on image, not to crop it. – furas Oct 24 '19 at 16:28
  • you can't have circle image. Images are always rectangle but some of pixel can be transparent (alpha channel in `RGBA`) and system will not display it. So you can crop rectangle and add alpha channel with information which pixel should be visible - and here you can use mask with circle from answer. And you have to save as png or tiff because jpg can't keep alpha channel. – furas Oct 24 '19 at 16:39
  • Example here... https://stackoverflow.com/a/51487201/2836621 – Mark Setchell Oct 24 '19 at 17:22
  • Possible duplicate of [cropping an image in a circular way, using python](https://stackoverflow.com/questions/51486297/cropping-an-image-in-a-circular-way-using-python) – Cris Luengo Oct 25 '19 at 00:52

3 Answers3

6

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()
furas
  • 134,197
  • 12
  • 106
  • 148
2

This answer explains how to apply a mask. First, read in the image:

import cv2
import numpy as np
img = cv2.imread('dog.jpg')

Next create a mask, or a blank image that is the same size as the source image:

h,w,_ = img.shape
mask = np.zeros((h,w), np.uint8)

Then, draw a circle on the mask. Change these parameters based on where the face is:

cv2.circle(mask, (678,321), 5, 255, 654)

Finally, mask the source image:

img = cv2.bitwise_and(img, img, mask= mask)

Here is the mask:

mask

And the output:

output

Stephen Meschke
  • 2,820
  • 1
  • 13
  • 25
0

The idea is to create a black mask then draw the desired region to crop out in white using cv2.circle(). From there we can use cv2.bitwise_and() with the original image and the mask. To crop the result, we can use cv2.boundingRect() on the mask to obtain the ROI then use Numpy slicing to extract the result. For this example I used the center point derived from the image's width and height

import cv2
import numpy as np

# Create mask and draw circle onto mask
image = cv2.imread('1.jpg')
mask = np.zeros(image.shape, dtype=np.uint8)
x,y = image.shape[1], image.shape[0]
cv2.circle(mask, (x//2,y//2), 300, (255,255,255), -1)

# Bitwise-and for ROI
ROI = cv2.bitwise_and(image, mask)

# Crop mask and turn background white
mask = cv2.cvtColor(mask, cv2.COLOR_BGR2GRAY)
x,y,w,h = cv2.boundingRect(mask)
result = ROI[y:y+h,x:x+w]
mask = mask[y:y+h,x:x+w]
result[mask==0] = (255,255,255)

cv2.imshow('result', result)
cv2.waitKey()
nathancy
  • 42,661
  • 14
  • 115
  • 137