当前位置: 首页 > news >正文

Python OpenCV 图像识别:QQ三国华容道5阶拼图自动化脚本开发指南

Python OpenCV 图像识别:QQ三国华容道5阶拼图自动化脚本开发指南

1. 游戏窗口定位与图像采集

开发自动化脚本的第一步是准确捕获游戏窗口内容。这里我们使用PyWin32库实现窗口定位,配合OpenCV进行图像采集:

import win32gui import numpy as np import cv2 def capture_game_window(window_title): hwnd = win32gui.FindWindow(None, window_title) if not hwnd: raise Exception("游戏窗口未找到") left, top, right, bottom = win32gui.GetWindowRect(hwnd) width = right - left height = bottom - top # 使用DXGI捕获窗口内容(需安装dxcam库) import dxcam camera = dxcam.create() frame = camera.grab(region=(left, top, right, bottom)) if frame is None: # 备用截图方案 from PIL import ImageGrab frame = np.array(ImageGrab.grab(bbox=(left, top, right, bottom))) frame = cv2.cvtColor(frame, cv2.COLOR_RGB2BGR) return frame

提示:DXGI捕获方式比传统截图快3-5倍,特别适合需要高频截图的场景

窗口定位后,我们需要从完整截图中提取拼图区域。通过边缘检测和轮廓分析可以自动定位拼图区域:

def locate_puzzle_area(image): gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) blurred = cv2.GaussianBlur(gray, (5, 5), 0) edged = cv2.Canny(blurred, 50, 150) contours, _ = cv2.findContours(edged.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) contours = sorted(contours, key=cv2.contourArea, reverse=True)[:5] puzzle_contour = None for contour in contours: peri = cv2.arcLength(contour, True) approx = cv2.approxPolyDP(contour, 0.02 * peri, True) if len(approx) == 4: puzzle_contour = approx break if puzzle_contour is None: raise Exception("拼图区域定位失败") # 透视变换矫正拼图区域 pts = puzzle_contour.reshape(4, 2) rect = np.zeros((4, 2), dtype="float32") s = pts.sum(axis=1) rect[0] = pts[np.argmin(s)] rect[2] = pts[np.argmax(s)] diff = np.diff(pts, axis=1) rect[1] = pts[np.argmin(diff)] rect[3] = pts[np.argmax(diff)] (tl, tr, br, bl) = rect width = max(np.linalg.norm(tr - tl), np.linalg.norm(br - bl)) height = max(np.linalg.norm(bl - tl), np.linalg.norm(br - tr)) dst = np.array([ [0, 0], [width - 1, 0], [width - 1, height - 1], [0, height - 1]], dtype="float32") M = cv2.getPerspectiveTransform(rect, dst) warped = cv2.warpPerspective(image, M, (int(width), int(height))) return warped

2. 拼图块分割与特征提取

获得矫正后的拼图区域后,我们需要将其分割为5×5的独立拼图块。这里采用自适应阈值分割方法:

def split_puzzle_blocks(puzzle_image, grid_size=5): height, width = puzzle_image.shape[:2] block_h, block_w = height // grid_size, width // grid_size blocks = [] for i in range(grid_size): for j in range(grid_size): y1 = i * block_h y2 = (i + 1) * block_h x1 = j * block_w x2 = (j + 1) * block_w block = puzzle_image[y1:y2, x1:x2] blocks.append((i, j, block)) return blocks

为提高识别准确率,我们采用混合特征提取方案:

  1. SIFT特征点检测(适合处理图像旋转和缩放)
  2. 感知哈希(快速比对相似度)
  3. 边缘直方图(增强纹理特征)
