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
- Here are two input images that are used to compare against a reference image:
first image:
second image:
-
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:
Second result:
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?
source to share
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:
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):
Trying to blur the trick with your second image also works!
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:
source to share