Python+OpenCV+ADB实现微信跳一跳辅助:感知决策执行全链路
简介这份资源面向对Python游戏自动化与计算机视觉感兴趣的开发者围绕微信小游戏「跳一跳」实现一套自动跳跃辅助工具涉及图像处理、目标识别与模拟点击等技能点适合具备Python基础、希望动手实践CV与自动化控制的读者。压缩包共279个文件约26.76MB以217个png截图与素材、36个json配置、6个py脚本为主另含少量md说明、txt文档及adb相关可执行文件与dll依赖目录结构便于按模块查阅。已有317人学习。源码完整呈现了从屏幕截图、OpenCV识别方块位置、计算跳跃距离到pyautogui模拟点击的闭环流程并包含多机型配置文件与容错思路读者可借此理解图像灰度化、边缘检测、模板匹配等知识在实际项目中的落地方式同时体会自动化脚本的节奏控制与鲁棒性设计仅建议用于学习研究。1. 跳一跳辅助用Python来玩微信跳一跳到底在玩什么跳一跳这个小游戏规则简单到一句话能说完按住屏幕蓄力松开后棋子跳到下一个方块上落在中心加分。但真拿手去点连续跳几十次不出错的人不多。于是「用 Python 来玩跳一跳」这件事本质不是写个外挂去刷分而是搭一条完整的感知—决策—执行链路截屏拿到当前画面算出棋子和目标方块的位置根据两点距离换算成按压时长再把这个时长通过调试通道发给手机。整条链路里图像识别是眼睛距离到时间的换算是大脑ADB 是手。这套东西适合谁适合已经会一点 Python、想找一个「有即时反馈、又不至于太复杂」的计算机视觉练手项目的人。它把 OpenCV 的模板匹配、颜色阈值、坐标计算、以及 adb 命令调用全串了一遍做完你对「截图→处理→下发指令」这个闭环会有实感。下面按我实际搭过的顺序讲参数和坑都摊开说。2. 环境与链路把截屏、算距离、发指令三段接起来2.1 为什么选 ADB 而不是别的通道常见做法是用 ADBAndroid Debug Bridge跟手机通信原因很实际它同时能干三件事——截屏、读屏幕分辨率、模拟按压。你不需要在手机上装任何东西只要打开 USB 调试电脑端 adb devices 能看到设备就行。另一条路是投屏工具自带的控制接口但那些接口往往不开放按压时长这种细粒度参数做跳一跳这种「按毫秒级控制蓄力」的场景不够用。链路是这样adb shell screencap把当前帧存到手机临时目录adb pull拉到电脑OpenCV 读图算出棋子底部中心和目标方块中心两点欧氏距离乘以一个经验系数得到按压毫秒数adb shell input swipe x y x y duration在屏幕同一点上「滑动」指定时长等效于长按。注意最后这条命令的四个坐标写成同一个点duration 就是按压时间。2.2 最小可运行骨架先装依赖再写主循环。依赖只有两个opencv-python 和纯标准库的 subprocess。pip install opencv-python numpy adb devices # 确认设备已授权输出里有 device 字样import subprocess import time import cv2 import numpy as np def screencap(pathscreen.png): # 截屏到手机临时目录再拉回本地避免直接 stdout 传二进制被截断 subprocess.run([adb, shell, screencap, -p, /sdcard/s.png], checkTrue) subprocess.run([adb, pull, /sdcard/s.png, path], checkTrue) return cv2.imread(path) def press(ms): # 同一点 swipeduration 即按压时长坐标取屏幕中下部任意固定点 subprocess.run( [adb, shell, input, swipe, 500, 1600, 500, 1600, str(ms)], checkTrue ) if __name__ __main__: for i in range(30): img screencap() # 这里先留空识别逻辑在下一章补 ms 600 # 占位实际由距离换算 press(ms) time.sleep(1.2) # 等棋子落地动画结束再截下一帧逻辑说明screencap -p的-p是 PNG 格式不加会输出 raw 格式OpenCV 读不了。input swipe用同点坐标是社区里验证过的长按等效写法比input tap更能控制时长。time.sleep(1.2)是给落地动画留时间太短会截到棋子还在空中的帧识别直接崩。参数说明swipe 的坐标要落在游戏可交互区域内一般取屏幕宽度一半、高度约 80% 处duration 单位是毫秒跳一跳里大致在 200 到 1500 之间超过 1500 基本是跳飞。这个骨架跑起来棋子会乱跳因为 ms 是写死的下一步就是把识别接上。3. 图像识别棋子定位与目标方块中心怎么算3.1 棋子定位模板匹配比颜色阈值稳棋子是一个固定形状的小人颜色随皮肤变但轮廓稳定。我一般用模板匹配先手动截一张只含棋子的图存成 template.png之后每帧用cv2.matchTemplate找最匹配的位置。比颜色阈值稳的原因是背景方块颜色会变尤其连续跳到不同色块时纯靠颜色分割容易把方块误判成棋子。def find_piece(img, tpl): res cv2.matchTemplate(img, tpl, cv2.TM_CCOEFF_NORMED) _, max_val, _, max_loc cv2.minMaxLoc(res) if max_val 0.7: # 匹配度过低说明这一帧没找到直接跳过 return None h, w tpl.shape[:2] # 返回棋子底部中心跳一跳的落点是棋子脚底不是几何中心 return (max_loc[0] w // 2, max_loc[1] h) tpl cv2.imread(template.png) piece find_piece(img, tpl)逻辑说明TM_CCOEFF_NORMED返回归一化相关系数越接近 1 越像。minMaxLoc取最大值位置就是匹配左上角。关键在返回值——落点用棋子底部中心因为游戏判定的是脚底接触方块的位置用几何中心会系统性偏上跳出来总是差一点。参数说明阈值 0.7 是经验值光照变化大可以降到 0.6但太低会误匹配到背景。模板图要单独截别从整屏里抠边缘留 2 像素背景反而匹配更准。3.2 目标方块中心从棋子位置往右上找跳一跳里下一个方块永远在棋子的右上方偶尔正上方。所以不用全图找只在棋子右上区域做处理速度快也不容易误判。方块顶面是个近似菱形或圆形的色块用边缘检测加轮廓筛选比较通用。def find_target(img, piece): px, py piece # 只取棋子右上方区域减少干扰 roi img[0:py, px:] gray cv2.cvtColor(roi, cv2.COLOR_BGR2GRAY) edges cv2.Canny(gray, 50, 150) contours, _ cv2.findContours(edges, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) best None for c in contours: area cv2.contourArea(c) if area 500: # 太小的噪点丢掉 continue x, y, w, h cv2.boundingRect(c) cx, cy x w // 2 px, y h // 2 # 换算回全图坐标 if best is None or area best[0]: best (area, cx, cy) return None if best is None else (best[1], best[2])逻辑说明ROI 裁剪把搜索范围压到棋子右上方Canny 找边缘findContours拿外轮廓按面积挑最大的那个当目标方块。RETR_EXTERNAL只取最外层避免方块内部纹理产生嵌套轮廓。坐标换算别忘了加回px这是新手最容易漏的一步漏了目标点会整体左移。参数说明Canny 的 50/150 是通用起点方块和背景对比弱时把低阈值降到 30。面积阈值 500 对应常见分辨率下的方块大小屏幕分辨率高就往上调。如果方块是纯色且和背景接近边缘检测会失效这时改用颜色阈值cv2.inRange更靠谱具体阈值用取色工具量一下方块颜色再定。3.3 距离到按压时长的换算拿到棋子底部中心 (px, py) 和目标中心 (tx, ty)距离就是欧氏距离。按压时长和距离近似线性系数靠实测标定。import math def distance_to_ms(piece, target, k1.35): d math.hypot(target[0] - piece[0], target[1] - piece[1]) return int(d * k)逻辑说明k是核心系数物理含义是「每像素距离需要多少毫秒蓄力」。它跟屏幕分辨率、游戏版本都有关必须自己标。标定方法先随便给个 k 跑一次看跳近了还是跳远了近了就调大 k远了调小反复三五次就能收敛到稳定值。参数说明我这边 1080 宽度的屏k 落在 1.3 到 1.4 之间。换手机要重新标别直接抄。另外距离算出来要加个下限保护太近的目标如果 ms 太小input swipe可能被系统当成点击加个max(ms, 200)兜底。4. 避坑与排查那些让辅助当场翻车的细节4.1 截屏拿到的是黑图或旧帧现象识别结果乱跳或者连续几帧棋子位置完全一样。原因通常是screencap还没写完就去 pull或者手机锁屏了。解决在 screencap 和 pull 之间不用加 sleepcheckTrue会等命令返回但要确认手机常亮、游戏在前台。连续几帧一样基本是 pull 到了缓存文件把手机端临时文件名每次改成带时间戳的或者 pull 完就删。4.2 棋子匹配突然失效现象跑了几十次后max_val掉到 0.5 以下返回 None程序卡住。原因是棋子跳到了和模板背景颜色接近的方块上或者游戏出了新皮肤。解决模板图选在纯色背景下截别带花哨背景代码里对 None 做处理跳过这一帧重新截而不是直接崩。连续三次 None 就停说明环境变了得重新截模板。4.3 按压时长对但总是跳偏现象距离算得没错k 也标过但落点总是偏左或偏右。原因是落点用了棋子几何中心而不是脚底或者目标中心取的是方块外接矩形中心而方块是斜的。解决棋子返回值改成底部中心前面代码已经这么做了目标方块如果是菱形外接矩形中心其实约等于菱形中心问题不大但如果是长条形方块得取顶面中心而不是整个轮廓中心。4.4 连续跳时节奏被打乱现象前几次准后面越跳越偏。原因是time.sleep固定 1.2 秒但游戏加分动画、方块生成动画时长会变截到了过渡帧。解决别用固定 sleep改成轮询——截一帧判断棋子是否静止连续两帧棋子位置差小于 3 像素静止了再算下一步。这样节奏自适应代价是每步多截一两次图。4.5 系数 k 换了分辨率就废现象同一台电脑换了个手机或者改了游戏窗口大小全乱。原因是 k 和像素距离强相关。解决把 k 做成按屏幕宽度归一化的形式比如k base_k * (screen_width / 1080)换设备时只调 base_k。更稳的做法是首次运行时自动标定故意跳一次根据落点和目标的偏差反推 k省得手动试。5. 进阶让这套辅助从「能跳」到「跳得准」前面四章跑通你已经能连续跳个几十次了。但想让它稳定上高分还有两件事值得做。第一件是自适应标定 k。手动试系数太玄学我一般写成自动的第一次按压用一个保守的 k跳完后截屏用模板匹配找到棋子新位置和预期落点比较偏差比例直接反馈修正 k。跑三五次就收敛换设备也不用重调。核心就一行比例修正def calibrate(k, actual_d, expect_d): # actual_d 是实际落点到目标的距离expect_d 是期望落点距离 # 跳远了说明 k 偏大按比例缩回来 return k * (expect_d / actual_d) if actual_d 0 else k逻辑说明expect_d理想情况是 0正好落在中心但实际用「目标中心到棋子新位置的距离」当反馈量更稳避免除零。这个修正每跳一次做一次k 会自己往真值靠。第二件是落点微调。跳一跳落在方块中心有额外加分而中心位置可以通过方块轮廓的几何中心更精确地估计。前面用外接矩形中心是图省事进阶做法是对轮廓做cv2.minAreaRect拿最小外接旋转矩形取它的中心对斜方块更准。代价是计算量略大但一帧几十毫秒完全够用。还有个容易被忽略的点按压时长的整数毫秒精度其实不够。input swipe的 duration 是毫秒整数但相邻两次跳跃需要的时长可能只差几毫秒四舍五入就偏了。缓解办法是让 k 的标定更细把系统误差摊到系数里而不是指望单次精度。最后说个我踩过的坑别在游戏正在加载或弹窗的时候发指令那时候截到的帧根本没有方块识别返回 None程序要么崩要么乱跳。加一个「连续 N 帧找不到目标就暂停并提示」的保护比事后 debug 省事得多。这套东西的价值不在刷多高分而在于它是一条完整的、你能完全掌控的感知—决策—执行链路改改识别逻辑就能迁移到别的场景。希望帮到你。本文还有配套的精品资源点击获取