mirror of https://github.com/opencv/opencv.git
Open Source Computer Vision Library
https://opencv.org/
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
167 lines
5.3 KiB
167 lines
5.3 KiB
""" |
|
Find Squares in image by finding countours and filtering |
|
""" |
|
#Results slightly different from C version on same images, but is |
|
#otherwise ok |
|
|
|
import math |
|
import cv2.cv as cv |
|
|
|
def angle(pt1, pt2, pt0): |
|
"calculate angle contained by 3 points(x, y)" |
|
dx1 = pt1[0] - pt0[0] |
|
dy1 = pt1[1] - pt0[1] |
|
dx2 = pt2[0] - pt0[0] |
|
dy2 = pt2[1] - pt0[1] |
|
|
|
nom = dx1*dx2 + dy1*dy2 |
|
denom = math.sqrt( (dx1*dx1 + dy1*dy1) * (dx2*dx2 + dy2*dy2) + 1e-10 ) |
|
ang = nom / denom |
|
return ang |
|
|
|
def is_square(contour): |
|
""" |
|
Squareness checker |
|
|
|
Square contours should: |
|
-have 4 vertices after approximation, |
|
-have relatively large area (to filter out noisy contours) |
|
-be convex. |
|
-have angles between sides close to 90deg (cos(ang) ~0 ) |
|
Note: absolute value of an area is used because area may be |
|
positive or negative - in accordance with the contour orientation |
|
""" |
|
|
|
area = math.fabs( cv.ContourArea(contour) ) |
|
isconvex = cv.CheckContourConvexity(contour) |
|
s = 0 |
|
if len(contour) == 4 and area > 1000 and isconvex: |
|
for i in range(1, 4): |
|
# find minimum angle between joint edges (maximum of cosine) |
|
pt1 = contour[i] |
|
pt2 = contour[i-1] |
|
pt0 = contour[i-2] |
|
|
|
t = math.fabs(angle(pt0, pt1, pt2)) |
|
if s <= t:s = t |
|
|
|
# if cosines of all angles are small (all angles are ~90 degree) |
|
# then its a square |
|
if s < 0.3:return True |
|
|
|
return False |
|
|
|
def find_squares_from_binary( gray ): |
|
""" |
|
use contour search to find squares in binary image |
|
returns list of numpy arrays containing 4 points |
|
""" |
|
squares = [] |
|
storage = cv.CreateMemStorage(0) |
|
contours = cv.FindContours(gray, storage, cv.CV_RETR_TREE, cv.CV_CHAIN_APPROX_SIMPLE, (0,0)) |
|
storage = cv.CreateMemStorage(0) |
|
while contours: |
|
#approximate contour with accuracy proportional to the contour perimeter |
|
arclength = cv.ArcLength(contours) |
|
polygon = cv.ApproxPoly( contours, storage, cv.CV_POLY_APPROX_DP, arclength * 0.02, 0) |
|
if is_square(polygon): |
|
squares.append(polygon[0:4]) |
|
contours = contours.h_next() |
|
|
|
return squares |
|
|
|
def find_squares4(color_img): |
|
""" |
|
Finds multiple squares in image |
|
|
|
Steps: |
|
-Use Canny edge to highlight contours, and dilation to connect |
|
the edge segments. |
|
-Threshold the result to binary edge tokens |
|
-Use cv.FindContours: returns a cv.CvSequence of cv.CvContours |
|
-Filter each candidate: use Approx poly, keep only contours with 4 vertices, |
|
enough area, and ~90deg angles. |
|
|
|
Return all squares contours in one flat list of arrays, 4 x,y points each. |
|
""" |
|
#select even sizes only |
|
width, height = (color_img.width & -2, color_img.height & -2 ) |
|
timg = cv.CloneImage( color_img ) # make a copy of input image |
|
gray = cv.CreateImage( (width,height), 8, 1 ) |
|
|
|
# select the maximum ROI in the image |
|
cv.SetImageROI( timg, (0, 0, width, height) ) |
|
|
|
# down-scale and upscale the image to filter out the noise |
|
pyr = cv.CreateImage( (width/2, height/2), 8, 3 ) |
|
cv.PyrDown( timg, pyr, 7 ) |
|
cv.PyrUp( pyr, timg, 7 ) |
|
|
|
tgray = cv.CreateImage( (width,height), 8, 1 ) |
|
squares = [] |
|
|
|
# Find squares in every color plane of the image |
|
# Two methods, we use both: |
|
# 1. Canny to catch squares with gradient shading. Use upper threshold |
|
# from slider, set the lower to 0 (which forces edges merging). Then |
|
# dilate canny output to remove potential holes between edge segments. |
|
# 2. Binary thresholding at multiple levels |
|
N = 11 |
|
for c in [0, 1, 2]: |
|
#extract the c-th color plane |
|
cv.SetImageCOI( timg, c+1 ); |
|
cv.Copy( timg, tgray, None ); |
|
cv.Canny( tgray, gray, 0, 50, 5 ) |
|
cv.Dilate( gray, gray) |
|
squares = squares + find_squares_from_binary( gray ) |
|
|
|
# Look for more squares at several threshold levels |
|
for l in range(1, N): |
|
cv.Threshold( tgray, gray, (l+1)*255/N, 255, cv.CV_THRESH_BINARY ) |
|
squares = squares + find_squares_from_binary( gray ) |
|
|
|
return squares |
|
|
|
|
|
RED = (0,0,255) |
|
GREEN = (0,255,0) |
|
def draw_squares( color_img, squares ): |
|
""" |
|
Squares is py list containing 4-pt numpy arrays. Step through the list |
|
and draw a polygon for each 4-group |
|
""" |
|
color, othercolor = RED, GREEN |
|
for square in squares: |
|
cv.PolyLine(color_img, [square], True, color, 3, cv.CV_AA, 0) |
|
color, othercolor = othercolor, color |
|
|
|
cv.ShowImage(WNDNAME, color_img) |
|
|
|
|
|
WNDNAME = "Squares Demo" |
|
def main(): |
|
"""Open test color images, create display window, start the search""" |
|
cv.NamedWindow(WNDNAME, 1) |
|
for name in [ "../c/pic%d.png" % i for i in [1, 2, 3, 4, 5, 6] ]: |
|
img0 = cv.LoadImage(name, 1) |
|
try: |
|
img0 |
|
except ValueError: |
|
print "Couldn't load %s\n" % name |
|
continue |
|
|
|
# slider deleted from C version, same here and use fixed Canny param=50 |
|
img = cv.CloneImage(img0) |
|
|
|
cv.ShowImage(WNDNAME, img) |
|
|
|
# force the image processing |
|
draw_squares( img, find_squares4( img ) ) |
|
|
|
# wait for key. |
|
if cv.WaitKey(-1) % 0x100 == 27: |
|
break |
|
|
|
if __name__ == "__main__": |
|
main() |
|
cv.DestroyAllWindows()
|
|
|