Ask Your Question

Revision history [back]

problem in python findContours() with connected and disconnected shapes

Hello everybody, I am running a project in python involving recognition of shapes in images and creation of a graph out of the recognition. The script should be as more general as possible in order to create a graph out of any image given in input. The approach I used is to find the contours of the shapes and re-draw them into a new image. However I am having some troubles when for example the image contains a circle connected to a line: in this case the line is lost and not drawn in the graph. I show you what I mean:

this is the input picture (please don't laught about it):

input image

After the processing, the graph created is this:

output graph

It is noticeable that the line of the left "arm" is missing and after a all afternoon of trial, I didn't get to anything. Moreover, Also the "smile" is recognized as pentagon even if the shape is disconnected.

The code written in python is a little ugly because I am not such an expert in python programming, so please excuse me for that:

import numpy as np
import cv2
import Image, ImageDraw
import math

#Function to find a cosine of angle between vectors
#from pt0->pt1 and pt0->pt2
def angle_cos(p1, p2, p0):
    dx1 = (p1[0]-p0[0]).astype('double')
    dy1 = (p1[1]-p0[1]).astype('double')
    dx2 = (p2[0]-p0[0]).astype('double')
    dy2 = (p2[1]-p0[1]).astype('double')
    return (dx1*dx2 + dy1*dy2) / np.sqrt((dx1*dx1 + dy1*dy1)*(dx2*dx2 + dy2*dy2) + 1e-10)

#Routine
img = cv2.imread('snowman.png')
cv2.imshow('starting picture', img)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

#Remove of noise, if any
blur = cv2.GaussianBlur(gray, (5,5), 0)

#Create a new image of the same size of the starting image
height, width = gray.shape
newimg = np.zeros((height, width, 3), np.uint8)

#Edge detector
thresh = 175
edges = cv2.Canny(blur, thresh, thresh*2)

#Find contours
contours,hierarchy = cv2.findContours(edges, cv2.cv.CV_RETR_CCOMP, cv2.CHAIN_APPROX_SIMPLE)
for b,cnt in enumerate(contours):
    if hierarchy[0,b,3] == -1: #Avoid inner contours
        approx = cv2.approxPolyDP(cnt,0.025*cv2.arcLength(cnt,True), True)
        if len(approx)==5:
            print "pentagon" + " with area = " +str(cv2.contourArea(cnt))
            cv2.drawContours(img,[cnt],-1,(255,0,0),-1)
            #draw the pentagon in the new image
            cv2.line(newimg, (approx[0][0][0], approx[0][0][1]), (approx[1][0][0], approx[1][0][1]), (255,0,0), 2)
            cv2.line(newimg, (approx[1][0][0], approx[1][0][1]), (approx[2][0][0], approx[2][0][1]), (255,0,0), 2)
            cv2.line(newimg, (approx[2][0][0], approx[2][0][1]), (approx[3][0][0], approx[3][0][1]), (255,0,0), 2)
            cv2.line(newimg, (approx[3][0][0], approx[3][0][1]), (approx[4][0][0], approx[4][0][1]), (255,0,0), 2)
            cv2.line(newimg, (approx[4][0][0], approx[4][0][1]), (approx[0][0][0], approx[0][0][1]), (255,0,0), 2)
            cv2.circle(newimg, (approx[0][0][0], approx[0][0][1]), 3, (255,255,255), -1)
            cv2.circle(newimg, (approx[1][0][0], approx[1][0][1]), 3, (255,255,255), -1)
            cv2.circle(newimg, (approx[2][0][0], approx[2][0][1]), 3, (255,255,255), -1)
            cv2.circle(newimg, (approx[3][0][0], approx[3][0][1]), 3, (255,255,255), -1)
            cv2.circle(newimg, (approx[4][0][0], approx[4][0][1]), 3, (255,255,255), -1)
        elif len(approx)==6:
            print "hexagon" + " with area = " +str(cv2.contourArea(cnt))
            cv2.drawContours(img,[cnt],-1,(255,100,100),-1)
            #draw the hexagon in the new image
            cv2.line(newimg, (approx[0][0][0], approx[0][0][1]), (approx[1][0][0], approx[1][0][1]), (255,100,100), 2)
            cv2.line(newimg, (approx[1][0][0], approx[1][0][1]), (approx[2][0][0], approx[2][0][1]), (255,100,100), 2)
            cv2.line(newimg, (approx[2][0][0], approx[2][0][1]), (approx[3][0][0], approx[3][0][1]), (255,100,100), 2)
            cv2.line(newimg, (approx[3][0][0], approx[3][0][1]), (approx[4][0][0], approx[4][0][1]), (255,100,100), 2)
            cv2.line(newimg, (approx[4][0][0], approx[4][0][1]), (approx[5][0][0], approx[5][0][1]), (255,100,100), 2)
            cv2.line(newimg, (approx[5][0][0], approx[5][0][1]), (approx[0][0][0], approx[0][0][1]), (255,100,100), 2)
            cv2.circle(newimg, (approx[0][0][0], approx[0][0][1]), 3, (255,255,255), -1)
            cv2.circle(newimg, (approx[1][0][0], approx[1][0][1]), 3, (255,255,255), -1)
            cv2.circle(newimg, (approx[2][0][0], approx[2][0][1]), 3, (255,255,255), -1)
            cv2.circle(newimg, (approx[3][0][0], approx[3][0][1]), 3, (255,255,255), -1)
            cv2.circle(newimg, (approx[4][0][0], approx[4][0][1]), 3, (255,255,255), -1)
            cv2.circle(newimg, (approx[5][0][0], approx[5][0][1]), 3, (255,255,255), -1)
        elif len(approx)==2:
            print "line"
            cv2.drawContours(img,[cnt],-1,(255,255,100),-1)
            #draw the line in the new image
            cv2.line(newimg, (approx[0][0][0], approx[0][0][1]), (approx[1][0][0], approx[1][0][1]), (255,255,100), 2)
            cv2.circle(newimg, (approx[0][0][0], approx[0][0][1]), 3, (255,255,255), -1)
            cv2.circle(newimg, (approx[1][0][0], approx[1][0][1]), 3, (255,255,255), -1)
        elif len(approx)==1:
            print "point"
            cv2.drawContours(img,[cnt],-1,(255,255,100),-1)
            #draw the point in the new image
            cv2.circle(newimg, (approx[0][0][0], approx[0][0][1]), 3, (255,255,255), -1)
        elif len(approx)==3:
            print "triangle" + " with area = " +str(cv2.contourArea(cnt))
            cv2.drawContours(img,[cnt],-1,(0,255,0),-1)
            #draw the triangle in the new image
            cv2.line(newimg, (approx[0][0][0], approx[0][0][1]), (approx[1][0][0], approx[1][0][1]), (0,255,0), 2)
            cv2.line(newimg, (approx[1][0][0], approx[1][0][1]), (approx[2][0][0], approx[2][0][1]), (0,255,0), 2)
            cv2.line(newimg, (approx[2][0][0], approx[2][0][1]), (approx[0][0][0], approx[0][0][1]), (0,255,0), 2)
            cv2.circle(newimg, (approx[0][0][0], approx[0][0][1]), 3, (255,255,255), -1)
            cv2.circle(newimg, (approx[1][0][0], approx[1][0][1]), 3, (255,255,255), -1)
            cv2.circle(newimg, (approx[2][0][0], approx[2][0][1]), 3, (255,255,255), -1)
        elif len(approx)==4:
            #check if it is a square
            if cv2.contourArea(approx) > 1000 and cv2.isContourConvex(approx):
                approx = approx.reshape(-1, 2)
                max_cos = np.max([angle_cos(approx[i%len(approx)], approx[i-2], approx[i-1] ) for i in xrange(2, len(approx)+1)])
                min_cos = np.min([angle_cos(approx[i%len(approx)], approx[i-2], approx[i-1] ) for i in xrange(2, len(approx)+1)])
                if min_cos >= -0.1 and max_cos <= 0.3:
                    print "square" + " with area = " +str(cv2.contourArea(cnt))
                    cv2.drawContours(img,[cnt],-1,(0,0,255),-1)
                    #draw the square in the new image
                    cv2.line(newimg,(approx[0][0], approx[0][1]), (approx[1][0], approx[1][1]), (0,0,255), 2)
                    cv2.line(newimg,(approx[1][0], approx[1][1]), (approx[2][0], approx[2][1]), (0,0,255), 2)
                    cv2.line(newimg,(approx[2][0], approx[2][1]), (approx[3][0], approx[3][1]), (0,0,255), 2)
                    cv2.line(newimg,(approx[3][0], approx[3][1]), (approx[0][0], approx[0][1]), (0,0,255), 2)
                    cv2.circle(newimg, (approx[0][0], approx[0][1]), 3, (255,255,255), -1)
                    cv2.circle(newimg, (approx[1][0], approx[1][1]), 3, (255,255,255), -1)
                    cv2.circle(newimg, (approx[2][0], approx[2][1]), 3, (255,255,255), -1)
                    cv2.circle(newimg, (approx[3][0], approx[3][1]), 3, (255,255,255), -1)                
        elif len(approx) >=7:
            #check if it is a circle
            area = cv2.contourArea(cnt)
            x, y, w, h = cv2.boundingRect(cnt)
            radius = w/2
            if abs(1 - (area / (math.pi * pow(radius, 2))))<=0.2:
                print "circle" + " with area = " +str(cv2.contourArea(cnt))
                M = cv2.moments(cnt)
                cx = int(M['m10']/M['m00'])
                cy = int(M['m01']/M['m00'])
                #draw the circle
                cv2.circle(newimg, (cx, cy), radius, (0,255,255), 2)
                #draw the center of the circle
                cv2.circle(newimg, (cx, cy), 3, (69,13,254), -1)
                cv2.drawContours(img,[cnt],-1,(0,255,255),-1)

            #otherwise check if it is an octagon    
            elif len(approx)==8:
                print "octagon" + " with area = " +str(cv2.contourArea(cnt))
                cv2.drawContours(img,[cnt],-1,(155,200,255),-1)
                #draw the octagon in the new image
                cv2.line(newimg, (approx[0][0][0], approx[0][0][1]), (approx[1][0][0], approx[1][0][1]), (0,255,0), 2)
                cv2.line(newimg, (approx[1][0][0], approx[1][0][1]), (approx[2][0][0], approx[2][0][1]), (0,255,0), 2)
                cv2.line(newimg, (approx[2][0][0], approx[2][0][1]), (approx[3][0][0], approx[3][0][1]), (0,255,0), 2)
                cv2.line(newimg, (approx[3][0][0], approx[3][0][1]), (approx[4][0][0], approx[4][0][1]), (0,255,0), 2)
                cv2.line(newimg, (approx[4][0][0], approx[4][0][1]), (approx[5][0][0], approx[5][0][1]), (0,255,0), 2)
                cv2.line(newimg, (approx[5][0][0], approx[5][0][1]), (approx[6][0][0], approx[6][0][1]), (0,255,0), 2)
                cv2.line(newimg, (approx[6][0][0], approx[6][0][1]), (approx[7][0][0], approx[7][0][1]), (0,255,0), 2)
                cv2.line(newimg, (approx[7][0][0], approx[7][0][1]), (approx[0][0][0], approx[0][0][1]), (0,255,0), 2)
                cv2.circle(newimg, (approx[0][0][0], approx[0][0][1]), 3, (255,255,255), -1)
                cv2.circle(newimg, (approx[1][0][0], approx[1][0][1]), 3, (255,255,255), -1)
                cv2.circle(newimg, (approx[2][0][0], approx[2][0][1]), 3, (255,255,255), -1)
                cv2.circle(newimg, (approx[3][0][0], approx[3][0][1]), 3, (255,255,255), -1)
                cv2.circle(newimg, (approx[4][0][0], approx[4][0][1]), 3, (255,255,255), -1)
                cv2.circle(newimg, (approx[5][0][0], approx[5][0][1]), 3, (255,255,255), -1)
                cv2.circle(newimg, (approx[6][0][0], approx[6][0][1]), 3, (255,255,255), -1)
                cv2.circle(newimg, (approx[7][0][0], approx[7][0][1]), 3, (255,255,255), -1)

cv2.imshow('graph', newimg)
cv2.imwrite('graph.png', newimg)
cv2.waitKey(0)
cv2.destroyAllWindows()
click to hide/show revision 2
made the code a little lighter with a function for drawing the graph and put some more constraints for finding the shapes

problem in python findContours() with connected and disconnected shapes

Hello everybody, I am running a project in python involving recognition of shapes in images and creation of a graph out of the recognition. The script should be as more general as possible in order to create a graph out of any image given in input. The approach I used is to find the contours of the shapes and re-draw them into a new image. However I am having some troubles when for example the image contains a circle connected to a line: in this case the line is lost and not drawn in the graph. I show you what I mean:

this is the input picture (please don't laught about it):

input image

After the processing, the graph created is this:

output graph

It is noticeable that the line of the left "arm" is missing and after a all afternoon of trial, I didn't get to anything. Moreover, Also the "smile" is recognized as pentagon even if the shape is disconnected.

The code written in python is a little ugly because I am not such an expert in python programming, so please excuse me for that:

import numpy as np
import cv2
import Image, ImageDraw
import math

#Function to find a cosine of angle between vectors
#from pt0->pt1 and pt0->pt2
def angle_cos(p1, p2, p0):
    dx1 = (p1[0]-p0[0]).astype('double')
    dy1 = (p1[1]-p0[1]).astype('double')
    dx2 = (p2[0]-p0[0]).astype('double')
    dy2 = (p2[1]-p0[1]).astype('double')
    return (dx1*dx2 + dy1*dy2) / np.sqrt((dx1*dx1 + dy1*dy1)*(dx2*dx2 + dy2*dy2) + 1e-10)

#function to create the final graph
def create_graph(vertex, color):
    for g in range(0, len(vertex)-1):
        for y in range(0, len(vertex[0][0])-1):
            cv2.circle(newimg, (vertex[g][0][y], vertex[g][0][y+1]), 3, (255,255,255), -1)
            cv2.line(newimg, (vertex[g][0][y], vertex[g][0][y+1]), (vertex[g+1][0][y], vertex[g+1][0][y+1]), color, 2)
    cv2.line(newimg, (vertex[len(vertex)-1][0][0], vertex[len(vertex)-1][0][1]), (vertex[0][0][0], vertex[0][0][1]), color, 2)

#Routine
img = cv2.imread('snowman.png')
cv2.imread('draw.jpg')
cv2.imshow('starting picture', img)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

#Remove of noise, if any
blur = cv2.GaussianBlur(gray, (5,5), 0)

#Create a new image of the same size of the starting image
height, width = gray.shape
newimg = np.zeros((height, width, 3), np.uint8)

#Edge detector
thresh = 175
edges = cv2.Canny(blur, thresh, thresh*2)

#Find contours
contours,hierarchy = cv2.findContours(edges, cv2.cv.CV_RETR_CCOMP, cv2.CHAIN_APPROX_SIMPLE)
for b,cnt in enumerate(contours):
    if hierarchy[0,b,3] == -1: #Avoid inner contours
        approx = cv2.approxPolyDP(cnt,0.025*cv2.arcLength(cnt,True), True)
        if len(approx)==5:
            if cv2.isContourConvex(approx):
                print "pentagon" + " with area = " +str(cv2.contourArea(cnt))
            cv2.drawContours(img,[cnt],-1,(255,0,0),-1)
    clr = (255, 0, 0)
                cv2.drawContours(img,[cnt],-1, clr,-1)
                #draw the pentagon in the new image
                create_graph(approx, clr)
            else:
                print str(len(approx))+" lines"
                clr = (204, 255, 0)
                cv2.drawContours(img,[cnt],-1, clr,-1)
                #draw 5 lines
                create_graph(approx, clr)
        elif len(approx)==6:
            if cv2.isContourConvex(approx):
                print "hexagon" + " with area = " +str(cv2.contourArea(cnt))
                clr = (0, 102, 255)
                cv2.drawContours(img,[cnt],-1, clr,-1)
                #draw the hexagon in the new image
                create_graph(approx, clr)
            else:
                print str(len(approx))+" lines"
                clr = (204, 255, 0)
                cv2.drawContours(img,[cnt],-1, clr,-1)
                #draw 6 lines
                create_graph(approx, clr)
        elif len(approx)==2:
            print "line"
            cv2.drawContours(img,[cnt],-1,(204, 255, 0),-1)
            #draw the line in the new image
            cv2.line(newimg, (approx[0][0][0], approx[0][0][1]), (approx[1][0][0], approx[1][0][1]), (255,0,0), 2)
            cv2.line(newimg, (approx[1][0][0], approx[1][0][1]), (approx[2][0][0], approx[2][0][1]), (255,0,0), 2)
            cv2.line(newimg, (approx[2][0][0], approx[2][0][1]), (approx[3][0][0], approx[3][0][1]), (255,0,0), 2)
            cv2.line(newimg, (approx[3][0][0], approx[3][0][1]), (approx[4][0][0], approx[4][0][1]), (255,0,0), 2)
            cv2.line(newimg, (approx[4][0][0], approx[4][0][1]), (approx[0][0][0], approx[0][0][1]), (255,0,0), (204, 255, 0), 2)
            cv2.circle(newimg, (approx[0][0][0], approx[0][0][1]), 3, (255,255,255), -1)
            cv2.circle(newimg, (approx[1][0][0], approx[1][0][1]), 3, (255,255,255), -1)
            cv2.circle(newimg, (approx[2][0][0], approx[2][0][1]), 3, (255,255,255), -1)
            cv2.circle(newimg, (approx[3][0][0], approx[3][0][1]), 3, (255,255,255), -1)
            cv2.circle(newimg, (approx[4][0][0], approx[4][0][1]), 3, (255,255,255), -1)
        elif len(approx)==6:
len(approx)==1:
            print "hexagon" + " with area = " +str(cv2.contourArea(cnt))
            cv2.drawContours(img,[cnt],-1,(255,100,100),-1)
"point"
            cv2.drawContours(img,[cnt],-1,(255,255,255),-1)
            #draw the hexagon point in the new image
            cv2.line(newimg, (approx[0][0][0], approx[0][0][1]), (approx[1][0][0], approx[1][0][1]), (255,100,100), 2)
            cv2.line(newimg, (approx[1][0][0], approx[1][0][1]), (approx[2][0][0], approx[2][0][1]), (255,100,100), 2)
            cv2.line(newimg, (approx[2][0][0], approx[2][0][1]), (approx[3][0][0], approx[3][0][1]), (255,100,100), 2)
            cv2.line(newimg, (approx[3][0][0], approx[3][0][1]), (approx[4][0][0], approx[4][0][1]), (255,100,100), 2)
            cv2.line(newimg, (approx[4][0][0], approx[4][0][1]), (approx[5][0][0], approx[5][0][1]), (255,100,100), 2)
            cv2.line(newimg, (approx[5][0][0], approx[5][0][1]), (approx[0][0][0], approx[0][0][1]), (255,100,100), 2)
            cv2.circle(newimg, (approx[0][0][0], approx[0][0][1]), 3, (255,255,255), -1)
            cv2.circle(newimg, (approx[1][0][0], approx[1][0][1]), 3, (255,255,255), -1)
            cv2.circle(newimg, (approx[2][0][0], approx[2][0][1]), 3, (255,255,255), -1)
            cv2.circle(newimg, (approx[3][0][0], approx[3][0][1]), 3, (255,255,255), -1)
            cv2.circle(newimg, (approx[4][0][0], approx[4][0][1]), 3, (255,255,255), -1)
            cv2.circle(newimg, (approx[5][0][0], approx[5][0][1]), 3, (255,255,255), -1)
        elif len(approx)==2:
            print "line"
            cv2.drawContours(img,[cnt],-1,(255,255,100),-1)
            #draw the line in the new image
            cv2.line(newimg, (approx[0][0][0], approx[0][0][1]), (approx[1][0][0], approx[1][0][1]), (255,255,100), 2)
            cv2.circle(newimg, (approx[0][0][0], approx[0][0][1]), 3, (255,255,255), -1)
            cv2.circle(newimg, (approx[1][0][0], approx[1][0][1]), 3, (255,255,255), -1)
        elif len(approx)==1:
            print "point"
            cv2.drawContours(img,[cnt],-1,(255,255,100),-1)
            #draw the point in the new image
            cv2.circle(newimg, (approx[0][0][0], approx[0][0][1]), 3, (255,255,255), -1)
        elif len(approx)==3:
            if cv2.isContourConvex(approx):
                print "triangle" + " with area = " +str(cv2.contourArea(cnt))
            cv2.drawContours(img,[cnt],-1,(0,255,0),-1)
    clr = (0, 255, 0)
                cv2.drawContours(img,[cnt],-1, clr,-1)
                #draw the triangle in the new image
            cv2.line(newimg, (approx[0][0][0], approx[0][0][1]), (approx[1][0][0], approx[1][0][1]), (0,255,0), 2)
            cv2.line(newimg, (approx[1][0][0], approx[1][0][1]), (approx[2][0][0], approx[2][0][1]), (0,255,0), 2)
            cv2.line(newimg, (approx[2][0][0], approx[2][0][1]), (approx[0][0][0], approx[0][0][1]), (0,255,0), 2)
            cv2.circle(newimg, (approx[0][0][0], approx[0][0][1]), 3, (255,255,255), -1)
            cv2.circle(newimg, (approx[1][0][0], approx[1][0][1]), 3, (255,255,255), -1)
            cv2.circle(newimg, (approx[2][0][0], approx[2][0][1]), 3, (255,255,255), -1)
    create_graph(approx, clr)
            else:
                print str(len(approx))+" lines"
                clr = (204, 255, 0)
                cv2.drawContours(img,[cnt],-1, clr,-1)
                #draw 3 lines
                create_graph(approx, clr)
        elif len(approx)==4:
            #check if it is a square
            if cv2.contourArea(approx) > 1000 and cv2.isContourConvex(approx):
                approx = approx.reshape(-1, 2)
                max_cos = np.max([angle_cos(approx[i%len(approx)], approx[i-2], approx[i-1] ) for i in xrange(2, len(approx)+1)])
                min_cos = np.min([angle_cos(approx[i%len(approx)], approx[i-2], approx[i-1] ) for i in xrange(2, len(approx)+1)])
                if min_cos >= -0.1 and max_cos <= 0.3:
                    print "square" + " with area = " +str(cv2.contourArea(cnt))
                    cv2.drawContours(img,[cnt],-1,(0,0,255),-1)
                    #draw the square in the new image
                    cv2.line(newimg,(approx[0][0], approx[0][1]), (approx[1][0], approx[1][1]), (0,0,255), 2)
                    cv2.line(newimg,(approx[1][0], approx[1][1]), (approx[2][0], approx[2][1]), (0,0,255), 2)
                    cv2.line(newimg,(approx[2][0], approx[2][1]), (approx[3][0], approx[3][1]), (0,0,255), 2)
                    cv2.line(newimg,(approx[3][0], approx[3][1]), (approx[0][0], approx[0][1]), (0,0,255), 2)
                    cv2.circle(newimg, (approx[0][0], approx[0][1]), 3, (255,255,255), -1)
                    cv2.circle(newimg, (approx[1][0], approx[1][1]), 3, (255,255,255), -1)
                    cv2.circle(newimg, (approx[2][0], approx[2][1]), 3, (255,255,255), -1)
                    cv2.circle(newimg, (approx[3][0], approx[3][1]), 3, (255,255,255), -1)                
-1)
            else:
                print str(len(approx))+" lines"
                clr = (204, 255, 0)
                create_graph(approx, clr)
        elif len(approx) >=7:
            if cv2.isContourConvex(approx):
                #check if it is a circle
             area = cv2.contourArea(cnt)
             x, y, w, h = cv2.boundingRect(cnt)
             radius = w/2
             if abs(1 - (area / (math.pi * pow(radius, 2))))<=0.2:
2))))<=0.1:
                    print "circle" + " with area = " +str(cv2.contourArea(cnt))
                 M = cv2.moments(cnt)
                 cx = int(M['m10']/M['m00'])
                 cy = int(M['m01']/M['m00'])
                 #draw the circle
                 cv2.circle(newimg, (cx, cy), radius, (0,255,255), 2)
                 #draw the center of the circle
                 cv2.circle(newimg, (cx, cy), 3, (69,13,254), -1)
                 cv2.drawContours(img,[cnt],-1,(0,255,255),-1)
              #otherwise check if it is an octagon    
             elif len(approx)==8:
                 print "octagon" + " with area = " +str(cv2.contourArea(cnt))
                cv2.drawContours(img,[cnt],-1,(155,200,255),-1)
    clr = (91, 0, 153)
                    cv2.drawContours(img,[cnt],-1, clr,-1)
                    #draw the octagon in the new image
                cv2.line(newimg, (approx[0][0][0], approx[0][0][1]), (approx[1][0][0], approx[1][0][1]), (0,255,0), 2)
                cv2.line(newimg, (approx[1][0][0], approx[1][0][1]), (approx[2][0][0], approx[2][0][1]), (0,255,0), 2)
                cv2.line(newimg, (approx[2][0][0], approx[2][0][1]), (approx[3][0][0], approx[3][0][1]), (0,255,0), 2)
                cv2.line(newimg, (approx[3][0][0], approx[3][0][1]), (approx[4][0][0], approx[4][0][1]), (0,255,0), 2)
                cv2.line(newimg, (approx[4][0][0], approx[4][0][1]), (approx[5][0][0], approx[5][0][1]), (0,255,0), 2)
                cv2.line(newimg, (approx[5][0][0], approx[5][0][1]), (approx[6][0][0], approx[6][0][1]), (0,255,0), 2)
                cv2.line(newimg, (approx[6][0][0], approx[6][0][1]), (approx[7][0][0], approx[7][0][1]), (0,255,0), 2)
                cv2.line(newimg, (approx[7][0][0], approx[7][0][1]), (approx[0][0][0], approx[0][0][1]), (0,255,0), 2)
                cv2.circle(newimg, (approx[0][0][0], approx[0][0][1]), 3, (255,255,255), -1)
                cv2.circle(newimg, (approx[1][0][0], approx[1][0][1]), 3, (255,255,255), -1)
                cv2.circle(newimg, (approx[2][0][0], approx[2][0][1]), 3, (255,255,255), -1)
                cv2.circle(newimg, (approx[3][0][0], approx[3][0][1]), 3, (255,255,255), -1)
                cv2.circle(newimg, (approx[4][0][0], approx[4][0][1]), 3, (255,255,255), -1)
                cv2.circle(newimg, (approx[5][0][0], approx[5][0][1]), 3, (255,255,255), -1)
                cv2.circle(newimg, (approx[6][0][0], approx[6][0][1]), 3, (255,255,255), -1)
                cv2.circle(newimg, (approx[7][0][0], approx[7][0][1]), 3, (255,255,255), -1)

    create_graph(approx, clr)
            else:
                print str(len(approx))+" lines"
                clr = (204, 255, 0)
                create_graph(approx, clr)
