diff --git a/exp/board_detect.py b/exp/board_detect.py --- a/exp/board_detect.py +++ b/exp/board_detect.py @@ -16,7 +16,7 @@ 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 @@ -64,13 +64,13 @@ def filterStones(contours,bwImg,stoneDim 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 @@ -110,23 +110,36 @@ class BoardDetector: # 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] @@ -158,20 +171,22 @@ class BoardDetector: 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 @@ -193,6 +208,7 @@ class BoardDetector: 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] @@ -202,15 +218,28 @@ class BoardDetector: 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 %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 diff --git a/exp/geometry.py b/exp/geometry.py --- a/exp/geometry.py +++ b/exp/geometry.py @@ -37,6 +37,13 @@ class Line(): 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)) diff --git a/exp/hough.py b/exp/hough.py --- a/exp/hough.py +++ b/exp/hough.py @@ -1,5 +1,8 @@ import os import sys +import math +from datetime import datetime +import logging as log import numpy as np import cv2 as cv @@ -7,6 +10,49 @@ 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) @@ -14,7 +60,6 @@ def show(img,filename="x"): 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) @@ -41,6 +86,15 @@ def filterDiag(edges): 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) @@ -78,20 +132,7 @@ if __name__=="__main__": # 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() diff --git a/exp/polar_hough.py b/exp/polar_hough.py --- a/exp/polar_hough.py +++ b/exp/polar_hough.py @@ -21,9 +21,9 @@ class PolarHough: 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): @@ -31,12 +31,16 @@ class PolarHough: 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