Skip to content

Instantly share code, notes, and snippets.

@bhive01
Last active August 27, 2016 21:25
Show Gist options
  • Save bhive01/b3f783e5ffd3e8c589041b297dfb9f03 to your computer and use it in GitHub Desktop.
Save bhive01/b3f783e5ffd3e8c589041b297dfb9f03 to your computer and use it in GitHub Desktop.
import argparse
import trans #pip install trans
import time
import cv2
import math
import pandas as pd
import numpy as np
def distance(p, q):
return math.sqrt(math.pow(math.fabs(p[0]-q[0]),2)+math.pow(math.fabs(p[1]-q[1]),2))
def lineEquation(l, m, j):
a = -((m[1] - l[1])/(m[0] - l[0]))
b = 1.0
c = (((m[1] - l[1])/(m[0] - l[0]))*l[0]) - l[1]
try:
pdist = (a*j[0]+(b*j[1])+c)/math.sqrt((a*a)+(b*b))
except:
return 0
else:
return pdist
def lineSlope(l, m):
dx = m[0] - l[0]
dy = m[1] - l[1]
if dy != 0:
align = 1
dxy = dy/dx
return dxy, align
else:
align = 0
dxy = 0.0
return dxy, align
def getSquares(contours,cid):
x,y,w,h= cv2.boundingRect(contours[cid])
return x,y,w,h
def updateCorner(p,ref,baseline,corner):
temp_dist = distance(p,ref)
if temp_dist > baseline:
baseline = temp_dist
corner = p
return baseline,corner
def getVertices(contours, cid, slope, quad):
M0 = (0.0,0.0)
M1 = (0.0,0.0)
M2 = (0.0,0.0)
M3 = (0.0,0.0)
x,y,w,h = cv2.boundingRect(contours[cid])
A = (x, y)
B = (x+w, y)
C = (x+w, h+y)
D = (x, y+h)
W = ((A[0]+B[0])/2, A[1])
X = (B[0], (B[1]+C[1])/2)
Y = ((C[0]+D[0])/2, C[1])
Z = (D[0], (D[1]+A[1])/2)
dmax = []
for i in range(4):
dmax.append(0.0)
pd1 = 0.0
pd2 = 0.0
if(slope > 5 or slope < -5 ):
for i in range(len(contours[cid])):
pd1 = lineEquation(C, A, contours[cid][i])
pd2 = lineEquation(B, D, contours[cid][i])
if(pd1 >= 0.0 and pd2 > 0.0):
dmax[1], M1 = updateCorner(contours[cid][i], W, dmax[1], M1)
elif(pd1 > 0.0 and pd2 <= 0):
dmax[2], M2 = updateCorner(contours[cid][i], X, dmax[2], M2)
elif(pd1 <= 0.0 and pd2 < 0.0):
dmax[3], M3 = updateCorner(contours[cid][i], Y, dmax[3], M3)
elif(pd1 < 0 and pd2 >= 0.0):
dmax[0], M0 = updateCorner(contours[cid][i], Z, dmax[0], M0)
else:
continue
else:
halfx = (A[0]+B[0])/2
halfy = (A[1]+D[1])/2
for i in range(len(contours[cid])):
if(contours[cid][i][0][0]<halfx and contours[cid][i][0][1]<=halfy):
dmax[2], M0 = updateCorner(contours[cid][i][0], C, dmax[2], M0)
elif(contours[cid][i][0][0]>=halfx and contours[cid][i][0][1]<halfy):
dmax[3], M1 = updateCorner(contours[cid][i][0], D, dmax[3], M1)
elif(contours[cid][i][0][0]>halfx and contours[cid][i][0][1]>=halfy):
dmax[0], M2 = updateCorner(contours[cid][i][0], A, dmax[0], M2)
elif(contours[cid][i][0][0]<=halfx and contours[cid][i][0][1]>halfy):
dmax[1], M3 = updateCorner(contours[cid][i][0], B, dmax[1], M3)
quad.append(M0)
quad.append(M1)
quad.append(M2)
quad.append(M3)
return quad
def updateCornerOr(orientation,IN):
if orientation == 0:
M0 = IN[0]
M1 = IN[1]
M2 = IN[2]
M3 = IN[3]
elif orientation == 1:
M0 = IN[1]
M1 = IN[2]
M2 = IN[3]
M3 = IN[0]
elif orientation == 2:
M0 = IN[2]
M1 = IN[3]
M2 = IN[0]
M3 = IN[1]
elif orientation == 3:
M0 = IN[3]
M1 = IN[0]
M2 = IN[1]
M3 = IN[2]
OUT = []
OUT.append(M0)
OUT.append(M1)
OUT.append(M2)
OUT.append(M3)
return OUT
def cross(v1,v2):
cr = v1[0]*v2[1] - v1[1]*v2[0]
return cr
def getIntersection(a1,a2,b1,b2,intersection):
p = a1
q = b1
r = (a2[0]-a1[0],a2[1]-a1[1])
s = (b2[0]-b1[0],b2[1]-b1[1])
if cross(r, s) == 0:
return False, intersection
t = cross((q[0]-p[0],q[1]-p[1]), s)/float(cross(r, s))
intersection = (int(p[0]+(t*r[0])),int(p[1]+(t*r[1])))
return True, intersection
def order_points(pts):
# initialzie a list of coordinates that will be ordered
# such that the first entry in the list is the top-left,
# the second entry is the top-right, the third is the
# bottom-right, and the fourth is the bottom-left
rect = np.zeros((4, 2), dtype = "float32")
# the top-left point will have the smallest sum, whereas
# the bottom-right point will have the largest sum
s = pts.sum(axis = 1)
rect[0] = pts[np.argmin(s)]
rect[2] = pts[np.argmax(s)]
# now, compute the difference between the points, the
# top-right point will have the smallest difference,
# whereas the bottom-left will have the largest difference
diff = np.diff(pts, axis = 1)
rect[1] = pts[np.argmin(diff)]
rect[3] = pts[np.argmax(diff)]
# return the ordered coordinates
return rect
def four_point_transform(image, pts, expand):
# obtain a consistent order of the points and unpack them
# individually
rect = order_points(pts)
# expand allows border around code in px
if expand != 0:
rect[0] = rect[0] + (-1*expand, -1*expand)
rect[1] = rect[1] + (expand, -1*expand)
rect[2] = rect[2] + (expand, expand)
rect[3] = rect[3] + (-1*expand, expand)
(tl, tr, br, bl) = rect
# compute the width of the new image, which will be the
# maximum distance between bottom-right and bottom-left
# x-coordiates or the top-right and top-left x-coordinates
widthA = np.sqrt(((br[0] - bl[0]) ** 2) + ((br[1] - bl[1]) ** 2))
widthB = np.sqrt(((tr[0] - tl[0]) ** 2) + ((tr[1] - tl[1]) ** 2))
maxWidth = max(int(widthA), int(widthB))
# compute the height of the new image, which will be the
# maximum distance between the top-right and bottom-right
# y-coordinates or the top-left and bottom-left y-coordinates
heightA = np.sqrt(((tr[0] - br[0]) ** 2) + ((tr[1] - br[1]) ** 2))
heightB = np.sqrt(((tl[0] - bl[0]) ** 2) + ((tl[1] - bl[1]) ** 2))
maxHeight = max(int(heightA), int(heightB))
# now that we have the dimensions of the new image, construct
# the set of destination points to obtain a "birds eye view",
# (i.e. top-down view) of the image, again specifying points
# in the top-left, top-right, bottom-right, and bottom-left
# order
dst = np.array([
[0, 0],
[maxWidth - 1, 0],
[maxWidth - 1, maxHeight - 1],
[0, maxHeight - 1]], dtype = "float32")
# compute the perspective transform matrix and then apply it
M = cv2.getPerspectiveTransform(rect, dst)
warped = cv2.warpPerspective(image, M, (maxWidth, maxHeight))
# return the warped image
return warped
#cv2.namedWindow("Original", cv2.WINDOW_NORMAL)
#cv2.namedWindow("CannyEdges", cv2.WINDOW_NORMAL)
cv2.namedWindow("mask", cv2.WINDOW_NORMAL)
#cv2.resizeWindow('Original', 600,600)
#cv2.resizeWindow('CannyEdges', 600,600)
ap = argparse.ArgumentParser()
ap.add_argument("-i", "--image", required = True,
help = "Path to the image")
args = vars(ap.parse_args())
img = cv2.imread(args["image"])
height, width, channels = img.shape
totalpx = height*width
minarea = 1000./12000000. * totalpx # 1000px^2/12MP smallest size acceptable square
maxarea = 150000./12000000. * totalpx #150kpx^2/12MP largest size acceptable square
edges = cv2.Canny(img, 100, 200)
cv2.imwrite("edges.png", edges)
#compute the contours to find the squares of the card
_, contours, hierarchy = cv2.findContours(edges, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
mindex = []
mu = [] # variable to store moments
mc = [] # variable to x,y coordinates in tuples
mx = [] # variable to x coordinate as integer
my = [] # variable to y coordinate as integer
marea = [] # variable to store area
msquare = [] # variable to store whether something is a square (1) or not (0)
mchild = [] # variable to store child hierarchy element
mheight = [] # fitted rectangle height
mwidth = [] # fitted rectangle width
mwhratio = [] # ratio of height/width
mcolorR = [] # variable to store R
mcolorG = [] # variable to store G
mcolorB = [] # variable to store B
mark = 0 # for counting the 3 marks in corners of QR Code
# extract moments from contour image
for x in range(0, len(contours)):
mu.append(cv2.moments(contours[x]))
marea.append(cv2.contourArea(contours[x]))
mchild.append(int(hierarchy[0][x][2]))
mindex.append(x)
# cycle through moment data and compute location for each moment
for m in mu:
if m['m00'] != 0: # this is the area term for a moment
mc.append((int(m['m10']/m['m00']), int(m['m01']/m['m00'])))
mx.append(int(m['m10']/m['m00']))
my.append(int(m['m01']/m['m00']))
else:
mc.append((0,0))
mx.append(0)
my.append(0)
# loop over our contours
for index, c in enumerate(contours):
if (marea[index] != 0 and marea[index] > minarea and marea[index] < maxarea): # area is greater than minarea
# finding squares
# from http://www.pyimagesearch.com/2014/04/21/building-pokedex-python-finding-game-boy-screen-step-4-6/
# approximate the contour
peri = cv2.arcLength(c, True)
approx = cv2.approxPolyDP(c, 0.02 * peri, True)
center, wh, angle = cv2.minAreaRect(c)
mwidth.append(wh[0])
mheight.append(wh[1])
mwhratio.append(wh[0]/wh[1])
# if our approximated contour has four points, then
# we can assume that we have found our screen
if len(approx) == 4:
msquare.append(1)
# if it's four pointed construct a mask for the contour, then compute mean RGB
# creates a mask equal in size to img
mask = np.zeros(img.shape[:2], dtype="uint8")
# draw and fill contour
cv2.drawContours(mask, [c], -1, 255, -1)
# erode slightly to get away from dirty edges
#mask = cv2.erode(mask, None, iterations = 2)
# extract median BGR value
mean = cv2.mean(img, mask=mask)[:3]
# append color data to mcolor array
mcolorR.append(int(mean[2]))
mcolorG.append(int(mean[1]))
mcolorB.append(int(mean[0]))
else:
# it's not a square
msquare.append(0)
mcolorR.append(0)
mcolorG.append(0)
mcolorB.append(0)
else:
# contour has an area of zero, not interesting
msquare.append(0)
mcolorR.append(0)
mcolorG.append(0)
mcolorB.append(0)
mwidth.append(0)
mheight.append(0)
mwhratio.append(0)
#make a pandas df
locarea = {'index' : mindex, 'X' : mx, 'Y' : my, 'width' : mwidth, 'height' : mheight, 'WHratio': mwhratio, 'Area' : marea, 'R' : mcolorR, 'G' : mcolorG, 'B' : mcolorB, 'square' : msquare, 'child' : mchild}
df = pd.DataFrame(locarea)
# filter df for attributes that would isolate squares of reasonable size
df2 = df[(df['Area'] > minarea) & (df['Area'] < maxarea) & (df['child'] != -1) & (df['square'] > 0) & (df['WHratio'] < 1.06) & (df['WHratio'] > 0.94)] #&
#print df
print df2
for index in df2['index']:
cv2.drawContours(img, contours, index, (0,0,255), -1)
cv2.imwrite("squares.png", img)
@bhive01
Copy link
Author

bhive01 commented Aug 27, 2016

https://www.reddit.com/r/computervision/comments/4zr7q9/image_color_correction_finding_color_chart/

Suggestion to use Template matching.
http://docs.opencv.org/3.1.0/d4/dc6/tutorial_py_template_matching.html

Another to use Otsu thresholding, which appears to find more squares than contours alone.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment