findContours output object

Hello,
I am currently building a solution to find a rectangle screen in a picture.

My workflow is:

  • Load the image via cv2.loadImage
  • Convert to grayscale
  • Downscale the image for faster performance
  • Run Canny Edge Detection on the image
  • Find the contours of the edged image
  • Loop over the individual contours, approximate the polygonal curve via approxPolyDP
  • Find the polygonal curve with 4 polygons, and save it.

I am using opencv-python with the version 4.5.3.

This is my code:

contours, hierarchy = cv2.findContours(img_condensed, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)
print(contours)
img_contour = img_grey
plt.title("All Contours")
cv2.drawContours(img_contour, contours, -1, (0, 255, 0), 3) 
plt.imshow(cv2.cvtColor(img_contour, cv2.COLOR_BGR2RGB))
plt.show()

# loop over contours
for (i, contour) in contours:
    # Get length of contour
    length = cv2.arcLength(contour, True)
    # Approximates a polygonal curve with specified precision; Returns array with polygons
    approx = cv2.approxPolyDP(contour, 0.015 * length, True)
    # Duplicate img because of destructive drawContours function
    img_contour = img_grey
    plt.title("Individual Contour")
    cv2.drawContours(img_contour, approx, -1, (0, 255, 0), 3) 
    plt.imshow(cv2.cvtColor(img_contour, cv2.COLOR_BGR2RGB))
    plt.show()
    # If the contour has 4 polygons, its our screen.
    if len(approx) == 4:
        # Save the screen
        screenCnt = approx
        # Stop searching
        break

My problem is:
In the for loop the contourvariable holds all contours, not an individual one.
Here are test images of the entire contour versus the individual contours in the loop:

The picture above is shown for every single iteration.
Somehow I can’t iterate over single contours, but it is taking all contours all the time.

I think this is a versioning problem, I tried imutils grabcontours function before, but I get the same result.

Can anyone help me?

no, problem is in your code

use your i index, not -1 to draw a single contour

also, filtering or sorting the contours list by contourArea() size, and keeping the largest only would be helpful here

Oh okay, I thought that the contour object holds a single contour, so If I display everything of a single contour, I will display a single contour.

So I changed my code, but it still has the same exact problem. When I try to

it produces an out of bounds error whenever it reaches i = 1
It seems like its outputting a single object, but that single object is not one single contour but rather all contours.

# Sorting the first 10 contours, from the largest to smallest contour Area
contours = sorted(contours, key = cv2.contourArea, reverse = False)[:10]

# loop over contours
for (i, contour) in enumerate(contours):
    # Get length of contour
    length = cv2.arcLength(contour, True)
    # Approximates a polygonal curve with specified precision; Returns array with polygons
    approx = cv2.approxPolyDP(contour, 0.015 * length, True)
    # If the contour has 4 polygons, its our screen.
    img_contour2 = img_grey
    print(i)
    plt.title("Individual contour (for loop)")
    cv2.drawContours(img_contour2, approx, i, (0, 255, 0), 3) 
    plt.imshow(cv2.cvtColor(img_contour2, cv2.COLOR_BGR2RGB))
    plt.show()
    if len(approx) == 4:
        # Save the screen
        screenCnt = approx
        # Stop searching
        break

approx is NOT a list of contours, but a single one.
also, drawContours takes a list, not a single countour. use something like:

cv2.drawContours(img_contour2, [approx], 0, (0, 255, 0), 3) 

or

cv2.drawContours(img_contour2, contours, i, (255, 0, 0), 3) 

last, you dont see green contours, since you draw to a single channel image
(so only the 1st color component (0) will be used)
better draw into your original color image …