Edubgr
Edubgr

Reputation: 13

Mask to filter the area of ​interest (OpenCV)

I need a mask to make the circle in this image stand out from the background, receiving a binary image, where white is the region of interest (the circle) and black everything else. So I can apply this mask in a video capture, where it is possible to see only the sphere. note: the background will generally be white.

I already created codes using the threshold or inRange, with a simple algorithm, that from a selection made by the user manually, marking the region of the circle, it removes the minimum and maximum rgb value, thus creating a parameter to apply in the inRange or threshold. However, as the background is usually white and clear, very similar to the color of the sphere, the binary mask includes the background, making the code a failure. Any other method for that?

import cv2
import numpy as np
ix,iy = 0,0
def selection_area(event,x,y,flags,param):
    global ix,iy
    global vx,vy
    if event == cv2.EVENT_LBUTTONDBLCLK:
        cv2.rectangle(img,(x-5,y-5),(x+5,y+5),(255,255,0),-1)
        if ix!=0 and iy!=0:
            cv2.rectangle(img,(x,y),(ix,iy),(255,0,0),1)
            vx=[x,ix]
            vy=[y,iy]
        ix,iy = x,y

def analyzeRGB(cimg):
    b=[];g=[];r=[];
    for j in cimg:
        for i in j:
            b.append(i[0])
            g.append(i[1])
            r.append(i[2])
    lower_blue= np.array([min(b),min(g),min(r)])
    upper_blue= np.array([max(b),max(g),max(r)])
    return lower_blue,upper_blue


cap = cv2.VideoCapture(0)
while(True):
    ret, frame = cap.read()
    cv2.imshow('frame',frame)
    if cv2.waitKey(1) & 0xFF == ord('q'):
        img=frame
        break
cap.release()
cv2.destroyAllWindows()

cv2.imshow('Analyze',img)

cv2.setMouseCallback('Analyze',selection_area)

while(1):
    cv2.imshow('Analyze',img)
    k = cv2.waitKey(20) & 0xFF
    if k == ord('q'):
        print (vx,vy)
        break
cv2.destroyAllWindows()
cut = img[min(vy)+5:max(vy)-5,min(vx)+5:max(vx)-5]

cv2.imshow("Cut",cut)
cv2.waitKey(0)
cv2.destroyAllWindows()
cv2.waitKey(0)

filter_RGB =analyzeRGB(cut)
img =  cv2.inRange(img, filter_RGB[0],filter_RGB[1])

cv2.imshow("Ready",img)
cv2.imshow("Cut",cut)
cv2.waitKey(0)
cv2.destroyAllWindows()

cap = cv2.VideoCapture(0)
while(True):
    ret, frame = cap.read()
    frame = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY);

    frame =cv2.inRange(frame,filter_RGB[0],filter_RGB[1])

    cv2.imshow("Frame",frame)
    if cv2.waitKey(1) & 0xFF == ord('q'):
        break
cap.release()
cv2.destroyAllWindows()

Upvotes: 1

Views: 3731

Answers (1)

Rotem
Rotem

Reputation: 32104

Finding the ball is challenging because the color is close to the background, and because of the hand.
The reflections from the ball and the non-uniformity makes it more challenging.

In case you know the exact radius of the ball, you may use cv2.HoughCircles for searching a circle with the exact radius.

My solution uses cv2.HoughCircles, but "cleans" the image first.
There is a good change that the solution is too specific to the image you have posted, and not going to work for the general case.

The solution uses the following stages:

  • Convert image to gray.
  • Apply median filter.
  • Use cv2.adaptiveThreshold - find edges with intensity close to the background intensity.
  • Mask dark pixels - assume the hand is darker than the ball and from the background.
    We need to mask the hand for avoiding "false circles" on the hand.
  • Use "opening" morphological operation for cleaning small clusters.
  • Use cv2.HoughCircles for finding circles.
    The parameters I used finds only one circle.
    You may think of some logic for eliminating other circles when more than one is found.

Here is the code:

import cv2
import numpy as np

# Read input image
img = cv2.imread('ball_in_hand.png')

# Convert to gray
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

# Apply median filter
gray = cv2.medianBlur(gray, 5)

# Apply adaptive threshold with gaussian size 15x15
thresh = cv2.adaptiveThreshold(gray, 255, adaptiveMethod=cv2.ADAPTIVE_THRESH_MEAN_C, thresholdType=cv2.THRESH_BINARY, blockSize=15, C=0)

# Use threshold for finding dark pixels - needs to be masked
_, dark_mask = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)

# Mask the dark pixels.
thresh = thresh & dark_mask

# Use "opening" morphological operation - cleaning up.
thresh = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5,5)))

rows, cols = thresh.shape

# Use HoughCircles for finding circles
circles = cv2.HoughCircles(thresh, cv2.HOUGH_GRADIENT, 1, minDist=rows//8, param1=50, param2=30, minRadius=rows//8, maxRadius=rows//2)

# mask will be the desired mask (filled circle)
mask = np.zeros_like(gray)

# Iterate circles
for c in circles[0,:]:
    # Draw green circle on the image for testing
    cv2.circle(img, (c[0], c[1]), c[2], (0, 255, 0), 2)

    # Draw filled circle for creating the mask
    cv2.circle(mask, (c[0], c[1]), c[2], 255, cv2.FILLED)

# Show images for testing
cv2.imshow('img', img)
cv2.imshow('gray', gray)
cv2.imshow('thresh', thresh)
cv2.imshow('dark_mask', dark_mask)
cv2.imshow('mask', mask)
cv2.waitKey(0)
cv2.destroyAllWindows()

Images:

mask (solution):
enter image description here

img:
enter image description here

gray:
enter image description here

dark_mask:
enter image description here

thresh:
enter image description here

Upvotes: 1

Related Questions