cv2.findContours() succeeds and fails on identical arrays - python

I've ran into very strange behavior with cv2.findContours where it both fails and succeeds executing code on two arrays which are identical.
First, I do some arbitrary image processing on an image and then receive my output, which is in grayscale. We assign the output to the variable estimate. estimate.shape = (512, 640), as it is a grayscale image. I then proceed to save this image to disk with cv2.imwrite('estimate.png', estimate), and this is where my code stars misbehaving.
First of all, I try to read the image from disk and process it using cv2.findContours() according to the documentation on OpenCV. The code looks as follows,and it executes successfully:
im = cv2.imread('estimate.png')
imgray = cv2.cvtColor(im, cv2.COLOR_BGR2GRAY)
ret, thresh = cv2.threshold(imgray, 127, 255, 0)
contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
This is working as expected. But now, let's try cv2.findContours() directly on the variable estimate, without needlessly saving to disk and reading from it:
ret, thresh = cv2.threshold(estimate, 127, 255, 0)
contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
>>> error: OpenCV(4.6.0) D:\a\opencv-python\opencv-python\opencv\modules\imgproc\src\thresh.cpp:1659: error: (-210:Unsupported format or combination of formats) in function 'cv::threshold'
Okay, the natural assumption here is that imgray is different from estimate. Let us check the data:
type(imgray)
>>> numpy.ndarray
type(estimate)
>>> numpy.ndarray
imgray.shape
>>> (512, 640)
estimate.shape
>>> (512, 640)
np.all(estimate == imgray)
>>> True
Okay, so the arrays are identical in shape and values. What is happening here? We're applying cv2.findContours() to the exact same numpy.ndarray. It fails when the array is created directly, and it succeeds if it is created via cv2.imread?
I'm using opencv-python=4.6.0.66 and python=3.9.12

Finally solved this. Turns out, despite looking the same, the problem was in the actual types of arrays.
cv2.findContours() takes an unsigned 8-bit integer array. Therefore, estimate needs to be converted: estimate = estimate.astype(np.uint8).
This can be checked using estimate.dtype and imgray.dtype.

Related

Error: (-215:Assertion failed) npoints > 0 while working with contours using OpenCV

