# HG changeset patch # User Laman # Date 2019-01-23 12:28:55 # Node ID 353129d558d3daed883433158d3d43e820efa7fd # Parent 7dd3594c335eff6a3eb2fcc35f9f576f9d890e01 refactored pipeline into BoardDetector with separate methods diff --git a/exp/stone_detect.py b/exp/board_detect.py rename from exp/stone_detect.py rename to exp/board_detect.py --- a/exp/stone_detect.py +++ b/exp/board_detect.py @@ -1,12 +1,11 @@ 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 @@ -15,19 +14,21 @@ 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 @@ -60,8 +61,8 @@ def filterStones(contours,bwImg,stoneDim 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 @@ -81,94 +82,126 @@ def groupLines(points,minCount,tolerance yield ab -if __name__=="__main__": - filepath=sys.argv[1] - annotations=DataFile(sys.argv[2]) - filename=os.path.basename(filepath) - corners=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") +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) - rect=img[y1:y2,x1:x2] - 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.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) - maskW=cv.erode(maskW,kernel,iterations=3) + # 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) + log.debug("bounding box: (%s,%s) - (%s,%s)",x1,y1,x2,y2) + return (x1,y1,x2,y2) - show(img,filename) - show(maskB,filename) - show(maskW,filename) - stones=cv.bitwise_or(maskB,maskW) - show(stones) - - stoneDims=(w/19,h/19) - print("stone dims:",tuple(x/2 for x in stoneDims),"-",stoneDims) + def _sampleColors(self,rect): + (h,w)=rect.shape[:2] + minirect=rect[h//4:3*h//4, w//4:3*w//4] + return kmeans(minirect) - (contours,hierarchy)=cv.findContours(stones,cv.RETR_LIST,cv.CHAIN_APPROX_SIMPLE) - stoneLocs=filterStones(contours,stones,stoneDims) + 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) - 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) + (contours,hierarchy)=cv.findContours(mask,cv.RETR_LIST,cv.CHAIN_APPROX_SIMPLE) + stoneLocs=filterStones(contours,mask,stoneDims) + + return stoneLocs - lineDict=dict() - minCount=min(max(math.sqrt(len(stoneLocs))-4,3),7) - print("min count:",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() - for ab in lineDict.values(): - if ab is line: continue - if line.points0 else math.pi/2+alpha,3),repr(line))) - (xa,ya)=line.a - (xb,yb)=line.b - cv.line(linesImg,(int(xa),int(ya)),(int(xb),int(yb)),(255,255,0),1) - res.sort() - show(linesImg) + def _constructLines(self,stoneLocs): + lineDict=dict() + minCount=min(max(math.sqrt(len(stoneLocs))-4,3),7) + 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() + for ab in lineDict.values(): + if ab is line: continue + if line.points",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)) + # 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: + 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): + 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)) + polarHough.put(point.toPolar(imgCenter)) + vanish=[EPoint.fromPolar(p,imgCenter) for p in polarHough.extract(2)] + 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) diff --git a/exp/polar_hough.py b/exp/polar_hough.py --- a/exp/polar_hough.py +++ b/exp/polar_hough.py @@ -1,5 +1,6 @@ import itertools import math +import logging as log import numpy as np import scipy.signal @@ -32,11 +33,11 @@ class PolarHough: 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): @@ -53,5 +54,5 @@ class PolarHough: 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])