Error when performing Hough circle transformation on OpenCV-python - python

The code below is intended to perform a Hough Circle transformation on a video. The code works as intended when a frame from the video is extracted and an Hough Circle transformation is performed. However, when I try to replicate the similar method to produce a video I get this error loop of ufunc does not support argument 0 of type NoneType which has no callable rint method. The cause of this error seems to be below the while loop. What could be the cause of this issue?
Many thanks in advance.
Update: I have changed my blurred = cv2.medianBlur(dframe, 25) to blurred = cv2.GaussianBlur(dframe,(11,11),0). This seem to work for a moment but then the code crashes to give me the same error.
import numpy as np
import cv2
import matplotlib.pyplot as plt
np.random.seed(42)
def fixColor(image):
return(cv2.cvtColor(image,cv2.COLOR_BGR2RGB))
video = cv2.VideoCapture("video.wmv")
#randomly select frames in an array
frameIds = video.get(cv2.CAP_PROP_FRAME_COUNT) *np.random.uniform(size = 55)
#store selected frames in an array
frames = []
for fid in frameIds:
video.set(cv2.CAP_PROP_FRAME_COUNT,fid)
ret, frame = video.read()
frames.append(frame)
video.release()
#calculate the median frame
medianFrame = np.median(frames,axis=0).astype(dtype=np.uint8)
#random sample
sample_frame = frames[0]
#convert to grayscale
grayMedianFrame = cv2.cvtColor(medianFrame, cv2.COLOR_BGR2GRAY)
graySample = cv2.cvtColor(sample_frame, cv2.COLOR_BGR2GRAY)
#subtract grayscale frames between sample and median
dframe = cv2.absdiff(graySample, grayMedianFrame)
blurred = cv2.medianBlur(dframe, 25)
#plt.imshow(fixColor(blurred))
#Hough circle
circles = cv2.HoughCircles(blurred, cv2.HOUGH_GRADIENT,1,120,param1= 50, param2=30,minRadius=0,maxRadius=0)
circles = np.uint16(np.around(circles))
for i in circles[0, :]:
# draw outer circle
cv2.circle(sample_frame, (i[0], i[1]), i[2], (0, 255, 0), 2)
# draw center of circle
cv2.circle(sample_frame, (i[0], i[1]), 2, (0, 255, 0), 9)
plt.imshow(sample_frame,cmap="gray") #outputs image from sample frame with Hough circle
#write in new video
writer = cv2.VideoWriter("output_hough.mp4",cv2.VideoWriter_fourcc(*"MP4V"),30,(512,512))
video = cv2.VideoCapture("video.wmv")
total_frames = video.get(cv2.CAP_PROP_FRAME_COUNT)
frameCnt = 0
########## CODE WORKS FINE UNTIL THIS POINT ##############
while(frameCnt < total_frames-1):
frameCnt +=1
ret, frame = video.read() #read frame 1 by 1
gframe = cv2.cvtColor(frame,cv2.COLOR_BGR2GRAY) #grayscale frame
dframe = cv2.absdiff(gframe, grayMedianFrame) #remove background
blurred = cv2.medianBlur(dframe, 25) #blur
#Hough transformation
circles = cv2.HoughCircles(blurred, cv2.HOUGH_GRADIENT,1,120,param1= 50, param2=30,minRadius=0,maxRadius=0)
circles = np.uint16(np.around(circles))
for i in circles[0, :]:
# draw outer circle
cv2.circle(frame, (i[0], i[1]), i[2], (0, 255, 0), 2)
# draw center of circle
cv2.circle(frame, (i[0], i[1]), 2, (0, 255, 0), 9)
writer.write(cv2.resize(frame,(512,512))) #write frame into output vid
video.release()
writer.release()

You did not show the full stack and the exact line that is throwing the error.
But, looking at your code I guess the problem lies in:
circles = np.uint16(np.around(circles))
What happens is that cv2.HoughCircles can return None if it can not find circles, then np.around is not working with None.
What you should do is check first that you got your circles by if circles is not None:

Related

How to get only water level contour with OpenCV python on raspberry pi

