Я хочу определить контур мяча, и объект всегда будет мячом, но у меня есть объект в середине, который может загораживать часть мяча, вот моя текущая ситуация: (https://i.sstatic.net/gwxnsVTI.png)
Я сделал это так, чтобы оно работало нормально в том случае, когда с обеих сторон достаточно мяча, но когда оно уменьшается, это не так хорошо: (https://i.sstatic.net/gYiZ9bCI.png)
Я знаю, что могу изменить параметры своего кода, просто объединяя обе части, когда они имеют достаточно хороший размер, но это становится очень ситуативным, когда это работает из-за шума, поэтому я не могу настолько сильно уменьшить параметры:
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()
Итак, я подумал, смогу ли я как-то определить мяч на основе его размера или чего-то в этом роде, поскольку это всегда один и тот же мяч. Я сделал что-то подобное в другом коде, где, если размер не соответствует ожидаемому, он «угадывает» весь шар и образует квадрат, но стало невероятно сложно откалибровать точные положения x и y:
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)
Наконец, мой вопрос: есть ли способ обнаружить арку с любой стороны и просто заполнить недостающий зазор в круге? Или еще лучший способ?
Пожалуйста, публикуйте входное изображение и изображение маски как оригинал, а не в сочетании с каким-либо другим изображением в виде снимков экрана.
Вы можете убрать небольшие пятна, оставив только две части мяча. Затем возьмите все белые точки и прикрепите к ним эллипс или замкните выпуклую оболочку и залейте ее белым, а затем используйте HoughCircles, чтобы подогнать к ней круг.
Вот один из способов сделать это в Python/OpenCV.
Поскольку вы не публиковали фактические входные файлы и не снимки экрана, я взял ваше изображение и обрезал его, чтобы получить в качестве входных данных черно-белое изображение маски.
Вход:
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()
Морфологически очищенное изображение:
Выпуклое изображение корпуса:
Результирующий круг на копии ввода:
Результирующий круг из minEnclosingCircle на входе:
Это хорошая идея. Но вместо Хафа я бы провел круг через точки, принадлежащие выпуклой оболочке.
@Cris Luengo FitCircle нет, есть только fitEllipse. Так что это придется закодировать на Python, не так ли? Или мне не хватает какого-то метода OpenCV? Было бы здорово, если бы кто-нибудь мог написать код fitCircle, аналогичный fitEllipse, в OpenCV!
Я не знаю, не хватает ли вам какого-то метода OpenCV. Я не очень хорошо знаю OpenCV. Но подогнать окружность к множеству точек довольно просто. В любом случае это гораздо проще, чем подгонка эллипса!
Я изменил свой ответ и включил результат альтернативного подхода, основанный на рисовании minEnclosingCircle вместо HoughCircle. Это самый близкий круг, который я могу найти, хотя и не совсем так. Это будет круг, охватывающий весь регион. Он проще тем, что не требует настройки параметров.
Круто, я не знал этой функции!
Вот еще один вариант того, что я написал ранее в качестве ответа, который вычисляет либо круг Хафа, либо минимальный охватывающий круг. В этом посте я вычисляю наименьшие квадраты, подходящие выпуклой оболочке к кругу после чтения входных данных, преобразования в оттенки серого, определения порога и использования морфологии для очистки небольших пятен. Наименьшее количество квадратов, соответствующих кругу, взято из scipy по адресу https://scipy-cookbook.readthedocs.io/items/Least_Squares_Circle.html. Однако подход с минимальным охватывающим кругом, показанный ранее, кажется лучшим (и самым простым) из трех подходов для входных данных в этом примере.
Вход:
import cv2
import numpy as np
from scipy import optimize
# 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)
x = hull[:,:,0]
y = hull[:,:,1]
# draw convex hull over copy of input
hull_img = img.copy()
cv2.polylines(hull_img, [hull], True, (0,0,255), 1)
# compute least squares fit to circle using scipy optimize
# see https://scipy-cookbook.readthedocs.io/items/Least_Squares_Circle.html
# == METHOD 1 ==
method_1 = 'algebraic'
# coordinates of the barycenter
x_m = np.mean(x)
y_m = np.mean(y)
# calculation of the reduced coordinates
u = x - x_m
v = y - y_m
# linear system defining the center in reduced coordinates (uc, vc):
# Suu * uc + Suv * vc = (Suuu + Suvv)/2
# Suv * uc + Svv * vc = (Suuv + Svvv)/2
Suv = np.sum(u*v)
Suu = np.sum(u**2)
Svv = np.sum(v**2)
Suuv = np.sum(u**2 * v)
Suvv = np.sum(u * v**2)
Suuu = np.sum(u**3)
Svvv = np.sum(v**3)
# Solving the linear system
A = np.array([ [ Suu, Suv ], [Suv, Svv]])
B = np.array([ Suuu + Suvv, Svvv + Suuv ])/2.0
uc, vc = np.linalg.solve(A, B)
# Calculate center
xc = x_m + uc
yc = y_m + vc
# Calculate distances from the center (xc, yc) and average radius
Ri = np.sqrt((x-xc)**2 + (y-yc)**2)
R = np.mean(Ri)
residual = np.sum((Ri-R)**2)
residual2= np.sum((Ri**2-R**2)**2)
print('')
print ("center:",(xc,yc), "radius:",R, "residual:",residual)
print('')
# draw circle
result = img.copy()
xc = int(xc)
yc = int(yc)
R = int(R)
cv2.circle(result, (xc, yc), R, (0, 0, 255), 1)
# save results
cv2.imwrite('input_mask_morph.png', morph)
cv2.imwrite('input_mask_convex_hull2a.png', hull_img)
cv2.imwrite('input_mask_circle4a.png', result)
# show images
cv2.imshow('thresh', thresh)
cv2.imshow('morph', morph)
cv2.imshow('convex hull', hull_img)
cv2.imshow('result', result)
cv2.waitKey(0)
cv2.destroyAllWindows()
Морфологически очищенное изображение:
Выпуклая оболочка на копии входных данных:
Результирующий круг наименьших квадратов на копии входных данных:
Если вы знаете, что это шар, то вместо того, чтобы пытаться найти алгоритм магического изменения пикселей, вам лучше попытаться угадать, какой «шар» на изображении более вероятен (какая тройка (xcenter, ycenter, радиус) максимизировать адекватность). Например, начните с того, что у вас есть (барицентр и радиус белых пикселей), и используйте своего рода градиентный спуск, чтобы попытаться настроить параметр для улучшения показателя.