Changeset - fc8be31ce773
[Not reviewed]
default
0 5 0
Laman - 9 years ago 2015-11-23 22:05:03

added intensity slider
5 files changed with 29 insertions and 18 deletions:
0 comments (0 inline, 0 general)
.hgignore
Show inline comments
 
^src/__pycache__/
 
\ No newline at end of file
 
^src/__pycache__/
 
^\.idea/
 
\ No newline at end of file
readme.md
Show inline comments
 
OneEye
 
======
 
 
Program to extract moves of a go game from a video and save them to SGF or broadcast them online.
 
 
Modules:
 
 
  * Video: grabbing still frames from a video file / stream. Planning to use FFmpeg.
 
  * Graphic: extracting game position from an image. Using Pillow.
 
  * Watcher: interpreting sequence of game positions as a sequence of moves.
 
  * Broadcaster: interfacing with a go server.
 
 
Written for Python 3.4.3. Dependencies: Pillow 2.7.0, NumPy 1.9.2. Start gui.py to run.
 
Written for Python 3.5.0. Dependencies: Pillow 2.7.0, NumPy 1.10.0. Start gui.py to run.
 
 
Licensed under GNU GPL v2.0 or later. See license.txt. Relicensing possible, don't hesitate to ask.
 
  
 
 
Graphic
 
-------
 
Interpolating the board grid from specified corner points.
 
 
Determining the point status based on majority voting of pixels in its neighbourhood (deciding by HSI intensity). Could be improved by considering saturation. Other methods could be better. Opened to research.
 
 
Autodetection of the board?
 
src/corners.py
Show inline comments
 
@@ -35,24 +35,27 @@ class Corners:
 
  
 
  
 
  def _slope(a,b):
 
    if(b.x==a.x): return float("inf")
 
    return (b.y-a.y)/(b.x-a.x)
 
 
    
 
  ## Order the corners (0,1,2,3) so they make a quadrangle with vertices KLMN in counter-clockwise order, K being in the upper left.
 
  #  
 
  #  For four points ABCD, there are 24 possible permutations corresponding to the desired KLMN.
 
  #  When we relax the condition of K being the upper left one, we get six groups of four equivalent permutations. KLMN ~ LMNK ~ MNKL ~ NKLM.
 
  #  
 
  #  We determine which of the points' triplets are oriented clockwise and which counter-clockwise (minus/plus in the table below)
 
  #  and swap them so that all triangles turn counter-clockwise.
 
  #  
 
  #  xxxx -> 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.
 
  #  
 
@@ -60,39 +63,31 @@ class Corners:
 
  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)
 
    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<lowestSlope or (slope==lowestSlope and corner.y<self.corners[ii].y)):
 
        kIndex=ii
 
        lowestSlope=slope
 
    
 
    self.corners=self.corners[kIndex:]+self.corners[:kIndex] # rotate the upper left corner to the first place
 
        
 
    return True # success
 
 
# import itertools
 
# points=[(10,8),(8,10),(12,10),(10,12)]
 
# for perm in itertools.permutations(points):
 
  # corn=Corners()
 
  # for p in perm: corn.add(p[0],p[1])
 
  # print(corn.canonizeOrder())
 
  # print(corn.corners)
src/gui.py
Show inline comments
 
@@ -24,25 +24,34 @@ class Application(tk.Frame):
 
    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)
 
    
 
 
    # 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)
 
    
 
    self.redrawImgView()
 
    
 
  ## Redraws the current image and its overlay.
 
@@ -58,52 +67,58 @@ class Application(tk.Frame):
 
        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')
 
    
 
    self.imgView.grid()
 
    
 
    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()
 
    
 
    shift=EPoint(origWidth-self.img.width()*sizeCoef,origHeight-self.img.height()*sizeCoef)/2
 
    
 
    self.boardView.redrawState(self.imgOrig,sizeCoef,shift)
 
    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")
 
    
 
    self.drawGrid()
 
    
 
    self.grid()
 
    
 
  def redrawState(self,img,sizeCoef,shift):
 
    self.detector.analyze(img,30,65,sizeCoef,shift)
 
 
  ## 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.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
 
      
src/image_analyzer.py
Show inline comments
 
@@ -15,26 +15,26 @@ class ImageAnalyzer:
 
        intersection=self.grid.intersections[r][c]
 
        
 
        if c>0: stoneWidth=sizeCoef*(intersection.x-self.grid.intersections[r][c-1].x)
 
        else: stoneWidth=sizeCoef*(self.grid.intersections[r][c+1].x-intersection.x)
 
        if r>0: stoneHeight=sizeCoef*(intersection.y-self.grid.intersections[r-1][c].y)
 
        else: stoneHeight=sizeCoef*(self.grid.intersections[r+1][c].y-intersection.y)
 
        
 
        self.board[r][c]=self.analyzePoint(image,intersection*sizeCoef+shift,stoneWidth,stoneHeight,tresB,tresW)
 
  
 
  def analyzePoint(self,image,imageCoords,stoneWidth,stoneHeight,tresB,tresW):
 
    b=w=e=0
 
    
 
    cmax=max(int(stoneWidth*2//5),2)
 
    rmax=max(int(stoneHeight*2//5),2)
 
    cmax=max(int(stoneWidth*2//7),2) # !! optimal parameters subject to further research
 
    rmax=max(int(stoneHeight*2//7),2)
 
    
 
    for r in range(-rmax,rmax+1):
 
      for c in range(-cmax,cmax+1):
 
        red,green,blue=image.getpixel((imageCoords.x+c,imageCoords.y+r))
 
        
 
        I=(red+green+blue)/255/3
 
        m=min(red,green,blue)
 
        S=1-m/I
 
        if 100*I<tresB: b+=1
 
        elif 100*I>tresW: w+=1
 
        else: e+=1
 
        
0 comments (0 inline, 0 general)