Changeset - ffa9f7f12374
[Not reviewed]
default
0 4 0
Laman - 6 years ago 2019-02-01 17:52:05

experimenting with own Hough transform
4 files changed with 131 insertions and 50 deletions:
0 comments (0 inline, 0 general)
exp/board_detect.py
Show inline comments
 
@@ -13,13 +13,13 @@ import scipy.cluster
 
import scipy.ndimage
 
import scipy.signal
 

	
 
from geometry import Line, point2lineDistance
 
from polar_hough import PolarHough
 
from annotations import DataFile,computeBoundingBox
 
from hough import show
 
from hough import show,prepareEdgeImg,HoughTransform
 
from analyzer.epoint import EPoint
 
from analyzer.corners import Corners
 

	
 
random.seed(361)
 
log.basicConfig(level=log.DEBUG,format="%(message)s")
 

	
 
@@ -61,19 +61,19 @@ def filterStones(contours,bwImg,stoneDim
 
			keep=False
 
		if keep:
 
			res.append((EPoint(*center),c))
 
			cv.drawMarker(contourImg,tuple(map(int,center)),(255,0,0),cv.MARKER_CROSS)
 
	log.debug("accepted: %s",len(res))
 
	log.debug("rejected: %s",len(contours)-len(res))
 
	show(contourImg)
 
	show(contourImg,"accepted and rejected stones")
 
	return res
 

	
 

	
 
def groupLines(points,minCount,tolerance):
 
	random.shuffle(points)
 
	sample=points[:57]
 
	sample=points[:]
 
	for (i,a) in enumerate(sample):
 
		for (j,b) in enumerate(sample):
 
			if j<=i: continue
 
			ab=Line(a,b)
 
			for c in points:
 
				if c is a or c is b: continue
 
@@ -107,29 +107,42 @@ class BoardDetector:
 
		quantized=quantize(rect,colors)
 
		show(quantized,filename)
 

	
 
		# detect black and white stones
 
		stones=self._detectStones(quantized,colors)
 

	
 
		# detect lines passing through the stones
 
		lines=self._constructLines(stones)
 
		# detect lines from edges and stones
 
		edgeImg=prepareEdgeImg(rect)
 
		hough=HoughTransform(edgeImg)
 
		hough.extract()
 
		stonesImg=np.zeros((self._rectH,self._rectW),np.uint8)
 
		for (point,c) in stones:
 
			cv.circle(stonesImg,(int(point.x),int(point.y)),2,255,-1)
 
		# cv.drawContours(stonesImg,[c for (point,c) in stones],-1,255,-1)
 
		show(stonesImg,"detected stones")
 
		# hough.update(stonesImg,5)
 
		hough=HoughTransform(stonesImg)
 
		hough.extract()
 

	
 
		# detect vanishing points of the lines
 
		imgCenter=EPoint(w//2-x1, h//2-y1)
 
		vanish=self._detectVanishingPoints(lines,imgCenter)
 
		(a,b,c,d)=(p-EPoint(x1,y1) for p in self._annotations[filename][0])
 
		(p,q,r,s)=(Line(a,b),Line(b,c),Line(c,d),Line(d,a))
 
		v1=p.intersect(r)
 
		v2=q.intersect(s)
 
		log.debug("true vanishing points: %s ~ %s, %s ~ %s",v1,v1.toPolar(imgCenter),v2,v2.toPolar(imgCenter))
 

	
 
		# rectify the image
 
		matrix=self._computeTransformationMatrix(vanish,lines)
 
		transformed=cv.warpPerspective(rect,matrix,(self._rectW,self._rectH))
 

	
 
		# determine precise board edges
 
		# # detect lines passing through the stones
 
		# lines=self._constructLines(stones)
 
		#
 
		# # detect vanishing points of the lines
 
		# imgCenter=EPoint(w//2-x1, h//2-y1)
 
		# (a,b,c,d)=(p-EPoint(x1,y1) for p in self._annotations[filename][0])
 
		# (p,q,r,s)=(Line(a,b),Line(b,c),Line(c,d),Line(d,a))
 
		# v1=p.intersect(r)
 
		# v2=q.intersect(s)
 
		# log.debug("true vanishing points: %s ~ %s, %s ~ %s",v1,v1.toPolar(imgCenter),v2,v2.toPolar(imgCenter))
 
		# vanish=self._detectVanishingPoints(lines,imgCenter,(v1.toPolar(imgCenter),v2.toPolar(imgCenter)))
 
		#
 
		# # rectify the image
 
		# matrix=self._computeTransformationMatrix(vanish,lines)
 
		# transformed=cv.warpPerspective(rect,matrix,(self._rectW,self._rectH))
 
		#
 
		# # determine precise board edges
 

	
 
	def _detectRough(self,img,filename):
 
		corners=self._annotations[filename][0]
 
		(x1,y1,x2,y2)=computeBoundingBox(corners)
 
		log.debug("bounding box: (%s,%s) - (%s,%s)",x1,y1,x2,y2)
 
		return (x1,y1,x2,y2)
 
@@ -155,26 +168,28 @@ class BoardDetector:
 
		kernel=np.ones((3,3),np.uint8)
 
		maskB=cv.inRange(quantized,colors[0]-unit,colors[0]+unit)
 
		maskB=cv.morphologyEx(maskB,cv.MORPH_OPEN,kernel,iterations=1)
 
		maskB=cv.erode(maskB,kernel,iterations=3)
 
		# distTransform = cv.distanceTransform(maskB,cv.DIST_L2,5)
 
		# maskB=cv.inRange(distTransform,6,10)
 
		show(maskB)
 
		show(maskB,"black areas")
 
		maskW=cv.inRange(quantized,colors[1]-unit,colors[1]+unit)
 
		maskW=cv.morphologyEx(maskW,cv.MORPH_OPEN,kernel,iterations=1)
 
		maskW=cv.erode(maskW,kernel,iterations=2)
 
		show(maskW)
 
		show(maskW,"white areas")
 
		stones=cv.bitwise_or(maskB,maskW)
 
		show(stones)
 
		show(stones,"black and white areas")
 
		return stones
 

	
 
	def _constructLines(self,stoneLocs):
 
		lineDict=dict()
 
		minCount=min(max(math.sqrt(len(stoneLocs))-4,3),7)
 
		# minCount=min(max(math.sqrt(len(stoneLocs))-4,3),7)
 
		minCount=6
 
		log.debug("min count: %s",minCount)
 
		for line in groupLines([point for (point,contour) in stoneLocs],minCount,2):
 
		points=[point for (point,contour) in stoneLocs]
 
		for line in groupLines(points,minCount,2):
 
			key=line.getSortedPoints()
 
			if key in lineDict: # we already have a line with the same incident points
 
				continue
 
			lineDict[line.getSortedPoints()]=line
 
			obsolete=set()
 
			for ab in lineDict.values():
 
@@ -190,30 +205,44 @@ class BoardDetector:
 

	
 
		# visualize
 
		linesImg=cv.cvtColor(np.zeros((self._rectH,self._rectW),np.uint8),cv.COLOR_GRAY2BGR)
 
		cv.drawContours(linesImg,[c for (point,c) in stoneLocs],-1,(255,255,255),-1)
 
		for (p,c) in stoneLocs:
 
			cv.drawMarker(linesImg,(int(p.x),int(p.y)),(255,0,0),cv.MARKER_CROSS)
 
		self._printLines(lines,points,linesImg)
 
		for line in lines:
 
			points=line.getSortedPoints()
 
			(xa,ya)=points[0]
 
			(xb,yb)=points[-1]
 
			cv.line(linesImg,(int(xa),int(ya)),(int(xb),int(yb)),(255,255,0),1)
 
		show(linesImg)
 

	
 
		return lines
 

	
 
	def _detectVanishingPoints(self,lines,imgCenter):
 
	def _printLines(self,lines,allPoints,img):
 
		for (i,line) in enumerate(lines):
 
			img_=np.copy(img)
 
			points=list(line.getSortedPoints())
 
			(a,b)=max(((a,b) for a in points for b in points if a<b),key=lambda ab: ab[0].dist(ab[1]))
 
			(xa,ya)=a
 
			(xb,yb)=b
 
			points.sort(key=lambda p: a.dist(p))
 
			cv.line(img_,(int(xa),int(ya)),(int(xb),int(yb)),(255,255,0),1)
 
			cv.imwrite("/tmp/{0}.png".format(i),img_)
 
			pointDists=",".join(str(round(p1.dist(p2),3)) for (p1,p2) in zip(points[:-1],points[1:]))
 
			log.debug("\t".join(map(str,[i,line,line.score(allPoints),pointDists])))
 

	
 
	def _detectVanishingPoints(self,lines,imgCenter,trueVs):
 
		polarHough=PolarHough(math.pi/180,10)
 
		for (i,ab) in enumerate(lines):
 
			for cd in lines[i+1:]:
 
				point=ab.intersect(cd)
 
				if 0<=point.x<=self._rectW and 0<=point.y<=self._rectH: continue
 
				log.debug("%s -> %s",point,point.toPolar(imgCenter))
 
				# log.debug("%s -> %s",point,point.toPolar(imgCenter))
 
				polarHough.put(point.toPolar(imgCenter))
 
		vanish=[EPoint.fromPolar(p,imgCenter) for p in polarHough.extract(2)]
 
		vanish=[EPoint.fromPolar(p,imgCenter) for p in polarHough.extract(2,trueVs)]
 
		log.debug(vanish)
 
		return vanish
 

	
 
	def _computeTransformationMatrix(self,vanish,lines):
 
		(v1,v2)=vanish
 
		(p,r)=sorted(lines,key=lambda p: point2lineDistance(p.a,p.b,v1))[:2]
exp/geometry.py
Show inline comments
 
@@ -34,12 +34,19 @@ class Line():
 
	def transform(self,matrix):
 
		a=EPoint.fromProjective(transformPoint(self.a.toProjective(),matrix))
 
		b=EPoint.fromProjective(transformPoint(self.b.toProjective(),matrix))
 
		if a is None or b is None: return None
 
		return Line(a,b)
 

	
 
	def score(self,points):
 
		score=len(self.points)
 
		for a in self.points:
 
			closest=sorted(points,key=lambda b: a.dist(b))[:4]
 
			score+=sum(0.01 for b in closest if b in self.points)
 
		return score
 

	
 
	def __str__(self): return "({0},{1})".format(self.a,self.b)
 
	def __repr__(self): return "Line({0},{1})".format(repr(self.a),repr(self.b))
 

	
 

	
 
def point2lineDistance(a,b,p):
 
	# https://en.wikipedia.org/wiki/Point-line_distance#Line_defined_by_two_points
exp/hough.py
Show inline comments
 
import os
 
import sys
 
import math
 
from datetime import datetime
 
import logging as log
 

	
 
import numpy as np
 
import cv2 as cv
 

	
 
from annotations import DataFile,computeBoundingBox
 

	
 

	
 
class HoughTransform:
 
	def __init__(self,img):
 
		(h,w)=img.shape[:2]
 
		diagLen=np.sqrt(h**2+w**2)
 
		self._center=(w//2,h//2)
 
		self._acc=np.zeros((360,int(diagLen//2)+1),dtype=np.int32)
 

	
 
		self.update(img)
 

	
 
	def extract(self):
 
		maxVal=self._acc.max()
 
		arr=np.expand_dims(np.uint8(255*self._acc//maxVal),axis=2)
 
		img=np.concatenate((arr,arr,arr),axis=2)
 
		log.debug(sorted(list(findPeaks(self._acc)),key=lambda rc: self._acc[rc],reverse=True)[:2*19])
 
		show(img,"Hough transform accumulator")
 

	
 
	def update(self,img,weight=1):
 
		start=datetime.now().timestamp()
 
		for (r,row) in enumerate(img):
 
			for (c,pix) in enumerate(row):
 
				if pix==0: continue
 
				for alphaDeg in range(0,360):
 
					d=self._computeDist(c,r,alphaDeg)
 
					if d>=0: self._acc[(alphaDeg,d)]+=weight
 
		log.debug("Hough updated in %s s",round(datetime.now().timestamp()-start,3))
 

	
 
	def _computeDist(self,x,y,alphaDeg):
 
		alphaRad=alphaDeg*math.pi/180
 
		(x0,y0)=self._center
 
		(dx,dy)=(x-x0,y-y0)
 
		d=dx*math.cos(alphaRad)+dy*math.sin(alphaRad)
 
		return int(d)
 

	
 

	
 
def findPeaks(arr2d): # naive implementation
 
	(h,w)=arr2d.shape
 
	neighbours=[(-1,-1),(-1,0),(-1,1),(0,-1),(0,1),(1,-1),(1,0),(1,1)]
 
	for r in range(h):
 
		for c in range(w):
 
			if all(r+dr<0 or r+dr>=h or c+dc<0 or c+dc>=w or arr2d[r,c]>arr2d[r+dr,c+dc] or (i<4 and arr2d[r,c]>=arr2d[r+dr,c+dc]) for (i,(dr,dc)) in enumerate(neighbours)):
 
				yield (r,c)
 

	
 

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

	
 

	
 
def filterVert(edges):
 
	# !! cv.morphologyEx()
 
	kernel = np.array([[1,0,1],[1,0,1],[1,0,1]],np.uint8)
 
	edges = cv.erode(edges,kernel)
 
	kernel=np.array([[0,1,0],[0,1,0],[0,1,0]],np.uint8)
 
	edges=cv.dilate(edges,kernel)
 
	return edges
 

	
 
@@ -38,12 +83,21 @@ def filterDiag(edges):
 
	edges2 = cv.erode(edges,kernel)
 
	kernel=np.array([[0,0,1],[0,1,0],[1,0,0]],np.uint8)
 
	edges2=cv.dilate(edges2,kernel)
 

	
 
	return edges1+edges2
 

	
 
def prepareEdgeImg(img):
 
	gray=cv.cvtColor(img,cv.COLOR_BGR2GRAY)
 
	show(gray,"greyscale image")
 
	edges=cv.Canny(gray,70,130)
 
	show(edges,"Canny edge detector")
 
	edges=filterHor(edges)+filterVert(edges)+filterDiag(edges)
 
	show(edges,"kernel filtered edges")
 
	return edges
 

	
 
def houghLines(bwImg):
 
	colorImg=cv.cvtColor(bwImg,cv.COLOR_GRAY2BGR)
 
	lines = cv.HoughLinesP(bwImg,1,np.pi/180,10,minLineLength=10,maxLineGap=40)
 
	if lines is None: lines=[]
 
	for line in lines:
 
		x1,y1,x2,y2 = line[0]
 
@@ -75,23 +129,10 @@ if __name__=="__main__":
 
	# kernel = np.ones((2,2),np.uint8)
 
	# edges = cv.morphologyEx(edges, cv.MORPH_DILATE, kernel)
 
	# show(edges)
 
	# edges=cv.morphologyEx(edges,cv.MORPH_ERODE,kernel)
 
	# show(edges)
 
	colorEdges=cv.cvtColor(edges,cv.COLOR_GRAY2BGR)
 
	# show(blurred)
 
	# show(small)
 

	
 
	# lines = cv.HoughLines(edges,1,np.pi/180,200)
 
	# if lines is None: lines=[]
 
	# for line in lines:
 
	# 	rho,theta = line[0]
 
	# 	a = np.cos(theta)
 
	# 	b = np.sin(theta)
 
	# 	x0 = a*rho
 
	# 	y0 = b*rho
 
	# 	x1 = int(x0 + 1000*(-b))
 
	# 	y1 = int(y0 + 1000*(a))
 
	# 	x2 = int(x0 - 1000*(-b))
 
	# 	y2 = int(y0 - 1000*(a))
 
	# 	cv.line(colorEdges,(x1,y1),(x2,y2),(0,0,255),1)
 
	houghLines(edges)
 
	# houghLines(edges)
 
	h=HoughTransform(edges)
 
	h.extract()
exp/polar_hough.py
Show inline comments
 
@@ -18,28 +18,32 @@ class PolarHough:
 
		self._acc=[[] for i in range(n)]
 

	
 
	def put(self,item):
 
		k=int(item[0]//self._anglePrecision)
 
		self._acc[k].append(item)
 

	
 
	def extract(self,count):
 
	def extract(self,count,trueVs):
 
		vanishingPoints=[]
 
		angles=self._extractAngles(count)
 
		angles=self._extractAngles(count,tuple(v[0] for v in trueVs))
 
		angles=[alpha for (alpha,prominence) in angles]
 
		bins=self._mapAngles(angles)
 
		for (alpha,bin) in zip(angles,bins):
 
			(length,sampleCount)=self._extractLength([dist for (beta,dist) in bin])
 
			vanishingPoints.append((alpha,length))
 
		return vanishingPoints
 

	
 
	def _extractAngles(self,k):
 
		lens=np.array(list(map(len,self._acc)))
 
		log.debug(lens)
 
	def _extractAngles(self,k,trueAngles):
 
		lens=np.array([0]+list(map(len,self._acc))+[0])
 
		marked=np.copy(lens[1:-1])
 
		for alpha in trueAngles:
 
			key=int(alpha/self._anglePrecision)
 
			marked[key]=-marked[key]-1
 
		log.debug(marked)
 
		(peakKeys,info)=scipy.signal.find_peaks(lens,prominence=0)
 
		res=sorted(zip(info["prominences"],peakKeys),reverse=True)[:k]
 
		res=[(key*self._anglePrecision,prominence) for (prominence,key) in res]
 
		res=[((key-1)*self._anglePrecision,prominence) for (prominence,key) in res]
 
		log.debug("(angle, prominence): %s ... %s",res,[alpha/self._anglePrecision for (alpha,_) in res])
 
		return res
 

	
 
	def _mapAngles(self,angles):
 
		res=[[] for alpha in angles]
 
		for (i,bin) in enumerate(self._acc):
0 comments (0 inline, 0 general)