cv2.imshow('worked-out picture',img)
cv2.imshow('graph', newimg)
cv2.imwrite('graph.png', newimg)
cv2.waitKey(0)
cv2.destroyAllWindows()

problem in python findContours() with connected and disconnected shapes

Hello everybody, I am running a project in python involving recognition of shapes in images and creation of a graph out of the recognition. The script should be as more general as possible in order to create a graph out of any image given in input. The approach I used is to find the contours of the shapes and re-draw them into a new image. However I am having some troubles when for example the image contains a circle connected to a line: in this case the line is lost and not drawn in the graph. I show you what I mean:

this is the input picture (please don't laught about it):

input image

After the processing, the graph created is this:

output graph

It is noticeable that the line of the left "arm" is missing and after a all afternoon of trial, I didn't get to anything. Moreover, Also the "smile" is recognized as pentagon even if the shape is disconnected.

The code written in python is a little ugly because I am not such an expert in python programming, so please excuse me for that:

import numpy as np
import cv2
import Image, ImageDraw
import math

#Function to find a cosine of angle between vectors
#from pt0->pt1 and pt0->pt2
def angle_cos(p1, p2, p0):
    dx1 = (p1[0]-p0[0]).astype('double')
    dy1 = (p1[1]-p0[1]).astype('double')
    dx2 = (p2[0]-p0[0]).astype('double')
    dy2 = (p2[1]-p0[1]).astype('double')
    return (dx1*dx2 + dy1*dy2) / np.sqrt((dx1*dx1 + dy1*dy1)*(dx2*dx2 + dy2*dy2) + 1e-10)

#function to create the final graph
def create_graph(vertex, color):
    for g in range(0, len(vertex)-1):
        for y in range(0, len(vertex[0][0])-1):
            cv2.circle(newimg, (vertex[g][0][y], vertex[g][0][y+1]), 3, (255,255,255), -1)
            cv2.line(newimg, (vertex[g][0][y], vertex[g][0][y+1]), (vertex[g+1][0][y], vertex[g+1][0][y+1]), color, 2)
    cv2.line(newimg, (vertex[len(vertex)-1][0][0], vertex[len(vertex)-1][0][1]), (vertex[0][0][0], vertex[0][0][1]), color, 2)

#Routine
img = cv2.imread('draw.jpg')
cv2.imshow('starting picture', img)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

#Remove of noise, if any
blur = cv2.GaussianBlur(gray, (5,5), 0)

#Create a new image of the same size of the starting image
height, width = gray.shape
newimg = np.zeros((height, width, 3), np.uint8)

#Edge detector
thresh = 175
edges = cv2.Canny(blur, thresh, thresh*2)

#Find contours
contours,hierarchy = cv2.findContours(edges, cv2.cv.CV_RETR_CCOMP, cv2.CHAIN_APPROX_SIMPLE)
for b,cnt in enumerate(contours):
    if hierarchy[0,b,3] == -1: #Avoid inner contours
        approx = cv2.approxPolyDP(cnt,0.025*cv2.arcLength(cnt,True), True)
        if len(approx)==5:
            if cv2.isContourConvex(approx):
                print "pentagon" + " with area = " +str(cv2.contourArea(cnt))
                clr = (255, 0, 0)
                cv2.drawContours(img,[cnt],-1, clr,-1)
                #draw the pentagon in the new image
                create_graph(approx, clr)
            else:
                print str(len(approx))+" lines"
                clr = (204, 255, 0)
                cv2.drawContours(img,[cnt],-1, clr,-1)
                #draw 5 lines
                create_graph(approx, clr)
        elif len(approx)==6:
            if cv2.isContourConvex(approx):
                print "hexagon" + " with area = " +str(cv2.contourArea(cnt))
                clr = (0, 102, 255)
                cv2.drawContours(img,[cnt],-1, clr,-1)
                #draw the hexagon in the new image
                create_graph(approx, clr)
            else:
                print str(len(approx))+" lines"
                clr = (204, 255, 0)
                cv2.drawContours(img,[cnt],-1, clr,-1)
                #draw 6 lines
                create_graph(approx, clr)
        elif len(approx)==2:
            print "line"
            cv2.drawContours(img,[cnt],-1,(204, 255, 0),-1)
            #draw the line in the new image
            cv2.line(newimg, (approx[0][0][0], approx[0][0][1]), (approx[1][0][0], approx[1][0][1]), (204, 255, 0), 2)
            cv2.circle(newimg, (approx[0][0][0], approx[0][0][1]), 3, (255,255,255), -1)
            cv2.circle(newimg, (approx[1][0][0], approx[1][0][1]), 3, (255,255,255), -1)
        elif len(approx)==1:
            print "point"
            cv2.drawContours(img,[cnt],-1,(255,255,255),-1)
            #draw the point in the new image
            cv2.circle(newimg, (approx[0][0][0], approx[0][0][1]), 3, (255,255,255), -1)
        elif len(approx)==3:
            if cv2.isContourConvex(approx):
                print "triangle" + " with area = " +str(cv2.contourArea(cnt))
                clr = (0, 255, 0)
                cv2.drawContours(img,[cnt],-1, clr,-1)
                #draw the triangle in the new image
                create_graph(approx, clr)
            else:
                print str(len(approx))+" lines"
                clr = (204, 255, 0)
                cv2.drawContours(img,[cnt],-1, clr,-1)
                #draw 3 lines
                create_graph(approx, clr)
        elif len(approx)==4:
            #check if it is a square
            if cv2.contourArea(approx) > 1000 and cv2.isContourConvex(approx):
                approx = approx.reshape(-1, 2)
                max_cos = np.max([angle_cos(approx[i%len(approx)], approx[i-2], approx[i-1] ) for i in xrange(2, len(approx)+1)])
                min_cos = np.min([angle_cos(approx[i%len(approx)], approx[i-2], approx[i-1] ) for i in xrange(2, len(approx)+1)])
                if min_cos >= -0.1 and max_cos <= 0.3:
                    print "square" + " with area = " +str(cv2.contourArea(cnt))
                    cv2.drawContours(img,[cnt],-1,(0,0,255),-1)
                    #draw the square in the new image
                    cv2.line(newimg,(approx[0][0], approx[0][1]), (approx[1][0], approx[1][1]), (0,0,255), 2)
                    cv2.line(newimg,(approx[1][0], approx[1][1]), (approx[2][0], approx[2][1]), (0,0,255), 2)
                    cv2.line(newimg,(approx[2][0], approx[2][1]), (approx[3][0], approx[3][1]), (0,0,255), 2)
                    cv2.line(newimg,(approx[3][0], approx[3][1]), (approx[0][0], approx[0][1]), (0,0,255), 2)
                    cv2.circle(newimg, (approx[0][0], approx[0][1]), 3, (255,255,255), -1)
                    cv2.circle(newimg, (approx[1][0], approx[1][1]), 3, (255,255,255), -1)
                    cv2.circle(newimg, (approx[2][0], approx[2][1]), 3, (255,255,255), -1)
                    cv2.circle(newimg, (approx[3][0], approx[3][1]), 3, (255,255,255), -1)
            else:
                print str(len(approx))+" lines"
                clr = (204, 255, 0)
                create_graph(approx, clr)
        elif len(approx) >=7:
            if cv2.isContourConvex(approx):
                #check if it is a circle
                area = cv2.contourArea(cnt)
                x, y, w, h = cv2.boundingRect(cnt)
                radius = w/2
                if abs(1 - (area / (math.pi * pow(radius, 2))))<=0.1:
                    print "circle" + " with area = " +str(cv2.contourArea(cnt))
                    M = cv2.moments(cnt)
                    cx = int(M['m10']/M['m00'])
                    cy = int(M['m01']/M['m00'])
                    #draw the circle
                    cv2.circle(newimg, (cx, cy), radius, (0,255,255), 2)
                    #draw the center of the circle
                    cv2.circle(newimg, (cx, cy), 3, (69,13,254), -1)
                    cv2.drawContours(img,[cnt],-1,(0,255,255),-1)
                 #otherwise check if it is an octagon    
                elif len(approx)==8:
                    print "octagon" + " with area = " +str(cv2.contourArea(cnt))
                    clr = (91, 0, 153)
                    cv2.drawContours(img,[cnt],-1, clr,-1)
                    #draw the octagon in the new image
                    create_graph(approx, clr)
            else:
                print str(len(approx))+" lines"
                clr = (204, 255, 0)
                create_graph(approx, clr)
cv2.imshow('worked-out picture',img)
cv2.imshow('graph', newimg)
cv2.waitKey(0)
cv2.destroyAllWindows()