8

I'm trying to display a filled contour using the cv2.drawContours function in OpenCV. I've developed a list of contours from an edge image derived from a Canny detection, and am finding the contours with RETR_EXTERNAL enabled for the hierarchy definition. However I'm running into an issue that despite using the -1 flag in the cv2.drawContours command to indicate a filled contour, only the contour itself(i.e. edge) is displayed. For example:

mask = np.zeros(rawimg.shape, np.uint8)
cv2.drawContours(mask, contours[246], -1, (0,255,255), -1)

results in just the outline of contour 246 being displayed. As I am only retrieving the external contours I don't think I am seeing just the difference between the internal and external contours found at each edge, so I'm a bit confused as to why it is displaying the contour, but not filling as the -1 flag would suggest it should.


EDIT: The full code is included below. The issue is with the line: cv2.drawContours(mask, cnt, 2, (0,255,255), -1) While this is formatted in the manner suggested by Dan, it results in the following image:

image linked. cnt is a single contour, so this makes sense that it would be referring to a single point in the contour. When the line is changed to:

cv2.drawContours(mask, cnt, -1, (0,255,255), -1)

the contour prints as before, however the contour is still not filled as the -1 flag at the end of the command would suggest it should be.

The test image:

is uploaded here

import os
import cv2
import numpy as np
from matplotlib import pyplot as plt
import copy as cp

path = 'C:\\Users\\...deleted...\\Desktop\\testimage6.jpg'



#Determine largest contour in the image
def maxContour(contours):
    cnt_list = np.zeros(len(contours))
    for i in range(0,len(contours)):
        cnt_list[i] = cv2.contourArea(contours[i])

    max_value = np.amax(cnt_list)
    max_index = np.argmax(cnt_list)
    cnt = contours[max_index]

    return cnt, max_index