I am using raspberry pi4 (8GB) with pi camera to detect water level . I have defined a line from 0,375 to 800,375 . If top most point of water level contour goes above this line then I want to call a function. Here is my code and attached image of setup. How do I get water level contour only. Does it require canny edge detection over contours to get clear water level ? first I am getting largest contour and then defining its top most point.
import numpy as np
import cv2
import time
from datetime import datetime
#color=(255,0,0)
color=(0,255,0)
thickness=2
kernel = np.ones((2,2),np.uint8) # added 01/07/2021
picflag = 0 # set value to 1 once picture is taken
# function to take still picture when water level goes beyond threshold
def takepicture(frame):
currentTime = datetime.now()
picTime = currentTime.strftime("%d.%m.%Y-%H%M%S") # Create file name for our picture
text = currentTime.strftime("%d.%m.%Y-%H:%M:%S")
font = cv2.FONT_HERSHEY_SIMPLEX # font
org = (05, 20) # org
fontScale = 0.5 # fontScale
color = (0, 0, 255) # Red color in BGR
thickness = 1 # Line thickness of 2 px
picName = picTime + '.png'
image = cv2.putText(frame, text, org, font, fontScale, color, thickness, cv2.LINE_AA, False)
cv2.imwrite(picName , image)
picflag = 1
return
cap = cv2.VideoCapture(0)
while(True):
# Capture frame-by-frame
ret, frame = cap.read() # ret = 1 if the video is captured; frame is the image
# Our operations on the frame come here
gray = cv2.cvtColor(frame,cv2.COLOR_BGR2GRAY)
#blur = cv2.GaussianBlur(gray,(21,21),0)
gray= cv2.medianBlur(gray, 3) #to remove salt and paper noise
#ret,thresh = cv2.threshold(gray,10,20,cv2.THRESH_BINARY_INV)
ret,thresh = cv2.threshold(gray,127,127,cv2.THRESH_BINARY_INV)
thresh = cv2.morphologyEx(thresh, cv2.MORPH_GRADIENT, kernel) # get outer boundaries only added 01/07/2021
thresh = cv2.dilate(thresh,kernel,iterations = 5) # strengthen weak pixels added 01/07/2021
img1, contours, hierarchy = cv2.findContours(thresh,cv2.RETR_TREE,cv2.CHAIN_APPROX_NONE)
#img1,contours,hierarchy = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE) #added 01/07/2021
cv2.line(frame, pt1=(0,375), pt2=(800,375), color=(0,0,255), thickness=2) # added 01/07/2021
if len(contours) != 0:
c = max(contours, key = cv2.contourArea) # find the largest contour
#x,y,w,h = cv2.boundingRect(c) # get bounding box of largest contour
img2=cv2.drawContours(frame, c, -1, color, thickness) # draw largest contour
#img2=cv2.drawContours(frame, contours, -1, color, thickness) # draw all contours
#img3 = cv2.rectangle(img2,(x,y),(x+w,y+h),(0,0,255),2) # draw red bounding box in img
#center = (x, y)
#print(center)
left = tuple(c[c[:, :, 0].argmin()][0])
right = tuple(c[c[:, :, 0].argmax()][0])
top = tuple(c[c[:, :, 1].argmin()][0])
bottom = tuple(c[c[:, :, 1].argmax()][0])
# Draw dots onto frame
cv2.drawContours(frame, [c], -1, (36, 255, 12), 2)
cv2.circle(frame, left, 8, (0, 50, 255), -1)
cv2.circle(frame, right, 8, (0, 255, 255), -1)
cv2.circle(frame, top, 8, (255, 50, 0), -1)
cv2.circle(frame, bottom, 8, (255, 255, 0), -1)
#print('left: {}'.format(left))
#print('right: {}'.format(right))
#print(format(top))
top_countour_point = top[1]
print(top_countour_point)
#print('bottom: {}'.format(bottom))
#if ((top_countour_point <= 375) and (picflag == 0)): #checking if contour top point is above line
#takepicture(frame)
#continue
#if ((top_countour_point > 375) and (picflag == 0)) :
#picflag = 0
#continue
# Display the resulting image
# cv2.line(frame, pt1=(0,375), pt2=(800,375), color=(0,0,255), thickness=2) # added 01/07/2021
#cv2.imshow('Contour',img3)
#cv2.imshow('thresh' ,thresh)
cv2.imshow('Contour',frame)
if cv2.waitKey(1) & 0xFF == ord('q'): # press q to quit
break
# When everything done, release the capture
cap.release()
cv2.destroyAllWindows()
Caveats
As it was pointed out in the comments, there is very little to work with based on your post. In general, I agree with s0mbre that you'd be better of with a water level sensor, and with kavko, that if you do want to use a camera, you need to better control your environment, with lighting, camera angles, background, etc.
However, that is not to say that it is not possible with your current setup, assuming, that it is a static setup except for the water level. As such, there are some assumptions that we can make.
Here are the steps that I took to get an approximate approach:
Gather image data
You have only provided one image with lines already on it, so that's not a lot to go on. What I did is that I removed the line that you added:
Fortunately there wasn't too much of a line left afterwards.
Image processing:
I have loaded the image (this would come from the code you have posted).
Using the assumptions above, I have decided to focus on a narrow slice of the image. I selected only the middle 60 pixels (1)
slc = frame[:, 300:360]
Next, I have converted it to greyscale (2)
gray_slc = cv2.cvtColor(slc, cv2.COLOR_BGR2GRAY)
I have used Canny edge detection (see docs here) to find the edges in the image (3)
edges = cv2.Canny(gray_slc, 50, 90)
After that, I have applied a Hough Transform, to find all the edges (related Stack Overflow answer) (4)
rho = 1
theta = np.pi / 180
threshold = 15
min_line_length = 50
max_line_gap = 20
lines = cv2.HoughLinesP(edges, rho, theta, threshold, np.array([]), min_line_length, max_line_gap)
Given that I can assume that all of the top lines are the edge of the container, I averaged the y coordinates of the lines, and picked the lower most one as the water level:
y_avgs = [(line[0, 1] + line[0, 3]) / 2 for line in lines]
water_level = max(y_avgs)
Having this, I just checked, if it is over the threshold you have selected:
trigger_threshold = 375
if water_level > trigger_threshold:
print("Water level is under the selected line")
Now, keep in mind, I only had the one image to go on. Considering lighting conditions, yours results may vary.

