Changeset - c96bd773ec28
[Not reviewed]
default
0 2 1
Laman - 6 years ago 2019-05-03 18:09:55

random image transformations
3 files changed with 106 insertions and 20 deletions:
0 comments (0 inline, 0 general)
exp/keras/prepare_data.py
Show inline comments
 
import os
 
import sys
 
import re
 
import random
 

	
 
import numpy as np
 
import cv2 as cv
 

	
 
sys.path.append("../exp")
 
from annotations import DataFile,computeBoundingBox
 
from annotations import DataFile,computeBoundingBox,Corners
 
from geometry import Line
 
from keras.transformation_matrices import getIdentity,getRotation,getTranslation,getScale,getMirroring,getProjection
 

	
 
random.seed(361)
 

	
 

	
 
class Sample:
 
	SIDE=256
 

	
 
	def __init__(self,img,grid):
 
		self.img=img
 
		self.grid=grid
 

	
 
	def transform(self):
 
		center=self._getCenter()
 
		m=getIdentity()
 
		t1=getTranslation(-center.x,-center.y)
 
		proj=getProjection()
 
		rot=getRotation()
 
		mir=getMirroring()
 
		for mi in [t1,mir,proj,rot]:
 
			m=np.matmul(mi,m)
 
		m=np.matmul(self._computeCrop(m),m)
 
		img=cv.warpPerspective(self.img,m,(self.SIDE,self.SIDE))
 
		grid=Corners(c.transform(m) for c in self.grid)
 
		Sample(img,grid).show()
 

	
 
	def _getCenter(self):
 
		(a,b,c,d)=self.grid
 
		p=Line.fromPoints(a,c)
 
		q=Line.fromPoints(b,d)
 
		return p.intersect(q)
 

	
 
	def _computeCrop(self,m):
 
		grid=Corners(c.transform(m) for c in self.grid)
 
		(x1,y1,x2,y2)=computeBoundingBox(grid)
 
		(wg,hg)=(x2-x1,y2-y1)
 
		(left,top,right,bottom)=[random.uniform(0.05,0.2) for i in range(4)]
 
		t2=getTranslation(left*wg-x1, top*hg-y1)
 
		scale=getScale(self.SIDE/(wg*(1+left+right)), self.SIDE/(hg*(1+top+bottom)))
 
		return np.matmul(scale,t2)
 

	
 
	def show(self):
 
		img=np.copy(self.img)
 
		for c in self.grid:
 
			cv.circle(img,(int(c.x),int(c.y)),3,[0,255,0],-1)
 
		show(img)
 

	
 

	
 
def traverseDirs(root):
 
	stack=[root]
 
	while len(stack)>0:
 
		d=stack.pop()
 
		contents=sorted(os.scandir(d),key=lambda f: f.name,reverse=True)
 
		if any(f.name=="annotations.json.gz" for f in contents):
 
			print(d)
 
			yield d
 
		for f in contents:
 
			if f.is_dir(): stack.append(f.path)
 

	
 

	
 
def harvestDir(path):
 
	annotations=DataFile(os.path.join(path,"annotations.json.gz"))
 
	imgFilter=lambda f: f.is_file() and re.match(r".*\.(jpg|jpeg|png|gif)$", f.name.lower())
 
	files=sorted(filter(imgFilter,os.scandir(path)),key=lambda f: f.name)
 
	boards=annotations["."]
 
	for f in files:
 
		img=cv.imread(f.path)
 
		for b in boards:
 
			crop(img,b)
 

	
 

	
 
def crop(img,board):
 
	margin=0.2
 
	(hi,wi)=img.shape[:2]
 
	(x1,y1,x2,y2)=computeBoundingBox(board.board)
 
	(wb,hb)=(x2-x1,y2-y1)
 
	dx1=min(int(wb*margin),x1)
 
	dx2=min(int(wb*margin),wi-x2)
 
	dy1=min(int(hb*margin),y1)
 
	dy2=min(int(hb*margin),hi-y2)
 
	xa=x1-random.randint(0,dx1)
 
	xb=x2+random.randint(0,dx2)
 
	ya=y1-random.randint(0,dy1)
 
	yb=y2+random.randint(0,dy2)
 
	show(img[ya:yb,xa:xb])
 
	return img[ya:yb,xa:xb]
 
			sample=Sample(img,b.grid)
 
			sample.transform()
 

	
 

	
 
def show(img,filename="x"):
 
	cv.imshow(filename,img)
 
	cv.waitKey(0)
 
	cv.destroyAllWindows()
 

	
 

	
 
if __name__=="__main__":
 
	root=sys.argv[1]
 
	for d in traverseDirs(root):
 
		harvestDir(d)
exp/keras/transformation_matrices.py
Show inline comments
 
new file 100644
 
import math
 
import random
 

	
 
import numpy as np
 

	
 

	
 
def getIdentity():
 
	return np.float32([
 
		[1,0,0],
 
		[0,1,0],
 
		[0,0,1]
 
	])
 

	
 

	
 
def getRotation():
 
	alpha=random.random()*2*math.pi
 
	return np.float32([
 
		[math.cos(alpha),math.sin(alpha),0],
 
		[-math.sin(alpha),math.cos(alpha),0],
 
		[0,0,1]
 
	])
 

	
 

	
 
def getTranslation(dx,dy):
 
	return np.float32([
 
		[1,0,dx],
 
		[0,1,dy],
 
		[0,0,1]
 
	])
 

	
 

	
 