if os.path.isfile(path):
    # Import the raw image to a working location and save to an isolated     variable
    # Import the raw image to a working location and save to an isolated     variable
    img = cv2.imread(path)
    rawimg = cv2.imread(path)
    saveimg = cv2.imread(path)
    imgray = cv2.cvtColor(saveimg, cv2.COLOR_BGR2GRAY)
    saveimgray = cp.copy(imgray)

    f1 = plt.figure(1)
    f1.set_size_inches(8,10)
    plt.title('Original Image')
    plt.xticks([]), plt.yticks([])
    plt.imshow(rawimg, cmap='gray')
    plt.savefig('output1.jpg', dpi=300)
    cv2.imshow('Raw Image',rawimg)
    cv2.waitKey(0)
    cv2.destroyWindow('Raw Image')

    # Impose an opening function as a filter
    kernel = np.ones((3,3),np.uint8)    
    opening = cv2.morphologyEx(img, cv2.MORPH_OPEN, kernel)

    f2 = plt.figure(2)
    f1.set_size_inches(8,10)
    plt.title('Opened Image')
    plt.xticks([]), plt.yticks([])
    plt.imshow(opening, cmap='gray')
    plt.savefig('output2.jpg', dpi=300)
    cv2.imshow('Opened Image', opening)
    cv2.waitKey(0)
    cv2.destroyWindow('Opened Image')


    #Extract the edges from the filtered image
    edges = cv2.Canny(opening,10,100)
    cv2.imshow('Edges', edges)
    cv2.waitKey(0)
    cv2.destroyWindow('Edges')
    f3=plt.figure(3)
    f3.set_size_inches(16,8)
    plt.title('Edge Image')
    plt.xticks([]), plt.yticks([])
    plt.imshow(edges, cmap='gray')
    plt.savefig('output3.jpg', dpi=300)


    #Detect contours in the edge image
    image, contours, hierarchy = cv2.findContours(edges, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
    cv2.drawContours(img, contours, -1, (0,255,255), 2)
    cv2.imshow('Contours Image', img)
    cv2.waitKey(0)
    cv2.destroyWindow('Contours Image')
    f4=plt.figure(4)
    f4.set_size_inches(16,8)
    plt.title('Contour Image')
    plt.xticks([]), plt.yticks([])
    plt.imshow(img)
    plt.savefig('output2.jpg', dpi=300)

    #Find maximum area contour    
    cnt, max_index = maxContour(contours)
    print(max_index)


    # Calculate contour-based statistics
    # TBD


    #Test of removing max contour
    #grayimg = cv2.cvtColor(rawimg, cv2.COLOR_BGR2GRAY)
    mask = np.zeros(rawimg.shape, np.uint8)
    cv2.drawContours(mask, cnt, 2, (0,255,255), -1) 
    #ret, mask = cv2.threshold(grayimg, 10, 255, cv2.THRESH_BINARY)
    mask_inv = cv2.bitwise_not(mask)
    cv2.imshow('Mask Image', mask)
    cv2.waitKey(0)
    cv2.destroyWindow('Mask Image')
    cv2.imshow('Mask Image', mask_inv)
    cv2.waitKey(0)
    cv2.destroyWindow('Mask Image')

    #Fit ellipse to contour and calculate ellipse statistics
    (x,y), (w,h), angle = cv2.fitEllipse(cnt)    
    rect = cv2.minAreaRect(cnt)
    box = cv2.boxPoints(rect)
    box = np.int0(box)
    x = np.int0(x)
    y = np.int0(y)
    w = np.int0(0.5*w)
    h = np.int0(0.5*h)


    #output2 = cv2.ellipse(img, center, dim, angle, 0, 360, (255,0,0), 12)
    output2 = cv2.ellipse(img, (x,y), (w,h), angle, 0, 360, (255,0,0), 2)
    output3 = cv2.drawContours(output2, [box], 0, (0,255,0), 2)
    cv2.imshow('Ellipse Image',output2)
    cv2.waitKey(0)
    cv2.destroyWindow('Ellipse Image')



else:
    print('file does not exist')`
Dan Mašek
  • 17,852
  • 6
  • 57
  • 85
  • 1
    IT should be `cv2.drawContours(mask, contours, 246, (0,255,255), -1)`. The second parameter should be a list of contours, where each contour is a list of points. The way you did it, it treats each point as a separate contour. I have this feeling I already wrote an answer for identical question, let me see if I can find it... – Dan Mašek Jul 21 '17 at 20:27
  • 4
    Possible duplicate of [Contour shows dots rather than a curve when retrieving it from the list, but shows the curve otherwise](https://stackoverflow.com/questions/36311398/contour-shows-dots-rather-than-a-curve-when-retrieving-it-from-the-list-but-sho) | Also somewhat related [C++ question](https://stackoverflow.com/questions/37264253/issue-with-drawcontours-opencv-c/37265611#37265611) – Dan Mašek Jul 21 '17 at 20:31
  • The suggested answer results in the same behavior as the original code. I've edited the question to include the complete code for reference purposes. Please note that the line in the referenced code (with a slightly different test image) is as follows: `cv2.drawContours(mask, cnt, 2, (0,255,255), -1)` – The man they call Jayne Jul 23 '17 at 00:03
  • 1
    @DanMašek I have updated the post to include the full script, as the suggested answer does not resolve the problem. If you could vote to reopen I would appreciate it so I can avoid reposting. – The man they call Jayne Jul 24 '17 at 18:27
  • 1
    Still the same problem. In first call to `cv2.drawContours` you give it `contours`, which is a **list of contours**. In second call, you give it `cnt`, which is (from your `maxContour` function) `contours[max_index]` -- i.e. **a contour**, rather than a list containing 1 contour . You already have the index of the contour you want to draw, so simply do `cv2.drawContours(mask, contours, max_index, (0,255,255), -1)` – Dan Mašek Jul 24 '17 at 21:09
  • @DanMašek I have to apologize, your original solution does work, I suspect my problem was related to a saved array value in Spyder. I had tried your solution before and got just the outline of the contour as I mentioned; this time it appears to be working fine after I restarted the kernel. Thanks for the patience. – The man they call Jayne Jul 24 '17 at 21:36
  • so what's the answer? – john k Nov 26 '17 at 01:15

1 Answers1

19

The function drawContours takes a list of contours as input. Try:

cv2.drawContours(
    image=mask,
    contours=[cnt],
    contourIdx=-1,
    color=(0,255,255),
    thickness=cv2.FILLED)

instead of: cv2.drawContours(mask, cnt, -1, (0,255,255), -1).

Michael Currie
  • 13,721
  • 9
  • 42
  • 58
Neeraj Jain
  • 598
  • 10
  • 10
  • This is the solution. However, it is questionable design why the function correctly draws all outlines and works as expected if fed with a single contour, only filling does not work ... – Joma Apr 25 '22 at 07:34