Detecção da bola
O atual algoritmo para a detecção da bola é simples e faz o uso do conceito de máscara de cor no espaço HSV e cálculo de contornos. Pode ser dividida em:
Captura da imagem;
Desfoque da imagem para eliminação de ruído;
Máscara para a cor da bola no espaço HSV;
Erosões e dilatações da imagem para reduzir o número de contornos;
Cálculo e identificação de contornos;
Escolha da bola dentre os contornos identificados usando o critério de área e formato.
É interessante notar que um importante critério para identificar a bola foi o da "circularidade". Dentre todos os contornos válidos (área dentro da faixa esperada), o escolhido como bola era o mais circular, seguindo o cálculo definido pelo MATLAB e demonstrado aqui.
A função que implementa essa detecção pode ser vista no código que segue.
Código que implementa a detecção da bola
def detect_ball(frame, output_frame):
# First we blur the image with a GaussianBlur
blurred = cv2.GaussianBlur(frame, (11, 11), 0)
# Construct a HSV mask for the green color
hsv = cv2.cvtColor(blurred, cv2.COLOR_BGR2HSV)
mask = cv2.inRange(hsv, greenLower, greenUpper)
# Erode and dilate the result to remove small noises
mask = cv2.erode(mask, None, iterations=4)
mask = cv2.dilate(mask, None, iterations=4)
# Then we calculate the countours of the resulting image
frame_cnts, _ = cv2.findContours(mask, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
if len(frame_cnts) == 0:
return False, output_frame, [], -1, -1, -1
# Calculate the circularity of the identified countours
areas = np.array([cv2.contourArea(c) for c in frame_cnts])
is_reading_valid = (areas > minimum_ball_area) & (areas < maximum_ball_area)
if np.sum(is_reading_valid) == 0:
return False, output_frame, [], -1, -1, -1
perimeters = np.array([cv2.arcLength(c,True) for c in frame_cnts])
circularities = 4 * np.pi *areas/(perimeters**2)
circularities = circularities*is_reading_valid
ball_cnt_idx = np.argmax(circularities)
# We get the one with the greatest circularity (4*pi*area/(perimeter^2))
# https://www.mathworks.com/help/images/identifying-round-objects.html;jsessionid=551254009a8e1c007e415ab76902
c = frame_cnts[ball_cnt_idx]
# And calculate the minimum enclosing circle
((x, y), radius) = cv2.minEnclosingCircle(c)
M = cv2.moments(c)
# Calculate the shape
approx = cv2.approxPolyDP(c,0.01*cv2.arcLength(c,True),True)
# If the shape is really close to a circle and the area is greater than the minimum
# the contour is considered to be the ball
if ((len(approx) > 6) & (len(approx) < 23):
center = (int(M["m10"] / M["m00"]), int(M["m01"] / M["m00"]))
cv2.circle(output_frame, (int(x), int(y)), int(radius), (0, 255, 0), 2)
return True, output_frame, frame_cnts, x, y, radius
return False, output_frame, [], -1, -1, -1