How to fix the Opencv ValueError in python? - python

How can I fix it?
import numpy as np
import cv2 as cv
im = cv.imread('good.jpg')
imgray = cv.cvtColor(im, cv.COLOR_BGR2GRAY)
ret, thresh = cv.threshold(imgray, 127, 255, 0)
im2, contours, hierarchy = cv.findContours(thresh, cv.RETR_TREE, cv.CHAIN_APPROX_SIMPLE)
Error
ValueError: not enough values to unpack (expected 3, got 2)

The problem is in your last line. To better understand it, here's a quote taken from the documentation:
See, there are three arguments in cv2.findContours() function, first one is source image, second is contour retrieval mode, third is contour approximation method. And it outputs the contours and hierarchy. Contours is a Python list of all the contours in the image. Each individual contour is a Numpy array of (x,y) coordinates of boundary points of the object.
So clearly cv2.findContours() only return two arguments (not 3). To fix it just change the last line to:
contours, hierarchy = cv.findContours(thresh, cv.RETR_TREE, cv.CHAIN_APPROX_SIMPLE)

Related

cv2.findContours function is not working in both versions

I am new to computer vision and haven't really went through any tutorials on thresholding or blurring or other filters.
I am using the below two piece of codes which finds out the contours in an image. On one hand the method is working but on the other it is not. I would need help in understanding the reason this is happening so as to convince myself what is happening in the background.
Working code snippet:
img=cv2.imread('path.jpg')
imgBlurred = cv2.GaussianBlur(img, (5, 5), 0)
gray = cv2.cvtColor(imgBlurred, cv2.COLOR_BGR2GRAY)
sobelx = cv2.Sobel(gray, cv2.CV_8U, 1, 0, ksize=3)
cv2.imshow("Sobel",sobelx)
cv2.waitKey(0)
ret2, threshold_img = cv2.threshold(sobelx, 120, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
im2, contours, hierarchy = cv2.findContours(threshold_img, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
Not working code snippet
# read image
src = cv2.imread(file_path, 1)
# show source image
cv2.imshow("Source", src)
# convert image to gray scale
gray = cv2.cvtColor(src, cv2.COLOR_BGR2GRAY)
# blur the image
blur = cv2.blur(gray, (3, 3))
# binary thresholding of the image
ret, thresh = cv2.threshold(blur, 200, 255, cv2.THRESH_BINARY)
# find contours
im2, contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
I would really appreciate if anyone can find out the reason for the wrong which is happening here.
The error that i am facing is:
Traceback (most recent call last): File "convexhull.py", line 27, in
im2, contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE) ValueError: not enough values to unpack
(expected 3, got 2)
Let me know if any other information is also required.
This is due to a change in openCV. Since version 4.0 findContours returns only 2 values: the contours and the hierarchy. Before, in version 3.x, it returned 3 values. You can use the documentation to compare the different versions.
The second code snippet should work when you change your code to:
# find contours
contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
Why the first snippet picks a different openCV version can't be determined from the information given.
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 that can be used later in the code.
Store the major version of OpenCV installed:
import cv2
major_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 major_version == '4':
contours, hierarchy = cv2.findContours(image_binary, cv2.RETR_TREE, cv.CHAIN_APPROX_SIMPLE)
elif major_version == '3':
image, contours, hierarchy = cv2.findContours(image_binary, cv2.RETR_TREE, cv.CHAIN_APPROX_SIMPLE)
The contours returned from the function in either scenarios will be stored in contours.

Drawing contours with bounded rectangles on existing image

My objective is to pick up an image, separate out the curves/contours that have a grayscale threshold below a local number (say 3), and then have rectangles around them, while writing this back onto the original image - as a way of detecting cracks on a grayscale image. Below is what I have come up with - by seeing tutorials online.
# import the necessary packages
import numpy as np
import cv2
# Load an color image in grayscale
img = cv2.imread('chest-ct-lungs.jpg',0)
ret,thresh = cv2.threshold(img,3,255,cv2.THRESH_BINARY_INV)
# Detect the contours in the image
image, contours =
cv2.findContours(thresh,cv2.RETR_TREE,cv2.CHAIN_APPROX_NONE)
# Draw all the contours
all_contour_img = cv2.drawContours(image, contours, -1, (0,255,0), 3)
cv2.imwrite('all_contour_img.png',all_contour_img)
# Use bounding rectangles
x,y,w,h = cv2.boundingRect(contours)
cv2.rectangle(all_contour_img,(x,y),(x+w,y+h),(0,255,0),2)
# Draw over original image
imwrite(uint8(double(img)+all_contour_img), 'output.png');
However, I am getting this as the output when I run it using python IDLE:
Traceback (most recent call last):
File "C:\Users\com\Desktop\python.py", line 17, in <module>
all_contour_img = cv2.drawContours(image, contours, -1, (0,255,0), 3)
TypeError: Expected cv::UMat for argument 'image'
Any inputs on where I am going wrong, as well as better practices of writing the above code - I'm a beginner.
I want this to happen :
Depending on the version of OpenCV you are using, cv2.findContours() will return a list of contours and some other stuff. All that you want is the list of contours. You can just ignore the other stuff and clean up your code by assigning those unused variables to an _.
cv2.findContours returns a LIST of contours. This is like a list of shapes. If you want to draw a bounding rectangle around each shape, you need to iterate through the list of contours.
# Import the necessary packages
import numpy as np
import cv2
# Load an color image in grayscale, threshold
img = cv2.imread('/home/stephen/Desktop/test.png')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
_, thresh = cv2.threshold(gray,3,255,cv2.THRESH_BINARY_INV)
# Detect the contours in the image
_, contours, _ = cv2.findContours(thresh,cv2.RETR_TREE,cv2.CHAIN_APPROX_NONE)
# Draw all the contours
img = cv2.drawContours(img, contours, -1, (0,255,0), 1)
# Iterate through all the contours
for contour in contours:
# Find bounding rectangles
x,y,w,h = cv2.boundingRect(contour)
# Draw the rectangle
cv2.rectangle(img,(x,y),(x+w,y+h),(255,255,0),1)
# Write the image
cv2.imwrite('/home/stephen/Desktop/lines.png', img);

FindContours of a single channel image in OpenCv (Python)

I've trouble using OpenCV's findContours(...) method to find the contours in a single channel image. The image is actually a numpy array with the shape (128, 128) and elements with real values between [0.0,1.0]. Initially the shape is (1,128,128,1) but I've used np.squeeze(...) to get rid of the first and last dimension. Keeping either of them doesn't solve my problem.
What I've tried:
image = np.squeeze(array) #using np.squeeze(array, [0]) won't help.
image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
contours, hierarchy = cv2.findContours(image, 1, 2)
The above code causes the following exception:
error: (-215) scn == 3 || scn == 4 in function cv::cvtColor
What I've also tried:
If I apply findContours(...) directly, so without using cvtColor(...), I get a different error:
error: (-210) [Start]FindContours supports only CV_8UC1 images when mode != CV_RETR_FLOODFILL otherwise supports CV_32SC1 images only in function cvStartFindContours_Impl
Some sources suggest to use a threshold to get an binary image which is required by findContours(...)[1]
ret, thresh = cv2.threshold(image, 1, 255, 0)
im2, contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
This won't help either and I receive the same exception complaining about the CV_8UC1 support.
The image is actually a numpy array with the shape (128, 128) and elements with real values between [0.0,1.0].
The error from cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) was due to the fact that you're trying to convert a single channel image from BGR (3 channels) to grayscale (1 channel). Your image is already grayscale, so this step is unnecessary.
The error from cv2.findContours was due to the wrong data type of the elements in the array. The documentation says the following about the input image:
Source, an 8-bit single-channel image. Non-zero pixels are treated as 1's. Zero pixels remain 0's, so the image is treated as binary . You can use compare, inRange, threshold , adaptiveThreshold, Canny, and others to create a binary image out of a grayscale or color one. If mode equals to RETR_CCOMP or RETR_FLOODFILL, the input can also be a 32-bit integer image of labels (CV_32SC1).
To fix this, you need to scale the values in your image to range [0.0,255.0], and then cast the result to np.uint8:
image_8bit = np.uint8(image * 255)
There are few other issue or quirks about the code in your question.
First of all, in one snippet cv2.findContours returns 2 values (OpenCV 2.x), and in the other it returns 3 values (OpenCV 3.x). Which version are you using?
Your first code sample contains the following:
contours, hierarchy = cv2.findContours(image, 1, 2)
Avoid using magic numbers. The 1 corresponds to cv2.RETR_LIST and the 2 corresponds to cv2.CHAIN_APPROX_SIMPLE. Since the RETR_LIST mode doesn't generate any hierarchy, you can ignored that return value:
contours, _ = cv2.findContours(binarized, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)
Another problem is most likely the fact that originally you didn't explicitly binarize the image (e.g. using cv2.threshold). While this won't result in exceptions, the result will probably not make much sense -- findContours divides the pixels into two groups -- zeros, and then everything non-zero. You will most likely want them partitioned differently.
threshold_level = 127 # Set as you need...
_, binarized = cv2.threshold(image_8bit, threshold_level, 255, cv2.THRESH_BINARY)
Sample script (OpenCV 3.x):
import numpy as np
import cv2
# Generate random image matching your description:
# shape is (128,128), values are real numbers in range [0,1]
image = np.random.uniform(0, np.nextafter(1,2), (128,128))
# Scale and convert data type
image_8bit = np.uint8(image * 255)
threshold_level = 127 # Set as you need...
_, binarized = cv2.threshold(image_8bit, threshold_level, 255, cv2.THRESH_BINARY)
_, contours, hierarchy = cv2.findContours(binarized, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)
# ... processing the contours, etc.
Try this:
cv2.threshold(image, 1, 255, cv2.THRESH_BINARY,image)
im2, contours, hierarchy = cv2.findContours(image, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
The cv2.findContours function accept thresholded images. The error is given because your input image is a grayscale image.

Copy area inside contours to another image

I have the following code in Python to find contours in my image:
import cv2
im = cv2.imread('test.jpg')
imgray = cv2.cvtColor(im, cv2.COLOR_BGR2GRAY)
ret, thresh = cv2.threshold(imgray, 127, 255, 0)
im2, contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
Now I want to copy the area inside the first contour to another image, but I can't find any tutorial or example code that shows how to do that.
Here's a fully working example. It's a bit overkill in that it outputs all the contours but I think you may find a way to tweak it to your liking. Also not sure what you mean by copying, so I'll assume you just want the contours outputted to a file.
We will start with an image like so (in this case you will notice I don't need to threshold the image). The script below can be broken down into 6 major steps:
Canny filter to find the edges
cv2.findContours to keep track of our contours, note that we only need outer contours hence the cv2.RETR_EXTERNAL flag.
cv2.drawContours draws the shapes of each contour to our image
Loop through all contours and put bounding boxes around them.
Use the x,y,w,h information of our boxes to help us crop every contour
Write the cropped image to a file.
import cv2
image = cv2.imread('images/blobs1.png')
edged = cv2.Canny(image, 175, 200)
contours, hierarchy = cv2.findContours(edged, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cv2.drawContours(image, contours, -1, (0,255,0), 3)
cv2.imshow("Show contour", image)
cv2.waitKey(0)
cv2.destroyAllWindows()
for i,c in enumerate(contours):
rect = cv2.boundingRect(c)
x,y,w,h = rect
box = cv2.rectangle(image, (x,y), (x+w,y+h), (0,0,255), 2)
cropped = image[y: y+h, x: x+w]
cv2.imshow("Show Boxes", cropped)
cv2.waitKey(0)
cv2.destroyAllWindows()
cv2.imwrite("blobby"+str(i)+".png", cropped)
cv2.imshow("Show Boxes", image)
cv2.waitKey(0)
cv2.destroyAllWindows()
Not familiar with cv2.findContours but I imagine that a contour is represented by an array of points with row/column (x/y values. If this is the case and the contour is a single pixel in width then there should be two points for every row - one each on the left and right extremes of the contour.
For each row in the contour
*select* all the points in the image that are between the two contour points for that row
save those points to a new array.
As #DanMaĆĄek points out, if the points in the contour array describe a simple shape with with only the ends, corners or breakpoints represented then you would need to fill in the gaps to use the method above.
Also if the contour shape is something like a star you would need to figure out a different method for determining if an image point is inside of the contour. The method I posted is a bit naive - but might be a good starting point. For a convoluted shape like a star there might be multiple points per row of the contour but it seems like the points would come in pairs and the points you are interested in would be between the pairs.
.

opencv find and save inner contours knowing the largest one

How can I plot/save the inner contours of an image using python opencv?
I know how can I get the largest contour, I want to save it and the inside holes, which are contours as well.
Original Image:
import numpy as np
import cv2
from matplotlib import pyplot as plt
rgb = cv2.imread('MIL_NPGERBV2.png')
grayImg = cv2.imread('MIL_NPGERBV2.png', cv2.CV_LOAD_IMAGE_GRAYSCALE)
#to apply properly contour algorithm we need convert to binary
(thresh, bwImage) = cv2.threshold(image, 128, 255, cv2.THRESH_BINARY_INV | cv2.THRESH_OTSU)
img1 = rgb.copy()
img2 = rgb.copy()
contours, hierarchy = cv2.findContours(bwImage,cv2.RETR_TREE,
cv2.CHAIN_APPROX_SIMPLE)
#show all contours
cv2.drawContours(img1, contours, -1, (0,255,0), 2)
out = np.hstack([img1])
cv2.imshow('Output', out)
cv2.waitKey(0)
cv2.destroyAllWindows()
OpenCV's findContours method can give you the internal contours if asked politely.
It's one of the mode options of cv2.findContours() :
CV_RETR_CCOMP retrieves all of the contours and organizes them into a
two-level hierarchy. At the top level, there are external boundaries
of the components. At the second level, there are boundaries of the
holes. If there is another contour inside a hole of a connected
component, it is still put at the top level.
So asking a nice
myContours = cv2.findContours(myImg,cv2.RETR_CCOMP,cv2.CHAIN_APPROX_SIMPLE)
should give you a sort of nested object array, with the first item giving the useless outlines and the rest with the insides.
I'll try to update the answer to include a MCVE later on (but only if you update the question to include one too ^^)

Categories