def extract_features(image): # SIFT特征 sift = cv2.SIFT_create() kp, des = sift.detectAndCompute(image, None) # 感知哈希 phash = compute_phash(image) # 边缘直方图 edges = cv2.Canny(image, 100, 200) hist = cv2.calcHist([edges], [0], None, [8], [0, 256]) hist = hist.flatten() hist /= hist.sum() # 归一化 return { 'sift': (kp, des), 'phash': phash, 'edge_hist': hist } def compute_phash(image, hash_size=16): # 缩放图像 resized = cv2.resize(image, (hash_size, hash_size)) # 转换为灰度图 gray = cv2.cvtColor(resized, cv2.COLOR_BGR2GRAY) # 计算DCT变换 dct = cv2.dct(np.float32(gray)) # 取左上角8x8区域(保留低频信息) dct_roi = dct[:8, :8] # 计算平均值(排除直流分量) avg = np.mean(dct_roi[1:, 1:]) # 生成哈希 hash_val = (dct_roi > avg).flatten() return hash_val

3. 图像匹配与拼图状态分析

通过特征比对确定每个拼图块的目标位置,我们设计了一个多级匹配策略:

  1. 初级筛选:使用感知哈希快速排除明显不匹配的块
  2. 中级匹配:使用边缘直方图比对相似度
  3. 精确匹配:对候选块使用SIFT特征点匹配
def match_blocks(blocks, target_image): target_blocks = split_puzzle_blocks(target_image) target_features = [extract_features(block) for _, _, block in target_blocks] # 构建位置映射关系 position_map = {} for i, j, block in blocks: block_features = extract_features(block) # 第一阶段:感知哈希筛选 phash_distances = [] for idx, tf in enumerate(target_features): dist = hamming_distance(block_features['phash'], tf['phash']) phash_distances.append((idx, dist)) # 取前3个候选 candidates = sorted(phash_distances, key=lambda x: x[1])[:3] # 第二阶段:边缘直方图比对 hist_distances = [] for idx, _ in candidates: hist_dist = cv2.compareHist( block_features['edge_hist'], target_features[idx]['edge_hist'], cv2.HISTCMP_BHATTACHARYYA ) hist_distances.append((idx, hist_dist)) best_candidate = min(hist_distances, key=lambda x: x[1]) # 第三阶段:SIFT特征验证 if block_features['sift'][1] is not None and target_features[best_candidate[0]]['sift'][1] is not None: # 使用FLANN匹配器 FLANN_INDEX_KDTREE = 1 index_params = dict(algorithm=FLANN_INDEX_KDTREE, trees=5) search_params = dict(checks=50) flann = cv2.FlannBasedMatcher(index_params, search_params) matches = flann.knnMatch( block_features['sift'][1], target_features[best_candidate[0]]['sift'][1], k=2 ) # 应用Lowe's比率测试 good_matches = [] for m, n in matches: if m.distance < 0.7 * n.distance: good_matches.append(m) if len(good_matches) > 10: position_map[(i, j)] = best_candidate[0] # 如果SIFT匹配失败,回退到第二阶段结果 if (i, j) not in position_map: position_map[(i, j)] = best_candidate[0] return position_map def hamming_distance(hash1, hash2): return sum(c1 != c2 for c1, c2 in zip(hash1, hash2))

4. 自动化求解算法实现

基于A*算法的改进版本来求解拼图路径,关键改进点包括:

  1. 启发式函数优化:结合曼哈顿距离和线性冲突检测
  2. 状态缓存:避免重复计算相同状态
  3. 移动优先级:优先移动边缘拼图块
