Python/OpenCV - Colored Droplets Recognition and Tracking - python

I'm a semi-noob in Video Analysis.
I have a Petri dish with some colored droplets inside and I must detect them, and keep trace of their position,area and color.
I want first to detect my Petri dish (maybe using HoughCircles) and define a ROI on which work later.
The problem is that mi dish detection is very "noisy": the program detects many circles (and I only need the one corresponding to the dish) and it never detects the right one.
Here is my code:
import cv2
import numpy as np
def main():
cap=cv2.VideoCapture("dropletsS.wmv")
cv2.namedWindow("prova")
while(1):
ret, RGBframe = cap.read()
grayFrame = cv2.cvtColor(RGBframe,cv2.COLOR_BGR2GRAY)
grayFrame=cv2.medianBlur(grayFrame,7)
circles=cv2.HoughCircles(grayFrame,cv2.HOUGH_GRADIENT ,50,50)
for c in circles[0,:]:
cv2.circle(RGBframe,(c[0],c[1]),c[2],(0,255,0),2)
cv2.imshow("prova", RGBframe)
cv2.imshow("grigio", grayFrame)
cv2.waitKey(10)
if __name__ == "__main__":
main()
And here is the result.
Do someone have some suggestions? Suggestions on the way I can later identify and track droplets are welcome too.
Thanks in advance!

Its kind of hard to come up with a solution without having much of an idea on how the dish actually looks like, but I'll try helping you anyways.
If the problem is what I think it is, then you can probably open and dilate your image to join all the discontinuous blobs.
Do the following before you apply Hough Transform:
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5,5)) #declare outside while
grayFrame = cv2.morphologyEx(grayFrame, cv2.MORPH_OPEN, kernel)
grayFrame = cv2.dilate(grayFrame, kernel, iterations = 2)
Let me know if the output is output image that you desire. Also play around with the parameters to get the required result. You can change the dimensions of the MORPH_ELLIPSE and also the number of iterations. Increasing any of them would increase the degree of dilation, thus more of the blobs would join up and vice versa.

Related

Python OpenCV: inRange() stopped working without change

I am currently doing real time object detection of an orange ball with Raspberry Pi 3 Model B. The code below is supposed to take a frame, then with the cv2.inRange() function, filter out the image using RGB (BGR). Then I apply dialation and erosion to remove noise. Then I find the contours and draw them. This code worked until now. However when I ran it today without changing it, I got the folowing error:
Traceback (most recent call last):
File "/home/pi/Desktop/maincode.py", line 12, in <module>
mask = cv2.inRange(frame, lower, upper)
error: /build/opencv-ISmtkH/opencv-2.4.9.1+dfsg/modules/core/src/arithm.cpp:2701: error: (-209) The lower bounary is neither an array of the same size and same type as src, nor a scalar in function inRange
Any help would be really awesome, because I was new to openCV and spent a lot of time proggraming this, and I have a competetion of robotics in 5 days.
Thank you in advance
import cv2
import cv2.cv as cv
import numpy as np
capture = cv2.VideoCapture(0)
while capture.isOpened:
ret, frame = capture.read()
im = frame
lower = np.array([0, 100 ,150], dtype = 'uint8')
upper = np.array([10,180,255], dtype = 'uint8')
mask = cv2.inRange(frame, lower, upper)
eroded = cv2.erode(mask, np.ones((7, 7)))
dilated = cv2.dilate(eroded, np.ones((7, 7)))
contours, hierarchy = cv2.findContours(dilated,cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE)
cv2.drawContours(im,contours,-1,(0,255,0),3)
cv2.imshow('colors',im)
cv2.waitKey(1)
The error you receive almost certainly means you have an empty image (or you mix up the sizes of your input image).
Webcam captures in OpenCV often start with one or a couple of black/emtpy images (crappy drivers). Since it goes too fast, that's why you don't notice this. However, this will have an influence on your application if you want to process the image. Therefore, I recommend you to check the image before proceeding with the calculations on them. Just add this after your capture.read() line:
if ret == True:
Note: make sure (by printing in the console or something) that this only happens when you start capturing. If this happens regularly (empty frames from your webcam), maybe there's something else wrong (or maybe with your webcam). Also check it on another computer.

Blob detection using OpenCV

