相关文件
想学Python的小伙伴可以关注小编的公众号【Python日志】
有很多的资源可以白嫖的哈,不定时会更新一下Python的小知识的哈!!
前言
今天给大家分享的这个案例是用 Python+OpenCV 实现了自动扫雷,并突破了人类的世界记录。(当然这不算哈)
咱们废话不多说,先看成果~
相信许多人很早就知道有扫雷这么一款经典的游(显卡测试)戏(软件),更是有不少人曾听说过中国雷圣,也是中国扫雷第一、世界综合排名第二的郭蔚嘉的顶顶大名。扫雷作为一款在Windows9x时代就已经诞生的经典游戏,从过去到现在依然都有着它独特的魅力:快节奏高精准的鼠标操作要求、快速的反应能力、刷新纪录的快感,这些都是扫雷给雷友们带来的、只属于扫雷的独一无二的兴奋点。
准备
准备动手制作一套扫雷自动化软件之前,你需要准备如下一些工具/软件/环境
开发环境
- Python3 环境 - 推荐3.6或者以上 [更加推荐Anaconda3,以下很多依赖库无需安装]
- numpy依赖库 [如有Anaconda则无需安装]
- PIL依赖库 [如有Anaconda则无需安装]
- opencv-python
- win32gui、win32api依赖库
- 支持Python的IDE [可选,如果你能忍受用文本编辑器写程序也可以]
扫雷软件
当然,在正式开始之前,我们还需要了解一下扫雷的基础知识。如果不清楚的同学可以参考中国最大的扫雷论坛saolei.net中的文章:
http://saolei.net/BBS/Title.asp?Id=177
好啦,那么我们的准备工作已经全部完成了!让我们开始吧~
实现思路
在去做一件事情之前最重要的是什么?是将要做的这件事情在心中搭建一个步骤框架。只有这样,才能保证在去做这件事的过程中,尽可能的做到深思熟虑,使得最终有个好的结果。我们写程序也要尽可能做到在正式开始开发之前,在心中有个大致的思路。
对于本项目而言,大致的开发过程是这样的:
- 完成窗体内容截取部分
- 完成雷块分割部分
- 完成雷块类型识别部分
- 完成扫雷算法
好啦,既然我们有了个思路,那就撸起袖子大力干!
窗体截取
其实对于本项目而言,窗体截取是一个逻辑上简单,实现起来却相当麻烦的部分,而且还是必不可少的部分。我们通过Spy++得到了以下两点信息:
class_name = "TMain"
title_name = "Minesweeper Arbiter "
- ms_arbiter.exe的主窗体类别为"TMain"
- ms_arbiter.exe的主窗体名称为"Minesweeper Arbiter"
注意到了么?主窗体的名称后面有个空格。正是这个空格让笔者困扰了一会儿,只有加上这个空格,win32gui才能够正常的获取到窗体的句柄。
本项目采用了win32gui来获取窗体的位置信息,具体代码如下:
hwnd = win32gui.FindWindow(class_name, title_name)
if hwnd:
left, top, right, bottom = win32gui.GetWindowRect(hwnd)
通过以上代码,我们得到了窗体相对于整块屏幕的位置。之后我们需要通过PIL来进行扫雷界面的棋盘截取。
我们需要先导入PIL库
from PIL import ImageGrab
然后进行具体的操作。
left += 15
top += 101
right -= 15
bottom -= 43
rect = (left, top, right, bottom)
img = ImageGrab.grab().crop(rect)
聪明的你肯定一眼就发现了那些奇奇怪怪的Magic Numbers,没错,这的确是Magic Numbers,是我们通过一点点细微调节得到的整个棋盘相对于窗体的位置。
注意:这些数据仅在Windows10下测试通过,如果在别的Windows系统下,不保证相对位置的正确性,因为老版本的系统可能有不同宽度的窗体边框。
好啦,棋盘的图像我们有了,下一步就是对各个雷块进行图像分割了~
雷块分割
在进行雷块分割之前,我们事先需要了解雷块的尺寸以及它的边框大小。经过笔者的测量,在ms_arbiter下,每一个雷块的尺寸为16px*16px。
知道了雷块的尺寸,我们就可以进行每一个雷块的裁剪了。首先我们需要知道在横和竖两个方向上雷块的数量。
block_width, block_height = 16, 16
blocks_x = int((right - left) / block_width)
blocks_y = int((bottom - top) / block_height)
之后,我们建立一个二维数组用于存储每一个雷块的图像,并且进行图像分割,保存在之前建立的数组中。
def crop_block(hole_img, x, y):
x1, y1 = x * block_width, y * block_height
x2, y2 = x1 + block_width, y1 + block_height
return hole_img.crop((x1, y1, x2, y2))
blocks_img = [[0 for i in range(blocks_y)] for i in range(blocks_x)]
for y in range(blocks_y):
for x in range(blocks_x):
blocks_img[x][y] = crop_block(img, x, y)
将整个图像获取、分割的部分封装成一个库,随时调用就OK啦~在笔者的实现中,我们将这一部分封装成了imageProcess.py,其中函数get_frame()用于完成上述的图像获取、分割过程。
雷块识别
这一部分可能是整个项目里除了扫雷算法本身之外最重要的部分了。笔者在进行雷块检测的时候采用了比较简单的特征