Last active
August 27, 2016 21:25
-
-
Save bhive01/b3f783e5ffd3e8c589041b297dfb9f03 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.