def getImageWithVideo(self, num): video = Video() filename = self.testMedia + "chessBoard%03d.jpg" % (num) image = video.readImage(filename) height, width = image.shape[:2] print ("read image %s: %dx%d" % (filename, width, height)) return image,video
def test_getSubRect(self): video = Video() image = video.readImage(testenv.testMedia+"chessBoard001.jpg") subImage = Video.getSubRect(image, (0, 0, 200, 200)) iheight, iwidth, channels = subImage.shape assert iheight == 200 assert iwidth == 200 assert channels == 3
def getVideo(self) -> Video: ''' get a Video (potentially headless) Returns: Video: the video handler for openCV ''' video = Video() video.headless = self.headless return video
def test_CreateBlank(self): video = Video() width = 400 height = 400 image = video.createBlank(width, height) iheight, iwidth, channels = image.shape print ("created blank %dx%d image with %d channels" % (iwidth, iheight, channels)) assert height == iheight assert width == iwidth assert channels == 3
def test_ReadJpg(self): video = Video() video.open(testenv.testMedia+'emptyBoard001.avi') for frame in range(0, 52): ret, jpgImage, quit = video.readFrame(show=True) assert ret assert jpgImage is not None height, width = jpgImage.shape[:2] # print ("%d: %d x %d" % (frame,width,height)) assert (width, height) == (640, 480) assert video.frames == 52
def test_ReadAvi(self): debug = False # "0","1" titles = ['scholarsmate', 'emptyBoard001'] expectedFrames = [334, 52] # (1920,1080),(1280,720), expectedSize = [(640, 480), (640, 480)] for index, title in enumerate(titles): args = Args("test") if Video.is_int(title): device = title else: device = testEnv.testMedia + title + ".avi" args.parse(["--input", device]) vision = ChessBoardVision(args.args) vision.showDebug = debug vision.open(args.args.input) frameIndex = 0 while True: cbImageSet = vision.readChessBoardImage() if not vision.hasImage: break frameIndex += 1 cbImage = cbImageSet.cbImage if debug: print("%d x %d" % (cbImage.width, cbImage.height)) assert (cbImage.width, cbImage.height) == expectedSize[index] assert (cbImageSet.frameIndex == frameIndex) if debug: print("%3d %.2fs" % (cbImageSet.frameIndex, cbImageSet.timeStamp)) assert frameIndex == expectedFrames[index] vision.save()
def test_Concatenate(self): video=Video() w=400 h=400 #https://stackoverflow.com/a/21170291/1497139 image1=video.createBlank(w, h, (192,192,192)) image2=video.createBlank(w, h, (255,128,0)) image3=video.createBlank(w, h, (255//3,255//3,255/3)) image4=video.createBlank(w, h, (0,0,255)) combined=video.as2x2(image1,image2,image3,image4,downScale=1) hc, wc= combined.shape[:2] assert hc==2*h assert wc==2*w video.showImage(combined, "combined",keyWait=1000)
def test_ReadVideoWithPostProcess(self): ''' test reading video frames with a post processing call back ''' video = Video.getVideo() video.open(testenv.testMedia + 'emptyBoard001.avi') for frame in range(0, 52): ret, jpgImage, quit = video.readFrame( show=True, postProcess=video.addTimeStamp)
def test_ReadVideo(self): ''' test reading an example video ''' video = Video.getVideo() video.open(testenv.testMedia + 'emptyBoard001.avi') video.play() print("played %d frames" % (video.frames)) assert video.frames == 52
def __init__(self, image, video=None): """ construct me from the given input image""" if video is None: video = Video() self.video = video self.image = image # guess the topleft color self.topleft = chess.WHITE self.height, self.width = self.image.shape[:2]
def __init__(self,frames,totalFrames,path,points,rotation=270,idealSize=800,ans=None): self.path=path self.title=Video.title(path) self.frames=frames self.totalFrames=totalFrames, self.points=points self.rotation=rotation self.idealSize=idealSize self.ans=ans
def test_ReadVideoWithPause(self): video = Video() video.open(testenv.testMedia+'emptyBoard001.avi') for frame in range(0, 62): if frame >= 10 and frame < 20: video.pause(True) else: video.pause(False) ret, jpgImage, quit = video.readFrame(show=True) # print (video.frames) assert ret assert jpgImage is not None height, width = jpgImage.shape[:2] # print ("%d: %d x %d" % (frame,width,height)) assert (width, height) == (640, 480) assert video.frames == 52
def __getstate__(self): state={} state["title"]=self.title device=self.device if not Video.is_int(device): cwd=os.getcwd() devicepath=os.path.dirname(device) root=os.path.commonpath([cwd,devicepath]) device=os.path.relpath(devicepath,root)+'/'+os.path.basename(device) state["device"]=device state["timestamps"]=self.timestamps return state
def setup(self, idealSize=640, video=None): # video access (for debugging and partly hiding open cv details) if video is None: self.video = Video() self.idealSize = idealSize s = idealSize self.pts_IdealSquare = np.asarray( [[0.0, 0.0], [s, 0.0], [s, s], [0.0, s]], dtype=np.float32) self.inverseTransform = cv2.getPerspectiveTransform( self.pts_dst, self.pts_IdealSquare) self.rotation = 0 # dict for average Colors self.averageColors = {} self.diffSumAverage = MovingAverage( ChessTrapezoid.DiffSumMovingAverageLength) # trapezoid representation of squares self.tsquares = {} for square in chess.SQUARES: tsquare = ChessTSquare(self, square) if ChessTrapezoid.debug: print(vars(tsquare)) self.tsquares[tsquare.square] = tsquare
def __init__(self,args,board=None): self.device=args.input self.title=Video.title(self.device) self.video=Video(self.title) self.args=args self.showDebug=args.debug self.start=None self.quitWanted=False self.hasImage=False self.timestamps=[] self.debug=args.debug if board is None: board=Board(args=args) self.board = board if self.args.fen is not None: self.board.updatePieces(self.args.fen) self.warp = Warp(args.warpPointList) self.warp.rotation = args.rotation if self.args.nowarp: self.warp.warping=True self.firstFrame=True self.speedup=args.speedup pass
def home(self): self.videoAnalyzer.vision.video = Video() return self.index("Home")
class ChessTrapezoid(Trapez2Square): """ Chess board Trapezoid (UK) / Trapezium (US) / Trapez (DE) as seen via a webcam image """ debug = False colorDebug = False showDebugImage = False rows = 8 cols = 8 # default radius of pieces PieceRadiusFactor = 3 DiffSumMovingAverageLength = 5 def __init__(self, trapezPoints, idealSize=640, rotation=0, video=None): self.rotation = rotation #trapezPoints=[topLeft,topRight,bottomRight,bottomLeft] shifts = self.rotation // 90 for shift in range(shifts): left = trapezPoints.pop(0) trapezPoints.append(left) topLeft, topRight, bottomRight, bottomLeft = trapezPoints super().__init__(topLeft, topRight, bottomRight, bottomLeft) self.setup(idealSize, video) def setup(self, idealSize=640, video=None): # video access (for debugging and partly hiding open cv details) if video is None: self.video = Video() self.idealSize = idealSize s = idealSize self.pts_IdealSquare = np.asarray( [[0.0, 0.0], [s, 0.0], [s, s], [0.0, s]], dtype=np.float32) self.inverseTransform = cv2.getPerspectiveTransform( self.pts_dst, self.pts_IdealSquare) self.rotation = 0 # dict for average Colors self.averageColors = {} self.diffSumAverage = MovingAverage( ChessTrapezoid.DiffSumMovingAverageLength) # trapezoid representation of squares self.tsquares = {} for square in chess.SQUARES: tsquare = ChessTSquare(self, square) if ChessTrapezoid.debug: print(vars(tsquare)) self.tsquares[tsquare.square] = tsquare def relativeToIdealXY(self, rx, ry): x = int(rx * self.idealSize) y = int(ry * self.idealSize) return x, y def tSquareAt(self, row, col, rotation=0): """ get the trapezoid chessboard square for the given row and column""" row, col = self.rotateIndices(row, col, rotation) squareIndex = (ChessTrapezoid.rows - 1 - row) * ChessTrapezoid.cols + col square = chess.SQUARES[squareIndex] return self.tsquares[square] def rotateIndices(self, row, col, rotation): """ rotate the indices or rows and columns according to the board rotation""" if rotation == 0: return row, col elif rotation == 90: return ChessTrapezoid.cols - 1 - col, row elif rotation == 180: return ChessTrapezoid.rows - 1 - row, ChessTrapezoid.cols - 1 - col elif rotation == 270: return col, ChessTrapezoid.rows - 1 - row else: raise Exception("invalid rotation %d for rotateIndices" % rotation) def genSquares(self): """ generator for all chess squares """ for square in chess.SQUARES: tsquare = self.tsquares[square] yield tsquare def drawCircle(self, image, center, radius, color, thickness=-1): """ draw a circle onto the given image at the given center point with the given radius, color and thickness. """ if color is not None: cv2.circle(image, center, radius, color=color, thickness=thickness) def drawRCircle(self, image, rcenter, rradius, color, thickness=-1): """ draw a circle with relative coordinates""" radius = int(rradius * self.idealSize) rx, ry = rcenter center = self.relativeToIdealXY(rx, ry) self.drawCircle(image, center, radius, color, thickness) def drawRCenteredText(self, image, text, rx, ry, color=(255, 255, 255)): x, y = self.relativeToIdealXY(rx, ry) self.video.drawCenteredText(image, text, x, y, fontBGRColor=color) def updatePieces(self, fen): """ update the piece positions according to the given FEN""" self.board = chess.Board(fen) for tsquare in self.genSquares(): piece = self.board.piece_at(tsquare.square) tsquare.piece = piece tsquare.fieldState = tsquare.getFieldState() def drawFieldStates(self, image, fieldStates, transformation=Transformation.ORIGINAL, channels=3): """ draw the states for fields with the given field states e.g. to set the mask image that will filter the trapezoid view according to piece positions when using maskImage""" if self.board is not None: for tsquare in self.genSquares(): if tsquare.fieldState in fieldStates: tsquare.drawState(image, transformation, channels) def prepareImageSet(self, cbImageSet): """ prepare the image set""" cbWarped = self.warpedBoardImage(cbImageSet.cbImage.image) averageColors = self.analyzeColors(cbWarped) cbImageSet.cbWarped = cbWarped cbIdeal = self.idealColoredBoard(cbWarped.width, cbWarped.height) cbImageSet.cbIdeal = cbIdeal cbImageSet.cbPreMove = self.preMoveBoard(cbWarped.width, cbWarped.height) cbImageSet.cbDiff = cbWarped.diffBoardImage(cbIdeal) return averageColors def warpedBoardImage(self, image): warped = cv2.warpPerspective(image, self.inverseTransform, (self.idealSize, self.idealSize)) return ChessBoardImage(warped, "warped") def diffSum(self, image, other): #diffImage=self.diff(other) #return diffImage.sum() # https://stackoverflow.com/questions/17829092/opencv-cv2-absdiffimg1-img2-sum-without-temporary-img diffSumValue = cv2.norm(image, other, cv2.NORM_L1) if ChessTrapezoid.debug: print("diffSum %.0f" % (diffSumValue)) return diffSumValue def idealColoredBoard(self, w, h, transformation=Transformation.IDEAL): """ draw an 'ideal' colored board according to a given set of parameters e.g. fieldColor, pieceColor, pieceRadius""" idealImage = self.video.getEmptyImage4WidthAndHeight(w, h, 3) for tsquare in self.genSquares(): tsquare.drawState(idealImage, transformation, 3) return ChessBoardImage(idealImage, "ideal") def preMoveBoard(self, w, h): """ get an image of the board as it was before any move """ refImage = self.video.getEmptyImage4WidthAndHeight(w, h, 3) for tsquare in self.genSquares(): tsquare.addPreMoveImage(refImage) return ChessBoardImage(refImage, "preMove ref") def drawDebug(self, image, color=(255, 255, 255)): """ draw debug information e.g. piecel symbol and an onto the given image""" for square in chess.SQUARES: tsquare = self.tsquares[square] tsquare.drawDebug(image, color) def byFieldState(self): # get a dict of fields sorted by field state sortedTSquares = {} for fieldState in FieldState: sortedTSquares[fieldState] = [] for tsquare in self.genSquares(): sortedTSquares[tsquare.fieldState].append(tsquare) return sortedTSquares def analyzeColors(self, cbImage): """ get the average colors per fieldState """ byFieldState = self.byFieldState() for fieldState in byFieldState.keys(): mask = self.video.getEmptyImage(cbImage.image) self.drawFieldStates(mask, [fieldState], Transformation.IDEAL, 1) masked = self.video.maskImage(cbImage.image, mask) countedFields = len(byFieldState[fieldState]) averageColor = Color(masked) self.averageColors[fieldState] = averageColor if ChessTrapezoid.showDebugImage: self.video.showImage(masked, fieldState.title()) if ChessTrapezoid.colorDebug: print("%15s (%2d): %s" % (fieldState.title(), countedFields, averageColor)) return self.averageColors def optimizeColorCheck(self, cbImage, averageColors, debug=False): optimalSelectivity = -100 colorStats = None for factor in [x * 0.05 for x in range(20, 41)]: """ optimize the factor for the color check""" startc = timer() fieldColorStatsCandidate = self.checkColors( cbImage, averageColors, factor) endc = timer() fieldColorStatsCandidate.analyzeStats(factor, endc - startc) if fieldColorStatsCandidate.minSelectivity > optimalSelectivity: optimalSelectivity = fieldColorStatsCandidate.minSelectivity colorStats = fieldColorStatsCandidate if debug: print("selectivity %5.1f white: %5.1f black: %5.1f " % (self.minSelectivity, self.whiteSelectivity, self.blackSelectivity)) return colorStats def checkColors(self, cbImage, averageColors, rangeFactor=1.0): """ check the colors against the expectation """ byFieldState = self.byFieldState() colorStats = FieldColorStats() for fieldState in byFieldState.keys(): # https://stackoverflow.com/questions/54019108/how-to-count-the-pixels-of-a-certain-color-with-opencv if fieldState in [ FieldState.WHITE_BLACK, FieldState.WHITE_EMPTY, FieldState.WHITE_WHITE ]: averageColor = averageColors[FieldState.WHITE_EMPTY] else: averageColor = averageColors[FieldState.BLACK_EMPTY] fields = byFieldState[fieldState] lower, upper = averageColor.colorRange(rangeFactor) if ChessTrapezoid.colorDebug: print("%25s (%2d): %s -> %s - %s" % (fieldState.title(), len(fields), averageColor, lower, upper)) for tsquare in fields: squareImage = tsquare.getSquareImage(cbImage) asExpected = cv2.inRange(squareImage, lower, upper) h, w = squareImage.shape[:2] pixels = h * w nonzero = cv2.countNonZero(asExpected) colorStats.push(fieldState, tsquare.an, nonzero / pixels * 100) #self.video.showImage(asExpected,tsquare.an) return colorStats def detectChanges(self, cbImageSet, detectState): """ detect the changes of the given imageset using the given detect state machine""" detectState.nextFrame() changes = {} validChanges = 0 diffSum = 0 cbImage = cbImageSet.cbImage cbDiff = cbImageSet.cbDiff for tsquare in self.genSquares(): squareChange = tsquare.squareChange(cbImage.image, cbDiff.image) changes[tsquare.an] = squareChange diffSum += abs(squareChange.diff) if squareChange.valid: validChanges += 1 #if self.frames==1: # tsquare.preMoveImage=np.copy(tsquare.squareImage) self.diffSumAverage.push(diffSum) diffSumDelta = self.diffSumAverage.mean() - diffSum detectState.check(validChanges, diffSum, diffSumDelta, squareChange.meanFrameCount) for tsquare in self.genSquares(): squareChange = changes[tsquare.an] tsquare.checkMoved(detectState) changes["validBoard"] = detectState.validBoard changes["valid"] = validChanges changes["diffSum"] = diffSum changes["diffSumDelta"] = diffSumDelta changes["validFrames"] = detectState.validFrames changes["invalidFrames"] = detectState.invalidFrames return changes
def test_ReadVideoWithPostProcess(self): video = Video() video.open(testenv.testMedia+'emptyBoard001.avi') for frame in range(0, 52): ret, jpgImage, quit = video.readFrame(show=True, postProcess=video.addTimeStamp)
def showDebug(self, video=None, keyWait=5): if video is None: video = Video() video.showImage(self.image, self.title, keyWait=keyWait)
class ChessBoardVision(JsonAbleMixin): """ implements access to chessboard images""" def __init__(self, args, board=None): self.device = args.input self.title = Video.title(self.device) self.video = Video(self.title) self.video.headless = Environment.inContinuousIntegration() self.args = args self.showDebug = args.debug self.start = None self.quitWanted = False self.hasImage = False self.timestamps = [] self.debug = args.debug if board is None: board = Board(args=args) self.board = board if self.args.fen is not None: self.board.updatePieces(self.args.fen) self.warp = Warp(args.warpPointList) self.warp.rotation = args.rotation if self.args.nowarp: self.warp.warping = True self.firstFrame = True self.speedup = args.speedup pass def open(self, device): self.video.capture(device) self.device = device self.firstFrame = True def readChessBoardImage(self): for i in range(self.speedup): self.hasImage, image, self.quitWanted = self.video.readFrame( self.showDebug) if self.quitWanted: return self.previous frames = self.video.frames if self.firstFrame: self.start = timer() timestamp = timer() - self.start self.chessBoardImageSet = ChessBoardImageSet(self, image, frames // self.speedup, timestamp) self.firstFrame = False self.timestamps.append(timestamp) return self.chessBoardImageSet def close(self): self.video.close() def __getstate__(self): state = {} state["title"] = self.title device = self.device if not Video.is_int(device): cwd = os.getcwd() devicepath = os.path.dirname(device) root = os.path.commonpath([cwd, devicepath]) device = os.path.relpath(devicepath, root) + '/' + os.path.basename(device) state["device"] = device state["timestamps"] = self.timestamps return state def __setstate__(self, state): self.title = state["title"] self.device = state["device"] self.timestamps = state["timestamps"] def save(self, path="games/videos"): env = Environment() savepath = str(env.projectPath) + "/" + path Environment.checkDir(savepath) jsonFile = savepath + "/" + self.title self.writeJson(jsonFile)
def test_ReadVideo(self): video = Video() video.open(testenv.testMedia+'emptyBoard001.avi') video.play() print ("played %d frames" % (video.frames)) assert video.frames == 52
def test_device(self): assert Video.is_int("0") v0 = int("0") assert v0 == 0