diff --git a/.hgignore b/.hgignore --- a/.hgignore +++ b/.hgignore @@ -1,3 +1,3 @@ -^src/__pycache__/ +__pycache__/ ^\.idea/ ^images/ diff --git a/src/corners.py b/src/corners.py --- a/src/corners.py +++ b/src/corners.py @@ -2,92 +2,92 @@ class Corners: - def __init__(self): - self.corners=[] - - ## Adds a new corner if there are less than four, replaces the closest otherwise. - def add(self,x,y): - a=EPoint(x,y) - # for i,c in enumerate(self.corners): # move an improperly placed point - # if a.dist(c)<20: - # self.corners[i]=a - # return - - if len(self.corners)<4: # add a new corner - self.corners.append(a) - - if len(self.corners)<4: - return - - index,minDist=0,float('inf') # replace the corner closest to the clicked point - for i,c in enumerate(self.corners): - if a.dist(c) KLMN | ABC | ABD | ACD | BCD | index | swap - # ------------ | :-: | :-: | :-: | :-: | ----: | ---- - # A BCD | + | + | + | + | 15 | 0 - # A BDC | + | + | - | - | 12 | CD - # A CBD | - | + | + | - | 6 | BC - # A CDB | - | - | + | + | 3 | AB - # A DBC | + | - | - | + | 9 | AD - # A DCB | - | - | - | - | 0 | BD - # - # For every non-degenerate quadrangle, there must be 1-3 edges going right-left (from a higher to a lower x coordinate). - # From these pick the one with the lowest slope (dy/dx) and declare its ending point the upper left corner. For the same slope pick the one further left. - # - # @return True for a convex quadrangle, False for concave and degenerate cases. - def canonizeOrder(self): - if len(self.corners)!=4: return False # erroneus call - - a,b,c,d=self.corners - abc=Corners._doubleTriangleArea(a,b,c) - abd=Corners._doubleTriangleArea(a,b,d) - acd=Corners._doubleTriangleArea(a,c,d) - bcd=Corners._doubleTriangleArea(b,c,d) - - if any(x==0 for x in (abc,abd,acd,bcd)): return False # collinear degenerate - - swaps=[(1,3),(0,1),(1,2),(0,3),(2,3),(0,0)] - index=(8 if abc>0 else 0)|(4 if abd>0 else 0)|(2 if acd>0 else 0)|(1 if bcd>0 else 0) - if index%3!=0: return False # concave degenerate - swap=swaps[index//3] - - self.corners[swap[0]], self.corners[swap[1]] = self.corners[swap[1]], self.corners[swap[0]] # counter-clockwise order - - kIndex=None - lowestSlope=float("inf") - - for i,corner in enumerate(self.corners): # find the NK edge: going right-left with the lowest slope, secondarily the one going down - ii=(i+1)%4 - slope=abs(Corners._slope(corner,self.corners[ii])) - if corner.x>self.corners[ii].x and (slope KLMN | ABC | ABD | ACD | BCD | index | swap + # ------------ | :-: | :-: | :-: | :-: | ----: | ---- + # A BCD | + | + | + | + | 15 | 0 + # A BDC | + | + | - | - | 12 | CD + # A CBD | - | + | + | - | 6 | BC + # A CDB | - | - | + | + | 3 | AB + # A DBC | + | - | - | + | 9 | AD + # A DCB | - | - | - | - | 0 | BD + # + # For every non-degenerate quadrangle, there must be 1-3 edges going right-left (from a higher to a lower x coordinate). + # From these pick the one with the lowest slope (dy/dx) and declare its ending point the upper left corner. For the same slope pick the one further left. + # + # @return True for a convex quadrangle, False for concave and degenerate cases. + def canonizeOrder(self): + if len(self.corners)!=4: return False # erroneus call + + a,b,c,d=self.corners + abc=Corners._doubleTriangleArea(a,b,c) + abd=Corners._doubleTriangleArea(a,b,d) + acd=Corners._doubleTriangleArea(a,c,d) + bcd=Corners._doubleTriangleArea(b,c,d) + + if any(x==0 for x in (abc,abd,acd,bcd)): return False # collinear degenerate + + swaps=[(1,3),(0,1),(1,2),(0,3),(2,3),(0,0)] + index=(8 if abc>0 else 0)|(4 if abd>0 else 0)|(2 if acd>0 else 0)|(1 if bcd>0 else 0) + if index%3!=0: return False # concave degenerate + swap=swaps[index//3] + + self.corners[swap[0]], self.corners[swap[1]] = self.corners[swap[1]], self.corners[swap[0]] # counter-clockwise order + + kIndex=None + lowestSlope=float("inf") + + for i,corner in enumerate(self.corners): # find the NK edge: going right-left with the lowest slope, secondarily the one going down + ii=(i+1)%4 + slope=abs(Corners._slope(corner,self.corners[ii])) + if corner.x>self.corners[ii].x and (slope=self.boardSize or row<0 or row>=self.boardSize: return False # out of range - if self.temp[row][col]: return False # already visited - if self.board[row][col]==Go.EMPTY: return True # found a liberty - if self.board[row][col]!=color: return False # opponent's stone - self.temp[row][col]=True # set visited - return self._floodFill(color,row,col-1) or self._floodFill(color,row,col+1) or self._floodFill(color,row-1,col) or self._floodFill(color,row+1,col) # check neighbors - - ## Removes stones at coordinates marked with True in self.temp. - def _remove(self): - for r in range(self.boardSize): - for c in range(self.boardSize): - if self.temp[r][c]: self.board[r][c]=Go.EMPTY + # check for suicide + self.temp=[[False]*self.boardSize for x in self.board] + if not self._floodFill(color,row,col): + self.board[row][col]=Go.EMPTY + return False + return True + + ## Checks for liberties of a stone at given coordinates. + # + # The stone's group is marked with True in self.temp, ready for capture if needed. Recursively called for stone's neighbors. + # + # @return True if alive, False if captured + def _floodFill(self,color,row,col): + if col<0 or col>=self.boardSize or row<0 or row>=self.boardSize: return False # out of range + if self.temp[row][col]: return False # already visited + if self.board[row][col]==Go.EMPTY: return True # found a liberty + if self.board[row][col]!=color: return False # opponent's stone + self.temp[row][col]=True # set visited + return self._floodFill(color,row,col-1) or self._floodFill(color,row,col+1) or self._floodFill(color,row-1,col) or self._floodFill(color,row+1,col) # check neighbors + + ## Removes stones at coordinates marked with True in self.temp. + def _remove(self): + for r in range(self.boardSize): + for c in range(self.boardSize): + if self.temp[r][c]: self.board[r][c]=Go.EMPTY diff --git a/src/grid.py b/src/grid.py --- a/src/grid.py +++ b/src/grid.py @@ -8,76 +8,64 @@ from epoint import * # # @return transformed point as a numpy.array def transformPoint(point,A): - return (A*numpy.matrix(point).transpose()).getA1() + return (A*numpy.matrix(point).transpose()).getA1() class Grid: - ## Creates a Grid from the provided Corners object. - # - # Finds the vanishing points of the board lines (corner points define perspectively transformed parallel lines). The vanishing points define the image horizon. - # - # The horizon can be used to construct a matrix for affine rectification of the image (restoring parallel lines parallelism). We transform the corner points by this matrix, - # interpolate them to get proper intersections' coordinates and then transform these back to get their placement at the original image. - # - # The result is stored in grid.intersections, a boardSize*boardSize list with [row][column] coordinates. - # - # @param corners a properly initialized Corners object. !! Needs a check for the proper initialization. - def __init__(self,corners): - # ad - # bc - a,b,c,d=[c.toProjective() for c in corners.corners] - - p1=numpy.cross(a,b) - p2=numpy.cross(c,d) - vanish1=numpy.cross(p1,p2) - # !! 32 bit int can overflow. keeping it reasonably small. might want to use a cleaner solution - vanish1=EPoint.fromProjective(vanish1).toProjective() # !! EPoint fails with point in infinity - - p3=numpy.cross(a,d) - p4=numpy.cross(b,c) - vanish2=numpy.cross(p3,p4) - vanish2=EPoint.fromProjective(vanish2).toProjective() - - horizon=numpy.cross(vanish1,vanish2) + ## Creates a Grid from the provided Corners object. + # + # Finds the vanishing points of the board lines (corner points define perspectively transformed parallel lines). The vanishing points define the image horizon. + # + # The horizon can be used to construct a matrix for affine rectification of the image (restoring parallel lines parallelism). We transform the corner points by this matrix, + # interpolate them to get proper intersections' coordinates and then transform these back to get their placement at the original image. + # + # The result is stored in grid.intersections, a boardSize*boardSize list with [row][column] coordinates. + # + # @param corners a properly initialized Corners object. !! Needs a check for the proper initialization. + def __init__(self,corners): + # ad + # bc + a,b,c,d=[c.toProjective() for c in corners.corners] - horizon=EPoint.fromProjective(horizon).toProjective() - - rectiMatrix=numpy.matrix([horizon,[0,1,0],[0,0,1]]) - rectiMatrixInv=numpy.linalg.inv(rectiMatrix) - - - affineCorners=[EPoint.fromProjective(transformPoint(x,rectiMatrix)) for x in (a,b,c,d)] - x=[affineCorners[0]-affineCorners[3],affineCorners[1]-affineCorners[2],affineCorners[0]-affineCorners[1],affineCorners[3]-affineCorners[2]] - - self.intersections=[] - boardSize=19 - for r in range(boardSize): - self.intersections.append([None]*boardSize) - rowStart=(affineCorners[0]*(boardSize-1-r)+affineCorners[1]*r) / (boardSize-1) - rowEnd=(affineCorners[3]*(boardSize-1-r)+affineCorners[2]*r) / (boardSize-1) - - for c in range(boardSize): - affineIntersection=(rowStart*(boardSize-1-c)+rowEnd*c) / (boardSize-1) - self.intersections[r][c]=EPoint.fromProjective(transformPoint(affineIntersection.toProjective(),rectiMatrixInv)) + p1=numpy.cross(a,b) + p2=numpy.cross(c,d) + vanish1=numpy.cross(p1,p2) + # !! 32 bit int can overflow. keeping it reasonably small. might want to use a cleaner solution + vanish1=EPoint.fromProjective(vanish1).toProjective() # !! EPoint fails with point in infinity - def stoneSizeAt(self,r,c,sizeCoef): - intersection=self.intersections[r][c] + p3=numpy.cross(a,d) + p4=numpy.cross(b,c) + vanish2=numpy.cross(p3,p4) + vanish2=EPoint.fromProjective(vanish2).toProjective() - if c>0: width=sizeCoef*(intersection.x-self.intersections[r][c-1].x) - else: width=sizeCoef*(self.intersections[r][c+1].x-intersection.x) - if r>0: height=sizeCoef*(intersection.y-self.intersections[r-1][c].y) - else: height=sizeCoef*(self.intersections[r+1][c].y-intersection.y) + horizon=numpy.cross(vanish1,vanish2) - return (width,height) + horizon=EPoint.fromProjective(horizon).toProjective() + + rectiMatrix=numpy.matrix([horizon,[0,1,0],[0,0,1]]) + rectiMatrixInv=numpy.linalg.inv(rectiMatrix) -# from corners import Corners -# corn=Corners() + affineCorners=[EPoint.fromProjective(transformPoint(x,rectiMatrix)) for x in (a,b,c,d)] + x=[affineCorners[0]-affineCorners[3],affineCorners[1]-affineCorners[2],affineCorners[0]-affineCorners[1],affineCorners[3]-affineCorners[2]] + + self.intersections=[] + boardSize=19 + for r in range(boardSize): + self.intersections.append([None]*boardSize) + rowStart=(affineCorners[0]*(boardSize-1-r)+affineCorners[1]*r) / (boardSize-1) + rowEnd=(affineCorners[3]*(boardSize-1-r)+affineCorners[2]*r) / (boardSize-1) -# corn.add(106,86) -# corn.add(57,321) -# corn.add(416,320) -# corn.add(365,86) -# corn.add(365,88) + for c in range(boardSize): + affineIntersection=(rowStart*(boardSize-1-c)+rowEnd*c) / (boardSize-1) + self.intersections[r][c]=EPoint.fromProjective(transformPoint(affineIntersection.toProjective(),rectiMatrixInv)) + + def stoneSizeAt(self,r,c,sizeCoef): + intersection=self.intersections[r][c] -# x=Grid(corn) + if c>0: width=sizeCoef*(intersection.x-self.intersections[r][c-1].x) + else: width=sizeCoef*(self.intersections[r][c+1].x-intersection.x) + if r>0: height=sizeCoef*(intersection.y-self.intersections[r-1][c].y) + else: height=sizeCoef*(self.intersections[r+1][c].y-intersection.y) + + return (width,height) diff --git a/src/gui.py b/src/gui.py --- a/src/gui.py +++ b/src/gui.py @@ -11,153 +11,153 @@ showGrid=False class Application(tk.Frame): - def __init__(self, master=None): - self.corners=Corners() - self.boardGrid=None - - tk.Frame.__init__(self, master) - self.grid(column=0,row=0) - self.createWidgets() + def __init__(self, master=None): + self.corners=Corners() + self.boardGrid=None + + tk.Frame.__init__(self, master) + self.grid(column=0,row=0) + self.createWidgets() + + def createWidgets(self): + # a captured frame with overlay graphics + self.imgView=tk.Canvas(self) + self.imgView.configure(width=480,height=360) + self.imgOrig=PIL.Image.open("../images/7.jpg") + + self.img=ImageTk.PhotoImage(self.imgOrig.resize((int(self.imgView['width']),int(self.imgView['height'])),resample=PIL.Image.BILINEAR)) + + self.imgView.bind('<1>',lambda e: self.addCorner(e.x,e.y)) + + self.imgView.grid(column=0,row=0) - def createWidgets(self): - # a captured frame with overlay graphics - self.imgView=tk.Canvas(self) - self.imgView.configure(width=480,height=360) - self.imgOrig=PIL.Image.open("../images/7.jpg") - - self.img=ImageTk.PhotoImage(self.imgOrig.resize((int(self.imgView['width']),int(self.imgView['height'])),resample=PIL.Image.BILINEAR)) - - self.imgView.bind('<1>',lambda e: self.addCorner(e.x,e.y)) - - self.imgView.grid(column=0,row=0) - - # board with detected stones - self.boardView=BoardView(self) - self.boardView.grid(column=1,row=0) + # board with detected stones + self.boardView=BoardView(self) + self.boardView.grid(column=1,row=0) - # more controls below the board - self.scaleTresB=tk.Scale(self, orient=tk.HORIZONTAL, length=200, from_=0.0, to=100.0, command=lambda x: self.redrawImgView()) - self.scaleTresW=tk.Scale(self, orient=tk.HORIZONTAL, length=200, from_=0.0, to=100.0, command=lambda x: self.redrawImgView()) - self.scaleTresB.set(30.0) - self.scaleTresW.set(60.0) - self.scaleTresB.grid(column=0,row=1,columnspan=2) - self.scaleTresW.grid(column=0,row=2,columnspan=2) + # more controls below the board + self.scaleTresB=tk.Scale(self, orient=tk.HORIZONTAL, length=200, from_=0.0, to=100.0, command=lambda x: self.redrawImgView()) + self.scaleTresW=tk.Scale(self, orient=tk.HORIZONTAL, length=200, from_=0.0, to=100.0, command=lambda x: self.redrawImgView()) + self.scaleTresB.set(30.0) + self.scaleTresW.set(60.0) + self.scaleTresB.grid(column=0,row=1,columnspan=2) + self.scaleTresW.grid(column=0,row=2,columnspan=2) + + # render everything + self.redrawImgView() + + ## Stores a grid corner located at x,y coordinates. + def addCorner(self,x,y): + self.corners.add(x,y) + if self.corners.canonizeOrder(): + self.boardGrid=Grid(self.corners) + self.boardView.setBoardGrid(self.boardGrid) - # render everything - self.redrawImgView() - - ## Stores a grid corner located at x,y coordinates. - def addCorner(self,x,y): - self.corners.add(x,y) - if self.corners.canonizeOrder(): - self.boardGrid=Grid(self.corners) - self.boardView.setBoardGrid(self.boardGrid) - - self.redrawImgView() - - ## Redraws the current image and its overlay. - def redrawImgView(self): - self.imgView.create_image(2,2,anchor="nw",image=self.img) + self.redrawImgView() + + ## Redraws the current image and its overlay. + def redrawImgView(self): + self.imgView.create_image(2,2,anchor="nw",image=self.img) - origWidth,origHeight=self.imgOrig.size - widthRatio=origWidth/self.img.width() - heightRatio=origHeight/self.img.height() - sizeCoef=max(widthRatio,heightRatio) - tresB=self.scaleTresB.get() - tresW=self.scaleTresW.get() + origWidth,origHeight=self.imgOrig.size + widthRatio=origWidth/self.img.width() + heightRatio=origHeight/self.img.height() + sizeCoef=max(widthRatio,heightRatio) + tresB=self.scaleTresB.get() + tresW=self.scaleTresW.get() + + for corner in self.corners.corners: + self.markPoint(corner.x,corner.y) - for corner in self.corners.corners: - self.markPoint(corner.x,corner.y) - - if self.boardGrid!=None and showGrid: - for r in range(19): - a=self.boardGrid.intersections[r][0] - b=self.boardGrid.intersections[r][-1] - self.imgView.create_line(a.x,a.y,b.x,b.y,fill='#00ff00') - for c in range(19): - a=self.boardGrid.intersections[0][c] - b=self.boardGrid.intersections[-1][c] - self.imgView.create_line(a.x,a.y,b.x,b.y,fill='#00ff00') + if self.boardGrid!=None and showGrid: + for r in range(19): + a=self.boardGrid.intersections[r][0] + b=self.boardGrid.intersections[r][-1] + self.imgView.create_line(a.x,a.y,b.x,b.y,fill='#00ff00') + for c in range(19): + a=self.boardGrid.intersections[0][c] + b=self.boardGrid.intersections[-1][c] + self.imgView.create_line(a.x,a.y,b.x,b.y,fill='#00ff00') - if self.boardGrid!=None and showBigPoints: - for r in range(19): - for c in range(19): - ((r1,c1),(r2,c2))=self.boardView.detector.relevantRect(self.boardGrid.intersections[r][c],*(self.boardGrid.stoneSizeAt(r,c,sizeCoef))) - self.imgView.create_rectangle(r1,c1,r2,c2,outline="#00ffff") - - self.imgView.grid() - - shift=EPoint(origWidth-self.img.width()*sizeCoef,origHeight-self.img.height()*sizeCoef)/2 - - self.boardView.redrawState(self.imgOrig,sizeCoef,shift,tresB,tresW) - - ## Marks a point at the image with a green cross. Used for corners. - def markPoint(self,x,y): - self.imgView.create_line(x-3,y-3,x+4,y+4,fill="#00ff00") - self.imgView.create_line(x-3,y+3,x+4,y-4,fill="#00ff00") - - self.imgView.grid() + if self.boardGrid!=None and showBigPoints: + for r in range(19): + for c in range(19): + ((r1,c1),(r2,c2))=self.boardView.detector.relevantRect(self.boardGrid.intersections[r][c],*(self.boardGrid.stoneSizeAt(r,c,sizeCoef))) + self.imgView.create_rectangle(r1,c1,r2,c2,outline="#00ffff") + + self.imgView.grid() + + shift=EPoint(origWidth-self.img.width()*sizeCoef,origHeight-self.img.height()*sizeCoef)/2 + + self.boardView.redrawState(self.imgOrig,sizeCoef,shift,tresB,tresW) + + ## Marks a point at the image with a green cross. Used for corners. + def markPoint(self,x,y): + self.imgView.create_line(x-3,y-3,x+4,y+4,fill="#00ff00") + self.imgView.create_line(x-3,y+3,x+4,y-4,fill="#00ff00") + + self.imgView.grid() ## Handles and presents the game state as detected by the program. class BoardView(tk.Canvas): - def __init__(self, master=None): - self.detector=ImageAnalyzer() - self.boardGrid=None - - tk.Canvas.__init__(self, master) - self.configure(width=360,height=360,background="#ffcc00") + def __init__(self, master=None): + self.detector=ImageAnalyzer() + self.boardGrid=None + + tk.Canvas.__init__(self, master) + self.configure(width=360,height=360,background="#ffcc00") + + self.drawGrid() + + self.grid() - self.drawGrid() - - self.grid() + ## Redraws and reananalyzes the board view. + # + # @param tresB upper intensity treshold for a pixel to be considered black, [0-100] + # @param tresW lower intensity treshold for a pixel to be considered white, [0-100] + def redrawState(self,img,sizeCoef,shift,tresB,tresW): + self.create_rectangle(0,0,360,360,fill="#ffcc00") + self.drawGrid() - ## Redraws and reananalyzes the board view. - # - # @param tresB upper intensity treshold for a pixel to be considered black, [0-100] - # @param tresW lower intensity treshold for a pixel to be considered white, [0-100] - def redrawState(self,img,sizeCoef,shift,tresB,tresW): - self.create_rectangle(0,0,360,360,fill="#ffcc00") - self.drawGrid() + self.detector.analyze(img,tresB,tresW,sizeCoef,shift) + + for r,row in enumerate(self.detector.board): + for c,point in enumerate(row): + self.drawStone(r,c,point) + + self.grid() - self.detector.analyze(img,tresB,tresW,sizeCoef,shift) - - for r,row in enumerate(self.detector.board): - for c,point in enumerate(row): - self.drawStone(r,c,point) - - self.grid() - - def drawGrid(self): - for i in range(19): - self.create_line(18,18*(i+1),360-18,18*(i+1),fill="#000000") # rows - self.create_line(18*(i+1),18,18*(i+1),360-18,fill="#000000") # cols - - self.drawStars() - - def drawStars(self): - for r in range(4,19,6): - for c in range(4,19,6): - self.create_oval(r*18-2,c*18-2,r*18+2,c*18+2,fill='#000000') - - ## Draws a stone at provided coordinates. - # - # For an unknown color draws nothing and returns False. - # - # @param r row coordinate, [0-18], counted from top - # @param c column coordinate, [0-18], counted from left - # @param color color indicator, Go.BLACK or Go.WHITE - def drawStone(self,r,c,color): - if color==Go.BLACK: hexCode='#000000' - elif color==Go.WHITE: hexCode='#ffffff' - else: return False - r+=1 - c+=1 - self.create_oval(c*18-9,r*18-9,c*18+9,r*18+9,fill=hexCode) - - def setBoardGrid(self,boardGrid): - self.boardGrid=boardGrid - self.detector.setGrid(boardGrid) + def drawGrid(self): + for i in range(19): + self.create_line(18,18*(i+1),360-18,18*(i+1),fill="#000000") # rows + self.create_line(18*(i+1),18,18*(i+1),360-18,fill="#000000") # cols + + self.drawStars() + + def drawStars(self): + for r in range(4,19,6): + for c in range(4,19,6): + self.create_oval(r*18-2,c*18-2,r*18+2,c*18+2,fill='#000000') + + ## Draws a stone at provided coordinates. + # + # For an unknown color draws nothing and returns False. + # + # @param r row coordinate, [0-18], counted from top + # @param c column coordinate, [0-18], counted from left + # @param color color indicator, Go.BLACK or Go.WHITE + def drawStone(self,r,c,color): + if color==Go.BLACK: hexCode='#000000' + elif color==Go.WHITE: hexCode='#ffffff' + else: return False + r+=1 + c+=1 + self.create_oval(c*18-9,r*18-9,c*18+9,r*18+9,fill=hexCode) + + def setBoardGrid(self,boardGrid): + self.boardGrid=boardGrid + self.detector.setGrid(boardGrid) root = tk.Tk() diff --git a/src/image_analyzer.py b/src/image_analyzer.py --- a/src/image_analyzer.py +++ b/src/image_analyzer.py @@ -2,47 +2,47 @@ class ImageAnalyzer: - - def __init__(self): - self.board=[[Go.EMPTY]*19 for r in range(19)] - self.grid=None - - def analyze(self,image,tresB,tresW,sizeCoef,shift): - if self.grid==None: return False - - for r in range(19): - for c in range(19): - intersection=self.grid.intersections[r][c] - - self.board[r][c]=self.analyzePoint(image,intersection*sizeCoef+shift,*(self.grid.stoneSizeAt(r,c,sizeCoef)),tresB,tresW) - - def analyzePoint(self,image,imageCoords,stoneWidth,stoneHeight,tresB,tresW): - b=w=e=0 + + def __init__(self): + self.board=[[Go.EMPTY]*19 for r in range(19)] + self.grid=None + + def analyze(self,image,tresB,tresW,sizeCoef,shift): + if self.grid==None: return False - ((x1,y1),(x2,y2))=self.relevantRect(imageCoords,stoneWidth,stoneHeight) + for r in range(19): + for c in range(19): + intersection=self.grid.intersections[r][c] + + self.board[r][c]=self.analyzePoint(image,intersection*sizeCoef+shift,*(self.grid.stoneSizeAt(r,c,sizeCoef)),tresB,tresW) + + def analyzePoint(self,image,imageCoords,stoneWidth,stoneHeight,tresB,tresW): + b=w=e=0 + + ((x1,y1),(x2,y2))=self.relevantRect(imageCoords,stoneWidth,stoneHeight) - for y in range(y1,y2+1): - for x in range(x1,x2+1): - red,green,blue=image.getpixel((x,y)) - - I=(red+green+blue)/255/3 - m=min(red,green,blue) - S=1-m/I - if 100*ItresW: w+=1 - else: e+=1 - - if b>=w and b>=e: return Go.BLACK - if w>=b and w>=e: return Go.WHITE - return Go.EMPTY + for y in range(y1,y2+1): + for x in range(x1,x2+1): + red,green,blue=image.getpixel((x,y)) + + I=(red+green+blue)/255/3 + m=min(red,green,blue) + S=1-m/I + if 100*ItresW: w+=1 + else: e+=1 - def relevantRect(self,imageCoords,stoneWidth,stoneHeight): - x=int(imageCoords.x) - y=int(imageCoords.y) - xmax=max(int(stoneWidth*2//7), 2) # !! optimal parameters subject to further research - ymax=max(int(stoneHeight*2//7), 2) + if b>=w and b>=e: return Go.BLACK + if w>=b and w>=e: return Go.WHITE + return Go.EMPTY - return ((x-xmax,y-ymax), (x+xmax,y+ymax)) - - def setGrid(self,grid): - self.grid=grid + def relevantRect(self,imageCoords,stoneWidth,stoneHeight): + x=int(imageCoords.x) + y=int(imageCoords.y) + xmax=max(int(stoneWidth*2//7), 2) # !! optimal parameters subject to further research + ymax=max(int(stoneHeight*2//7), 2) + + return ((x-xmax,y-ymax), (x+xmax,y+ymax)) + + def setGrid(self,grid): + self.grid=grid