Changeset - c842bba99503
[Not reviewed]
default
0 9 1
Laman - 8 years ago 2016-11-23 22:18:22

rectangle representation of input intersections
10 files changed with 80 insertions and 22 deletions:
0 comments (0 inline, 0 general)
.hgignore
Show inline comments
 
modified file chmod 100644 => 100755
 
^src/__pycache__/
 
^\.idea/
 
\ No newline at end of file
 
^\.idea/
 
^images/
license.txt
Show inline comments
 
modified file chmod 100644 => 100755
project.md
Show inline comments
 
new file 100755
 
OneEye
 
======
 
 
Program to extract moves of a go game from video and save them or broadcast online.
 
 
Modules:
 
  - Video: grabbing still frames from a video file / stream. Using 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.
 
 
 
Graphic
 
-------
 
Interpolating the board grid from specified corner points. Using vanishing points and horizon in projective geometry.
 
 
Determining the point status based on majority voting of pixels in its neighbourhood (deciding by HSI intensity).
 
 
Autodetection of the board?
 
 
 
Watcher
 
-------
 
Base case: we have two correctly recognized positions differing by a single move (single added stone). Easy.
 
 
Issues:
 
  - illegal positions -> ignorable
 
  - positions unreachable from the previous state
 
    - reachable from any past state. (Incorrect states inbetween). How to pick the correct leaf of such a tree?
 
    - reachable by more than one move. Issues with branching factor.
 
  - board shifts -> repaired manually (or automatically), further positions have to be reevaluated
 
  - stone shifts
 
    - stone stops being recognized -> fixable manually and even ignorable
 
    - stone is recognized at an empty intersection. It can be occupied later for real. What do?
 
readme.md
Show inline comments
 
modified file chmod 100644 => 100755
src/corners.py
Show inline comments
 
modified file chmod 100644 => 100755
src/epoint.py
Show inline comments
 
modified file chmod 100644 => 100755
src/go.py
Show inline comments
 
modified file chmod 100644 => 100755
src/grid.py
Show inline comments
 
modified file chmod 100644 => 100755
 
@@ -39,36 +39,45 @@ class Grid:
 
    vanish2=EPoint.fromProjective(vanish2).toProjective()
 
    
 
    horizon=numpy.cross(vanish1,vanish2)
 
 
    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))
 
 
  def stoneSizeAt(self,r,c,sizeCoef):
 
    intersection=self.intersections[r][c]
 
 
    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)
 
 
 
# from corners import Corners
 
# corn=Corners()
 
 
# corn.add(106,86)
 
# corn.add(57,321)
 
# corn.add(416,320)
 
# corn.add(365,86)
 
# corn.add(365,88)
 
 
# x=Grid(corn)
src/gui.py
Show inline comments
 
modified file chmod 100644 => 100755
 
import tkinter as tk
 
from PIL import ImageTk
 
import PIL
 
import math
 
from epoint import *
 
from corners import *
 
from grid import *
 
from image_analyzer import *
 
from go import *
 
 
showBigPoints=True
 
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 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)
 
@@ -36,100 +37,110 @@ class Application(tk.Frame):
 
 
    # 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.
 
  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()
 
 
    for corner in self.corners.corners:
 
      self.markPoint(corner.x,corner.y)
 
    
 
    if self.boardGrid!=None:
 
    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')
 
    
 
    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")
 
    
 
    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()
 
    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")
 
    
 
    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()
 
 
    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.
 
  #  
src/image_analyzer.py
Show inline comments
 
modified file chmod 100644 => 100755
 
from go import *
 
 
 
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]
 
        
 
        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)
 
        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
 
    
 
    cmax=max(int(stoneWidth*2//7),2) # !! optimal parameters subject to further research
 
    rmax=max(int(stoneHeight*2//7),2)
 
    ((x1,y1),(x2,y2))=self.relevantRect(imageCoords,stoneWidth,stoneHeight)
 
    
 
    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))
 
    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*I<tresB: b+=1
 
        elif 100*I>tresW: 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
 
    
 
  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
0 comments (0 inline, 0 general)