HoughCircles circle detection not working?

I have been trying to write a program that can detect circles on my screen.
This is my screen before code processing
As you can see on the image, there are three circles that the code should detect. I am using HoughCircles function from OpenCV library to achieve this task. My code is below.
ss = gui.screenshot()
img = cv2.cvtColor(np.array(ss), cv2.COLOR_RGB2BGR)
output = img.copy()
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
circles = cv2.HoughCircles(gray, cv2.HOUGH_GRADIENT, 1.2, 100)
if circles is not None:
print("circles found", len(circles))
circles = np.round(circles[0, :]).astype("int")
for (x, y, r) in circles:
cv2.circle(output, (x, y), r, (0, 255, 0), 4)
cv2.rectangle(output, (x - 5, y - 5), (x + 5, y + 5), (0, 128, 255), -1)
cv2.imshow("output", np.hstack([gray, output]))
cv2.waitKey(0)
cv2.imshow("output", gray)
cv2.waitKey(0)
I am first taking screenshot of my screen. Then, I convert it to use it for opencv.
However, this code does not detect any circles for the screenshot shown in the first picture. I know this because when ran, my program does not print "circles found". Moreover, to show that I have been taking screenshots and transforming them to grayscale properly, I have this image taken from the last two lines of my code.
picture in a gray scale
To show that my code works with other circle images, here is a picture of a regular circle:
before detection
after detection
Any help would be very appreciated!
Here's an alternative solution to detect the circles without using the Hough Transform. As your input image has a very distinct blue hue to the blobs of interest, you can try to create a segmentation mask based on their HSV values. Then, detect contours and approximate each contour using a circle. The last step can be implemented using the cv2.minEnclosingCircle, which, as its name suggest, can compute the Minimum Enclosing Circle of a contour.
Let's see the code:
# image path
path = "D://opencvImages//"
fileName = "XUzFw.png"
# Reading an image in default mode:
inputImage = cv2.imread(path + fileName)
# Create a deep copy of the input for results:
inputImageCopy = inputImage.copy()
# Convert the image to the HSV color space:
hsvImage = cv2.cvtColor(inputImage, cv2.COLOR_BGR2HSV)
# Set the HSV values:
lowRange = np.array([78, 0, 158])
uppRange = np.array([125, 255, 255])
# Create the HSV mask
mask = cv2.inRange(hsvImage, lowRange, uppRange)
This generates the following segmentation mask:
As you can see, the only blobs that remain are the circles. Now, let's compute the contours and find the minimum enclosing circle:
# Find the circle blobs on the binary mask:
contours, hierarchy = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
# Use a list to store the center and radius of the target circles:
detectedCircles = []
# Look for the outer contours:
for i, c in enumerate(contours):
# Approximate the contour to a circle:
(x, y), radius = cv2.minEnclosingCircle(c)
# Compute the center and radius:
center = (int(x), int(y))
radius = int(radius)
# Draw the circles:
cv2.circle(inputImageCopy, center, radius, (0, 0, 255), 2)
# Store the center and radius:
detectedCircles.append([center, radius])
# Let's see the results:
cv2.namedWindow("Circles", cv2.WINDOW_NORMAL)
cv2.imshow("Circles", inputImageCopy)
cv2.waitKey(0)
This is the result of the detection:
Additionally, you can check out the data stored in the detectedCircles list:
# Check out the detected circles:
for i in range(len(detectedCircles)):
# Get circle data:
center, r = detectedCircles[i]
# Print it:
print("i: "+str(i)+" x: "+str(center[0])+" y: "+str(center[1])+" r: "+str(r))
Which yields:
i: 0 x: 395 y: 391 r: 35
i: 1 x: 221 y: 391 r: 36
i: 2 x: 567 y: 304 r: 35
These are the parameters of houghCircles that works for me. You should also consider running a gaussian blur over the image before trying to find the circles.
I'm not a huge fan of houghCircles. I find it to be really finicky and I don't like how much of what it does is hidden inside the function. It makes tuning it mostly trial-and-error. These parameters work for this particular image, but I wouldn't count on this continuing to work under different lighting conditions or for different colors.
import cv2
import numpy as np
# load image
img = cv2.imread("spheres.png");
# grayscale
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY);
gray = cv2.GaussianBlur(gray,(5,5),0);
# circles
circles = cv2.HoughCircles(gray, cv2.HOUGH_GRADIENT, dp = 1, minDist = 100, param1=65, param2=20, minRadius=20, maxRadius=50)
# draw circles
if circles is not None:
# round to ints
circles = np.uint16(np.around(circles));
for circle in circles[0, :]:
# unpack and draw
x, y, radius = circle;
center = (x,y);
cv2.circle(img, center, radius, (255, 0, 255), 3);
# show
cv2.imshow("Image", img);
cv2.imshow("Gray", gray);
cv2.waitKey(0);

