Python – OpenCV – есть ли способ «заполнить» ожидаемый контур?

Я хочу определить контур мяча, и объект всегда будет мячом, но у меня есть объект в середине, который может загораживать часть мяча, вот моя текущая ситуация: (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)    


Наконец, мой вопрос: есть ли способ обнаружить арку с любой стороны и просто заполнить недостающий зазор в круге? Или еще лучший способ?

Если вы знаете, что это шар, то вместо того, чтобы пытаться найти алгоритм магического изменения пикселей, вам лучше попытаться угадать, какой «шар» на изображении более вероятен (какая тройка (xcenter, ycenter, радиус) максимизировать адекватность). Например, начните с того, что у вас есть (барицентр и радиус белых пикселей), и используйте своего рода градиентный спуск, чтобы попытаться настроить параметр для улучшения показателя.

chrslg 23.06.2024 19:38

Пожалуйста, публикуйте входное изображение и изображение маски как оригинал, а не в сочетании с каким-либо другим изображением в виде снимков экрана.

fmw42 23.06.2024 19:48

Вы можете убрать небольшие пятна, оставив только две части мяча. Затем возьмите все белые точки и прикрепите к ним эллипс или замкните выпуклую оболочку и залейте ее белым, а затем используйте HoughCircles, чтобы подогнать к ней круг.

fmw42 23.06.2024 19:50
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
0
3
89
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Вот один из способов сделать это в Python/OpenCV.

Поскольку вы не публиковали фактические входные файлы и не снимки экрана, я взял ваше изображение и обрезал его, чтобы получить в качестве входных данных черно-белое изображение маски.

  • Прочитайте маску ввода
  • Преобразовать в оттенки серого
  • порог Оцу
  • Примените морфологию, чтобы убрать мелкие пятна.
  • Возьмите выпуклую оболочку и нарисуйте ее белой заливкой на черном фоне.
  • Получите круги Хафа для выпуклой оболочки. Настраивайте Mindist и Minradius до тех пор, пока не получите один круг, а не нулевой.
  • Нарисуйте круг Хафа на копии ввода
  • Сохранить результаты
  • Показать результаты

Вход:

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 24.06.2024 00:20

@Cris Luengo FitCircle нет, есть только fitEllipse. Так что это придется закодировать на Python, не так ли? Или мне не хватает какого-то метода OpenCV? Было бы здорово, если бы кто-нибудь мог написать код fitCircle, аналогичный fitEllipse, в OpenCV!

fmw42 24.06.2024 06:35

Я не знаю, не хватает ли вам какого-то метода OpenCV. Я не очень хорошо знаю OpenCV. Но подогнать окружность к множеству точек довольно просто. В любом случае это гораздо проще, чем подгонка эллипса!

Cris Luengo 24.06.2024 17:40

Я изменил свой ответ и включил результат альтернативного подхода, основанный на рисовании minEnclosingCircle вместо HoughCircle. Это самый близкий круг, который я могу найти, хотя и не совсем так. Это будет круг, охватывающий весь регион. Он проще тем, что не требует настройки параметров.

fmw42 24.06.2024 17:54

Круто, я не знал этой функции!

Cris Luengo 24.06.2024 18:10

Вот еще один вариант того, что я написал ранее в качестве ответа, который вычисляет либо круг Хафа, либо минимальный охватывающий круг. В этом посте я вычисляю наименьшие квадраты, подходящие выпуклой оболочке к кругу после чтения входных данных, преобразования в оттенки серого, определения порога и использования морфологии для очистки небольших пятен. Наименьшее количество квадратов, соответствующих кругу, взято из 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()

Морфологически очищенное изображение:

Выпуклая оболочка на копии входных данных:

Результирующий круг наименьших квадратов на копии входных данных:

Другие вопросы по теме