Load all images from input/, rescaling to max 500x500px and converting to grayscale.
In [3]:
%pylab inline
from IPython.display import Image, display
import PIL.Image
import matplotlib.image as mpimg
import scipy.ndimage
import cv2 # For Sobel etc
import glob
np.set_printoptions(suppress=True, linewidth=200) # Better printing of arrays
plt.rcParams['image.cmap'] = 'jet' # Default colormap is jet
In [4]:
filenames = glob.glob('input/*')
print(len(filenames))
In [5]:
from matplotlib.pyplot import figure, imshow, axis
from matplotlib.image import imread
# hSize = 20
# wSize = 15
col = 4
def showImagesMatrix(image_arr, col=10, cmap_='Greys_r', wSize=15, hSize=20):
fig = figure( figsize=(wSize, hSize))
n = len(image_arr)
row = n/col
if (n%col != 0):
row += 1
for i in range(n):
a=fig.add_subplot(row,col,i+1)
image = image_arr[i]
imshow(image, cmap=cmap_)
axis('off')
In [6]:
# Load all images (after rescaling and grayscale)
img_arr = []
for i in range(len(filenames)):
filename = filenames[i]
img_orig = PIL.Image.open(filename)
img_width, img_height = img_orig.size
# Resize
aspect_ratio = min(500.0/img_width, 500.0/img_height)
new_width, new_height = ((np.array(img_orig.size) * aspect_ratio)).astype(int)
img = img_orig.resize((new_width,new_height), resample=PIL.Image.BILINEAR)
img = img.convert('L') # grayscale
img = np.array(img)
img = cv2.blur(img, (3,3)) # Blur it
img_arr.append(img)
# showImagesMatrix(img_arr,col)
In [7]:
grad_mags = []
grad_phases = []
for img in img_arr:
# img = img_arr[20]
gx = cv2.Sobel(img,cv2.CV_64F,1,0)
gy = cv2.Sobel(img,cv2.CV_64F,0,1)
grad_mag = gx*gx+gy*gy
grad_phase = np.arctan2(gy, gx) # from -pi to pi
grad_phase_masked = grad_phase.copy()
gradient_mask_threshold = 2*np.mean(grad_mag.flatten())
grad_phase_masked[grad_mag < gradient_mask_threshold] = np.nan
# figure( figsize=(15, 15))
# imshow(grad_mag, cmap='Greys_r')
# imshow(grad_phase_masked)
grad_mags.append(grad_mag)
grad_phases.append(grad_phase_masked)
In [8]:
# showImagesMatrix(grad_mags, col=5, cmap_='Greys_r')
In [9]:
# showImagesMatrix(grad_phases, col=5, cmap_='jet')
In [10]:
def getSaddle(gray_img):
img = gray_img.astype(np.float64)
gx = cv2.Sobel(img,cv2.CV_64F,1,0)
gy = cv2.Sobel(img,cv2.CV_64F,0,1)
gxx = cv2.Sobel(gx,cv2.CV_64F,1,0)
gyy = cv2.Sobel(gy,cv2.CV_64F,0,1)
gxy = cv2.Sobel(gx,cv2.CV_64F,0,1)
S = gxx*gyy - gxy**2
return S
In [11]:
def nonmax_sup(img, win=10):
w, h = img.shape
# img = cv2.blur(img, ksize=(5,5))
img_sup = np.zeros_like(img, dtype=np.float64)
for i,j in np.argwhere(img):
# Get neigborhood
ta=max(0,i-win)
tb=min(w,i+win+1)
tc=max(0,j-win)
td=min(h,j+win+1)
cell = img[ta:tb,tc:td]
val = img[i,j]
if np.sum(cell.max() == cell) > 1:
print(cell.argmax())
if cell.max() == val:
img_sup[i,j] = val
return img_sup
In [13]:
# i = 5
# s = saddles[i].copy()
# s2 = nonmax_sup(s)
# print(np.sum(s2!=0))
# pts = np.argwhere(s2!=0)
# # print(pts)
# figure(figsize=(20,20))
# plt.grid()
# imshow(img_arr[i], cmap='Greys_r', interpolation='None')
# plt.plot(pts[:,1], pts[:,0], 'o')
In [14]:
def pruneSaddle(s):
thresh = 128
score = (s>0).sum()
while (score > 10000):
thresh = thresh*2
s[s<thresh] = 0
score = (s>0).sum()
In [15]:
saddles = []
for img in img_arr:
# img = img_arr[20]
saddle = getSaddle(img)
saddle = -saddle
saddle[saddle<0] = 0
pruneSaddle(saddle)
saddles.append(saddle)
# figure( figsize=(15, 15))
# imshow(saddle, cmap='Greys_r')
# imshow(grad_phase_masked)
In [16]:
# showImagesMatrix(saddles, col=5)
In [17]:
s = saddles[0].copy()
plt.figure(figsize=(10,10))
imshow(s, cmap='Greys_r')
# plt.hist(s[:])
Out[17]:
In [65]:
def simplifyContours(contours):
for i in range(len(contours)):
# Approximate contour and update in place
contours[i] = cv2.approxPolyDP(contours[i],0.04*cv2.arcLength(contours[i],True),True)
def is_square(cnt, eps=3.0, xratio_thresh = 0.5):
# 4x2 array, rows are each point, columns are x and y
center = cnt.sum(axis=0)/4
# Side lengths of rectangular contour
dd0 = np.sqrt(((cnt[0,:] - cnt[1,:])**2).sum())
dd1 = np.sqrt(((cnt[1,:] - cnt[2,:])**2).sum())
dd2 = np.sqrt(((cnt[2,:] - cnt[3,:])**2).sum())
dd3 = np.sqrt(((cnt[3,:] - cnt[0,:])**2).sum())
# diagonal ratio
xa = np.sqrt(((cnt[0,:] - cnt[2,:])**2).sum())
xb = np.sqrt(((cnt[1,:] - cnt[3,:])**2).sum())
xratio = xa/xb if xa < xb else xb/xa
# Check whether all points part of convex hull
# ie. not this http://i.stack.imgur.com/I6yJY.png
# all corner angles, angles are less than 180 deg, so not necessarily internal angles
ta = getAngle(dd3, dd0, xb)
tb = getAngle(dd0, dd1, xa)
tc = getAngle(dd1, dd2, xb)
td = getAngle(dd2, dd3, xa)
angle_sum = np.round(ta+tb+tc+td)
is_convex = np.abs(angle_sum - 360) < 5
angles = np.array([ta,tb,tc,td])
good_angles = np.all((angles > 40) & (angles < 140))
# side ratios
dda = dd0 / dd1
if dda < 1:
dda = 1. / dda
ddb = dd1 / dd2
if ddb < 1:
ddb = 1. / ddb
ddc = dd2 / dd3
if ddc < 1:
ddc = 1. / ddc
ddd = dd3 / dd0
if ddd < 1:
ddd = 1. / ddd
side_ratios = np.array([dda,ddb,ddc,ddd])
good_side_ratios = np.all(side_ratios < eps)
# Return whether side ratios within certain ratio < epsilon
return (
# abs(1.0 - dda) < eps and
# abs(1.0 - ddb) < eps and
# xratio > xratio_thresh and
# good_side_ratios and
# is_convex and
good_angles)
def getAngle(a,b,c):
# Get angle given 3 side lengths, in degrees
k = (a*a+b*b-c*c) / (2*a*b)
# Handle floating point errors
if (k < -1):
k=-1
elif k > 1:
k=1
return np.arccos(k) * 180.0 / np.pi
def getContourVals(cnt, img):
cimg = np.zeros_like(img)
cv2.drawContours(cimg, [cnt], 0, color=255, thickness=-1)
return img[cimg!=0]
def pruneContours(contours, hierarchy, saddle):
new_contours = []
new_hierarchies = []
for i in range(len(contours)):
cnt = contours[i]
h = hierarchy[i]
# Must be child
if h[2] != -1:
continue
# Only rectangular contours allowed
if len(cnt) != 4:
continue
# Only contours that fill an area of at least 8x8 pixels
if cv2.contourArea(cnt) < 8*8:
continue
if not is_square(cnt):
continue
# TODO : Remove those where internal luma variance is greater than threshold
cnt = updateCorners(cnt, saddle)
# If not all saddle corners
if len(cnt) != 4:
continue
new_contours.append(cnt)
new_hierarchies.append(h)
new_contours = np.array(new_contours)
new_hierarchy = np.array(new_hierarchies)
if len(new_contours) == 0:
return new_contours, new_hierarchy
# norm_contours = new_contours[:,:,0,:] - new_contours[:,[0],0,:]
# median_contour = np.median(norm_contours, axis=0).astype(int)
# diff = np.sqrt(np.sum((norm_contours - median_contour)**2,axis=2))
# mask=np.all(diff < 60, axis=1)
# # print(mask.shape)
# new_contours = new_contours[mask]
# new_hierarchy = new_hierarchy[mask]
# Prune contours below median area
areas = [cv2.contourArea(c) for c in new_contours]
mask = [areas >= np.median(areas)*0.25] and [areas <= np.median(areas)*2.0]
new_contours = new_contours[mask]
new_hierarchy = new_hierarchy[mask]
return np.array(new_contours), np.array(new_hierarchy)
In [19]:
def getContours(img, edges, iters=10):
# Morphological Gradient to get internal squares of canny edges.
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE,(3,3))
edges_gradient = cv2.morphologyEx(edges, cv2.MORPH_GRADIENT, kernel)
_, contours, hierarchy = cv2.findContours(edges_gradient, cv2.RETR_CCOMP, cv2.CHAIN_APPROX_SIMPLE)
simplifyContours(contours)
return np.array(contours), hierarchy[0]
In [20]:
def updateCorners(contour, saddle):
# print(contour)
ws = 4 # half window size (+1)
new_contour = contour.copy()
for i in range(len(contour)):
# print(i, contour[i,0,:])
cc,rr = contour[i,0,:]
rl = max(0,rr-ws)
cl = max(0,cc-ws)
window = saddle[rl:min(saddle.shape[0],rr+ws+1),cl:min(saddle.shape[1],cc+ws+1)]
# window = saddle[rr-ws:rr+ws+1,cc-ws:cc+ws+1]
# print(window.astype(np.int)/1000)
br, bc = np.unravel_index(window.argmax(), window.shape)
s_score = window[br,bc]
br -= min(ws,rl)
bc -= min(ws,cl)
# print(s_score, br, bc)
if s_score > 0:
new_contour[i,0,:] = cc+bc,rr+br
else:
# print("no saddle")
return []
return new_contour
img = img_arr[6].copy()
s = saddles[6]
edges = cv2.Canny(img, 20, 250)
contours_all, hierarchy = getContours(img,edges)
contours, hierarchy = pruneContours(contours_all, hierarchy, s)
c = contours[2]
c2 = updateCorners(c, s)
# print(c2)
plt.figure(figsize=(10,10))
img = img / 2
# cv2.drawContours(img,[c],-1,155,-1) # Fill mask with 1's inside contour
# img[saddle!=0] /= 2
cv2.drawContours(img,[c2],-1,255,-1) # Fill mask with 1's inside contour
# imshow(img, cmap='Greys_r');
Out[20]:
In [21]:
print(c)
cc,rr = c[0,0,:]
plt.figure(figsize=(30,30))
# imshow(img[rr-15:rr+16,cc-15:cc+16], cmap='Greys_r');
Out[21]:
In [22]:
img = img_arr[6].copy()
s = saddles[6]
edges = cv2.Canny(img, 20, 250)
contours_all, hierarchy = getContours(img,edges)
contours, hierarchy = pruneContours(contours_all, hierarchy, s)
# print(contours.shape)
# print(contours.tolist())
plt.figure(figsize=(10,10))
img = img / 2
# cv2.drawContours(img,contours_all,-1,150,3) # Fill mask with 1's inside contour
cv2.drawContours(img,contours,-1,255,-1) # Fill mask with 1's inside contour
cv2.drawContours(img,contours,-1,150,0) # Fill mask with 1's inside contour
# for i in range(len(contours)):
# cv2.putText(img, '%d' % i, (contours[i,0,0,0], contours[i,0,0,1]-6), cv2.FONT_HERSHEY_SIMPLEX, 0.5, 255)
imshow(img, cmap='Greys_r');
In [23]:
annotated_imgs = []
for i, img in enumerate(img_arr):
edges = cv2.Canny(img, 20, 250)
s = saddles[i]
contours_all, hierarchy = getContours(img,edges)
contours, hierarchy = pruneContours(contours_all, hierarchy, s)
img = img / 2
cv2.drawContours(img,contours,-1,255,-1) # Fill mask with 1's inside contour
cv2.drawContours(img,contours,-1,150,0) # Outline
annotated_imgs.append(img)
# showImagesMatrix(annotated_imgs, col=5, wSize=30, hSize=40)
In [24]:
adj_arr = []
for iii in range(len(img_arr)):
# i = 3
img=img_arr[iii].copy()
# plt.figure(figsize=(10,10))
edges = cv2.Canny(img, 20, 250)
s = saddles[iii]
contours_all, hierarchy = getContours(img,edges)
contours, hierarchy = pruneContours(contours_all, hierarchy, s)
cnts = contours.squeeze().copy()
N = len(cnts)
adj_mtx = np.zeros([N,N],dtype=np.int)
matches = {}
for i in range(N):
for k in range(4):
pt = cnts[i,k,:]
key = "%d_%d" % (pt[0],pt[1])
if matches.has_key(key):
matches[key].append(i)
else:
matches[key] = [i]
# matches[]
# print(matches.values())
for adj_set in matches.values():
if len(adj_set) > 1:
# print(adj_set)
for i in range(len(adj_set)-1):
for j in range(i+1,len(adj_set)):
adj_mtx[adj_set[i],adj_set[j]] += 1
# View
img = img / 2
cv2.drawContours(img,contours,-1,190,-1) # Fill mask with 1's inside contour
cv2.drawContours(img,contours,-1,150,0) # Outline
for i,j in np.argwhere(adj_mtx):
# print(i,j, adj_mtx[i,j])
cnt_a = contours[i]
cnt_b = contours[j]
cnt_a_ctr = cnt_a[:,0,:].sum(axis=0) / 4
cnt_b_ctr = cnt_b[:,0,:].sum(axis=0) / 4
line_str = 55 + adj_mtx[i,j] * 100
cv2.line(img, tuple(cnt_a_ctr), tuple(cnt_b_ctr), line_str,2)
adj_arr.append(img)
# imshow(img, cmap='Greys_r');
In [25]:
showImagesMatrix(adj_arr, col=5, wSize=10, hSize=10)
In [26]:
img = img_arr[6].copy()
s = saddles[6]
edges = cv2.Canny(img, 20, 250)
contours_all, hierarchy = getContours(img,edges)
contours, hierarchy = pruneContours(contours_all, hierarchy, s)
# print(contours.shape)
# print(contours.tolist())
img = img / 2
cnt_i = 0
cnt = contours[cnt_i].squeeze()
print(cnt)
da = cnt[1,:]-cnt[0,:]
db = cnt[2,:]-cnt[1,:]
dc = cnt[3,:]-cnt[2,:]
dd = cnt[0,:]-cnt[3,:]
# cnt += -db*3
cnt2 = cnt.copy()
cnt2[3,:] = cnt[0,:]
cnt2[2,:] = cnt[1,:]
cnt2[0,:] = cnt[0,:] + dd*5
cnt2[1,:] = cnt[1,:] - db*5
cv2.drawContours(img,[cnt],-1,255,-1) # Fill mask with 1's inside contour
cv2.drawContours(img,[cnt],-1,150,0) # Fill mask with 1's inside contour
cv2.drawContours(img,[cnt2],-1,255,-1) # Fill mask with 1's inside contour
cv2.drawContours(img,[cnt2],-1,150,0) # Fill mask with 1's inside contour
plt.figure(figsize=(20,10))
# imshow(img, cmap='Greys_r');
Out[26]:
In [40]:
img = img_arr[6].copy()
s = saddles[6]
edges = cv2.Canny(img, 20, 250)
contours_all, hierarchy = getContours(img,edges)
contours, hierarchy = pruneContours(contours_all, hierarchy, s)
# print(contours.shape)
# print(contours.tolist())
img = img / 2
cnt_i = 0
cnt = contours[cnt_i].squeeze()
# print(cnt)
da = cnt[1,:]-cnt[0,:]
db = cnt[2,:]-cnt[1,:]
dc = cnt[3,:]-cnt[2,:]
dd = cnt[0,:]-cnt[3,:]
# 0 da 1
# dd db
# 3 dc 2
p0 = cnt[0,:]
p1 = cnt[1,:]
p2 = cnt[2,:]
p3 = cnt[3,:]
pA = np.matrix(p0).T + np.matrix(da).T * np.arange(-7,8) # horz 1
pA2 = np.matrix(p3).T + np.matrix(dc).T * np.arange(-7,8) # horz 2
# vert
pB = np.matrix(p0).T + np.matrix(dd).T * np.arange(-7,8)
pB2 = np.matrix(p1).T + np.matrix(db).T * np.arange(-7,8)
# cnt += -db*3
# cnt2 = cnt.copy()
# cnt2[3,:] = cnt[0,:]
# cnt2[2,:] = cnt[1,:]
# cnt2[0,:] = cnt[0,:] + dd*5
# cnt2[1,:] = cnt[1,:] - db*5
plt.figure(figsize=(20,10))
imshow(img, cmap='Greys_r');
plt.plot(pA[0,:].A, pA[1,:].A, 'og')
plt.plot(pA2[0,:].A, pA2[1,:].A, 'xg')
plt.plot(pB[0,:].A, pB[1,:].A, 'ob')
plt.plot(pB2[0,:].A, pB2[1,:].A, 'xb')
plt.plot(p0[0], p0[1], 'or');
In [149]:
def getIdentityGrid(N):
a = np.arange(N)
b = a.copy()
aa,bb = np.meshgrid(a,b)
return np.vstack([aa.flatten(), bb.flatten()]).T
def getChessGrid(quad):
quadA = np.array([[0,1],[1,1],[1,0],[0,0]],dtype=np.float32)
M = cv2.getPerspectiveTransform(quadA, quad.astype(np.float32))
quadB = getIdentityGrid(4)-1
quadB_pad = np.pad(quadB, ((0,0),(0,1)), 'constant', constant_values=1)
C_thing = (np.matrix(M)*quadB_pad.T).T
# bad = (C_thing[:,2] < 0.3).A.flatten()
C_thing[:,:2] /= C_thing[:,2]
return C_thing
img_i = 6
img = img_arr[img_i].copy()
s = saddles[img_i]
cnt_i = 2
cnt = contours[cnt_i].squeeze()
s2 = nonmax_sup(s)
s2[s2<100000]=0
spts = np.argwhere(s2)
def getMinSaddleDist(saddle_pts, pt):
best_dist = None
best_pt = pt
for saddle_pt in saddle_pts:
saddle_pt = saddle_pt[::-1]
dist = np.sum((saddle_pt - pt)**2)
if best_dist is None or dist < best_dist:
best_dist = dist
best_pt = saddle_pt
return best_pt, np.sqrt(best_dist)
# print(s2[spts[:,0],spts[:,1]].min())
warp_grid = getChessGrid(cnt)
warp_grid_fix = warp_grid.copy()
for pt_i in range(len(warp_grid_fix)):
pt2, d = getMinSaddleDist(spts, warp_grid_fix[pt_i,:2].A.flatten())
if (d < 5): # px
warp_grid_fix[pt_i,:2] = pt2
plt.figure(figsize=(20,10))
img_over = img.copy()
cv2.drawContours(img_over,[cnt],-1,255,0) # Fill mask with 1's inside contour
imshow(img_over, cmap='Greys_r');
axs = plt.axis()
plt.plot(spts[:,1],spts[:,0],'o')
plt.plot(warp_grid[:,0].A, warp_grid[:,1].A,'r.')
plt.plot(warp_grid_fix[:,0].A, warp_grid_fix[:,1].A,'ro')
kA = cnt[1,:]
kB, d = getMinSaddleDist(spts, cnt[1,:])
print(kA, kB, d)
# plt.plot(kA[0], kA[1],'ys')
plt.plot([kA[0],kB[0]], [kA[1],kB[1]],'ys-')
plt.axis(axs)
# vals = getContourVals(cnt, img)
# print(np.std(vals))
# print(spts == [214,308])
# print(spts)
Out[149]:
Start with quad, get perspective transform M, take points for 3x3 quad around it, keep only those where a saddle is within N pixels. Redo M transform with new set of good points, rinse repeat till we have a full 9x9 grid.
In [654]:
def findGoodPoints(grid, spts, max_px_dist=5):
# Snap grid points to closest saddle point within range and return updated
# grid = Nx2 points on grid
new_grid = grid.copy()
chosen_spts = set()
N = len(new_grid)
grid_good = np.zeros(N,dtype=np.bool)
hash_pt = lambda pt: "%d_%d" % (pt[0], pt[1])
for pt_i in range(N):
pt2, d = getMinSaddleDist(spts, grid[pt_i,:2].A.flatten())
if hash_pt(pt2) in chosen_spts:
d = max_px_dist
else:
chosen_spts.add(hash_pt(pt2))
if (d < max_px_dist): # max dist to replace with
new_grid[pt_i,:2] = pt2
grid_good[pt_i] = True
return new_grid, grid_good
def getInitChessGrid(quad):
quadA = np.array([[0,1],[1,1],[1,0],[0,0]],dtype=np.float32)
M = cv2.getPerspectiveTransform(quadA, quad.astype(np.float32))
return makeChessGrid(M,1)
def makeChessGrid(M, N=1):
ideal_grid = getIdentityGrid(2+2*N)-N
ideal_grid_pad = np.pad(ideal_grid, ((0,0),(0,1)), 'constant', constant_values=1) # Add 1's column
# warped_pts = M*pts
grid = (np.matrix(M)*ideal_grid_pad.T).T
grid[:,:2] /= grid[:,2] # normalize by t
grid = grid[:,:2] # remove 3rd column
return grid, ideal_grid, M
def generateNewBestFit(grid_ideal, grid, grid_good):
a = np.float32(grid_ideal[grid_good])
b = np.float32(grid[grid_good])
M = cv2.findHomography(a, b, cv2.RANSAC)
return M
In [653]:
img_i = 13
img = img_arr[img_i].copy()
s = saddles[img_i]
edges = cv2.Canny(img, 20, 250)
contours_all, hierarchy = getContours(img,edges)
contours, hierarchy = pruneContours(contours_all, hierarchy, s)
for cnt_i in range(len(contours)):
# cnt_i = 5
print ("On Contour %d" % cnt_i)
cnt = contours[cnt_i].squeeze()
s2 = nonmax_sup(s)
s2[s2<100000]=0
spts = np.argwhere(s2)
grid_curr, ideal_grid, M = getInitChessGrid(cnt)
# print(M)
for grid_i in range(7):
grid_curr, ideal_grid, _ = makeChessGrid(M, N=(grid_i+1))
grid_next, grid_good = findGoodPoints(grid_curr, spts)
M, _ = generateNewBestFit(ideal_grid, grid_next, grid_good)
print('I %d (N=%d), num_good: %d of %d' % (grid_i, grid_i+1, np.sum(grid_good), grid_good.size))
if M is None:
print ("Failed to converge on this one")
break
# print(M2)
if M is None:
continue
elif np.sum(grid_good) > 30:
break
# View
plt.figure(figsize=(20,10))
img_over = img.copy()
cv2.drawContours(img_over,[cnt],-1,255,0) # Fill mask with 1's inside contour
imshow(img_over, cmap='Greys_r');
axs = plt.axis()
plt.plot(spts[:,1],spts[:,0],'o')
plt.plot(grid_next[:,0].A, grid_next[:,1].A,'rs')
plt.plot(grid_next[grid_good,0].A, grid_next[grid_good,1].A,'rs', markersize=12)
# plt.plot(grid_step2[:,0].A, grid_step2[:,1].A,'gs')
# plt.plot([kA[0],kB[0]], [kA[1],kB[1]],'ys-')
plt.axis(axs)
Out[653]:
In [524]:
def getGrads(img):
img = cv2.blur(img,(5,5))
gx = cv2.Sobel(img,cv2.CV_64F,1,0)
gy = cv2.Sobel(img,cv2.CV_64F,0,1)
grad_mag = gx*gx+gy*gy
grad_phase = np.arctan2(gy, gx) # from -pi to pi
grad_phase_masked = grad_phase.copy()
gradient_mask_threshold = 2*np.mean(grad_mag.flatten())
grad_phase_masked[grad_mag < gradient_mask_threshold] = np.nan
return grad_mag, grad_phase_masked, grad_phase, gx, gy
# grad_mag, grad_phase_masked, grad_phase, gx, gy = getGrads(img_warp)
def getBestLines(img_warped):
grad_mag, grad_phase_masked, grad_phase, gx, gy = getGrads(img_warped)
# X
gx_pos = gx.copy()
gx_pos[gx_pos < 0] = 0
gx_neg = -gx.copy()
gx_neg[gx_neg < 0] = 0
score_x = np.sum(gx_pos, axis=0) * np.sum(gx_neg, axis=0)
# Y
gy_pos = gy.copy()
gy_pos[gy_pos < 0] = 0
gy_neg = -gy.copy()
gy_neg[gy_neg < 0] = 0
score_y = np.sum(gy_pos, axis=1) * np.sum(gy_neg, axis=1)
# Choose best internal set of 7
a = np.array([(offset + np.arange(7) + 1)*32 for offset in np.arange(1,11-2)])
scores_x = np.array([np.sum(score_x[pts]) for pts in a])
scores_y = np.array([np.sum(score_y[pts]) for pts in a])
# 15x15 grid, so along an axis a set of 7, and an internal 7 at that, so 13x13 grid, 7x7 possibility inside
# We're also using a 1-padded grid so 17x17 grid
# We only want the internal choices (13-7) so 6x6 possible options in the 13x13
# so 2,3,4,5,6,7,8 to 8,9,10,11,12,13,14 ignoring 0,1 and 15,16,17
best_lines_x = a[scores_x.argmax()]
best_lines_y = a[scores_y.argmax()]
return (best_lines_x, best_lines_y)
In [708]:
def loadImage(filepath):
img_orig = PIL.Image.open(filepath)
img_width, img_height = img_orig.size
# Resize
aspect_ratio = min(500.0/img_width, 500.0/img_height)
new_width, new_height = ((np.array(img_orig.size) * aspect_ratio)).astype(int)
img = img_orig.resize((new_width,new_height), resample=PIL.Image.BILINEAR)
img = img.convert('L') # grayscale
img = np.array(img)
return img
def findChessboard(img, min_pts_needed=15, max_pts_needed=25):
blur_img = cv2.blur(img, (3,3)) # Blur it
saddle = getSaddle(blur_img)
saddle = -saddle
saddle[saddle<0] = 0
pruneSaddle(saddle)
s2 = nonmax_sup(saddle)
s2[s2<100000]=0
spts = np.argwhere(s2)
edges = cv2.Canny(img, 20, 250)
contours_all, hierarchy = getContours(img, edges)
contours, hierarchy = pruneContours(contours_all, hierarchy, saddle)
curr_num_good = 0
curr_grid_next = None
curr_grid_good = None
curr_M = None
for cnt_i in range(len(contours)):
#print ("On Contour %d" % cnt_i)
cnt = contours[cnt_i].squeeze()
grid_curr, ideal_grid, M = getInitChessGrid(cnt)
for grid_i in range(7):
grid_curr, ideal_grid, _ = makeChessGrid(M, N=(grid_i+1))
grid_next, grid_good = findGoodPoints(grid_curr, spts)
num_good = np.sum(grid_good)
#print('I %d (N=%d), num_good: %d of %d' % (grid_i, grid_i+1, num_good, grid_good.size))
if num_good < 4:
M = None
#print ("Failed to converge on this one")
break
M, _ = generateNewBestFit(ideal_grid, grid_next, grid_good)
# Check that a valid and reasonable M was returned
if M is None or np.abs(M[0,0] / M[1,1]) > 15 or np.abs(M[1,1] / M[0,0]) > 15:
# if M is None:
M = None
#print ("Failed to converge on this one")
break
if M is None:
continue
elif num_good > curr_num_good:
curr_num_good = num_good
curr_grid_next = grid_next
curr_grid_good = grid_good
curr_M = M
# If we found something with more than max needed, good enough to stop here
if num_good > max_pts_needed:
break
# If we found something
if curr_num_good > min_pts_needed:
final_ideal_grid = getIdentityGrid(2+2*7)-7
return curr_M, final_ideal_grid, curr_grid_next, curr_grid_good, spts
else:
return None, None, None, None, None
# return M, ideal_grid, grid_next, grid_good, spts
img = loadImage('input/img_10.png')
M, ideal_grid, grid_next, grid_good, spts = findChessboard(img)
print(M)
# View
if M is not None:
M, _ = generateNewBestFit((ideal_grid+8)*32, grid_next, grid_good) # generate mapping for warping image
print(M)
img_warp = cv2.warpPerspective(img, M, (17*32, 17*32), flags=cv2.WARP_INVERSE_MAP)
best_lines_x, best_lines_y = getBestLines(img_warp)
xy_unwarp = getUnwarpedPoints(best_lines_x, best_lines_y, M)
plt.figure(figsize=(20,20))
plt.subplot(212)
imshow(img_warp, cmap='Greys_r')
# plt.figure(figsize=(20,10))
[plt.axvline(line, color='red', lw=2) for line in best_lines_x];
[plt.axhline(line, color='green', lw=2) for line in best_lines_y];
plt.subplot(211)
axs = plt.axis()
imshow(img, cmap='Greys_r');
axs = plt.axis()
plt.plot(spts[:,1],spts[:,0],'o')
plt.plot(grid_next[:,0].A, grid_next[:,1].A,'rs')
plt.plot(grid_next[grid_good,0].A, grid_next[grid_good,1].A,'rs', markersize=12)
plt.plot(xy_unwarp[:,0], xy_unwarp[:,1], 'go', markersize=15)
plt.axis(axs)
Now that we have matched an oversized grid to the chessboard, warp the image for that oversized grid. Then we know the chessboard is somewhere in the ~64 possible start positions. Convolve an ideal black/white grid with all 64 points (maybe an inverted one too) to get the best chessboard position. Better perhaps apply black/white and white/black grid to full grid to find orientation, then strip sides until you have the best internal 8, for both
In [671]:
def getUnwarpedPoints(best_lines_x, best_lines_y, M):
x,y = np.meshgrid(best_lines_x, best_lines_y)
xy = np.vstack([x.flatten(), y.flatten()]).T.astype(np.float32)
xy = np.expand_dims(xy,0)
xy_unwarp = cv2.perspectiveTransform(xy, M)
return xy_unwarp[0,:,:]
xy_unwarp = getUnwarpedPoints(best_lines_x, best_lines_y, M)
imshow(img, cmap='Greys_r');
axs = plt.axis()
plt.plot(xy_unwarp[:,0], xy_unwarp[:,1], 'ro')
plt.axis(axs);
In [492]:
a = np.array([(offset + np.arange(7) + 1)*32 for offset in np.arange(2,11-3)])
scores = np.array([np.sum(score_x[pts]) for pts in a])
# 15x15 grid, so along an axis a set of 7, and an internal 7 at that, so 13x13 grid, 7x7 possibility inside
# We're also using a 1-padded grid so 17x17 grid
# We only want the internal choices (13-7) so 6x6 possible options in the 13x13
# so 3,4,5,6,7,8 to 9,10,11,12,13,14 ignoring 0,1,2 and 15,16,17
best_lines = a[scores.argmax()]
print(best_lines)
plt.plot(score_x)
for i in range(17):
plt.axvline(i*32, color='green')
[plt.axvline(line, color='red') for line in best_lines]
plt.xlim([0,544])
Out[492]:
Idea 1 - Choose random tile, splay out all points, ICP to 9x9 with strongest saddle scores Idea 2 - For all tiles, splay out all points, keep points that overlap within error and/or near saddle Idea 3 - self-organizing map, use 9x9 grid of points with initial shape from random tile. The overall energy then is the deviation from this shape, for each saddle peak (randomly N times) pull closest point, pull other points elastically. Idea 4 - Choose random tile, brute force all 9x9 grids, score each based on closest saddle per each (up to max), keep the min scoring one.
In [689]:
def getBoardOutline(best_lines_x, best_lines_y, M):
d = best_lines_x[1] - best_lines_x[0]
ax = [best_lines_x[0]-d, best_lines_x[-1]+d]
ay = [best_lines_y[0]-d, best_lines_y[-1]+d]
x,y = np.meshgrid(ax, ay)
xy = np.vstack([x.flatten(), y.flatten()]).T.astype(np.float32)
xy = xy[[0,1,3,2,0],:]
xy = np.expand_dims(xy,0)
xy_unwarp = cv2.perspectiveTransform(xy, M)
return xy_unwarp[0,:,:]
In [704]:
filenames = glob.glob('input/*')
fig = figure( figsize=(40, 40))
n = len(filenames)
row = n/col
if (n%col != 0):
row += 1
for i in range(n):
filename = filenames[i]
print ("Processing %d/%d : %s" % (i+1,n,filename))
img = loadImage(filename)
M, ideal_grid, grid_next, grid_good, spts = findChessboard(img)
# View
if M is not None:
M, _ = generateNewBestFit((ideal_grid+8)*32, grid_next, grid_good) # generate mapping for warping image
img_warp = cv2.warpPerspective(img, M, (17*32, 17*32), flags=cv2.WARP_INVERSE_MAP)
best_lines_x, best_lines_y = getBestLines(img_warp)
xy_unwarp = getUnwarpedPoints(best_lines_x, best_lines_y, M)
board_outline_unwarp = getBoardOutline(best_lines_x, best_lines_y, M)
a=fig.add_subplot(row,col,i+1)
axs = plt.axis()
imshow(img, cmap='Greys_r');
axs = plt.axis()
# plt.plot(spts[:,1],spts[:,0],'o')
# plt.plot(grid_next[:,0].A, grid_next[:,1].A,'rs')
# plt.plot(grid_next[grid_good,0].A, grid_next[grid_good,1].A,'rs', markersize=3)
plt.plot(xy_unwarp[:,0], xy_unwarp[:,1], 'r.',)
plt.plot(board_outline_unwarp[:,0], board_outline_unwarp[:,1], 'ro-', markersize=5, linewidth=3)
plt.axis(axs)
plt.title("%s : N %d" % (filename, np.sum(grid_good)))
axis('off')
print(" N good %d" % np.sum(grid_good))
else:
a=fig.add_subplot(row,col,i+1)
imshow(img, cmap='Greys_r');
plt.title("%s : Fail" % (filename))
print(" Fail")
In [ ]: