I've got a shape like a speech bubble. And I only want to detect the ellipse of this shape like in the image with the green encircled one.
I tried with closed morphology, but certain parts of the bubbles are also removed. I used a Kernel with a matrix of 20, 20. The shape becomes more rectangular. Maybe I have to change the kernel matrix more like this:
0 1 0
1 1 1
0 1 0
I also tried to draw a convex hull, but it also has no effect. And a inner convex hull is not possible. Here is my code for drawing a convex hull:
for i in range (max_index):
hull = cv2.convexHull(contours[i])
cv2.drawContours(image, [hull], 0, (0, 255, 0), 2)
I retrieved the contours with the parameters cv2.RETR_EXTERNAL
and cv2.CHAIN_APPROX_NONE
This is the best I was able to get:
It is not the most clever way to do it. What I am doing here is actually simple, despite the verbose code.
First, I get the gray image and add a lot of blur and in the same way you tried, apply threshold and find contours. I then take the biggest contour and find the ellipse that fits this contour with fitEllipse
. This is all in getEllipse
function.
In this first round, the ellipse will be skewed because the tail is getting in the way. So, I use this not so good ellipse to process the original image and give another try.
The function grayEllipse filters an image by an ellipse. So, I use the ellipse from the first try to get process the original image and highlight the points the are inside the first ellipse. I use this image as input in this second round.
By repeating the process, the final ellipse I get in the second time is much less skewed.
Here is the code:
import cv2
import numpy as np
def getEllipse(imgray):
ret, thresh = cv2.threshold(imgray, 20, 255, cv2.THRESH_BINARY|cv2.THRESH_OTSU)
_, contours, _ = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)
maxArea = 0
best = None
for contour in contours:
area = cv2.contourArea(contour)
if area > maxArea :
maxArea = area
best = contour
ellipse = cv2.fitEllipse(best)
el = np.zeros(imgray.shape)
cv2.ellipse(el, ellipse,(255,255,255),-1)
return el
def grayEllipse(el, img):
el = np.dstack((el,el,el))
el = el*img
el = el/(255)
el = el.astype('uint8')
imgray = cv2.cvtColor(el, cv2.COLOR_BGR2LAB)[...,0]
return imgray
image = cv2.imread("./baloon.png", cv2.IMREAD_COLOR)
img = image.copy()
imgray = cv2.cvtColor(image, cv2.COLOR_BGR2LAB)[...,0]
imgray = cv2.GaussianBlur(imgray, (79,79), 0)
el = getEllipse(imgray)
imgray = grayEllipse(el, img.copy())
imgray = cv2.GaussianBlur(imgray, (11,11), 0)
el = getEllipse(imgray)
imgray = grayEllipse(el, img.copy())
ret, thresh = cv2.threshold(imgray, 20, 255, cv2.THRESH_BINARY|cv2.THRESH_OTSU)
_, contours, _ = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)
maxArea = 0
best = None
for contour in contours:
area = cv2.contourArea(contour)
if area > maxArea :
maxArea = area
best = contour
ellipse = cv2.fitEllipse(best)
cv2.ellipse(image, ellipse, (0,255,0),3)
while True:
cv2.imshow("result", image)
k = cv2.waitKey(30) & 0xff
if k == 27:
break