Python 使用 Tesseract 自动化游戏

tesseract

开始试手

写游戏工具第一步当然是打开 Cheat Engine, 载入进程. 然而发现怎么找都找不到相关数据, 合理怀疑是 Webview, 也懒得开 Wireshark 求证了, 换过一种思路, 便想到了 OCR.

截图

利用 Windows API 找到窗口信息后, 直接调用 pyautogui.screenshot 就可以截取游戏界面了

# 找到窗口句柄
hwnd = win32gui.FindWindow(None, APP_NAME)

# 获取窗口信息
left, top, right, bottom = win32gui.GetWindowRect(hwnd)
width, height = right - left, bottom - top

# 截图
im = pyautogui.screenshot(region=(left, top, width, height))

# 保存落盘给 OCR 用
im.save(APP_SHOT_FILENAME)

OCR

想从界面中提取出需要的数字, 必须先把多余的信息去掉, 所以先转灰度, 然后再二值化一下.

import cv2

img = cv2.imread("screenshot.png")

# 根据比例裁剪一下
# ...

# 灰度
img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

# ref: http://opencv.jp/opencv-2svn/cpp/miscellaneous_image_transformations.html?highlight=threshold#threshold
# 将黑字保持, 其它转白
_, img = cv2.threshold(img, 40, 255, cv2.THRESH_BINARY)

cv2.imwrite("final.png", img)
threshold二值化出数字

之后就可以用 tesseract 识别一下, 然而效果并不美妙...

# psm: tesseract --help-extra
numbers = pytesseract.image_to_string(img, config='--psm 6 digits -c tessedit_char_whitelist="123456789"')

# 然而出来的数字可能会有重叠, 可能会有额外的数字. 很尴尬

所以换一种思路, 直接把白色格子抽出来识别, 准确率不是问题了.

# 查找边界
contours, _ = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)

# 遍历明显的边界, 判断
for contour in contours:
  # 获取格子
  x, y, w, h = cv2.boundingRect(contour)
  # 判断后再识别
find the grid找出格子

然而在循环中调用 tesseract 太慢了, 思考了一下, 将格子留白位尽量去掉, 然后组合成行, 再一次过识别.

# ...
numbers = []

# 判断是否一个格子的大约大小
if w > cellMinSize and h > cellMinSize and w < cellMaxSize and h < cellMaxSize:
  # 剪切出图像数据
  number = img[y : y + h, x : x + w]

  if number.size > 0:
    x, y, w, h = 0, 0, number.shape[1], number.shape[0]

    # 将格子周围留白去掉
    offset = 6
    number = number[
      y + offset : y + h - offset, x + offset : x + w - offset
    ]
    numbers.append(number)

# 组合成一行的图片数据
numbers = cv2.hconcat(numbers)    
# 再进行 OCR

这下速度与准确率都上去了~ 就开始重组回矩阵数据并开始写算法.

消消算法

这个游戏的玩法就是消除鼠标拖拽区域中相加为 10 的格子, 所以整个简易算法就好了, 只检测格子的 3 点, 6 点方向上的格子, 消除后置 0.

TARGET_SUM = 10
# 相邻层级, 相邻 1 -> n
level = 1
nums = []
# 3 点方向
for l in range(1, level + 1):
  if j + l < col:
  next = chessboard[i][j + l]
  
  # 将 3 点方向格子加入
  nums.append(next)

  # 如果找到
  if sum(nums) == TARGET_SUM:
    # 将坐标推入队列, 有子进程负责自动化
    taskQueue.put(([i, j], [i, j + l]), False)
    # 置 0, 标记消除
    for clean in range(0, l + 1):
      chessboard[i][j + clean] = 0
      found = True
      break

    # 超过目标值, 找不到辣!
    if sum(nums) > TARGET_SUM:
      break

  # 已经消除了, 就不用再判断 6 点方向了
  if found:
    continue

# 6 点方向
# ...

操作鼠标消除

到这已经没有技术量了, 定义好格子大小, 程序偏移就可以用 pyautogui 操作了

# 从队列中获取矩阵中索引
task = taskQueue.get(block=False)
fromCell, toCell = task
# 计算坐标
# ...
# 移动到首格
pyautogui.moveTo(fromPos)
time.sleep(0.06)
# 拖动到尾格
pyautogui.dragTo(toPos, duration=0.3)

操作完后, 等分涨就完辣! 当然, 这算法看下来就知道, 是没有考虑跨行/列矩阵的情况, 手玩一下就好了嘛.

130分好难...130分好难

相关文档

项目地址

项目已经开源到 Github, 就没测过高分屏下各种数据对不对得上, 缩放姑且是写了, 诶嘿

-- Fin --