import heapq from collections import defaultdict class PuzzleSolver: def __init__(self, initial_state, goal_state): self.initial_state = initial_state self.goal_state = goal_state self.size = len(initial_state) def solve(self): open_set = [] heapq.heappush(open_set, (0, tuple(map(tuple, self.initial_state)))) came_from = {} g_score = defaultdict(lambda: float('inf')) g_score[tuple(map(tuple, self.initial_state))] = 0 f_score = defaultdict(lambda: float('inf')) f_score[tuple(map(tuple, self.initial_state))] = self.heuristic(self.initial_state) open_set_hash = {tuple(map(tuple, self.initial_state))} while open_set: current = heapq.heappop(open_set)[1] open_set_hash.remove(current) if self.is_goal(current): return self.reconstruct_path(came_from, current) for neighbor in self.get_neighbors(list(map(list, current))): neighbor_tuple = tuple(map(tuple, neighbor)) tentative_g_score = g_score[current] + 1 if tentative_g_score < g_score[neighbor_tuple]: came_from[neighbor_tuple] = current g_score[neighbor_tuple] = tentative_g_score f_score[neighbor_tuple] = tentative_g_score + self.heuristic(neighbor) if neighbor_tuple not in open_set_hash: heapq.heappush(open_set, (f_score[neighbor_tuple], neighbor_tuple)) open_set_hash.add(neighbor_tuple) return None # 无解 def heuristic(self, state): # 曼哈顿距离 + 线性冲突 distance = 0 size = len(state) for i in range(size): for j in range(size): if state[i][j] == 0: continue goal_i, goal_j = divmod(state[i][j] - 1, size) distance += abs(i - goal_i) + abs(j - goal_j) # 线性冲突检测 for i in range(size): for j in range(size): if state[i][j] == 0: continue goal_i, _ = divmod(state[i][j] - 1, size) if goal_i == i: for k in range(j + 1, size): if state[i][k] == 0: continue goal_k, _ = divmod(state[i][k] - 1, size) if goal_k == i and state[i][j] > state[i][k]: distance += 2 return distance def get_neighbors(self, state): neighbors = [] size = len(state) blank_i, blank_j = self.find_blank(state) for di, dj in [(0, 1), (1, 0), (0, -1), (-1, 0)]: new_i, new_j = blank_i + di, blank_j + dj if 0 <= new_i < size and 0 <= new_j < size: new_state = [row[:] for row in state] new_state[blank_i][blank_j], new_state[new_i][new_j] = new_state[new_i][new_j], new_state[blank_i][blank_j] neighbors.append(new_state) return neighbors def find_blank(self, state): for i in range(len(state)): for j in range(len(state[i])): if state[i][j] == 0: return i, j return -1, -1 def is_goal(self, state): return all(state[i][j] == self.goal_state[i][j] for i in range(len(state)) for j in range(len(state[i]))) def reconstruct_path(self, came_from, current): path = [] while current in came_from: prev = came_from[current] diff = self.get_move_difference(prev, current) path.append(diff) current = prev path.reverse() return path def get_move_difference(self, state1, state2): state1 = list(map(list, state1)) state2 = list(map(list, state2)) size = len(state1) blank1_i, blank1_j = self.find_blank(state1) blank2_i, blank2_j = self.find_blank(state2) # 返回移动的拼图块数字和方向 moved_piece = state1[blank2_i][blank2_j] direction = (blank1_i - blank2_i, blank1_j - blank2_j) return moved_piece, direction

5. 自动化操作与图形界面

使用PyAutoGUI实现自动化操作,同时开发可视化调试界面:

