I want to detect the contour of a ball and the object will always be a ball, but i have an object in the middle that can obstruct part of the ball, here's my current situation : (https://i.sstatic.net/gwxnsVTI.png)
I made it so it works just fine in this case where theres a good amount of the ball on both sides, but when it reduces its not as good: (https://i.sstatic.net/gYiZ9bCI.png)
I know I can change the parameters of my code where I just concatenate both parts when they are of a good enough size, but it gets very situational when it works because of the noise, so I can't reduce the parameters that much:
import cv2
import numpy as np
from pyfirmata2 import Arduino, SERVO
import time
import serial
# Camera Connection/Setup
cap = cv2.VideoCapture(0)
while True:
_, frame = cap.read()
hsv_frame = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)
#Blue color
low_blue = np.array([100, 100, 20])
high_blue = np.array([200, 255, 255])
blue_mask = cv2.inRange(hsv_frame, low_blue, high_blue)
#blue_mask = cv2.GaussianBlur(blue_mask,(5,5),0)
contours, hierarchy = cv2.findContours(blue_mask, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
contours = sorted(contours, key=lambda x:cv2.contourArea(x), reverse=True)
sum = 0
emptymask = []
for cnt in contours:
if cv2.contourArea(cnt) > 300: #If the parts are big enough it assumes its a part of the ball
sum += cv2.contourArea(cnt)
emptymask.append(cnt)
#print(sum)
emptymask = cv2.vconcat(emptymask)
(x, y, w, h) = cv2.boundingRect(emptymask)
cv2.rectangle(frame, (x, y), (x + w, y + h), (0, 255, 0), 2)
cv2.imshow("Frame", frame)
cv2.imshow("Mask", blue_mask)
key = cv2.waitKey(1)
if key == 27:
break
cap.release()
cv2.destroyAllWindows()
So I was thinking wether I can sort of deduce the ball based on its size or something like that, since it's always the same ball. I did do something like that in another code where if it's not the size expected, it "guesses" the whole ball and makes a square, but it became incredibly hard to calibrate the exact x and y positions:
if reference > 6000: #See if the object is cropped or not
cv2.rectangle(frame, (x, y), (x + w, y + w), (0, 255, 0), 2)
elif ((x>180 and x<230) and (y>170 and y<240) or (y<5 and x<225)): #If it is, it will calculate the rest of the area
cv2.rectangle(frame, (x+ball_diameter, y+h), (x, y+h - ball_diameter), (0, 255, 0), 2)
elif (x>230 and x<290) and (y>140 and y<170): #If it is, it will calculate the rest of the area
cv2.rectangle(frame, (x+w, y+ball_diameter), (x+w - ball_diameter, y), (0, 255, 0), 2)
elif (x>270 and x<300 and (y<140 or y>230)) or (y>220 and y<250) or (y<5 and x>225): #If it is, it will calculate the rest of the area
cv2.rectangle(frame, (x+w, y+h), (x+w - ball_diameter, y+h - ball_diameter), (0, 255, 0), 2)
else:
cv2.rectangle(frame, (x, y), (x + ball_diameter, y + ball_diameter), (0, 255, 0), 2)
Finally, my question is : Is there a way to detect the arch from either side and just fill the missing circle gap ? Or an even better way ?
Here is one way to do that in Python/OpenCV.
Since you did not post actual input files and not screen snaps, I took your image and cropped it to get the black/white mask image as my input.
Input:
import cv2
import numpy as np
# Read image
img = cv2.imread('input_mask.png')
hh, ww = img.shape[:2]
# Convert to grayscale
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# threshold
thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY+cv2.THRESH_OTSU)[1]
# apply morphology to clean up small spots leaving only two largest ones
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5,5))
morph = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, kernel)
morph = cv2.morphologyEx(morph, cv2.MORPH_CLOSE, kernel)
# get convex hull
points = np.column_stack(np.where(morph.transpose() > 0))
hull = cv2.convexHull(points)
# draw convex hull in white filled on black
hull_img = np.zeros_like(morph)
cv2.fillPoly(hull_img, [hull], 255)
# get Hough circles
circles = cv2.HoughCircles(hull_img, cv2.HOUGH_GRADIENT, 1, minDist=210, param1=150, param2=10, minRadius=40, maxRadius=100)
print(circles)
# draw circles
result = img.copy()
for circle in circles[0]:
# draw the circle in the output image, then draw a rectangle
# corresponding to the center of the circle
(x,y,r) = circle
x = int(x)
y = int(y)
r = int(r)
cv2.circle(result, (x, y), r, (0, 0, 255), 1)
# ALTERNATELY: get minEnclosingCircle
center, radius = cv2.minEnclosingCircle(hull)
cx = int(round(center[0]))
cy = int(round(center[1]))
rr = int(round(radius))
# draw minEnclosingCircle over copy of input
result2 = img.copy()
cv2.circle(result2, (cx,cy), rr, (0, 0, 255), 1)
# save results
cv2.imwrite('input_mask_morph.png', morph)
cv2.imwrite('input_mask_convex_hull.png', hull_img)
cv2.imwrite('input_mask_circle3.png', result)
cv2.imwrite('input_mask_circle3b.png', result2)
# show images
cv2.imshow('thresh', thresh)
cv2.imshow('morph', morph)
cv2.imshow('convex hull', hull_img)
cv2.imshow('result', result)
cv2.imshow('result2', result2)
cv2.waitKey(0)
cv2.destroyAllWindows()
Morphology cleaned image:
Convex hull image:
Resulting circle on copy of input:
Resulting circle from minEnclosingCircle on input: