def getCalcuDoku(self): img = self.cutOut(self.img) self.calcuDoku = CalcuDoku(self.size) self.extractSudoku(img, self.size) print self.sudoku print self.right print self.below self.sectors = [[None for i in range(self.size)] for j in range(self.size)] for i in range(0, self.size): for j in range(0, self.size): if not self.sectors[i][j]: if self.sudoku[i][j]: ops = self.sudoku[i][j] b = Block(ops) self.calcuDoku.addBlock(b) b.addLocation(i+1, j+1) self.sectors[i][j] = b self.discover(i, j) else: print "huh" sys.exit(1) return self.calcuDoku
class ImageReader: """ based on https://github.com/KoffeinFlummi/SudokuSolver """ def __init__(self, debug, path, size): self.debug = debug self.size = size try: img = cv2.imread(path, 1) assert(img != None) except: print "Could not open image. Please make sure that the file you specified exists and is a valid image file.", path sys.exit(1) # size max 800 if img.shape[0] > img.shape[1]: sizecoef = 800. / img.shape[0] else: sizecoef = 800. / img.shape[1] if sizecoef < 1: img = cv2.resize(img, (0,0), fx=sizecoef, fy=sizecoef) self.showImage(img, "image") self.img = img def showImage(self, img, name): if self.debug: cv2.namedWindow(name) cv2.imshow(name, img) cv2.waitKey() def cutOut(self, img): # Grayscale image for easier processing gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) canny = cv2.Canny(gray, 50, 200) self.showImage(canny, "edge") # Detect contours contours, hierarchy = cv2.findContours(canny, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE) # Filter contours for things that might be squares squares = [] for contour in contours: contour = cv2.approxPolyDP(contour, 0.02 * cv2.arcLength(contour, True), True) if len(contour) == 4 and cv2.isContourConvex(contour): squares.append(contour) # Find the biggest one. squares = [sorted(squares, key=lambda x: cv2.contourArea(x))[-1]] squares[0] = squares[0].reshape(-1, 2) imgcontours = img cv2.drawContours(imgcontours, squares, -1, (0,0,255)) self.showImage(imgcontours, "squares") # Arrange the border points of the contour we found so that they match pointsNew. pointsOriginal = sorted(squares[0], key=lambda x: x[0]) pointsOriginal[0:2] = sorted(pointsOriginal[0:2], key=lambda x: x[1]) pointsOriginal[2:4] = sorted(pointsOriginal[2:4], key=lambda x: x[1]) pointsOriginal = numpy.float32(pointsOriginal) pointsNew = numpy.float32([[0,0],[0,450],[450,0],[450,450]]) # Warp the image to be a square. persTrans = cv2.getPerspectiveTransform(pointsOriginal, pointsNew) fixedImage = cv2.warpPerspective(img, persTrans, (450,450)) # setup a reverse warp self.pointsOriginal = pointsOriginal self.warp = cv2.getPerspectiveTransform(pointsNew, pointsOriginal) self.showImage(fixedImage, "perspectivefix") return fixedImage def extractSudoku(self, img, size): """ Extracts the actual numbers from the image using tesseract. """ self.sudoku = [] self.right = [] self.below = [] border = 6 # how much to cut off the edges to eliminate any of the lines between the cells piece = 450 / size for i in range(size): sudoku_temp = [] right_temp = [] below_temp = [] for j in range(size): value = self.findNumber(img, i, j, piece, border) sudoku_temp.append(value.strip()) edge = self.findEdge(img, i, j, piece, border, "right") right_temp.append(edge) edge = self.findEdge(img, i, j, piece, border, "below") below_temp.append(edge) self.sudoku.append(sudoku_temp) self.right.append(right_temp) self.below.append(below_temp) def findEdge(self, img, i, j, piece, border, direction): if direction == "below": subimg = img[ max(0, (i+1)*piece-border):(i+1)*piece+border, j*piece+border:(j+1)*piece-border ] else: subimg = img[ i*piece+border:(i+1)*piece-border, max(0, (j+1)*piece-border):(j+1)*piece+border ] subimg = cv2.cvtColor(subimg, cv2.COLOR_BGR2RGB) ret,thresh = cv2.threshold(subimg,127,255,cv2.THRESH_BINARY) # black-and-white for most contrast (occur, bins) = numpy.histogram(thresh, 2) (black, white) = occur return black*3 > white def findNumber(self, img, i, j, piece, border): subimg = img[i*piece+border:(i+1)*piece-border, j*piece+border:(j+1)*piece-border] subimg = cv2.cvtColor(subimg, cv2.COLOR_BGR2RGB) ret,thresh = cv2.threshold(subimg,127,255,cv2.THRESH_BINARY) # black-and-white for most contrast cv2.imwrite("tesseract/input.png", thresh) try: subprocess.check_output("tesseract tesseract/input.png tesseract/output -psm 8 calcudoku-chars", shell=True) digit = (open("tesseract/output.txt", "r").read()) except: digit = "" pass print "digit", digit.strip() return digit def discover(self, i, j): for (vertical, lateral) in [(0,1), (0,-1), (1,0), (-1,0)]: ii = i+vertical ilat = i + (vertical-1)/2 jj = j+lateral jlat = j + (lateral-1)/2 if ii in range(self.size) and jj in range(self.size): if not self.sectors[ii][jj]: if lateral and not self.right[i][jlat] or vertical and not self.below[ilat][j]: block = self.sectors[i][j] self.sectors[ii][jj] = block block.addLocation(ii+1, jj+1) self.calcuDoku.printMatrix() self.discover(ii, jj) def getCalcuDoku(self): img = self.cutOut(self.img) self.calcuDoku = CalcuDoku(self.size) self.extractSudoku(img, self.size) print self.sudoku print self.right print self.below self.sectors = [[None for i in range(self.size)] for j in range(self.size)] for i in range(0, self.size): for j in range(0, self.size): if not self.sectors[i][j]: if self.sudoku[i][j]: ops = self.sudoku[i][j] b = Block(ops) self.calcuDoku.addBlock(b) b.addLocation(i+1, j+1) self.sectors[i][j] = b self.discover(i, j) else: print "huh" sys.exit(1) return self.calcuDoku def writeSolution(self, solution): overlay = 255*numpy.ones((450,450,3), numpy.uint8) piece = 450 / self.size for i in range(0, self.size): for j in range(0, self.size): cv2.putText(overlay, str(solution[j][i]), (piece*i+35, piece*j+80), cv2.FONT_HERSHEY_SIMPLEX, 2, 10) self.showImage(overlay, "Overlay") fixedImage = cv2.warpPerspective(overlay, self.warp, (self.img.shape[1],self.img.shape[0])) self.showImage(fixedImage, "Perspective overlay") self.overlayImage(fixedImage) self.showImage(self.img, "Solution") def overlayImage(self, overlay): for x in range(min(overlay.shape[1], self.img.shape[1])): for y in range(min(overlay.shape[0], self.img.shape[0])): source = self.img[y, x] over = overlay[y, x] # skip black and white pixels, as we do not use an alpha channel if over[0] != 255 and over[0] != 0: self.img[y, x] = (source + over)/2