Python cv2 incorrectly detects square shape in multiple images

I am having a problem detecting a square with opencv.

  • Here is the reference image where I find the form is mapped to use in cv2.matchShapes

reference image with square

  1. Here are two input images that are used to compare against a reference image:

first image:

a reference image with the same square with five inside it

second image:

reference image with the same square with five inside it and different symbols and letters

  1. Here is the code I used (sorry for the bad format):

    #!/usr/bin/env python
    # coding: utf-8
    
    import numpy as np
    import cv2
    import argparse
    import math
    
    ap = argparse.ArgumentParser()
    ap.add_argument("-i", "--image", required=True, help="path to input image")
    args = vars(ap.parse_args())
    
    img = cv2.imread(args["image"])
    # img = cv2.bitwise_not(img,img)
    # gray = cv2.imread(args["image"],0)
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    
    # gray = cv2.GaussianBlur(gray, (5, 5), 0)
    cv2.imshow('gray', gray)
    cv2.waitKey(0)
    
    ret, thresh = cv2.threshold(gray, 230, 255, 1)
    cv2.imshow('thresh', thresh)
    # cv2.imwrite('./wni230.png',thresh)
    cv2.waitKey(0)
    
    square_cnts = []
    
    ##################################################
    shape = cv2.imread('./shape1.png')
    shape_gray = cv2.cvtColor(shape, cv2.COLOR_BGR2GRAY)
    ret, shape_thresh = cv2.threshold(shape_gray, 0, 255, 0)
    tmpimage, contours, h = cv2.findContours(shape_thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
    shape_cnt0 = contours[0]
    shape_approx = []
    for i in contours:
        approx = cv2.approxPolyDP(i, 0.01*cv2.arcLength(i, True), True)
        # print len(approx)
        if len(approx) == 4:
            shape_approx.append(len(approx))
    ##################################################
    # tmpimage,contours,h = cv2.findContours(thresh,1,2)
    # cv2.RETR_TREE
    tmpimage, contours, h = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
    cnt0 = contours[0]
    # from skimage import measure
    # contours = measure.find_contours(thresh, 0.8)
    
    for cnt in contours[::-1]:
        print cv2.arcLength(cnt, True)
        print cnt
        approx = cv2.approxPolyDP(cnt, 0.1*cv2.arcLength(cnt, True), True)
        print len(approx)
        if len(approx) == 5:
            print "pentagon"
            cv2.drawContours(img, [cnt], 0, 255, 2)
            cv2.imshow('tmppentagon', img)
            cv2.waitKey(0)
        elif len(approx) == 3:
            print "triangle"
            cv2.drawContours(img, [cnt], 0, (0, 255, 0), 2)
            cv2.imshow('tmptriangle', img)
            cv2.waitKey(0)
        elif len(approx) == 2:
            print 'approx:'
            print approx
            ret = cv2.matchShapes(cnt, cnt0, 1, 0.0)
            print 'match shape ret:%s' % ret
            print "two approx line"
            cv2.drawContours(img, [cnt], 0, (0, 255, 0), 2)
            cv2.imshow('twoline?', img)
            cv2.waitKey(0)
        elif len(approx) == 4:
    
            ret = cv2.matchShapes(cnt, cnt0, 1, 0.0)
            print 'match shape ret:%s' % ret
            if ret > 0.5:
                print "Parallelogram"
            elif 0.3 < ret < 0.5:
                print "Rectangle"
            elif 0 < ret < 0.3:
                print "Rhombus"
            else:
                print "square"
            print cv2.arcLength(cnt, True)
            print approx
            cv2.drawContours(img, [approx], 0, (0, 0, 255), 2)
            cv2.imshow('tmpsquare', img)
            cv2.waitKey(0)
            if int(cv2.arcLength(cnt, True)) >= 96:
    
                if math.fabs(math.sqrt((approx[0][0][0]-approx[1][0][0])**2+(approx[0][0][1]-approx[1][0][1])**2) - math.sqrt((approx[1][0][0]-approx[2][0][0])**2+(approx[1][0][1]-approx[2][0][1])**2)) <= 5:
                    x, y, w, h = cv2.boundingRect(cnt)
                    cv2.imshow('final',img[y:y+h,x:x+w])
                    cv2.waitKey(0)
                print 'target but long squere detected...'
                cv2.waitKey(0)
        elif len(approx) == 9:
            print "half-circle"
            cv2.drawContours(img,[cnt],0,(255,255,0),2)
            cv2.imshow('tmphalfcircle',img)
            cv2.waitKey(0)
        elif len(approx) > 15:
            print "circle"
            cv2.drawContours(img,[cnt],0,(0,255,255),2)
            cv2.imshow('tmpcircle',img)
            cv2.waitKey(0)
    
    cv2.imshow('img', img)
    cv2.imwrite('tmp.png', img)
    cv2.waitKey(0)
    cv2.destroyAllWindows()
    
          

Results for two input images, respectively:

First result:

console out put allocates zero difference between outlines, with outline area outlined for square for first input image

Second result:

console set selection to 4.21 difference between the contours, with the selected contour area for the square for the second input image

Env:

python 2.7.10

opencv 3.2.0

Questions:

  • Why is the length of cv2.approxPolyDP always 2 instead of 4 for both images? I expect the result to be 4 for the square of the image.

  • Why do my results cv2.matchShapes

    differ from each other? I think 0 is the perfect output, but why does matching against the second image result in such a large number?

+3


source to share


1 answer


Looking through the documentation for these functions, it seems that it approxPolyDP

returns 2 because it can only find outlines of 2 points that actually connect the polygon in the way you describe. Take a look at the Ramer-Douglas-Picker algorithm , which is why polyDP is based. Likewise, the result of a shapeMatch is high if there is a large difference between the two matched shapes. Usually this value will not be that large, unless the shapes are different from each other, but in this case, you seem to fit the contours of your second image!

Look at here:

tmpimage, contours, h = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnt0 = contours[0]

      

cnt0

is the first outline of the second image. Now you say below:

 elif len(approx) == 2:
        print 'approx:'
        print approx
        ret = cv2.matchShapes(cnt, cnt0, 1, 0.0)

      

you are comparing your first contour in the provided image on the same image! you got zero difference for the first one because it was the first contour of the image, which is no longer desired in another test image, and this is unlikely to happen, so you won't get a match.

Also, it looks like you never get four points approxPolyDP

because your loops are not connected / fired in the right place. To verify this, use the following code at the top of the loop:

for cnt in contours[::-1]:
    print cv2.arcLength(cnt, True)
    approx_polygon_shape = cv2.approxPolyDP(cnt, 0.087 * cv2.arcLength(cnt, True), True)
    print "number of approx points", len(approx_polygon_shape)
    print "approx shape", approx_polygon_shape
    tempimage = input_image.copy()
    cv2.drawContours(tempimage, [approx_polygon_shape], -1, (0,255,0), 1)
    cv2.imshow("polyshape show", tempimage)
    cv2.waitKey(0)

      

No matter what number I entered to change the second parameter epsilon to approxPolyDP, I was unable to get four points. I would either get triangles or a gigantic score (I was six this time, I guess). In your normal code, you are multiplying arclength by 0.1, in this situation it turns out that you usually only get 2 points per contour!

For me this returns:



enter image description here

It looks like the blank area in the upper right corner is causing the problem, you might want to blur, which looking at your code looks like you already knew this might be the problem. Re-adding the commented line noted here:

    img = cv2.imread(args["image"])
    # img = cv2.bitwise_not(img,img)
    # gray = cv2.imread(args["image"],0)
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

    #UNCOMMENT!!!VVV
    gray = cv2.GaussianBlur(gray, (5, 5), 0)
    cv2.imshow('gray', gray)
    cv2.waitKey(0)

      

allows enough blur for only four points, even with your normal multiplication 0.1

(example output, note that I am not using python2 and modified the code slightly to make the output more reasonable):

enter image description here

Trying to blur the trick with your second image also works!

enter image description here

If you look at the actual grayscale image, it works because it has been blurry enough that the outlines are close enough for approxPolyDP to work and "jump" over that little gap that was causing problems before. This is what this blurry image looks like:

enter image description here

+3


source







All Articles