import pyautogui import tkinter as tk from PIL import Image, ImageTk class PuzzleSolverGUI: def __init__(self, root, solver): self.root = root self.solver = solver self.setup_ui() def setup_ui(self): self.root.title("QQ三国华容道自动化助手") # 状态显示区域 self.status_frame = tk.Frame(self.root) self.status_frame.pack(pady=10) self.status_label = tk.Label( self.status_frame, text="准备就绪", font=("Arial", 12) ) self.status_label.pack() # 图像显示区域 self.image_frame = tk.Frame(self.root) self.image_frame.pack() self.canvas = tk.Canvas( self.image_frame, width=600, height=400 ) self.canvas.pack() # 控制按钮 self.control_frame = tk.Frame(self.root) self.control_frame.pack(pady=10) self.start_btn = tk.Button( self.control_frame, text="开始自动求解", command=self.start_solving, width=15 ) self.start_btn.pack(side=tk.LEFT, padx=5) self.pause_btn = tk.Button( self.control_frame, text="暂停", command=self.pause_solving, width=15, state=tk.DISABLED ) self.pause_btn.pack(side=tk.LEFT, padx=5) self.reset_btn = tk.Button( self.control_frame, text="重置", command=self.reset_solver, width=15 ) self.reset_btn.pack(side=tk.LEFT, padx=5) # 日志区域 self.log_frame = tk.Frame(self.root) self.log_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10) self.log_text = tk.Text( self.log_frame, height=10, wrap=tk.WORD ) self.log_text.pack(fill=tk.BOTH, expand=True) scrollbar = tk.Scrollbar(self.log_text) scrollbar.pack(side=tk.RIGHT, fill=tk.Y) self.log_text.config(yscrollcommand=scrollbar.set) scrollbar.config(command=self.log_text.yview) def update_image(self, image): image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB) image = Image.fromarray(image) image = image.resize((600, 400), Image.LANCZOS) photo = ImageTk.PhotoImage(image) self.canvas.create_image(0, 0, anchor=tk.NW, image=photo) self.canvas.image = photo def log_message(self, message): self.log_text.insert(tk.END, message + "\n") self.log_text.see(tk.END) self.root.update() def start_solving(self): self.status_label.config(text="正在求解中...") self.start_btn.config(state=tk.DISABLED) self.pause_btn.config(state=tk.NORMAL) # 在后台线程中运行求解过程 import threading solving_thread = threading.Thread(target=self.run_solving) solving_thread.daemon = True solving_thread.start() def run_solving(self): try: # 1. 捕获游戏窗口 game_image = capture_game_window("QQ三国") self.update_image(game_image) self.log_message("成功捕获游戏窗口") # 2. 定位拼图区域 puzzle_area = locate_puzzle_area(game_image) self.update_image(puzzle_area) self.log_message("拼图区域定位成功") # 3. 分割拼图块 blocks = split_puzzle_blocks(puzzle_area) self.log_message(f"成功分割为{len(blocks)}个拼图块") # 4. 获取目标图像(右上角参考图) # 这里需要根据实际游戏界面调整坐标 target_image = game_image[50:250, 400:600] # 示例坐标 target_blocks = split_puzzle_blocks(target_image) # 5. 匹配拼图块 position_map = match_blocks(blocks, target_image) self.log_message("拼图块匹配完成") # 6. 构建初始状态矩阵 size = 5 initial_state = [[0]*size for _ in range(size)] goal_state = [[i*size + j + 1 for j in range(size)] for i in range(size)] goal_state[size-1][size-1] = 0 # 空白块 for (i, j), target_idx in position_map.items(): goal_i, goal_j = divmod(target_idx, size) initial_state[i][j] = goal_i * size + goal_j + 1 # 设置空白块 blank_pos = next((i, j) for i in range(size) for j in range(size) if initial_state[i][j] == size*size) initial_state[blank_pos[0]][blank_pos[1]] = 0 # 7. 求解拼图 solver = PuzzleSolver(initial_state, goal_state) solution = solver.solve() if not solution: self.log_message("未找到解决方案") return self.log_message(f"找到解决方案,共{len(solution)}步") # 8. 执行自动化操作 for step in solution: if self.paused: while self.paused: time.sleep(0.1) if self.stopped: return piece, (di, dj) = step self.log_message(f"移动拼图块 {piece}: 方向({di}, {dj})") # 计算拼图块在屏幕上的位置 block_size = puzzle_area.shape[0] // size center_x = 400 + j * block_size + block_size // 2 center_y = 200 + i * block_size + block_size // 2 # 执行鼠标操作 pyautogui.moveTo(center_x, center_y) pyautogui.dragRel( dj * block_size, di * block_size, duration=0.25 ) time.sleep(0.5) # 操作间隔 self.log_message("拼图完成!") self.status_label.config(text="拼图完成") except Exception as e: self.log_message(f"错误: {str(e)}") self.status_label.config(text="发生错误") finally: self.start_btn.config(state=tk.NORMAL) self.pause_btn.config(state=tk.DISABLED) def pause_solving(self): self.paused = not self.paused if self.paused: self.pause_btn.config(text="继续") self.status_label.config(text="已暂停") else: self.pause_btn.config(text="暂停") self.status_label.config(text="正在求解中...") def reset_solver(self): self.stopped = True self.start_btn.config(state=tk.NORMAL) self.pause_btn.config(state=tk.DISABLED) self.status_label.config(text="已重置") self.log_message("系统已重置") # 启动GUI if __name__ == "__main__": root = tk.Tk() app = PuzzleSolverGUI(root, None) root.mainloop()

