1

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()

Edubgr
  • 13
  • 1
  • 5

1 Answers1

1

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

Rotem
  • 30,366
  • 4
  • 32
  • 65
  • Thanks, with that i got more possibilities to work. However, I have not yet achieved a pleasant result. This ball will be identified in a generally white environment, with several other balls. What i need is just to know if the ball is on the right or left side of the screen, So the precision doesn’t need to be accurate. I just need the external noise to be eliminated. – Edubgr Apr 11 '20 at 13:04
  • With or without the hand? – Rotem Apr 11 '20 at 13:08
  • Without the hand. – Edubgr Apr 11 '20 at 14:33
  • Without the hand, I think `cv2.adaptiveThreshold` is going to give better results. In case you can choose different ball, you can make it a much easier problem. – Rotem Apr 11 '20 at 14:38
  • Yes, but unfortunately the ball has to be that color and the white background. If it were colored, I would already have the solution. Using only `cv2.adaptiveThereshold` I got some results, but it still has a lot of noise and `cv2.HoughCircles` still doesn't work wel – Edubgr Apr 11 '20 at 15:27
  • You may try: `cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)`. In case the background is uniform enough, I think it's going to work. – Rotem Apr 11 '20 at 17:42
  • It would be functional if it were all white, but there is still the chance that part of the image will show a colorful environment, hindering filtering. – Edubgr Apr 11 '20 at 19:17
  • Is it a philosophical issue? Why don't you post a sample image? – Rotem Apr 11 '20 at 19:24
  • Because I don't have the environment ready yet. But here I have the image of something that will be similar to the environment when it is correct. Remembering also that this detection will be made by a camera in real time. https://imgur.com/mkxHoBq https://imgur.com/BP7iwVU https://imgur.com/yTilRz1 https://imgur.com/WhoqDt9 https://imgur.com/RMRaSUF – Edubgr Apr 11 '20 at 20:07
  • I can't think of any robust solution. You can try using `imflatfield`. It's a MATLAB function that I [implemented](https://stackoverflow.com/questions/61087996/imflatfield-matlab-for-python-use) in Python. It makes the background more uniform. The colorful area on the right side is problematic. In case you just need to know if the ball is in the right or left side, consider cutting it from the image. – Rotem Apr 11 '20 at 20:50
  • https://www.youtube.com/watch?v=yGpsuEsl1Kk The idea is to create a system to detect which side the robot should go, so that it finds the ball in the last phase. As you can see, the image will have a lot of noise. Detecting the black ball is simple, but the white is a problem. Any applicable ideas? – Edubgr Apr 11 '20 at 22:10
  • The only idea I can think of, is installing a small flashlight for illuminating the silver balls. I don't know if it's viable solution. You can try posting a new question. Post the video, and post a reference to the current question. You might get better luck next time. – Rotem Apr 12 '20 at 20:23