opencv not capturing all frames

I am trying to detect the position of the ball throughout a video. In the beginning, I crop the video because my original clip has some random things at the top. I don't know why but the program doesn't capture all the frames - it sort of stops after the first frame. I tried it on another computer and it works. I have no idea what's wrong.
import cv2
import numpy as np
# Open the video
cap = cv2.VideoCapture('video-4.mp4')
while(1):
success, frame = cap.read()
# Take each frame
if success:
crop_img = frame[100:3000, 100:3000].copy()
gray = cv2.cvtColor(crop_img, cv2.COLOR_BGR2GRAY)
circles = cv2.HoughCircles(gray, cv2.HOUGH_GRADIENT, dp=1.5, minDist=505,param1=75, param2=30, minRadius=8, maxRadius=10)
# ensure at least some circles were found
if circles is not None:
# convert the (x, y) coordinates and radius of the circles to integers
circles = np.round(circles[0, :]).astype("int")
# loop over the (x, y) coordinates and radius of the circles
for (x, y, r) in circles:
print(x,y,r)
# draw the circle in the output image, then draw a rectangle
# corresponding to the center of the circle
cv2.circle(crop_img, (x, y), r, (0, 255, 0), 4)
cv2.rectangle(crop_img, (x - 5, y - 5),
(x + 5, y + 5), (0, 128, 255), -1)
cv2.namedWindow("hi", cv2.WINDOW_NORMAL)
cv2.imshow('hi', crop_img)
cv2.waitKey(0)
else:
break
cv2.destroyAllWindows()
You are using y without initialising it.
You are using cv2.CAP_PROP_FRAME_WIDTH as though it is the width but it isn't. It is just a "define" to tell an OpenCV function what to return.
You are indexing frame by width first, when you should use height as the first index.

Why is OpenCV creating weird rectangles?

I am making a software to track the changes in the areas of different bubbles over the span of a video. I am trying to use the OpenCV multitracker to track these circles, which were created by a Hough's Circle Transformation. My understanding is, I need to define a bounding box to pass to this multitracker tool. So I made the box with basic geometry, but it gave a weird result when I was displaying the box, through the code seen below:
import cv2
import numpy as np
import matplotlib.pyplot as plt
vidObj = cv2.VideoCapture('Data/video.mp4')
success = True
tracked = False
multiTracker = cv2.MultiTracker_create()
def Find_Circles(img):
# Convert to gray-scale
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# Blur the image to reduce noise
img_blur = cv2.bilateralFilter(gray, 7, 50, 50)
# Apply hough transform on the image
circles = cv2.HoughCircles(img_blur, cv2.HOUGH_GRADIENT, 1, 70, param1=110, param2=10, minRadius=20, maxRadius=100)
return circles
def Draw_Circles(img,circles,tracked):
if circles is not None:
circles = np.uint16(np.around(circles))
if tracked is False:
for i in circles[0, :]:
cv2.circle(img, (i[0], i[1]), i[2], (0, 255, 0),2)
cv2.rectangle(img, (i[0]-i[2], i[1]-i[2]), (i[0]+i[2], i[1]+i[2]), (0, 255, 0),2)
else:
(success, circles) = multiTracker.update(img)
print(circles)
while success:
success, img = vidObj.read()
if img is not None:
Draw_Circles(img, Find_Circles(img),tracked)
tracked = True
imgplot = plt.imshow(img,cmap='gray')
plt.show()
else:
success = False
Here is the result. Any ideas why? Can provide video in mp4 format if needed.
This line causes error:
circles = np.uint16(np.around(circles))
Values in "circles" can be negative, so you can not use uint16 (unsigned integer) type. Use int16:
circles = np.int16(np.around(circles))

