谷歌提供的机器学习快速入门课程:
https://developers.google.com/machine-learning/crash-course/ml-intro
其他资源更新中
当你年老时,你是否常常想,要是年轻时多努力下该好。
谷歌提供的机器学习快速入门课程:
https://developers.google.com/machine-learning/crash-course/ml-intro
其他资源更新中
更新了基于OpenCV的算法,计算更准确。
还有Arduino实现,溜了溜了
——————————-
感谢来自 @神奇的战士 的跳跃距离算法
原项目地址:wangshub/wechat_jump_game
知乎专栏:教你用Python来玩微信跳一跳
1.3 再更: 悄咪咪地加上Arduino版
1.3 又更了: 竟然有1k赞还上了日报,一本满足。昨天研究了一晚上,加入了大家喜爱的OpenCV。现在计算准确率已经很好了,不会出现误差累积。感谢 @船D长 :用Python+Opencv让电脑帮你玩微信跳一跳 给我的启发,大神的代码简洁优雅,非常受用。
1.2 更:谢谢大家的400赞,非常开心。想说一下,我是因为手边只有树莓派才用树莓派控制舵机的,它毕竟是一台200+的小型计算机,肯定是大材小用了。想要自己动手做一个的知友们可以不用急着买树莓派,给我一两天的时间。我的arduino已经到啦,正在测试!
本项目源码: yangyiLTS/wechat_jump_game_iOS
现在已有的跳一跳辅助原理有以下这些:
日天派:
平民方法:
基本步骤:1、获取游戏画面;2、图像分析计算跳跃距离;3、模拟触摸手机屏幕进行游戏。
其中针对不同平台也有不同的实现方案:
先上效果:
基本思路是:
运行环境&工具
servo_down = 3.8
servo_up = 5
文档的开头需要注释掉这句,这是Arduino使用的
#from servo_control_arduino import arduino_servo_run
设置树莓派的ip地址
ip_addr = '192.168.199.181'
main()函数里面需要选择 send_time()
# #send_time() 为树莓派控制函数
send_time(t)
# #arduino_servo_run() 为arduino控制函数
##arduino_servo_run(t/1000)
pip install pyfirmata
如果做完下面步骤,程序跑起来之后,发现舵机即使不动也会发出 “滋滋”的声音而且动作缓慢,是因为电脑USB口供电不足所致。这个时候需要对舵机使用外接5V电源,接上5V电源之后,还要把外接电源的地线(负极)跟Arduino的地线(板上的GND口)连在一起。
这里填入上一步看到的端口
# 修改串口编号 如果Arduino驱动正确,在Arduino IDE可以看到串口编号
serial_int = 'COM3'
这里根据Arduino的型号选择,不需要的那行注释或删掉
# 如果是Arduino UNO 使用这一行
board = pyfirmata.Arduino(serial_int)
# 如果是Arduino Mega 使用这一行 pyfirmata库暂不支持Nano
board = pyfirmata.ArduinoMega(serial_int)
然后调试一下舵机的最高点和最低点
# 设置舵机的高点和低点 单位:角度
# 范围 0-180°
servo_high = 45
servo_low = 37
舵机要根据实际的安装位置调试,运动幅度不宜太大,直接运行servo_control_arduino.py文件舵机会按设定位置来循环三次,如果舵机运动正常,则Arduino部分工作正常。
这行代码位于文件开头,确保没有被注释
from servo_control_arduino import arduino_servo_run
到文档的靠后的部分找到main()函数,其中
控制函数选择 arduino_servo_run(),需要把send_time(t)注释掉
# #send_time() 为树莓派控制函数
# send_time(t)
# #arduino_servo_run() 为arduino控制函数
arduino_servo_run(t/1000)
pip install numpy
pip install opencv-python
im = ImageGrab.grab((654, 0, 1264, 1080)) im.save('a.png', 'png')
其中(654, 0, 1264, 1080)是截屏的范围,我的显示器分辨率是1080p,截取屏幕中间的部分得到的图片大小是610*1080,但这个时候图片最左边的一列的像素是黑色的。
是因为截图残留的黑边所致,这个黑边出现在截图的左边或者右边都会导致落点的计算偏差,打开screenshot_backups文件夹里面的图片会发现计算的轨迹像上图一样飘到整个图的上方。这时候的解决办法是:
找到pull_screenshot()函数:
# 使用PIL库截取Windows屏幕
def pull_screenshot():
im = ImageGrab.grab((654, 0, 1264, 1080))
im.save('a.png', 'png')
代码中(654, 0, 1264, 1080),表示截图的坐标。其中654,1264为截图的左边界和右边界,需要修改这两个边界使截图的尺寸变小。
举个例子,我发现默认参数的情况下出来的截图左边有3个像素的黑边,右边有1个像素的黑边,这个时候截图函数需要改成:
# 使用PIL库截取Windows屏幕
def pull_screenshot():
im = ImageGrab.grab((657, 0, 1263, 1080)) # 左边增加3个像素,右边减少一个
im.save('a.png', 'png')
修改完截图函数之后还需要修改默认图片的宽高,位置是在# Magic Number下面:
# Magic Number,不设置可能无法正常执行,请根据具体截图从上到下按需设置
under_game_score_y = 170 # 截图中刚好低于分数显示区域的 Y 坐标
press_coefficient = 2.38 # 长按的时间系数,
piece_base_height_1_2 = 10 # 二分之一的棋子底座高度,可能要调节
# 图片的宽和高
w,h = 610,1080
继续上面的例子,这个时候图片的宽和高需要改成
# 图片的宽和高
w,h = 606,1080
然后再次运行程序检查截图是否还有黑边。
import cv2
import numpy as np
棋子是一个非常特殊的目标,用PS把它抠出来,保存为模板使用OpevCV的模板匹配函数,准确率几乎完美。
meth = eval('cv2.TM_CCORR_NORMED') piece_template = cv2.imread('piece.png',0) # 棋子模板 # 模板匹配 获取棋子坐标 def find_piece(img): res = cv2.matchTemplate(img, piece_template, meth) min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(res) piece_x, piece_y = max_loc # cv2.matchTemplate函数返回的是模板匹配最大值左上角的坐标 # 下面修正为棋子底盘中点坐标 piece_x = int(piece_x + piece_w / 2) piece_y = piece_y + piece_h - piece_base_height_1_2 return piece_x, piece_y
其中piece.png是棋子模板,长这样:
这个模板是必须的,但是它只适配610*1080的截图尺寸,如果分辨率跟我的有差异,需要另外扣一个模板,保存为png格式。如果对opencv没兴趣的话看到这里就可以跳过了。GitHub上还有其它目标的模板,但是不是一定要重新扣,原因下面讲。
我最初的想是对有加分的特殊目标(徐记士多,魔方,下水道,播放器)使用模板匹配,通过函数返回值使主函数增加延时,让我们可以吃到特殊目标的加分。但是后来发现模板匹配的效果不理想,可能是选用的匹配算法问题?或是模板问题?稍后尝试修复。
现在wechat_autojump_iOS&Win_opencv.py文件里有一段代码是进行特殊目标模板匹配的,但是因为我把置信度阈值调得很高(低了又乱匹配),所以匹配成功率非常低。如果不成功,则采用下面的算法寻找目标。
接下来继续寻找落点坐标,现在要把图像的边缘提取出来,游戏界面都是纯色,提取边缘非常容易:
代码是
img2 = cv2.GaussianBlur(img2, (3, 3), 0) # 先对图片高斯模糊
img_canny = cv2.Canny(img2, 1, 10) # 执行canny函数
输出的图像已经变成只有边缘的二值图像了
提取边缘之后尝试对连击之后的小圆点进行模板匹配,但是效果一样不理想,大部分时候会跳过这一步。
到了最后一步,就是找到棋子的落点,在代码中即board_,board_y。这个点有几个特点:
然后我的思路是先找board_y_top:
# 遍历起点为分数下沿
board_y_top = under_game_score_y
for i in img_canny[under_game_score_y:]:
if max(i): # i是一整行像素的list,max(i)返回最大值,一旦最大值存在,则找到了board_y_top
break
board_y_top += 1
# board_y_top的像素可能有多个 对它们的坐标取平均值
board_x = int(np.mean(np.nonzero(img_canny[board_y_top])))
然后从board_y_top开始找图形的侧边缘,因为是对称图形只要找左右边缘之一就可以了。但是在两个落点非常近的时候,棋子会挡住其中一个边缘,造成影响。所以先根据棋子位置判断棋子在目标落点的左边还是右边,再选择与棋子不同的位置寻找侧边沿
x1 = board_x
fail_count = 0
if board_x > piece_x:
for i in img_canny[board_y_top:board_y_top+80]:
try:
x = max(np.nonzero(i)[0])
except:
pass
if x > x1:
x1 = x
board_y += 1
if fail_count < 5 and fail_count != 0:
fail_count -= 1
elif fail_count > 5 and board_y - board_y_bottom >10:
result = 1
board_y -= 3
break
elif fail_count > 5 and board_y - board_y_bottom <= 10:
result = 0
break
else:
fail_count += 1
else:
for i in img_canny[board_y_top:board_y_top+80]:
try:
x = min(np.nonzero(i)[0])
except:
pass
if x < x1:
x1 = x
board_y += 1
if fail_count < 5 and fail_count != 0:
fail_count -= 1
elif fail_count > 5 and board_y - board_y_bottom > 10:
board_y -= 3
result = 1
break
elif fail_count > 5 and board_y - board_y_bottom <= 10:
result = 0
break
else:
fail_count += 1
这段代码非常的不pythonic,得想办法优化,其中零零碎碎的整数是一些容差参数,因为在像素角度不是绝对的圆形和方形,在方型平面上的效果会比圆形平面好,但是总体效果都很不错。
if result == 0:
board_y = piece_y - abs(board_x - piece_x) * math.sqrt(3) / 3
如图:在绿色方块跳至灰色方块的过程中,出现操作误差。连续“Z形”路径中误差会逐渐累积。这个问题在落点方块较小时有一定的发生概率。我尝试过添加一些纠正算法,但效果不明显。这个误差会在Z形路径中断时(出现连续3个落点在一条直线上)自动修正。如果误差较大棋子即将掉落,可以终止程序,手动修改时间系数纠正。
来自一只正在艰难地转CS的通信狗,并没有二维码。第一次发文章,有很多小问题,欢迎各路大佬指教,给大佬倒茶。
转载自:https://zhuanlan.zhihu.com/p/32526110