I am trying to do some white blob detection using OpenCV. But my script failed to detect the big white block which is my goal while some small blobs are detected. I am new to OpenCV, and am i doing something wrong when using simpleblobdetection in OpenCV? [Solved partially, please read below]
And here is the script:
#!/usr/bin/python
# Standard imports
import cv2
import numpy as np;
from matplotlib import pyplot as plt
# Read image
im = cv2.imread('whiteborder.jpg', cv2.IMREAD_GRAYSCALE)
imfiltered = cv2.inRange(im,255,255)
#OPENING
kernel = np.ones((5,5))
opening = cv2.morphologyEx(imfiltered,cv2.MORPH_OPEN,kernel)
#write out the filtered image
cv2.imwrite('colorfiltered.jpg',opening)
# Setup SimpleBlobDetector parameters.
params = cv2.SimpleBlobDetector_Params()
params.blobColor= 255
params.filterByColor = True
# Create a detector with the parameters
ver = (cv2.__version__).split('.')
if int(ver[0]) < 3 :
detector = cv2.SimpleBlobDetector(params)
else :
detector = cv2.SimpleBlobDetector_create(params)
# Detect blobs.
keypoints = detector.detect(opening)
# Draw detected blobs as green circles.
# cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS ensures
# the size of the circle corresponds to the size of blob
print str(keypoints)
im_with_keypoints = cv2.drawKeypoints(opening, keypoints, np.array([]), (0,255,0), cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS)
# Show blobs
##cv2.imshow("Keypoints", im_with_keypoints)
cv2.imwrite('Keypoints.jpg',im_with_keypoints)
cv2.waitKey(0)
EDIT:
By adding a bigger value of area maximum value, i am able to identify a big blob but my end goal is to identify the big white rectangle exist or not. And the white blob detection i did returns not only the rectangle but also the surrounding areas as well. [This part solved]
EDIT 2:
Based on the answer from #PSchn, i update my code to apply the logic, first set the color filter to only get the white pixels and then remove the noise point using opening. It works for the sample data and i can successfully get the keypoint after blob detection.
If you just want to detect the white rectangle you can try to set a higher threshold, e.g. 253, erase small object with an opening and take the biggest blob. I first smoothed your image, then thresholding it:
and the opening:
now you just have to use findContours and take the boundingRect. If your rectangle is always that white it should work. If you get lower then 251 with your threshold the other small blobs will appear and your region merges with them, like here:
Then you could still do an opening several times and you get this:
But i dont think that it is the fastest idea ;)
You could try setting params.maxArea to something obnoxiously large (somewhere in the tens of thousands): the default may be something lower than the area of the rectangle you're trying to detect. Also, I don't know how true this is or not, but I've heard that detection by color is bugged with a logic error, so it may be worth a try disabling it just in case that is causing problems (this has probably been fixed in later versions, but it could still be worth a try)

Deciding parameters in HoughCircles method of OpenCV

I am trying to detect circles in an image, and am using OpenCV Python for the same. I am facing problems when I use the HoughCircles method. I am using the following custom image , but my code is unable to detect both circles.
I tried the following implementation
circles = cv2.HoughCircles(thresh1,cv2.cv.CV_HOUGH_GRADIENT,2,1,param1=100,param2=100,minRadius=0,maxRadius=1000)
and this is only properly detecting the bigger circle in the image. I'm pretty sure if I tinker around with the parameters , I might hit upon a combination that works, but is there any way I can calculate, or figure out the parameters, given an image?
EDIT
Here is the entire code that I have written:
import cv2
import numpy as np
def show(s , i):
cv2.imshow(s , i)
cv2.waitKey(0)
cv2.destroyAllWindows()
img = cv2.imread('ball2.jpg')
show("img" , img)
img = cv2.medianBlur(img,5)
cimg = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
ret,thresh1 = cv2.threshold(cimg,10,255,cv2.THRESH_BINARY)
show('thresh' , thresh1)
circles = cv2.HoughCircles(thresh1,cv2.cv.CV_HOUGH_GRADIENT,2,1,param1=100,param2=100,minRadius=0,maxRadius=1000)
print circles
circles = np.uint16(np.around(circles))
for i in circles[0,:]:
# draw the outer circle
cv2.circle(thresh1,(i[0],i[1]),i[2],(100,150,120),2)
# draw the center of the circle
cv2.circle(thresh1,(i[0],i[1]),2,(0,0,0),3)
cv2.imshow('detected circles',thresh1)
cv2.waitKey(0)
cv2.destroyAllWindows()
Have you seen http://www.pyimagesearch.com/2014/07/21/detecting-circles-images-using-opencv-hough-circles/?
Author there suggests to tinker with minDist, as the most important parameter, but you have that set to 1, so rather we should expect false positive than not found circles.
I suggest also to increase param1 to 200 to set upper threshold for the internal Canny edge detector for increased detection.
Also I found some people reported weird anomaly, where increasing maxradius resulted in getting fewer circles. Sometimes it's good idea to leave optional parameters as default (value 0).
From my experience with openCV it often ends up with tinkering parameters to get best results.