Find all circles in image

I am new to python & image processing. I am working on a hobby project in which I want to find ALL circles in image and then figure out which one have cross ('X') marked inside it. I have put some code together to find circles so far (below). It works on one image but fails to recognize all circles on another one. Please guide me how i can improve performance of find_circles algorithm.
Test Image:
Result Image:
import cv2
import cv
import numpy as np
import operator
from PIL import Image
def find_circles(img):
im_gray = cv2.imread(img, cv2.CV_LOAD_IMAGE_GRAYSCALE)
(thresh, im_bw) = cv2.threshold(im_gray, 128, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)
img_bw = cv2.threshold(im_gray, thresh, 255, cv2.THRESH_BINARY)[1]
cv2.imwrite('img_bw.png',img_bw)
rows, cols =img_bw.shape
circles = cv2.HoughCircles(img_bw,cv.CV_HOUGH_GRADIENT,1,rows/32, param1=100,param2=40,minRadius=0,maxRadius=100)
circles = np.uint16(np.around(circles))
return circles
def draw_circles(img, circles):
img = cv2.imread(img,0)
cimg = cv2.cvtColor(img,cv2.COLOR_GRAY2BGR)
for i in circles[0,:]:
# draw the outer circle
cv2.circle(cimg,(i[0],i[1]),i[2],(0,255,0),2)
# draw the center of the circle
cv2.circle(cimg,(i[0],i[1]),2,(0,0,255),3)
cv2.putText(cimg,str(i[0])+str(',')+str(i[1]), (i[0],i[1]), cv2.FONT_HERSHEY_SIMPLEX, 0.4, 255)
return cimg
def main():
img = "query_circle9.png"
circles = find_circles(img)
img_circle = draw_circles(img,circles)
cv2.imwrite('cricle.png',img_circle)
if __name__=='__main__':
main()
#!/usr/bin/env python
import cv2
def draw_circles(img, circles):
# img = cv2.imread(img,0)
cimg = cv2.cvtColor(img,cv2.COLOR_GRAY2BGR)
for i in circles[0,:]:
# draw the outer circle
cv2.circle(cimg,(i[0],i[1]),i[2],(0,255,0),2)
# draw the center of the circle
cv2.circle(cimg,(i[0],i[1]),2,(0,0,255),3)
cv2.putText(cimg,str(i[0])+str(',')+str(i[1]), (i[0],i[1]), cv2.FONT_HERSHEY_SIMPLEX, 0.4, 255)
return cimg
def detect_circles(image_path):
gray = cv2.imread(image_path, cv2.CV_LOAD_IMAGE_GRAYSCALE)
gray_blur = cv2.medianBlur(gray, 13) # Remove noise before laplacian
gray_lap = cv2.Laplacian(gray_blur, cv2.CV_8UC1, ksize=5)
dilate_lap = cv2.dilate(gray_lap, (3, 3)) # Fill in gaps from blurring. This helps to detect circles with broken edges.
# Furture remove noise introduced by laplacian. This removes false pos in space between the two groups of circles.
lap_blur = cv2.bilateralFilter(dilate_lap, 5, 9, 9)
# Fix the resolution to 16. This helps it find more circles. Also, set distance between circles to 55 by measuring dist in image.
# Minimum radius and max radius are also set by examining the image.
circles = cv2.HoughCircles(lap_blur, cv2.cv.CV_HOUGH_GRADIENT, 16, 55, param2=450, minRadius=20, maxRadius=40)
cimg = draw_circles(gray, circles)
print("{} circles detected.".format(circles[0].shape[0]))
# There are some false positives left in the regions containing the numbers.
# They can be filtered out based on their y-coordinates if your images are aligned to a canonical axis.
# I'll leave that to you.
return cimg
The result:
cimg = detect_circles("circles.png")
There are some left-over false detection. If your images are aligned, then you can filter these false positives based on their y-coordinates. I'll leave that to you.

Categories