跳到主要内容基于 OpenCV 的 Python 自动扫雷实现 | 极客日志PythonAI算法
基于 OpenCV 的 Python 自动扫雷实现
介绍使用 Python、OpenCV 和 win32gui 库开发自动扫雷程序的完整流程。涵盖环境搭建、窗口截取、图像分割、像素识别及核心扫雷算法的实现细节。通过特征点颜色判断方块状态,结合逻辑推理自动点击,实现自动化游戏操作。
PgDevote9 浏览 基于 OpenCV 的 Python 自动扫雷实现
前言
自动化技术在游戏测试与辅助领域有着广泛的应用。本文介绍如何使用 Python、OpenCV 以及 Windows API 库开发一个自动扫雷程序。该程序能够自动截取游戏窗口,识别棋盘状态,并通过逻辑算法自动完成点击操作。
准备工作
在开始编写代码之前,需要确保开发环境满足以下要求:
1. 开发环境
- Python 版本:推荐使用 Python 3.6 或更高版本。
- Anaconda(可选):如果安装 Anaconda,大部分依赖库无需单独配置。
- 核心依赖库:
numpy:用于数值计算。
PIL (Pillow):用于图像处理。
opencv-python:用于图像捕捉与分析。
pywin32:包含 win32gui 和 win32api,用于获取窗口句柄和控制鼠标键盘。
- IDE:支持 Python 的开发工具,如 PyCharm 或 VS Code。
2. 扫雷软件
本项目必须使用 Minesweeper Arbiter (MS-Arbiter) 作为目标游戏。这是目前支持自动化控制的扫雷专用软件,普通 Windows 自带扫雷无法通过标准接口进行自动化交互。
实现思路
整个项目的开发流程可以划分为四个主要步骤:
- 窗体截取:获取游戏主窗口的坐标及截图。
- 雷块分割:将截取的图像按网格切分为单个方块。
- 雷块识别:分析每个方块的像素特征,判断其状态(数字、地雷、旗帜等)。
- 扫雷算法:根据识别结果,执行逻辑推理并控制鼠标点击。
0x01 窗体截取
窗体截取是自动化操作的基础。我们需要精确定位游戏窗口的边界,以便后续截取棋盘区域。
获取窗口句柄
通过 Spy++ 工具分析 MS-Arbiter 的主窗口信息:
class_name = "TMain"
title_name = "Minesweeper Arbiter "
注意:窗口名称末尾包含一个空格,这是 win32gui.FindWindow 成功匹配的关键。代码如下:
import win32gui
hwnd = win32gui.FindWindow(class_name, title_name)
if hwnd:
left, top, right, bottom = win32gui.GetWindowRect(hwnd)
else:
raise Exception("未找到扫雷窗口")
截取棋盘区域
获取窗口矩形后,需要扣除标题栏和边框,仅保留棋盘部分。这部分坐标通常需要通过调试微调获得。
from PIL import ImageGrab
left +=
top +=
right -=
bottom -=
rect = (left, top, right, bottom)
img = ImageGrab.grab().crop(rect)
15
101
15
43
注意:上述 Magic Numbers(魔法数字)仅在 Windows 10 下测试通过。若在其他系统运行,需根据实际窗口边框宽度重新校准坐标。
0x02 雷块分割
在获取完整棋盘图像后,下一步是将图像分割成独立的单元格,以便逐个分析。
确定网格尺寸
经过测量,在 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 = [[None for _ in range(blocks_y)] for _ 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)
为了便于调用,可将此过程封装为 imageProcess.py 模块中的 get_frame() 函数。
0x03 雷块识别
图像识别是核心环节之一。本方案采用中心点像素颜色结合特征点检测的方式,兼顾效率与准确性。
颜色映射逻辑
OpenCV 默认读取 BGR 格式,而 PIL 读取 RGB 格式,转换时需注意通道顺序。以下是具体的状态判断逻辑:
def analyze_block(self, block, location):
block = imageProcess.pil_to_cv(block)
block_color = block[8, 8]
x, y = location[0], location[1]
if self.equal(block_color, self.rgb_to_bgr((192, 192, 192))):
if not self.equal(block[8, 1], self.rgb_to_bgr((255, 255, 255))):
self.blocks_num[x][y] = -2
self.is_started = True
else:
self.blocks_num[x][y] = -1
elif self.equal(block_color, self.rgb_to_bgr((0, 0, 255))):
self.blocks_num[x][y] = 1
elif self.equal(block_color, self.rgb_to_bgr((0, 128, 0))):
self.blocks_num[x][y] = 2
elif self.equal(block_color, self.rgb_to_bgr((255, 0, 0))):
self.blocks_num[x][y] = 3
elif self.equal(block_color, self.rgb_to_bgr((0, 0, 128))):
self.blocks_num[x][y] = 4
elif self.equal(block_color, self.rgb_to_bgr((128, 0, 0))):
self.blocks_num[x][y] = 5
elif self.equal(block_color, self.rgb_to_bgr((0, 128, 128))):
self.blocks_num[x][y] = 6
elif self.equal(block_color, self.rgb_to_bgr((0, 0, 0))):
if self.equal(block[6, 6], self.rgb_to_bgr((255, 255, 255))):
self.blocks_num[x][y] = 9
elif self.equal(block[5, 8], self.rgb_to_bgr((255, 0, 0))):
self.blocks_num[x][y] = 0
else:
self.blocks_num[x][y] = 7
elif self.equal(block_color, self.rgb_to_bgr((128, 128, 128))):
self.blocks_num[x][y] = 8
else:
self.blocks_num[x][y] = -3
self.is_mine_form = False
if self.blocks_num[x][y] == -3 or not self.blocks_num[x][y] == -1:
self.is_new_start = False
状态说明
- 1-8:表示周围数字。
- 9:表示地雷。
- 0:表示已插旗。
- -1:表示未打开。
- -2:表示已打开但无数字(空白)。
- -3:未知类型。
通过直接读取像素值,避免了复杂的模板匹配,显著提升了识别速度。
0x04 扫雷算法实现
算法部分决定了程序的智能程度。基本策略分为两步:先标记地雷,再挖掘安全区。
1. 核生成与边界处理
由于棋盘边缘不存在完整的九宫格,需要在生成邻域坐标时排除越界访问。
def generate_kernel(k, k_width, k_height, block_location):
ls = []
loc_x, loc_y = block_location[0], block_location[1]
for now_y in range(k_height):
for now_x in range(k_width):
if k[now_y][now_x]:
rel_x, rel_y = now_x - 1, now_y - 1
ls.append((loc_y + rel_y, loc_x + rel_x))
return ls
kernel_width, kernel_height = 3, 3
kernel = [[1, 1, 1], [1, 1, 1], [1, 1, 1]]
if x == 0:
for i in range(kernel_height):
kernel[i][0] = 0
if x == self.blocks_x - 1:
for i in range(kernel_height):
kernel[i][kernel_width - 1] = 0
if y == 0:
for i in range(kernel_width):
kernel[0][i] = 0
if y == self.blocks_y - 1:
for i in range(kernel_width):
kernel[kernel_height - 1][i] = 0
to_visit = generate_kernel(kernel, kernel_width, kernel_height, location)
2. 地雷标记逻辑
遍历所有已打开的数字方块,统计周围未打开的方块数量。若数量等于当前数字,则剩余未打开方块均为地雷。
def count_unopen_blocks(blocks):
count = 0
for single_block in blocks:
if self.blocks_num[single_block[1]][single_block[0]] == -1:
count += 1
return count
def mark_as_mine(blocks):
for single_block in blocks:
if self.blocks_num[single_block[1]][single_block[0]] == -1:
self.blocks_is_mine[single_block[1]][single_block[0]] = 1
unopen_blocks = count_unopen_blocks(to_visit)
if unopen_blocks == self.blocks_num[x][y]:
mark_as_mine(to_visit)
3. 安全点击逻辑
再次遍历数字方块,统计周围已知地雷数量。若已知地雷数 + 未打开方块数 == 当前数字,则剩余未打开方块为安全区。
def mark_to_click_block(blocks):
for single_block in blocks:
if not self.blocks_is_mine[single_block[1]][single_block[0]] == 1:
if self.blocks_num[single_block[1]][single_block[0]] == -1:
if not (single_block[1], single_block[0]) in self.next_steps:
self.next_steps.append((single_block[1], single_block[0]))
def count_mines(blocks):
count = 0
for single_block in blocks:
if self.blocks_is_mine[single_block[1]][single_block[0]] == 1:
count += 1
return count
mines_count = count_mines(to_visit)
if mines_count == self.blocks_num[x][y]:
mark_to_click_block(to_visit)
4. 执行循环
整合上述逻辑,形成主循环。当检测到新步骤时,模拟鼠标操作。
self.iterate_blocks_image(BoomMine.analyze_block)
self.iterate_blocks_number(BoomMine.detect_mine)
self.iterate_blocks_number(BoomMine.detect_to_click_block)
if self.is_in_form(mouseOperation.get_mouse_point()):
for to_click in self.next_steps:
on_screen_location = self.rel_loc_to_real(to_click)
mouseOperation.mouse_move(on_screen_location[0], on_screen_location[1])
mouseOperation.mouse_click()
其中 mouseOperation 模块基于 win32api 发送窗口消息,确保点击动作能被游戏窗口正确接收。
总结
本方案通过图像识别与逻辑推理相结合的方式,实现了扫雷游戏的自动化。主要优势在于:
- 高效性:直接像素采样比模板匹配更快。
- 稳定性:使用 MS-Arbiter 配合 WinAPI 保证了操作的精准度。
- 可扩展性:模块化设计便于移植到其他类似游戏。
未来可进一步优化概率算法,解决死局情况下的随机猜测问题,提升通关率。
相关免费在线工具
- 加密/解密文本
使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
- RSA密钥对生成器
生成新的随机RSA私钥和公钥pem证书。 在线工具,RSA密钥对生成器在线工具,online
- Mermaid 预览与可视化编辑
基于 Mermaid.js 实时预览流程图、时序图等图表,支持源码编辑与即时渲染。 在线工具,Mermaid 预览与可视化编辑在线工具,online
- 随机西班牙地址生成器
随机生成西班牙地址(支持马德里、加泰罗尼亚、安达卢西亚、瓦伦西亚筛选),支持数量快捷选择、显示全部与下载。 在线工具,随机西班牙地址生成器在线工具,online
- Gemini 图片去水印
基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online
- curl 转代码
解析常见 curl 参数并生成 fetch、axios、PHP curl 或 Python requests 示例代码。 在线工具,curl 转代码在线工具,online