6. 性能优化与错误处理

为提高脚本的稳定性和执行效率,我们实现了以下优化措施:

  1. 多级图像缓存:减少重复计算
  2. 动态灵敏度调整:根据图像质量自动调整识别参数
  3. 异常恢复机制:自动检测并恢复错误状态
class PerformanceOptimizer: def __init__(self): self.feature_cache = {} self.last_state = None self.sensitivity = 0.5 # 初始灵敏度 self.error_count = 0 def get_cached_features(self, image): # 计算图像指纹作为缓存键 fingerprint = self.image_fingerprint(image) if fingerprint in self.feature_cache: return self.feature_cache[fingerprint] features = extract_features(image) self.feature_cache[fingerprint] = features return features def image_fingerprint(self, image): # 使用缩小后的图像灰度值作为指纹 small_img = cv2.resize(image, (8, 8)) gray = cv2.cvtColor(small_img, cv2.COLOR_BGR2GRAY) return gray.tobytes() def adjust_sensitivity(self, success): if success: self.error_count = max(0, self.error_count - 1) if self.error_count == 0: self.sensitivity = min(1.0, self.sensitivity + 0.05) else: self.error_count += 1 if self.error_count > 3: self.sensitivity = max(0.1, self.sensitivity - 0.1) self.error_count = 0 def recover_from_error(self, current_image): if self.last_state is None: return False # 尝试从上次成功状态恢复 try: # 比较当前状态与上次状态 diff = cv2.absdiff(current_image, self.last_state['image']) diff_pixels = np.sum(diff > 25) if diff_pixels < 100: # 变化不大,可能只是识别错误 return True # 尝试重新定位拼图块 current_blocks = split_puzzle_blocks(current_image) position_map = match_blocks(current_blocks, self.last_state['target']) # 验证恢复结果 if len(position_map) >= 20: # 至少匹配20个块 return True except Exception: pass return False def save_state(self, image, target, blocks, position_map): self.last_state = { 'image': image.copy(), 'target': target.copy(), 'blocks': [(i, j, b.copy()) for i, j, b in blocks], 'position_map': position_map.copy() }

7. 实战案例与调优建议

在实际开发中,我们遇到了几个典型问题及解决方案:

  1. 光照变化导致识别失败

    • 解决方法:实现自适应直方图均衡化
    def adaptive_histogram_equalization(image): lab = cv2.cvtColor(image, cv2.COLOR_BGR2LAB) l, a, b = cv2.split(lab) clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8,8)) l = clahe.apply(l) lab = cv2.merge((l, a, b)) return cv2.cvtColor(lab, cv2.COLOR_LAB2BGR)
  2. 拼图块边缘粘连

    • 解决方法:应用形态学操作分离
    def separate_blocks(image): gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) _, binary = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU) # 形态学开操作去除小噪点 kernel = np.ones((3,3), np.uint8) opening = cv2.morphologyEx(binary, cv2.MORPH_OPEN, kernel, iterations=2) # 距离变换分离粘连块 dist_transform = cv2.distanceTransform(opening, cv2.DIST_L2, 5) _, sure_fg = cv2.threshold(dist_transform, 0.5*dist_transform.max(), 255, 0) sure_fg = np.uint8(sure_fg) return sure_fg
  3. 游戏反自动化检测

    • 解决方法:随机化操作间隔和移动轨迹
    def human_like_drag(start_x, start_y, end_x, end_y): # 添加随机偏移 points = [(start_x, start_y)] num_points = random.randint(3, 8) for i in range(1, num_points): progress = i / num_points x = start_x + (end_x - start_x) * progress y = start_y + (end_y - start_y) * progress # 添加随机抖动 x += random.randint(-5, 5) y += random.randint(-5, 5) points.append((x, y)) points.append((end_x, end_y)) # 执行拖拽 pyautogui.moveTo(points[0][0], points[0][1]) for x, y in points[1:]: pyautogui.dragTo(x, y, duration=random.uniform(0.1, 0.3), button='left') time.sleep(random.uniform(0.05, 0.15))

