Changeset - 353129d558d3
[Not reviewed]
default
1 1 1
Laman - 6 years ago 2019-01-23 12:28:55

refactored pipeline into BoardDetector with separate methods
2 files changed with 97 insertions and 63 deletions:
0 comments (0 inline, 0 general)
exp/board_detect.py
Show inline comments
 
file renamed from exp/stone_detect.py to exp/board_detect.py
 
import sys
 

	
 
from polar_hough import PolarHough
 

	
 
sys.path.append("../src")
 

	
 
import os
 
import math
 
import random
 
import logging as log
 

	
 
import cv2 as cv
 
import numpy as np
 
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 analyzer.epoint import EPoint
 

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

	
 

	
 
def kmeans(img):
 
	arr=np.reshape(img,(-1,3)).astype(np.float)
 
	colors=np.array([[0,0,0],[255,255,255],[193,165,116]],np.float)
 
	print(colors)
 
	log.debug(colors)
 
	(centers,distortion)=scipy.cluster.vq.kmeans(arr,colors)
 
	print("k-means centers:",centers)
 
	log.debug("k-means centers: %s",centers)
 
	return centers
 

	
 

	
 
def quantize(img,centers):
 
	origShape=img.shape
 
	data=np.reshape(img,(-1,3))
 
@@ -57,14 +58,14 @@ def filterStones(contours,bwImg,stoneDim
 
		if coverage2<0.8:
 
			cv.drawMarker(contourImg,tuple(map(int,center)),(0,127,255),cv.MARKER_DIAMOND,12)
 
			keep=False
 
		if keep:
 
			res.append((EPoint(*center),c))
 
			cv.drawMarker(contourImg,tuple(map(int,center)),(255,0,0),cv.MARKER_CROSS)
 
	print("accepted:",len(res))
 
	print("rejected:",len(contours)-len(res))
 
	log.debug("accepted: %s",len(res))
 
	log.debug("rejected: %s",len(contours)-len(res))
 
	show(contourImg)
 
	return res
 

	
 

	
 
def groupLines(points,minCount,tolerance):
 
	random.shuffle(points)
 
@@ -78,58 +79,86 @@ def groupLines(points,minCount,tolerance
 
				if point2lineDistance(a,b,c)<=tolerance:
 
					ab.points.add(c)
 
			if len(ab.points)>=minCount:
 
				yield ab
 

	
 

	
 
if __name__=="__main__":
 
	filepath=sys.argv[1]
 
	annotations=DataFile(sys.argv[2])
 
	filename=os.path.basename(filepath)
 
	corners=annotations[filename][0]
 
class BoardDetector:
 
	def __init__(self,annotationsPath):
 
		self._annotations=DataFile(annotationsPath)
 

	
 
		self._rectW=0
 
		self._rectH=0
 

	
 
	def __call__(self,img,filename):
 
		# approximately detect the board
 
		(h,w)=img.shape[:2]
 
		log.debug("image dimensions: %s x %s",w,h)
 
		show(img,filename)
 
		(x1,y1,x2,y2)=self._detectRough(img,filename)
 
		rect=img[y1:y2,x1:x2]
 
		self._rectW=x2-x1
 
		self._rectH=y2-y1
 

	
 
		# quantize colors
 
		colors=self._sampleColors(rect)
 
		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 vanishing points of the lines
 
		vanish=self._detectVanishingPoints(lines,EPoint(w//2-x1, h//2-y1))
 

	
 
		# rectify the image
 
		# determine precise board edges
 

	
 
	def _detectRough(self,img,filename):
 
		corners=self._annotations[filename][0]
 
	(x1,y1,x2,y2)=computeBoundingBox(corners)
 
	(w,h)=(x2-x1,y2-y1)
 
	img=cv.imread(filepath)
 
	(x3,x4,y3,y4)=(x1+w//4,x1+3*w//4,y1+h//4,y1+3*h//4)
 
	print("x3,x4,y3,y4:",x3,x4,y3,y4)
 
	rect=img[y3:y4,x3:x4,:]
 
	centers=kmeans(rect)
 
	print("x1,x2,y1,y2:",(x1,x2,y1,y2))
 
	img[y1:y2,x1:x2,:]=quantize(img[y1:y2,x1:x2,:],centers)
 
	print("image quantized")
 
		log.debug("bounding box: (%s,%s) - (%s,%s)",x1,y1,x2,y2)
 
		return (x1,y1,x2,y2)
 

	
 
	def _sampleColors(self,rect):
 
		(h,w)=rect.shape[:2]
 
		minirect=rect[h//4:3*h//4, w//4:3*w//4]
 
		return kmeans(minirect)
 

	
 
	rect=img[y1:y2,x1:x2]
 
	def _detectStones(self,quantized,colors):
 
		(h,w)=quantized.shape[:2]
 
		mask=self._maskStones(quantized,colors)
 
		stoneDims=(w/19,h/19)
 
		log.debug("stone dims: %s - %s",tuple(x/2 for x in stoneDims),stoneDims)
 

	
 
		(contours,hierarchy)=cv.findContours(mask,cv.RETR_LIST,cv.CHAIN_APPROX_SIMPLE)
 
		stoneLocs=filterStones(contours,mask,stoneDims)
 

	
 
		return stoneLocs
 

	
 
	def _maskStones(self,quantized,colors):
 
	unit=np.array([1,1,1],dtype=np.uint8)
 
	kernel=np.ones((3,3),np.uint8)
 
	maskB=cv.inRange(rect,centers[0]-unit,centers[0]+unit)
 
		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=4)
 
	maskW=cv.inRange(rect,centers[1]-unit,centers[1]+unit)
 
		show(maskB)
 
		maskW=cv.inRange(quantized,colors[1]-unit,colors[1]+unit)
 
	maskW=cv.erode(maskW,kernel,iterations=3)
 

	
 
	show(img,filename)
 
	show(maskB,filename)
 
	show(maskW,filename)
 
		show(maskW)
 
	stones=cv.bitwise_or(maskB,maskW)
 
	show(stones)
 

	
 
	stoneDims=(w/19,h/19)
 
	print("stone dims:",tuple(x/2 for x in stoneDims),"-",stoneDims)
 

	
 
	(contours,hierarchy)=cv.findContours(stones,cv.RETR_LIST,cv.CHAIN_APPROX_SIMPLE)
 
	stoneLocs=filterStones(contours,stones,stoneDims)
 
		return stones
 

	
 
	linesImg=cv.cvtColor(np.zeros((h,w),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)
 
	matsImg=np.copy(linesImg)
 

	
 
	def _constructLines(self,stoneLocs):
 
	lineDict=dict()
 
	minCount=min(max(math.sqrt(len(stoneLocs))-4,3),7)
 
	print("min count:",minCount)
 
		log.debug("min count: %s",minCount)
 
	for line in groupLines([point for (point,contour) in stoneLocs],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()
 
@@ -138,37 +167,41 @@ if __name__=="__main__":
 
			if line.points<ab.points: # == impossible
 
				del lineDict[key]
 
				break
 
			if ab.points<line.points:
 
				obsolete.add(ab.getSortedPoints())
 
		for key in obsolete: del lineDict[key]
 
		log.debug("valid lines: %s",len(lineDict))
 
		lines=sorted(lineDict.values(), key=lambda ab: len(ab.points), reverse=True)
 

	
 
	print("valid lines:",len(lineDict))
 
	lines=sorted(lineDict.values(), key=lambda ab: len(ab.points), reverse=True)
 
	res=[]
 
		# 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)
 
	for line in lines:
 
		v=line.b-line.a
 
		alpha=math.atan(v.y/v.x)
 
		res.append((round(math.pi/2-alpha if alpha>0 else math.pi/2+alpha,3),repr(line)))
 
		(xa,ya)=line.a
 
		(xb,yb)=line.b
 
			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)
 
	res.sort()
 
	show(linesImg)
 

	
 
	imgSize=img.shape[:2]
 
	print("image size:",imgSize)
 
	imgCenter=EPoint(imgSize[1]/2,imgSize[0]/2)-EPoint(x1,y1)
 
		return lines
 

	
 
	def _detectVanishingPoints(self,lines,imgCenter):
 
	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<=w and 0<=point.y<=h: continue
 
			print(point,"->",point.toPolar(imgCenter))
 
				if 0<=point.x<=self._rectW and 0<=point.y<=self._rectH: continue
 
				log.debug("%s -> %s",point,point.toPolar(imgCenter))
 
			polarHough.put(point.toPolar(imgCenter))
 
	vanish=[EPoint.fromPolar(p,imgCenter) for p in polarHough.extract(2)]
 
	print(vanish)
 
	(a,b,c,d)=corners
 
	(p,q,r,s)=(Line(a,b),Line(b,c),Line(c,d),Line(d,a))
 
	v1=p.intersect(r)
 
	v2=q.intersect(s)
 
	print("true vanishing points:",v1,"~",v1.toPolar(imgCenter),"and",v2,"~",v2.toPolar(imgCenter))
 
		log.debug(vanish)
 
		return vanish
 

	
 
if __name__=="__main__":
 
	detector=BoardDetector(sys.argv[2])
 
	filepath=sys.argv[1]
 
	filename=os.path.basename(filepath)
 
	img=cv.imread(filepath)
 
	detector(img,filename)
exp/polar_hough.py
Show inline comments
 
import itertools
 
import math
 
import logging as log
 

	
 
import numpy as np
 
import scipy.signal
 

	
 
from geometry import angleDiff
 

	
 
@@ -29,17 +30,17 @@ class PolarHough:
 
			(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)))
 
		print(lens)
 
		log.debug(lens)
 
		(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]
 
		print("(angle, prominence):",res,"...",[alpha/self._anglePrecision for (alpha,_) 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):
 
			beta=i*self._anglePrecision
 
@@ -50,8 +51,8 @@ class PolarHough:
 
	def _extractLength(self,arr):
 
		acc=np.zeros(self._maxLength+1,dtype=np.int32)
 
		for dist in arr:
 
			dist=min(dist,self._maxLength)
 
			acc[int(dist//self._lengthPrecision)]+=1
 
		res=acc.argmax()
 
		print("(length, count):",(res*self._lengthPrecision,acc[res]))
 
		log.debug("(length, count): %s",(res*self._lengthPrecision,acc[res]))
 
		return (res*self._lengthPrecision,acc[res])
0 comments (0 inline, 0 general)