Extract foreground from individual frames using opencv for python

The problem
I'm working with a camera that posts a snapshot to the web every 5 seconds or so. The camera is monitoring a line of people. I'd like my script to be able to tell me how long the line of people is.
What I've tried
At first, I thought I could do this using BackgroundSubtractorMOG, but this is just producing a black image. Here's my code for that, modified to use an image instead of a video capture:
import numpy as np
import cv2
frame = cv2.imread('sample.jpg')
fgbg = cv2.BackgroundSubtractorMOG()
fgmask = fgbg.apply(frame)
cv2.imshow('frame', fgmask)
cv2.waitKey()
Next, I looked at foreground extraction on an image, but this is interactive and doesn't suit my use case of needing the script to tell me how long the line of people is.
I also tried to use peopledetect.py, but since the image of the line is from an elevated position, that script doesn't detect any people.
I'm brand new to opencv, so any help is greatly appreciated. I can supply any additional details upon request.
Note:
I'm not so much looking for someone to solve the overall problem, as I am just trying to figure out a way to separate out the people from the background. However, I am open to approaching the problem a different way if you think you have a better solution.
EDIT: Here's a sample image as requested:
I figured it out! #QED helped me get there. Basically, you can't do this with just one image. You need AT LEAST 2 frames to compare so the algorithm can tell what's different (foreground) and what's the same (background). So I took 2 frames and looped through them to "train" the algorithm. Here's my code:
import numpy as np
import cv2
i = 1
while(1):
fgbg = cv2.BackgroundSubtractorMOG()
while(i < 3):
print 'img' + `i` + '.jpg'
frame = cv2.imread('img' + `i` + '.jpg')
fgmask = fgbg.apply(frame)
cv2.imshow('frame', fgmask)
i += 1
k = cv2.waitKey(30) & 0xff
if k == 27:
break
cv2.destroyAllWindows()
And here's the result from 2 consecutive images!

Extract external contour or silhouette of image in Python

I want to extract the silhouette of an image, and I'm trying to do it using the contour function of MatplotLib. This is my code:
from PIL import Image
from pylab import *
# read image to array
im = array(Image.open('HOJA.jpg').convert('L'))
# create a new figure
figure()
# show contours with origin upper left corner
contour(im, origin='image')
axis('equal')
show()
This is my original image:
And this is my result:
But I just want to show the external contour, the silhouette. Just the read lines in this example.
How can I do it? I read the documentation of the contour function, but I can't get what I want.
If you know a better way to do this in Python, please tell me! (MatplotLib, OpenCV, etc.)
If you want to stick with your contour approach you can simply add a levels argument with a value 'thresholding' the image between the white background and the leaf.
You could use the histogram to find an appropriate value. But in this case any value slightly lower than 255 will do.
So:
contour(im, levels=[245], colors='black', origin='image')
Make sure you checkout Scikit-Image if you want to do some serious image processing. It contains several edge detection algoritms etc.
http://scikit-image.org/docs/dev/auto_examples/
For those who want the OpenCV solution, here it is:
ret,thresh = cv2.threshold(image,245,255,0)
contours, hierarchy = cv2.findContours(thresh,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_NONE)
tam = 0
for contorno in contours:
if len(contorno) > tam:
contornoGrande = contorno
tam = len(contorno)
cv2.drawContours(image,contornoGrande.astype('int'),-1,(0,255,0),2)
cv2.imshow('My image',image)
cv2.waitKey()
cv2.destroyAllWindows()
In this example, I only draw the biggest contour. Remember that 'image' must be a single-channel array.
You should change the parameters of the threshold function, the findContours function and the drawContours function to get what you want.
threshold Documentation
findContours Documentation
drawContours Documentation
I do the conversion to 'int' in the drawContours function because there is a bug in the Open CV 2.4.3 version, and if you don't do this conversion, the program breaks.
This is the bug.
I would recommand using OpenCV for performance.
It has a findContour functions accessible from python using the cv2 binding.
This function can be set to return only the external contour.
You will have to threshold your image as well.

Categories