I am getting a Zero Division Error with some images (Even though a lot of them work just fine) :
Here's the code :
image = skimage.io.imread('test.png', False)
image_gray = skimage.io.imread('test.png', True)
blurred = cv2.GaussianBlur(img_as_ubyte(image_gray), (5, 5), 0)
thresh = threshold_li(blurred)
binary = blurred > thresh
binary_cv2 = img_as_ubyte(binary)
# find contours in the thresholded image
cnts = cv2.findContours(binary_cv2.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if imutils.is_cv2() else cnts[1]
# loop over the contours
for c in cnts:
# compute the center of the contour
M = cv2.moments(c)
cX = int(M["m10"] / M["m00"])
cY = int(M["m01"] / M["m00"])
# draw the contour and center of the shape on the image
cv2.drawContours(img_as_ubyte(image), [c], -1, (0, 255, 0), 2)
cv2.circle(img_as_ubyte(image), (cX, cY), 7, (255, 255, 255), -1)
cv2.putText(img_as_ubyte(image), "center", (cX - 20, cY - 20),
cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 255, 255), 2)
viewer = ImageViewer(image)
viewer.show()
Traceback (most recent call last):
File "Center.py", line 26, in <module>
cX = int(M["m10"] / M["m00"])
ZeroDivisionError: float division by zero
Thanks in advance!
Error is self-evident. You cannot divide a number by zero. If M["m00"] is zero, then you need to handle it appropriately. Check for 0 values in M["m00"].
if M["m00"] != 0:
cX = int(M["m10"] / M["m00"])
cY = int(M["m01"] / M["m00"])
else:
# set values as what you need in the situation
cX, cY = 0, 0
Probably you have bad contours
Note Since the contour moments are computed using Green formula, you
may get seemingly odd results for contours with self-intersections,
e.g. a zero area (m00) for butterfly-shaped contours.
Calculate the the center of mass, e.g.:
cx = 0
cy = 0
for p in contour:
cx += p[0][0]
cy += p[0][1]
cx = int(cx/len(contour))
cy = int(cy/len(contour))
or look into boundingRect() (3.4.3).
Related: Finding the center of a contour using opencv and visual c++
Related
i found this code on GitHub and it is a function that serves to find circles based on camera frames, yet my problem is that i would like to modify it so that i could choose which shape it has to find. I tried to do it for a square but unfortunately i couldn't do it.
Is there someone who could possibly help me?
Thanks a lot
This is the code of the function that find circles:
def find_circles(frame, mask):
contours = cv2.findContours(mask.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
contours = imutils.grab_contours(contours)
center = None
if len(contours) > 0:
c = max(contours, key=cv2.contourArea)
((x, y), radius) = cv2.minEnclosingCircle(c)
M = cv2.moments(c) #Finds center point
center = (int(M["m10"] / M["m00"]), int(M["m01"] / M["m00"]))
if radius > 10:
cv2.circle(frame, (int(x), int(y)), int(radius),
(0, 255, 255), 2)
cv2.circle(frame, center, 5, (0, 0, 0), -1)
return center
I want to find the principal axis of a blob which goes through the centroid of the blob. I was able to find the centroid of the blob, but how can I find the principal axis?
Here's what I have tried:
import cv2
import numpy as np
img = cv2.imread('skin6.jpg')
imgray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
ret, thresh2 = cv2.threshold(imgray, 155, 255, cv2.THRESH_BINARY_INV)
#find the maximum contour
contours, heir = cv2.findContours(thresh2, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)
c = max(contours, key = cv2.contourArea)
tmpArea = np.zeros(img.shape)
cv2.drawContours(tmpArea,[c],0,(255, 255, 255),cv2.FILLED)
#centroid
M = cv2.moments(c)
cx = int(M['m10']/M['m00'])
cy = int(M['m01']/M['m00'])
cv2.circle(tmpArea, (cx, cy), 5, (0, 0, 255), -1)
cv2.imshow("tmpArea", tmpArea)
cv2.waitKey(0)
These are the images that I used:
I'm expecting something like this. It should connect with the contour properly:
Expected
You can use cv2.fitEllipse on your detected contour. There's an OpenCV tutorial on that topic. You get the center of the fitted ellipse, the length of both axes (please have a detailed look at cv2.ellipse), and the rotation angle. From that information, it's just some math to get the principal axis through the center.
Here's your code with some modifications and additions:
import cv2
import numpy as np
# Images
img = cv2.imread('images/1KXQA.jpg')
img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
thresh2 = cv2.threshold(img_gray, 155, 255, cv2.THRESH_BINARY_INV)[1]
tmpArea = np.zeros(img.shape)
# Contours
contours = cv2.findContours(thresh2, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)[0]
c = max(contours, key=cv2.contourArea)
cv2.drawContours(tmpArea,[c],0,(255, 255, 255),cv2.FILLED)
# Centroid
M = cv2.moments(c)
cx = int(M['m10']/M['m00'])
cy = int(M['m01']/M['m00'])
cv2.circle(tmpArea, (cx, cy), 5, (0, 0, 255), -1)
# Ellipse
e = cv2.fitEllipse(c)
cv2.ellipse(tmpArea, e, (0, 255, 0), 2)
# Principal axis
x1 = int(np.round(cx + e[1][1] / 2 * np.cos((e[2] + 90) * np.pi / 180.0)))
y1 = int(np.round(cy + e[1][1] / 2 * np.sin((e[2] + 90) * np.pi / 180.0)))
x2 = int(np.round(cx + e[1][1] / 2 * np.cos((e[2] - 90) * np.pi / 180.0)))
y2 = int(np.round(cy + e[1][1] / 2 * np.sin((e[2] - 90) * np.pi / 180.0)))
cv2.line(tmpArea, (x1, y1), (x2, y2), (255, 255, 0), 2)
# Output
cv2.imshow('tmpArea', tmpArea)
cv2.waitKey(0)
The output looks like this:
----------------------------------------
System information
----------------------------------------
Platform: Windows-10-10.0.16299-SP0
Python: 3.8.5
NumPy: 1.19.5
OpenCV: 4.5.1
----------------------------------------
I want to detect the egg from a video feed and when I try to use threshold on it, it does not get the full egg.
I tried to apply different threshold steps from this https://opencv-python-tutroals.readthedocs.io/en/latest/py_tutorials/py_imgproc/py_thresholding/py_thresholding.html
Applying the threshold to different contours, below are the results
ret, img = cap.read()
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
ret,th1 = cv2.threshold(gray,127,255,cv2.THRESH_BINARY)
th2 = cv2.adaptiveThreshold(gray,255,cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY,11,2)
th3 = cv2.adaptiveThreshold(gray,255,cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY,11,2)
ret2,th4 = cv2.threshold(gray,0,255,cv2.THRESH_BINARY+cv2.THRESH_OTSU)
blur = cv2.GaussianBlur(gray,(5,5),0)
ret3,th5 = cv2.threshold(blur,0,255,cv2.THRESH_BINARY+cv2.THRESH_OTSU)
dummy,cnts,hier = cv2.findContours(th1,cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE)
for c in cnts:
M = cv2.moments(c)
if M["m00"] != 0:
cX = int(M["m10"] / M["m00"])
cY = int(M["m01"] / M["m00"])
else:
cX, cY = 0, 0
cv2.drawContours(img, [c], -1, (0, 255, 0), 2)
cv2.circle(img, (cX, cY), 2, (0, 0, 0), -1)
cv2.imshow("Global",th1)
cv2.imshow("Adaptive Mean",th2)
cv2.imshow("Adaptive Gaussian",th3)
cv2.imshow("Otsu's",th4)
cv2.imshow("Otsu's after Blur",th5)
https://imgur.com/a/qgLMkj6
UPDATE:
After using the answer from #Martin, I've come up with this
https://i.stack.imgur.com/2EQVM.jpg
Through getting the contour with the largest area. But there were other contours with a big area also. The next question is what can I do to filter out the other contour below? I'm thinking of identifying which contours has corners or not because the egg is elliptical. Other way is to crop out the image since the egg is only on the upper part of the image but I don't know how.
CODE:
dummy,cnts,hier = cv2.findContours(close.astype(np.uint8),cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)
#print (len(cnts))
for c in cnts:
M = cv2.moments(c)
area = cv2.contourArea(c)
print (area)
if area >46000:
if M["m00"] != 0:
cX = int(M["m10"] / M["m00"])
cY = int(M["m01"] / M["m00"])
else:
cX, cY = 0, 0
cv2.drawContours(img, [c], -1, (0, 255, 0), 2)
cv2.circle(img, (cX, cY), 2, (0, 0, 0), -1)
cv2.imshow("th5",img)
The reason why you are not getting full Egg is because the threshold is too high. You need to lower it a little bit
like:
limit = 100 # possible lower
ret,th1 = cv2.threshold(gray,limit,255,cv2.THRESH_BINARY)
Your problem is quite big though, because the background (object on which the egg is) has the same color as your egg. You may want to try edge detection instead of thresholding.
Check this out:
During playing with your image I was able to get the edges(only half way) :
code:
import cv2
import numpy as np
img = cv2.imread('eBxV8IA.jpg')
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
blur = cv2.GaussianBlur(gray,(15,15),0)
lap = cv2.Laplacian(blur,cv2.CV_64F)
blur = cv2.GaussianBlur(lap,(45,45),0)
cv2.imshow("Global",blur)
I was able to exactly detect the egg, but unfortunately also with a lot of noice
Code:
import cv2
import numpy as np
img = cv2.imread('eBxV8IA.jpg')
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
blur = cv2.GaussianBlur(gray,(15,15),0)
lap = cv2.Laplacian(blur,cv2.CV_64F)
blur = cv2.GaussianBlur(lap,(45,45),0)
blur[blur<0]=0
blur = 255.*blur/np.amax(blur)
dummy,cnts,hier = cv2.findContours(blur.astype(np.uint8),cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE)
for c in cnts:
M = cv2.moments(c)
if M["m00"] != 0:
cX = int(M["m10"] / M["m00"])
cY = int(M["m01"] / M["m00"])
else:
cX, cY = 0, 0
cv2.drawContours(img, [c], -1, (0, 255, 0), 2)
cv2.circle(img, (cX, cY), 2, (0, 0, 0), -1)
cv2.imshow("Global",img)
Im working in python and opencv library.
I can threshold a camera capture, find contours (more than one) and draw.
But I have a problem. I try to identify those contours with an unique id or tag. (for example: Id: 1 , Id:2) to track them.
I need this contours use a persistent id.
The goal is draw a line and count more than one contour and sometimes more than one near contours converts in a big one.
Note: Im working with a depth camera and my image its an array of depth.
add a piece of code.
Thanks in advance.
countours = cv2.findContours(mask, cv2.RETR_EXTERNAL,
cv2.CHAIN_APPROX_SIMPLE)[1]
# only proceed if at least one contour was found
if len(countours) > 0:
# find the largest contour in the mask, then use
# it to compute the minimum enclosing circle and
# centroid
for (i,c) in enumerate(countours):
((x, y), radius) = cv2.minEnclosingCircle(c)
M = cv2.moments(c)
if M["m00"] > 0:
center = (int(M["m10"] / M["m00"]), int(M["m01"] / M["m00"]))
centerString = str(center)
x = (int(M["m10"] / M["m00"]))
y = (int(M["m01"] / M["m00"]))
else:
center = int(x), int(y)
if radius > 10:
# draw the circle and centroid on the frame,
cv2.circle(frame, (int(x), int(y)), int(radius),
(0, 255, 255), 2)
cv2.circle(frame, center, 5, (0, 0, 255), -1)
# then update the ponter trail
if self.previous_position:
cv2.line(self.trail, self.previous_position, center,
(255, 255, 255), 2)
cv2.add(self.trail, frame, frame)
print center
self.previous_position = center
if len(countours) < 1:
center = 0
self.trail = numpy.zeros((self.cam_height, self.cam_width, 3),
numpy.uint8)
self.previous_position = None
Two options. First off, the contours are already in a Python list, so the indices of that list can be used to enumerate them. In fact you're already doing that in some sense with (i,c) in enumerate(countours). You can just use the index i to 'color' each contour with the value i drawing on a blank image, and then you'll know which contour is which just by examining the image. The other, probably better option IMO, is to use cv2.connectedComponents() to label the binary images instead of the contours of the binary image. Also pre-labeling you might try morphological operations to close up blobs.
EDIT: I follow your recommendation and I have new problems :)
Label is not persistent. When I move both contours, numbers of labels change.
Add some pictures and my code.
Hand label 0 and circle 1
Hand label 1 and circle 0
while True:
cnt += 1
if (cnt % 10) == 0:
now = time.time()
dt = now - last
fps = 10/dt
fps_smooth = (fps_smooth * smoothing) + (fps * (1.0-smoothing))
last = now
c = dev.color
cad = dev.cad
dev.wait_for_frames()
c = cv2.cvtColor(c, cv2.COLOR_RGB2GRAY)
depth = dev.depth * dev.depth_scale * 1000
print depth
#d = cv2.applyColorMap(depth.astype(np.uint8), cv2.COLORMAP_SUMMER)
print depth
res = np.float32(dev.depth)
depth = 255 * np.logical_and(depth >= 100, depth <= 500)
res2 = depth.astype(np.uint8)
kernel = np.ones((3, 3), np.uint8)
#res2 = cv2.blur(res2,(15,15))
res2 = cv2.erode(res2, kernel, iterations=4)
res2 = cv2.dilate(res2, kernel, iterations=8)
im_floodfill = res2.copy()
h, w = res2.shape[:2]
mask2 = np.zeros((h + 2, w + 2), np.uint8)
cv2.floodFill(im_floodfill, mask2, (0, 0), 255)
im_floodfill_inv = cv2.bitwise_not(im_floodfill)
im_out = res2 | im_floodfill_inv
im_out = cv2.blur(im_out,(5,5))
label = cv2.connectedComponentsWithStats(im_out)
n = label[0] - 1
cog = np.delete(label[3], 0, 0)
for i in xrange(n):
# im = cv2.circle(im,(int(cog[i][0]),int(cog[i][1])), 10, (0,0,255), -1)
cv2.putText(im_out, str(i), (int(cog[i][0]), int(cog[i][1])), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 255), 1)
cv2.imshow("Depth", res)
cv2.imshow("OUT", im_out)
cv2.imshow( "C", res2)
I try to calculate the center of mass of objects in a binary image with openCV and Python. I use cv2.findContours and cv2.moments but I always get a wrong x direction. It has always a positive offset of a few pixels and I don't get why. I think I did it exactly like in the openCV doc.
Here is a example. Green is the contour found with cv2.findContours. Red the calculated center of mass.
My code is:
import cv2
import numpy as np
img = cv2.imread('C:/Users/Arno/Documents/Masterarbeit/Matlab/rect2.png', 0)
cimg = cv2.cvtColor(img, cv2.COLOR_GRAY2BGR)
contours = cv2.findContours(img, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
cnt1 = contours[0]
cnt2 = contours[1]
cv2.drawContours(cimg, cnt2, -1, (0, 255, 0), 2)
M = cv2.moments(cnt1)
cx = int(M['m10']/M['m00'])
cy = int(M['m01']/M['m00'])
cv2.circle(cimg, (cx, cy), 1, (0, 0, 255), 2)
cv2.imshow('center of mass', cimg)