I am trying to make a program that will accept an image of a pool table and detect balls with OpenCV and Python. My algorithm right now is basically: 1) grayscaling the image; 2) applying median blur; 3) applying Gaussian blur; 4) applying cv2.HoughCircles() to detect circles.
Unfortunately, cv2.HoughCircles() detects random circles in the image:
When I called cv2.Canny() to see the edges used in cv2.HoughCircles, the results included a lot of noise (due to the color/texture of the carpet):
How should I remove the false detected circles? Changing the values of "param2" to increase the accumulator threshold will cause the black eight ball to not be detected. I'm thinking of either 1) applying a mask to filter out everything but the pool table (and then determining a ROI from the mask); 2) applying a rectangle detection algorithm to detect the pool table; 3) applying fiducials to try an determine an ROI from the image.
Here is the original image.
The code is below:
import cv2
img = cv2.imread("Assets/Setup.jpg", 1)
grayscale_frame = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
median_blur_frame = cv2.medianBlur(grayscale_frame, 5)
gaussian_blur_frame = cv2.GaussianBlur(median_blur_frame, (5, 5), 0)
circles = cv2.HoughCircles(gaussian_blur_frame, cv2.HOUGH_GRADIENT, dp=1, minDist=45, param1=75, param2=14,
minRadius=40, maxRadius=45)
for circle in circles[0]:
print(circle)
cv2.circle(img, (int(circle[0]), int(circle[1])), int(circle[2]), (255, 255, 255), 5)
cv2.imshow("Frame", img)
cv2.waitKey(0)
cv2.destroyAllWindows()
Related
I am trying to find accurate locations for the corners on ink blotches as seen below:
My idea is to fit lines to the edges and then find where they intersect. As of now, I've tried using cv2.approxPolyDP() with various values of epsilon to approximate the edges, however this doesn't look like the way to go. My cv2.approxPolyDP code gives the following result:
Ideally, this is what I want to produce (drawn on paint):
Are there CV functions in place for this sort of problem? I've considered using Gaussian blurring before the threshold step although that method does not seem like it would be very accurate for corner finding. Additionally, I would like this to be robust to rotated images, so filtering for vertical and horizontal lines won't necessarily work without other considerations.
Code:*
import numpy as np
from PIL import ImageGrab
import cv2
def process_image4(original_image): # Douglas-peucker approximation
# Convert to black and white threshold map
gray = cv2.cvtColor(original_image, cv2.COLOR_BGR2GRAY)
gray = cv2.GaussianBlur(gray, (5, 5), 0)
(thresh, bw) = cv2.threshold(gray, 128, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
# Convert bw image back to colored so that red, green and blue contour lines are visible, draw contours
modified_image = cv2.cvtColor(bw, cv2.COLOR_GRAY2BGR)
contours, hierarchy = cv2.findContours(bw, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
cv2.drawContours(modified_image, contours, -1, (255, 0, 0), 3)
# Contour approximation
try: # Just to be sure it doesn't crash while testing!
for cnt in contours:
epsilon = 0.005 * cv2.arcLength(cnt, True)
approx = cv2.approxPolyDP(cnt, epsilon, True)
# cv2.drawContours(modified_image, [approx], -1, (0, 0, 255), 3)
except:
pass
return modified_image
def screen_record():
while(True):
screen = np.array(ImageGrab.grab(bbox=(100, 240, 750, 600)))
image = process_image4(screen)
cv2.imshow('window', image)
if cv2.waitKey(25) & 0xFF == ord('q'):
cv2.destroyAllWindows()
break
screen_record()
A note about my code: I'm using screen capture so that I can process these images live. I have a digital microscope that can display live feed on a screen, so the constant screen recording will allow me to sample from the video feed and locate the corners live on the other half of my screen.
Here's a potential solution using thresholding + morphological operations:
Obtain binary image. We load the image, blur with cv2.bilateralFilter(), grayscale, then Otsu's threshold
Morphological operations. We perform a series of morphological open and close to smooth the image and remove noise
Find distorted approximated mask. We find the bounding rectangle coordinates of the object with cv2.arcLength() and cv2.approxPolyDP() then draw this onto a mask
Find corners. We use the Shi-Tomasi Corner Detector already implemented as cv2.goodFeaturesToTrack() for corner detection. Take a look at this for an explanation of each parameter
Here's a visualization of each step:
Binary image -> Morphological operations -> Approximated mask -> Detected corners
Here are the corner coordinates:
(103, 550)
(1241, 536)
Here's the result for the other images
(558, 949)
(558, 347)
Finally for the rotated image
(201, 99)
(619, 168)
Code
import cv2
import numpy as np
# Load image, bilaterial blur, and Otsu's threshold
image = cv2.imread('1.png')
mask = np.zeros(image.shape, dtype=np.uint8)
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
blur = cv2.bilateralFilter(gray,9,75,75)
thresh = cv2.threshold(blur, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)[1]
# Perform morpholgical operations
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (10,10))
opening = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, kernel, iterations=1)
close = cv2.morphologyEx(opening, cv2.MORPH_CLOSE, kernel, iterations=1)
# Find distorted rectangle contour and draw onto a mask
cnts = cv2.findContours(close, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
rect = cv2.minAreaRect(cnts[0])
box = cv2.boxPoints(rect)
box = np.int0(box)
cv2.drawContours(image,[box],0,(36,255,12),4)
cv2.fillPoly(mask, [box], (255,255,255))
# Find corners
mask = cv2.cvtColor(mask, cv2.COLOR_BGR2GRAY)
corners = cv2.goodFeaturesToTrack(mask,4,.8,100)
offset = 25
for corner in corners:
x,y = corner.ravel()
cv2.circle(image,(x,y),5,(36,255,12),-1)
x, y = int(x), int(y)
cv2.rectangle(image, (x - offset, y - offset), (x + offset, y + offset), (36,255,12), 3)
print("({}, {})".format(x,y))
cv2.imshow('image', image)
cv2.imshow('thresh', thresh)
cv2.imshow('close', close)
cv2.imshow('mask', mask)
cv2.waitKey()
Note: The idea for the distorted bounding box came from a previous answer in How to find accurate corner positions of a distorted rectangle from blurry image
After seeing the description of the corners, here is what I would recommend:
by any method, find the gross location of the corners (for instance by looking for the extreme values of (±X+±Y, ±X+±Y) or (±X, ±Y)).
consider a strip than joins two corners, with a certain width. Extract the pixels in that strip, on a portion close to the corner, rotate to make it horizontal and average the values along horizontals.
you will obtain a gray profile that tells you the accurate position of the edge, at the mean between the background and foreground intensities.
repeat on all four edges and at both ends. This will give you four accurate corners, by intersection.
I'm using openCV to detect some coins, first I used some functions to fill the coin area so I can make a solid white circle where the coin is, then im trying to use houghCircles to detect the white circle so I can crop it to send to a neural network. But the houghCircle is not detecting anything, any tips on this?
Here is the code:
import numpy as np
import cv2
gray = cv2.imread('coin25a2.jpg',0)
color = cv2.imread('coin25a2.jpg',1)
gray_blur = cv2.GaussianBlur(gray, (15,15), 0)
thresh = cv2.adaptiveThreshold(gray_blur, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY_INV, 11,1)
kernel = np.ones((3, 3), np.uint8)
closing = cv2.morphologyEx(thresh, cv2.MORPH_CLOSE, kernel, iterations=7)
circles = cv2.HoughCircles(closing,cv2.HOUGH_GRADIENT,1, 200, 20,30,30, 0)
circles = np.uint16(np.around(circles))
print(circles)
cv2.imshow("a", closing)
cv2.waitKey(0)
The circles variable is not returning any valid (x,y,r).
circles = cv2.HoughCircles(closing,cv2.HOUGH_GRADIENT,1, 200, 20,30,30, 0)
The last parameter is the maximum radius of the circle that you want to find. I think you need to put a large value there, instead of 0.
A better plan is to go with only the default parameters and adjust later.
cv2.HoughCircles(image, method, dp, minDist)
, which is the same as
cv2.HoughCircles(closing,cv2.HOUGH_GRADIENT,1, 200)
I am trying to get the external contour of an image using opencv and python.
I found a solution to this problem here (Process image to find external contour) but the solution does not work for me - instead of the contour image it opens two new images (one which is all black and the other one black and white).
This is the code I am using:
import cv2 # Import OpenCV
import numpy as np # Import NumPy
# Read in the image as grayscale - Note the 0 flag
im = cv2.imread("img.jpg", 0)
# Run findContours - Note the RETR_EXTERNAL flag
# Also, we want to find the best contour possible with CHAIN_APPROX_NONE
_ ,contours, hierarchy = cv2.findContours(im.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
# Create an output of all zeroes that has the same shape as the input
# image
out = np.zeros_like(im)
# On this output, draw all of the contours that we have detected
# in white, and set the thickness to be 3 pixels
cv2.drawContours(out, contours, -1, 255, 3)
# Spawn new windows that shows us the donut
# (in grayscale) and the detected contour
cv2.imshow('Donut', im)
cv2.imshow('Output Contour', out)
# Wait indefinitely until you push a key. Once you do, close the windows
cv2.waitKey(0)
cv2.destroyAllWindows()
The illustration shows the two windows I get instead of the contour.
You are doing some mistakes that compromise your result. Reading from the documentation it says that:
For better accuracy, use binary images (see step 3).
finding contours is like finding white object from black background (see step 2).
You don't stick with these rules so you don't get good results. Also you are plotting your results to a black image and they are not visible.
Below is the full solution for your case.
I am also using an adaptive threshold for better results.
# Step 1: Read in the image as grayscale - Note the 0 flag
im = cv2.imread("/home/jorge/Downloads/input.jpg", 0)
cv2.imshow('Original', im)
cv2.waitKey(0)
cv2.destroyAllWindows()
# Step 2: Inverse the image to get black background
im2 = im.copy()
im2 = 255 - im2
cv2.imshow('Inverse', im2)
cv2.waitKey(0)
cv2.destroyAllWindows()
# Step 3: Get an adaptive binary image
im3 = cv2.adaptiveThreshold(im2, 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY_INV, 11, 2)
cv2.imshow('Inverse_binary', im3)
cv2.waitKey(0)
cv2.destroyAllWindows()
# Step 4: find contours
_, contours, hierarchy = cv2.findContours(im3.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
# Step 5: This creates a white image instead of a black one to plot contours being black
out = 255*np.ones_like(im)
cv2.drawContours(out, contours, -1, (0, 255, 0), 3)
cv2.drawContours(im, contours, -1, (0, 255, 0))
I want to count the number of metal rods in an image using image processing. The images are all similar to this one:
I thought of using Hough Circle Transform, but the rods aren't precisely circular and also there are imperfections on the faces. Another idea was to use watershed algorithm. I took following steps:
Grayscale conversion
a CLAHE enhancement
Pyramid Mean Shift Filter to remove the texture and irregularities
Applied a gaussian blur.
Otsu's Thresholding
Result:
upper image is after 4, lower after 5
Clearly, I cannot use this image for Watershed.
I then tried using a simple thresholding, which gives better results, but still not accurate enough. Also I want the counting algorithm to be independent of the image used, and that means simple thresholding won't do.
I searched the net for algorithms to detect circular objects, but they all seem to be dependent on the segmentation or edge detection. I don't understand how to proceed from this point. Am I missing something basic here?
Edit: The basic preprocessing code:
img = cv2.imread('testc.jpg')
img = cv2.pyrMeanShiftFiltering(img, 21, 51)
img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
clahe = cv2.createCLAHE(clipLimit=3.0, tileGridSize=(7,7))
cl1 = clahe.apply(img)
cl1 = cv2.GaussianBlur(cl1, (5,5), 1)
ret3,thresh = cv2.threshold(cl1,0,255,cv2.THRESH_BINARY+cv2.THRESH_OTSU)
Watershed:
D = ndimage.distance_transform_edt(thresh)
localMax = peak_local_max(D, indices=False, min_distance=35,
labels=thresh)
# perform a connected component analysis on the local peaks,
# using 8-connectivity, then appy the Watershed algorithm
markers = ndimage.label(localMax, structure=np.ones((3, 3)))[0]
labels = watershed(-D, markers, mask=thresh)
print("[INFO] {} unique segments found".format(len(np.unique(labels)) - 1))
# loop over the unique labels returned by the Watershed
# algorithm
for label in np.unique(labels):
# if the label is zero, we are examining the 'background'
# so simply ignore it
if label == 0:
continue
# otherwise, allocate memory for the label region and draw
# it on the mask
mask = np.zeros(gray.shape, dtype="uint8")
mask[labels == label] = 255
# detect contours in the mask and grab the largest one
cnts = cv2.findContours(mask.copy(), cv2.RETR_EXTERNAL,
cv2.CHAIN_APPROX_SIMPLE)[-2]
c = max(cnts, key=cv2.contourArea)
# draw a circle enclosing the object
((x, y), r) = cv2.minEnclosingCircle(c)
cv2.circle(img, (int(x), int(y)), int(r), (0, 255, 0), 2)
cv2.putText(img, "#{}".format(label), (int(x) - 10, int(y)),
cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 0, 255), 2)
Code for Watershed algorithm I took mostly from pyimagesearch I am sorry for not giving the link, I am allowed to post only two links currently (and No Images).
This is not the only thing that I have tried, but is the best approach currently.
I have created a black and white mask using various OpenCV filters. There are four circles that are clearly visible
I am trying to outline these circles using HoughCircles, but it gives many false positives and generally bad results:
circles = cv2.HoughCircles(combined, cv.CV_HOUGH_GRADIENT, 1, 300, np.array([]), 10, 30, 60, 300)
How can I properly detect the circular shapes in the black and white image?
Here is runnable code that can be used with the black and white image:
import numpy as np
import cv2
import cv
image = cv2.imread("image.png")
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
circles = cv2.HoughCircles(gray, cv.CV_HOUGH_GRADIENT, 1, 300, np.array([]), 10, 30, 60, 300)
if circles is not None:
circles = np.uint16(np.around(circles))
for i in circles[0,:]:
cv2.circle(image, (i[0], i[1]), i[2], (0, 255, 0), 1)
cv2.circle(image, (i[0], i[1]), 2, (0, 0, 255), 3)
cv2.imshow("thing", image)
cv2.waitKey(0)
cv2.destroyAllWindows()
First of all, if I'm not mistaken, Hough Transform for circles expects a hollow circle, not a full one. This means you need to extract only the boundary/perimeter of the circles before applying the Hough Transform. OpenCV's findContours and arcLength functions will help you find the perimeters.
Second of all, unfortunately, in my experience Hough Transform is very sensitive to variations of the circle shape, meaning that if the shape you're trying to detect is "almost" a circle, it might not be able to detect it.
My advice is you should try to make your objects "rounder" by applying the Closing morphological operation on your binary image, with a disk-shaped structuring element. Then extract the perimeter of the objects in the image, and only then apply the Hough Transform. Hopefully, this will give you good enough results.
Alternatively, you can try to detect circles using the RANSAC algorithm. Here is an implementation for detecting lines, but you can adjust it for circles - just choose 3 points randomly (instead of 2), and define a circle that goes through them. Next, find all the points that lie near that circle (those are called the inlier points). These will be the points that satisfy the inequality:
where (x,y) is the point, (x0,y0) is the center of the circle, r is its radius, and margin is a parameter you'll have to tune.
The rest of the algorithm is the same.
Good luck!