def getScale(kx,ky=0):
 
	if not ky: ky=kx
 
	return np.float32([
 
		[kx,0,0],
 
		[0,ky,0],
 
		[0,0,1]
 
	])
 

	
 

	
 
def getMirroring():
 
	return np.float32([
 
		[random.choice((1,-1)),0,0],
 
		[0,1,0],
 
		[0,0,1]
 
	])
 

	
 

	
 
def getProjection():
 
	dx=random.uniform(-0.001,0.001)
 
	dy=random.uniform(-0.001,0.001)
 
	return np.float32([
 
		[1,0,0],
 
		[0,1,0],
 
		[dx,dy,1]
 
	])
src/analyzer/corners.py
Show inline comments
 
import logging
 

	
 
from .epoint import EPoint
 

	
 
log=logging.getLogger(__name__)
 

	
 

	
 
class Corners:
 
	def __init__(self,cornerList=[]):
 
		self._corners=cornerList[:]
 
		self._corners=list(cornerList)
 
		self._is_canon=False
 
		self._canonizeOrder()
 

	
 
	## Adds a new corner if there are less than four, replaces the closest otherwise.
 
	def add(self,x,y):
 
		a=EPoint(x,y)
 
		# for i,c in enumerate(self.corners): # move an improperly placed point
 
			# if a.dist(c)<20:
 
				# self.corners[i]=a
 
				# return
 

	
 
		if len(self._corners)<4: # add a new corner
 
			self._corners.append(a)
 

	
 
		if len(self._corners)<4:
 
			return
 

	
 
		index,minDist=0,float('inf') # replace the corner closest to the clicked point
 
		for i,c in enumerate(self._corners):
 
			if a.dist(c)<minDist:
 
				index,minDist=i,a.dist(c)
 

	
 
		self._corners[index]=a
 
		self._canonizeOrder()
 

	
 
	## Order the corners (0,1,2,3) so they make a quadrangle with vertices KLMN in counter-clockwise order, K being in the upper left.
 
	#
 
	#  For four points ABCD, there are 24 possible permutations corresponding to the desired KLMN.
 
	#  When we relax the condition of K being the upper left one, we get six groups of four equivalent permutations. KLMN ~ LMNK ~ MNKL ~ NKLM.
 
	#
 
	#  We determine which of the points' triplets are oriented clockwise and which counter-clockwise (minus/plus in the table below)
 
	#  and swap them so that all triangles turn counter-clockwise.
 
	#
 
	#  xxxx -> KLMN | ABC | ABD | ACD | BCD | index | swap
 
	#  ------------ | :-: | :-: | :-: | :-: | ----: | ----
 
	#  A BCD        |  +  |  +  |  +  |  +  |    15 | 0
 
	#  A BDC        |  +  |  +  |  -  |  -  |    12 | CD
 
	#  A CBD        |  -  |  +  |  +  |  -  |     6 | BC
 
	#  A CDB        |  -  |  -  |  +  |  +  |     3 | AB
 
	#  A DBC        |  +  |  -  |  -  |  +  |     9 | AD
 
	#  A DCB        |  -  |  -  |  -  |  -  |     0 | BD
 
	#
 
	#  For every non-degenerate quadrangle, there must be 1-3 edges going right-left (from a higher to a lower x coordinate).
 
	#  From these pick the one with the lowest slope (dy/dx) and declare its ending point the upper left corner. For the same slope pick the one further left.
 
	#
 
	#  @return True for a convex quadrangle, False for concave and degenerate cases.
 
	def _canonizeOrder(self):
 
		def false():
 
			self._is_canon=False
 
			return False
 

	
 
		if len(self._corners)!=4: return false()
 

	
 
		a,b,c,d=self._corners
 
		abc=doubleTriangleArea(a,b,c)
 
		abd=doubleTriangleArea(a,b,d)
 
		acd=doubleTriangleArea(a,c,d)
 
		bcd=doubleTriangleArea(b,c,d)
 

	
 
		if any(x==0 for x in (abc,abd,acd,bcd)): return false() # collinear degenerate
 

	
 
		swaps=[(1,3),(0,1),(1,2),(0,3),(2,3),(0,0)]
 
		index=(8 if abc>0 else 0)|(4 if abd>0 else 0)|(2 if acd>0 else 0)|(1 if bcd>0 else 0)
 
		if index%3!=0: return false() # concave degenerate
 
		swap=swaps[index//3]
 

	
 
		self._corners[swap[0]], self._corners[swap[1]] = self._corners[swap[1]], self._corners[swap[0]] # counter-clockwise order
 

	
 
		kIndex=None
 
		lowestSlope=float("inf")
 

	
 
		for i,corner in enumerate(self._corners): # find the NK edge: going right-left with the lowest slope, secondarily the one going down
 
			ii=(i+1)%4
 
			slope=abs(getSlope(corner, self._corners[ii]))
 
			if corner.x>self._corners[ii].x and (slope < lowestSlope or (slope == lowestSlope and corner.y < self._corners[ii].y)):
 
				kIndex=ii
 
				lowestSlope=slope
 

	
 
		self._corners= self._corners[kIndex:] + self._corners[:kIndex] # rotate the upper left corner to the first place
 

	
 
		self._is_canon=True # success
 
		return True
 

	
 
	def scale(self,scale):
 
		self._corners=[c * scale for c in self._corners]
 

	
 
	def __iter__(self):
 
		return iter(self._corners)
 

	
 
	def __len__(self):
 
		return len(self._corners)
 

	
 
	def is_canon(self):
 
		return self._is_canon
 

	
 

	
0 comments (0 inline, 0 general)