基于 OpenCV 的 Python 自动扫雷实现
本文介绍使用 Python、OpenCV 和 win32gui 库开发自动扫雷程序的完整流程。涵盖环境搭建、窗口截取、图像分割、像素识别及核心扫雷算法的实现细节。通过特征点颜色判断方块状态,结合逻辑推理自动点击,实现自动化游戏操作。

本文介绍使用 Python、OpenCV 和 win32gui 库开发自动扫雷程序的完整流程。涵盖环境搭建、窗口截取、图像分割、像素识别及核心扫雷算法的实现细节。通过特征点颜色判断方块状态,结合逻辑推理自动点击,实现自动化游戏操作。

自动化技术在游戏测试与辅助领域有着广泛的应用。本文介绍如何使用 Python、OpenCV 以及 Windows API 库开发一个自动扫雷程序。该程序能够自动截取游戏窗口,识别棋盘状态,并通过逻辑算法自动完成点击操作。
在开始编写代码之前,需要确保开发环境满足以下要求:
numpy:用于数值计算。PIL (Pillow):用于图像处理。opencv-python:用于图像捕捉与分析。pywin32:包含 win32gui 和 win32api,用于获取窗口句柄和控制鼠标键盘。本项目必须使用 Minesweeper Arbiter (MS-Arbiter) 作为目标游戏。这是目前支持自动化控制的扫雷专用软件,普通 Windows 自带扫雷无法通过标准接口进行自动化交互。
整个项目的开发流程可以划分为四个主要步骤:
窗体截取是自动化操作的基础。我们需要精确定位游戏窗口的边界,以便后续截取棋盘区域。
通过 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 += 15
top += 101
right -= 15
bottom -= 43
rect = (left, top, right, bottom)
img = ImageGrab.grab().crop(rect)
注意:上述 Magic Numbers(魔法数字)仅在 Windows 10 下测试通过。若在其他系统运行,需根据实际窗口边框宽度重新校准坐标。

橙色区域为最终提取的棋盘图像。
在获取完整棋盘图像后,下一步是将图像分割成独立的单元格,以便逐个分析。
经过测量,在 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() 函数。
图像识别是核心环节之一。本方案采用中心点像素颜色结合特征点检测的方式,兼顾效率与准确性。
OpenCV 默认读取 BGR 格式,而 PIL 读取 RGB 格式,转换时需注意通道顺序。以下是具体的状态判断逻辑:
def analyze_block(self, block, location):
# 转换为 OpenCV 格式 (BGR)
block = imageProcess.pil_to_cv(block)
# 获取中心点颜色
block_color = block[8, 8]
x, y = location[0], location[1]
# 定义状态常量
# -1: 未打开
# -2: 已打开且空白
# -3: 未初始化
# 0-8: 对应数字
# 9: 地雷
# 0: 插旗 (此处复用 0 表示标记状态)
# 灰色背景 (未打开)
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 # 未打开
# 蓝色 (1)
elif self.equal(block_color, self.rgb_to_bgr((0, 0, 255))):
self.blocks_num[x][y] = 1
# 绿色 (2)
elif self.equal(block_color, self.rgb_to_bgr((0, 128, 0))):
self.blocks_num[x][y] = 2
# 红色 (3)
elif self.equal(block_color, self.rgb_to_bgr((255, 0, 0))):
self.blocks_num[x][y] = 3
# 深蓝 (4)
elif self.equal(block_color, self.rgb_to_bgr((0, 0, 128))):
self.blocks_num[x][y] = 4
# 深红 (5)
elif self.equal(block_color, self.rgb_to_bgr((128, 0, 0))):
self.blocks_num[x][y] = 5
# 青色 (6)
elif self.equal(block_color, self.rgb_to_bgr((0, 128, 128))):
self.blocks_num[x][y] = 6
# 黑色 (7/9/0)
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 # 其他黑色情况
# 深灰 (8)
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
通过直接读取像素值,避免了复杂的模板匹配,显著提升了识别速度。
算法部分决定了程序的智能程度。基本策略分为两步:先标记地雷,再挖掘安全区。
由于棋盘边缘不存在完整的九宫格,需要在生成邻域坐标时排除越界访问。
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)
遍历所有已打开的数字方块,统计周围未打开的方块数量。若数量等于当前数字,则剩余未打开方块均为地雷。
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)
再次遍历数字方块,统计周围已知地雷数量。若已知地雷数 + 未打开方块数 == 当前数字,则剩余未打开方块为安全区。
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)
整合上述逻辑,形成主循环。当检测到新步骤时,模拟鼠标操作。
# 图像分析
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 发送窗口消息,确保点击动作能被游戏窗口正确接收。
本方案通过图像识别与逻辑推理相结合的方式,实现了扫雷游戏的自动化。主要优势在于:
未来可进一步优化概率算法,解决死局情况下的随机猜测问题,提升通关率。

微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
生成新的随机RSA私钥和公钥pem证书。 在线工具,RSA密钥对生成器在线工具,online
基于 Mermaid.js 实时预览流程图、时序图等图表,支持源码编辑与即时渲染。 在线工具,Mermaid 预览与可视化编辑在线工具,online
解析常见 curl 参数并生成 fetch、axios、PHP curl 或 Python requests 示例代码。 在线工具,curl 转代码在线工具,online
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online