When I run this code:
import cv2
image = cv2.imread('screenshoot10.jpg')
cv2.imshow('input image', image)
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
edged = cv2.Canny(gray, 30, 200)
cv2.imshow('canny edges', edged)
_, contours = cv2.findContours(edged, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
cv2.imshow('canny edges after contouring', edged)
print(contours)
print('Numbers of contours found=', len(contours))
cv2.drawContours(image, contours, -1, (0, 255, 0), 3)
cv2.imshow('contours', image)
cv2.waitKey(0)
cv2.destroyAllWindows()
I am getting this error:
OpenCV(4.1.1)
C:\projects\opencv-python\opencv\modules\imgproc\src\drawing.cpp:2509:
error: (-215:Assertion failed) npoints > 0 in function
'cv::drawContours'
What am I doing wrong?
According to the documentation for findContours, the method returns (contours, hierarchy), so I think the code should be:
contours, _ = cv2.findContours(edged,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_NONE)
instead of
_, contours = cv2.findContours(edged,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_NONE)
Depending on the OpenCV version, cv2.findContours() has varying return signatures. In v3.4.X, three items are returned.
image, contours, hierarchy = cv2.findContours(image, mode, method[, contours[, hierarchy[, offset]]])
In v2.X and v4.1.X, two items are returned.
contours, hierarchy = cv2.findContours(image, mode, method[, contours[, hierarchy[, offset]]])
You can easily obtain the contours regardless of the version like this:
cnts = cv2.findContours(image, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
for c in cnts:
...
Since the last two values are always the same, we can further condense it into a single line using [-2:] to extract the contours from the tuple returned by cv2.findContours()
cnts, _ = cv2.findContours(image, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)[-2:]
Because this question shows up on top when looking for Error: (-215:Assertion failed) npoints > 0 while working with contours using OpenCV, I want to point out another possible reason as to why one can get this error:
I was using the BoxPoints function after doing minAreaRect to get a rotated rectangle around a contour. I hadn't done np.int0 to its output (to convert the returned array to integer values) before passing it to drawContours. This fixed it:
rect = cv2.minAreaRect(cnt)
box = cv2.cv.BoxPoints(rect)
box = np.int0(box) # convert to integer values
cv2.drawContours(im,[box],0,(0,0,255),2)
All the solutions provided here are either fixed for a particular version or they do not store all the values returned from cv2.findContours(). You can store all the values returned from every tuple of the function independent of the version of cv2.
The following snippet will work irrespective of the OpenCV version installed in your system/environment and will also store all the tuples in individual variables.
First, get the version of OpenCV installed (we don't want the entire version just the major number either 3 or 4) :
import cv2
version = cv2.__version__[0]
Based on the version either of the following two statements will be executed and the corresponding variables will be populated:
if version == '4':
contours, hierarchy = cv2.findContours(binary_image, cv2.RETR_TREE, cv.CHAIN_APPROX_SIMPLE)
elif version == '3':
img, contours, hierarchy = cv2.findContours(binary_image, cv2.RETR_TREE, cv.CHAIN_APPROX_SIMPLE)
The contours returned from the function in either scenarios will be stored in contours.
Note: the above snippet is written assuming either version 3 or 4 of OpenCV is installed. For older versions, please refer to the documentation or update to the latest.
If you are using OpenCV version 4, and wondering what the first variable returned by cv2.findContours() of version 3 is; its just the same as the input image (in this case binary_image).

FindContours support only 8uC1 and 32sC1 images

i hava problem in fire detection
my code is :
ret, frame = cap.read()
lab_image = cv2.cvtColor(frame, cv2.COLOR_BGR2LAB)
L , a , b = cv2.split(lab_image)
ret,thresh_L = cv2.threshold(L,70,255,cv2.THRESH_BINARY_INV+cv2.THRESH_OTSU)
ret,thresh_a = cv2.threshold(a,70,255,cv2.THRESH_BINARY_INV+cv2.THRESH_OTSU)
ret,thresh_b = cv2.threshold(b,70,255,cv2.THRESH_BINARY_INV+cv2.THRESH_OTSU)
thresh_image = cv2.merge((thresh_L, thresh_a, thresh_b))
dilation = cv2.dilate(thresh_image, None, iterations=2)
gray = cv2.cvtColor(thresh_image,cv2.COLOR_
(cnts, _) = cv2.findContours(dilation.copy(), cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)
for c in cnts:
if cv2.contourArea(c) < args["min_area"]:
continue
(x,y,w,h) = cv2.boundingRecy(c)
cv2.rectangle(frame,(x,y),(x+w, y+h), (0,255,0), 2)
cv2.imshow('frame1',frame)
and when i run this program , see this error
FindContours support only 8uC1 and 32sC1 images in function cvStartFindContours
please help me .
tnx
img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
Use this line on your image to convert it from BGR to grayscale (8UC1) format before finding contours. FindContours function only supports a grayscale image format.
In my solution I had to convert the dtype into uint8.
Yes, my image was binary image(single channel), however in my code somehow the thresh_image was changed into float32 data type. But cv2.findContours() cannot handle float32.
So I had to explicitly convert float32 --> uint8.
thresh_image = thresh_image.astype(np.uint8)
For completion, the 8UC1 format is 8 byte, unsigned, single channel.
In addition to cv2 grayscale, single-channel uint8 format will also be valid, in case anyone is building the image outside of cv2 functions and encounters this error.
The documention of findContours is clearly saying that it can afford to take single channel images as inputs(i.e 8uc1 and 32sc1) But you are sending 3 channel image.here is the documentation of findcontours http://docs.opencv.org/2.4/modules/imgproc/doc/structural_analysis_and_shape_descriptors.html#findcontours
I got this error and found the cause:
I created: gray_img = np.zeros((width,height,3),np.uint8)
In gray_img, depth = 3, it doesn't match findContours.
Then I recreated: gray_img = np.zeros((width,height),np.uint8)
it worked.

OpenCV findContours in python

I am working in python on openCV 3.0. In order to find the largest white pixel region, first of all thresholded gray image to binary image.
import cv2
import numpy as np
img = cv2.imread('graimage.png')
img = cv2.resize(img,(400,500))
gray = img.copy()
(thresh, im_bw) = cv2.threshold(img, 0, 255, cv2.THRESH_BINARY )
derp,contours,hierarchy = cv2.findContours(im_bw,cv2.RETR_LIST,cv2.CHAIN_APPROX_SIMPLE)
cnts = max(cnts, key=cv2.contourArea)
But it shows error as follows.
cv2.error: ..../opencv/modules/imgproc/src/contours.cpp:198: error: (-210) [Start]FindContours supports only CV_8UC1 images when mode != CV_RETR_FLOODFILL otherwise supports CV_32SC1 images only in function cvStartFindContours.
It looks like this was answered in the comments, but just to mark the question as answered:
CV_8UC1 means 8-bit pixels, unsigned, and only one channel, so grayscale. It looks like you're reading it in with 3 color channels, or CV_8UC3. You can check the image type by printing img.dtype and img.shape. The dtype should be uint8, and the shape should be (#, #), indicating two dimensions. I'm guessing you'll see that shape prints (#, #, 3) for your image as-is, indicating three color channels.
As #user3515225 said, you can fix that by reading the image in as grayscale using cv2.imread('img.png', cv2.IMREAD_GRAYSCALE). That assumes you have no use for color anywhere else, though. If you want a separate grayscale copy of the image, then replace gray = img.copy() with gray = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY) instead.

How to resize a contour in opencv 2.4.11 python? (Goal: Object extraction)

I'm super new to opencv, so pardon my ignorance...
Basically: I have an object of interest in my image. I would like to extract it.
My problems arise from downscaling the original image in order to facilitate processing. I have found a contour of the object on the smaller image. What I would really like to do is use the information about that contour to extract the object from the original full size image.
I can really only think of two ways to do this, but I have no idea which of these actually make sense in opencv:
Resize the contour. Then draw it on the original image.
(The one I'm using, without success...) Use the contour to create a mask. Resize the mask. Then add the mask to the original image.
I'm using No. 2, but I think there is a problem with the mask. Resized, it no longer contains the contour.
Here are the important parts of my current code:
image = cv2.imread(path)
orig = image.copy()
...
#resize the image
image = cv2.resize(image, dim, interpolation = cv2.INTER_AREA)
....
#convert to gray scale
...
#get contour
(cnts, _) = cv2.findContours(edged.copy(), cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
...
screenCnt = cv2.approxPolyDP(c, 0.01 * peri, True) #<--the contour
...
#create the mask
mask = np.ones(image.shape,np.uint8)*255
cv2.drawContours(mask,[screenCnt],0,(0,0,0),-1)
cv2.imshow("Small mask",mask) #<--- looks good
print(mask.shape) #<---returns a 3 element tuple
cv2.waitKey(0)
#now resize the mask
mask_big = cv2.resize(mask,(0,0),fx=ratio,fy=300)
cv2.imshow("Big mask",mask_big) #<--- ends up big, but all white (no contour)
cv2.waitKey(0)
I've been scouring the internet without luck, but I think I'm missing something fundamental here.
Thanks a million to all answerers!
As I see it is quite old question. However, I had the same one and was't able to find quick answer with sample of code. Here is the example (for opencv 3.4)
_, contours, _ = cv2.findContours(mask.astype(np.uint8), cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)
coef_y = img_orig.shape[0] / img_resized.shape[0]
coef_x = img_orig.shape[1] / img_resized.shape[1]
for contour in contours:
contour[:, :, 0] = contour[:, :, 0] * coef_x
contour[:, :, 1] = contour[:, :, 1] * coef_y
cv2.drawContours(img_orig, contour, -1, (0, 255, 0), 2)

viewing an image like a binary image with cv2 module in python (going in loop)

I try again to do better my question (http://stackoverflow.com/questions/14349550/convert-image-in-a-binary-image-with-cv2-module-in-python)
In this code (it's a little bit changed from the first ) I can easily work with my converted binary image , extracting contours, calculating areas.. but it's not again possible to execute this code line: cv2.imshow('gray_image',imgray).
it show me a total grey window and that's all. I have to quit python because goes in loop.
imgray = cv2.cvtColor(im,cv2.COLOR_BGR2GRAY)
ret,thresh = cv2.threshold(imgray,127,255,0)
contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE)
print('oggetti trovati')
print(len(contours))
cnt = contours[0]
print(len(cnt))
cv2.imshow('gray_image',imgray)
What can I do?
Here you need to add cv2.waitKey() after cv2.imshow('gray_image',imgray). This function is for handling any window event like creating or showing an image in OpenCV.

Categories

Resources