# HG changeset patch # User Laman # Date 2019-03-11 14:39:44 # Node ID 5617054647db10ac6701f97a5131accf739947e3 # Parent d07ae4bfa14582d219fbd9ffd6d071e2bdf114ce grid construction and evaluation diff --git a/exp/board_detect.py b/exp/board_detect.py --- a/exp/board_detect.py +++ b/exp/board_detect.py @@ -81,6 +81,10 @@ class BoardDetector: self._rectH=0 self._rect=None + self._hough=None + self._rectiMatrix=None + self._inverseMatrix=None + def __call__(self,img,filename): # approximately detect the board (h,w)=img.shape[:2] @@ -106,14 +110,14 @@ class BoardDetector: # detect lines from edges and stones edgeImg=prepareEdgeImg(rect) - hough=HoughTransform(edgeImg) + self._hough=HoughTransform(edgeImg) 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) show(stonesImg,"detected stones") - hough.update(stonesImg,10) - lines=hough.extract() + self._hough.update(stonesImg,10) + lines=self._hough.extract() linesImg=np.copy(rect) for line in itertools.chain(*lines): @@ -123,18 +127,10 @@ class BoardDetector: # # rectify the image matrix=self._computeTransformationMatrix(lines[0][0],lines[0][-1],lines[1][0],lines[1][-1]) transformed=cv.warpPerspective(rect,matrix,(self._rectW,self._rectH)) + rectiLines=[[line.transform(matrix) for line in pack] for pack in lines] # determine precise board edges - intersections=[] - for p in lines[0]: - for q in lines[1]: - intersections.append(p.intersect(q)) - sack=DiagonalRansac(intersections,19) - diagonals=sack.extract(10,2000) - log.debug("diagonals candidates: %s",diagonals) - for line in diagonals: - self._drawLine(linesImg,line,[0,255,255]) - show(linesImg,"diagonals candidates") + self._detectBestGrid(rectiLines,linesImg) def _detectRough(self,img,filename): corners=self._annotations[filename][0] @@ -181,10 +177,11 @@ class BoardDetector: def _computeTransformationMatrix(self,p,q,r,s): # p || q, r || s (a,b,c,d)=Corners([p.intersect(r),p.intersect(s),q.intersect(r),q.intersect(s)]) # canonize the abcd order - a_=EPoint(b.x,min(a.y,d.y)) - b_=EPoint(b.x,max(b.y,c.y)) - c_=EPoint(c.x,max(b.y,c.y)) - d_=EPoint(c.x,min(a.y,d.y)) + pad=20 + a_=EPoint(b.x+pad,min(a.y,d.y)+pad) + b_=EPoint(b.x+pad,max(b.y,c.y)-pad) + c_=EPoint(c.x-pad,max(b.y,c.y)-pad) + d_=EPoint(c.x-pad,min(a.y,d.y)+pad) abcd=[list(point) for point in (a,b,c,d)] abcd_=[list(point) for point in (a_,b_,c_,d_)] log.debug("abcd: %s ->",(a,b,c,d)) @@ -199,8 +196,72 @@ class BoardDetector: transformed=cv.warpPerspective(rect,matrix,(self._rectW,self._rectH)) show(transformed,"rectified image") + self._rectiMatrix=matrix + self._inverseMatrix=np.linalg.inv(matrix) return matrix + def _detectBestGrid(self,lines,img): + intersections=[] + for p in lines[0]: + for q in lines[1]: + intersections.append(p.intersect(q)) + + sack=DiagonalRansac(intersections,19) + diagonals=sack.extract(10,3000) + log.debug("diagonals candidates: %s",diagonals) + for line in diagonals: + self._drawLine(img,line.transform(self._inverseMatrix),[0,255,255]) + show(img,"diagonals candidates") + + best=(0,None) + transformedImg=cv.warpPerspective(img,self._rectiMatrix,(self._rectW,self._rectH)) + + for e in diagonals: + for f in diagonals: + center=e.intersect(f) + if not center: continue + if center.x<0 or center.x>self._rectW or center.y<0 or center.y>self._rectH: continue + for line in itertools.chain(*lines): + for i in range(1,10): # 10th is useless, 11-19 are symmetrical to 1-9 + grid=self._constructGrid(e,f,line,i) + if not grid: continue + score=self._scoreGrid(grid) + if score>best[0]: + best=(score,grid) + log.debug("new best grid: %s",score) + self._showGrid(transformedImg,grid) + return best[1] + + def _constructGrid(self,e,f,line,i): + """Contruct a grid. + + :param e: (Line) one diagonal + :param f: (Line) other diagonal + :param line: (Line) one of the grid lines + :param i: (int) line's index among the grid's lines, 1<=i<=9""" + center=e.intersect(f) + p1=line.intersect(e) + p2=line.intersect(f) + a=center+9*(p1-center)/(10-i) + b=center+9*(p2-center)/(10-i) + c=2*center-a + d=2*center-b + # abort unfitting sizes + if not all(0<=point.x<self._rectW and 0<=point.y<self._rectH for point in (a,b,c,d)): + return None + if any(g.dist(h)<19*10 for (g,h) in [(a,b),(a,c),(a,d),(b,c),(b,d),(c,d)]): + return None + (a,b,c,d)=Corners([a,b,c,d]) + rows=[] + cols=[] + for j in range(19): + rows.append(Line.fromPoints((a*(18-j)+b*j)/18,(d*(18-j)+c*j)/18)) + cols.append(Line.fromPoints((a*(18-j)+d*j)/18,(b*(18-j)+c*j)/18)) + return (rows,cols) + + def _scoreGrid(self,lines): + return sum(self._hough.scoreLine(p.transform(self._inverseMatrix)) for p in itertools.chain(*lines)) + def _drawLine(self,img,line,color=None): if not color: color=[0,255,0] (h,w)=img.shape[:2] @@ -220,6 +281,16 @@ class BoardDetector: return cv.line(img,(int(a.x),int(a.y)),(int(b.x),int(b.y)),color) + def _showGrid(self,img,lines): + img=np.copy(img) + (rows,cols)=lines + for row in rows: + for col in cols: + point=row.intersect(col) + xy=(int(point.x),int(point.y)) + cv.circle(img,xy,4,[0,0,255],-1) + show(img,"grid candidate") + if __name__=="__main__": detector=BoardDetector(sys.argv[2]) diff --git a/exp/hough.py b/exp/hough.py --- a/exp/hough.py +++ b/exp/hough.py @@ -66,7 +66,7 @@ class HoughTransform: (alphaDeg,d)=keys[k] line=Line(alphaDeg*math.pi/180,d-self._diagLen//2) res[-1].append(self._transformOutput(line)) - res[-1].sort(key=lambda line: line.d if line.alpha<math.pi else -line.d) + res[-1].sort(key=lambda line: line.d) i+=1 self.show(img) @@ -82,6 +82,13 @@ class HoughTransform: self._acc[(alphaDeg,d)]+=weight log.debug("Hough updated in %s s",round(datetime.now().timestamp()-start,3)) + def scoreLine(self,line): + transformed=self._transformInput(line) + alphaDeg=round(transformed.alpha*180/math.pi)%180 + d=round(transformed.d+self._diagLen//2) + if not 0<=d<self._diagLen: return 0 + return self._acc[(alphaDeg,d)] + def show(self,img=None): if img is None: img=self._createImg() show(img,"Hough transform accumulator") @@ -114,6 +121,15 @@ class HoughTransform: res.append((k,i)) return res + def _transformInput(self,line): + reflectedLine=Line(math.pi*2-line.alpha,line.d) + (x,y)=self._center + basis=EPoint(x,-y) + shiftedLine=reflectedLine.shiftBasis(basis) + if shiftedLine.alpha>=math.pi: + shiftedLine=Line(shiftedLine.alpha-math.pi,-shiftedLine.d) + return shiftedLine + def _transformOutput(self,line): (x,y)=self._center basis=EPoint(-x,y) diff --git a/src/analyzer/corners.py b/src/analyzer/corners.py --- a/src/analyzer/corners.py +++ b/src/analyzer/corners.py @@ -88,7 +88,6 @@ class Corners: self._corners= self._corners[kIndex:] + self._corners[:kIndex] # rotate the upper left corner to the first place - log.debug(self._corners) self._is_canon=True # success return True