经过实际测试,这套解决方案在5阶困难模式下的成功率从最初的60%提升到了92%,平均完成时间从3分钟缩短到45秒左右。关键优化点包括:

  • 特征提取阶段:混合使用SIFT和感知哈希,准确率提升35%
  • 求解算法:优化后的A*算法比传统DFS快8-10倍
  • 操作模拟:人性化的拖拽操作避免了游戏检测
http://www.cnnetsun.cn/news/3194556.html

相关文章:

  • AI Agent 多步推理:把复杂命令拆成工具调用序列的正确姿势
  • 【RT-DETR涨点改进】03 动态Query初始化:让RT-DETR不再“漏掉”小目标
  • PyTorch 2.0 RNN/LSTM/GRU 实战对比:IMDB情感分析任务,GRU比LSTM快30%
  • Nginx 1.7.11.3 + nginx-rtmp-module 配置:Windows 10 下 3 步搭建 HLS 直播服务器
  • DC-DC电源转换电路设计与I2C控制实现
  • 2026最新Kali Linux虚拟机部署指南:从零搭建渗透测试环境
  • 电商系统测试实战指南:从核心模块到高阶技能
  • OpenCore csr-active-config 12位掩码详解:从 00000000 到 FF0F0000 的完整配置指南
  • 嵌入式系统智能散热方案:DRV8213与PID控制实践
  • 如何用Translumo实现游戏屏幕实时翻译:终极新手指南
  • 基于async-http-client与HMAC-SHA256的HTTP请求签名实战指南
  • Spring AOP与ELK栈构建AI人脸识别系统操作审计日志方案
  • CBAM 注意力模块 PyTorch 实战:3步集成 ResNet-50 提升 ImageNet 分类精度 1.5%
  • WK2204 SPI转4路UART驱动移植:NVIDIA Jetson设备树与16MHz晶振适配3步修改
  • Git clean 安全指南:未跟踪文件清理与预演防护
  • 飞腾麒麟信安系统XDMCP漏洞修复与国产化平台安全加固实战
  • 高效自动化缠论分析工具:ChanlunX通达信插件的完整实战指南
  • 百考通开题报告:智能辅助,让研究起点更坚实,专业而不失个性
  • MC6470与PIC18F87J50的嵌入式运动控制系统设计
  • Windows 自动化工具避坑 3 要点:从鼠标连点器看 SendInput 与热键冲突
  • IS31FL3731与PIC18F87J50的LED矩阵控制方案
  • 设备控制(Modbus 地址配置)
  • Windows Server 2012 R2 部署 Docker 方案对比:Docker Toolbox vs Linux VM 性能与适用性分析
  • MAX9744与PIC18LF47K40音频放大系统设计与优化
  • 前端转大模型:代码实践里的关键取舍
  • 高斯混合模型(GMM) vs K-Means:5个数据集实测对比与选型指南
  • Docker 24.0 存储路径迁移:3种方法对比与 daemon.json 配置详解
  • AppLovin 试玩广告横竖屏自适应:3种CSS/JS方案与真机测试要点
  • 5分钟快速上手MediaCrawler:多平台数据采集终极指南
  • ResNet-50 特征图可视化:从Grad-CAM到3D卷积核的5步调试法