[关闭]
@coder-pig 2023-01-17T16:44:43.000000Z 字数 4316 阅读 296

Van♂Python | 手搓游戏"辅助",你怎么跟我玩?

van♂Python


0x1、引言

春节前最后一周,留守的同事都在摸鱼,杰哥也是如此,百无聊赖中点开了一个小游戏。本想着打发时间,然后被其中一个 "孤岛求生" 的玩法给拿捏了:

这不妥妥滴 2D版的微信跳一跳?玩法也很简单:

手残杰哥就没跳过20+,每次都是差一点点,死了几次,气到想砸手机,焯!

这哪能忍啊?有道是 科技才是第一生产力,得想办法整个 辅助!搜了一圈没有找到现成能用的,估摸着是冷门氪金小游戏,玩的人不多。又看了下微信跳一跳的脚本,场景不同,改的话基本是重新写了。索性,就自己手搓一个吧!

思路分析

把上面的玩法简单抽象下,其实就是 小学三四年级的常见数学问题《速度、时间与路程间的关系》,怎么说?

而这里很明显就是 求时间,想办法采集到 路程和速度,一相除,答案就出来了~

行吧,有思路了,直接开整!!


0x2、路程采集

合适的木头长度就是 两块石头间的距离,而人物会一直处于石头的中心,所以更准确的描述是:两个石头中心点的距离,即图中黄线:

接着是石头中心点的确定,它也是 数字编号的中心点,所以这里的思路是 识别出编号的区域,然后求出中心点

数字文字?我尝试用 《破大防!这个开源库,竟能让APP日常任务自动化变得如此简单》 提到的OCR库-chineseocr_lite进行识别:

em...并没有识别出数字1,那就只能自己来处理图片了,把图片中数字部分放大:

数字区域是白色的 可以尝试 灰度二值化 处理下,只关注数字区域,先裁剪下图片,提高处理速度:

  1. def crop_area(pic_path, save_dir, start_x, start_y, end_x, end_y):
  2. """
  3. 裁剪图片
  4. :param save_dir: 保存路径
  5. :param pic_path: 图片路径
  6. :param start_x: x轴起始坐标
  7. :param start_y: y轴起始坐标
  8. :param end_x: x轴终点坐标
  9. :param end_y: y轴终点坐标
  10. :return: 生成的截图路径
  11. """
  12. img = Image.open(pic_path)
  13. region = img.crop((start_x, start_y, end_x, end_y))
  14. save_path = os.path.join(save_dir, "crop_" + str(round(time.time() * 1000)) + ".png")
  15. region.save(save_path)
  16. print(save_path)
  17. return save_path
  18. crop_pic = crop_area(screenshot_pic, temp_dir, 0, 1260, 1080, 1338)

裁剪后的图片:

接着灰度二值化一下,试了几次,发现240比较合适:

  1. def picture_to_black_white(pic_path, save_dir, threshold=127):
  2. """
  3. 图片二值化(黑白)
  4. :param save_dir:
  5. :param pic_path: 图片路径
  6. :param threshold: 灰度阈值,默认127
  7. :return: 转换后的图片路径
  8. """
  9. img = Image.open(pic_path)
  10. save_path = os.path.join(save_dir, "bw_" + str(round(time.time() * 1000)) + ".jpg")
  11. if threshold == 127:
  12. img.convert('1').save(save_path)
  13. else:
  14. img.convert('L').point([0 if x < threshold else 1 for x in range(256)], '1').save(save_path)
  15. return save_path
  16. bw_pic = picture_to_black_white(crop_pic, temp_dir, 240)

处理后的图片:

可以,效果很Nice,紧接着就是检测文字区域了,这里有两个思路~

思路①:卷积运算进行边缘检测

借鉴《python实现图像边缘检测》,采用 卷积运算 来进行边缘检测,具体方法如下:

看不太懂?没关系,直接CV代码:

  1. # 求卷积
  2. def convolution(img, x, y):
  3. convolution_list = [1, 1, 1, 1, -8, 1, 1, 1, 1] # 卷积核列表
  4. color_list = []
  5. xl = [x - 1, x, x + 1]
  6. yl = [y - 1, y, y + 1]
  7. for j in yl:
  8. for i in xl:
  9. color = img.getpixel((i, j)) # 取出色值
  10. color_list.append(color)
  11. c = 0
  12. for i, j in zip(convolution_list, color_list):
  13. c = c + i * j
  14. return c
  15. # 边缘检测
  16. def edge_detection(origin_pic):
  17. origin_img = Image.open(origin_pic)
  18. w, h = origin_img.size
  19. new_img = Image.new('L', (w, h), "white")
  20. for x in range(1, w - 1):
  21. for y in range(1, h - 1):
  22. c = convolution(origin_img, x, y)
  23. if c > 0:
  24. s = 0
  25. else:
  26. s = 255
  27. new_img.putpixel((x, y), s)
  28. new_img.save("test_n.png")

运行看看:

em?好像不太对劲,没有想象中的框,一开始还以为是算法问题,后面看了下二值化后的图片,发现竟然有0和255外的色值:

搜了下有人说是PIL模块的point()二值化不彻底,于是我遍历每个元素手动设置了一波,后面发现也是如此。折腾了好一会儿,才找到了问题的根源:

保存的图片格式为 .jpg,默认清晰度是75,有损压缩导致出现 马赛克。清晰度可以通过 quality 参数指定,1(最差)到95(最佳),使用时应尽量避免高于95的值,100会禁用部分JPEG压缩算法,并导致大文件图像质量几乎没有任何增益。

说的很好,我选择保存成 .png 格式,再次运行:

牛批,真的把数字的轮廓抠出来了,接着就是计算中心点咯,找到两个数字各自的左右x坐标,求平均值:

edge_detection() 的循环中,加个 Set (自带去重),把边缘的点的x坐标都丢进去。

接着转化为List,升序排列,列表头尾元素 分别为第一个数字的左边第二个数字的右边。从左到右遍历,如果前后两个x坐标相差大于50,说明前一个元素是 第一个数字的右边,后一个元素是 第二个数字的左边,得到四个x坐标,依次求出两中心点和距离,具体代码如下:

运行输出结果:

卷积运算进行边缘检测可以,接着来试试第二个思路。


思路②:土方法无脑遍历坐标点

还是那个二值化后的图片,图中的数字有两个:

从前和后开始扫,可以快速得出:第一个数字的左边坐标第二个数字的右边坐标,然后 取中点,一个向前,一个向后扫,可以快速得出 第一个数字的右边坐标第二个数字的左边坐标,这样可以减少无效遍历次数,直接写出具体代码:

运行输出结果如下:

虽然x坐标和卷积计算检测出来的结果差1,但最终计算出的 距离是一样的,为什么差1,读者可以自己思考下~

行吧,路程的测量方法已经get√到了,接着到速度~


0x3、采集速度

这个就简单些了,控制变量固定长按的时间,比如:100ms,然后 按压前截图按压后截图,计算两块木板的长度差,除以100ms,就能拿到 木板变长的速度

看到这里估计有些童鞋会收:你仿佛在逗我,来,给你个秒表,按压个100ms我看看?

em...手动肯定是不行的,但是 自动 可以哇,利用在 《杰哥带你玩转Android自动化》 专栏学到的姿势就阔以~

先是 长按屏幕,ADB中并 没有直接提供长按屏幕的命令,但是我们可以通过 滑动来模拟长按。当 滑动前后两个坐标差值足够小,android系统就会认为我们进行了长按操作。直接写出工具代码:

  1. def long_click_xy(x, y, duration):
  2. return start_cmd('adb shell input swipe %d %d %d %d %d' % (x, y, x + 1, y + 1, duration))

接着就是点击坐标的确定,直接打开 开发者模式 → 找到 指针位置 → 打开开关,此时点击屏幕,就能看到触摸点的XY坐标了~

行吧,接着就是

接着就是点击坐标的确定了,这个简单

看到这里估计有些童鞋想说:你仿佛在逗我?来,你按压个100ms我看看

手动不行,自动可以啊

看到这里有些童鞋估计要

按压前截图,按压后截图

然后比较

  1. 出现的问题:

在Pillow中的PIL.Image.save()方法中,使用默认参数保存jpg图片的过程中发现图片被压缩的很严重,导致原来很大的图片变成几十KB。但是有些时候往往需要图片的大小不能变化太大或不能太小。

  1. 问题原因:

这是因为在保存为jpg的过程中,PIL.Image.save()方法内部使用压缩算法对图片进行的压缩处理。

  1. 解决方法

在保存的时候,加上一些参数。

form PIL import Image

img = Image.open("xxx.jpg")

img.save(img_name, quality=95)

quality参数: 保存图像的质量,值的范围从1(最差)到95(最佳)。 默认值为75,使用中应尽量避免高于95的值; 100会禁用部分JPEG压缩算法,并导致大文件图像质量几乎没有任何增益。

使用此参数后,图片大小会增加。如果图片的大小还不能满足你的需求,是否还有其他方式去增加图片大小呢?

通过查阅资料并尝试,发现save方法还有一个可以配合quality使用的参数,能够大大增加图片大小:

img.save(new_name, quality=95, subsampling=0) # 可能的子采样值是0,1和2

1

subsampling参数:子采样,通过实现色度信息的分辨率低于亮度信息来对图像进行编码的实践。

注意: 以上方法的参数只针对于保存为JPG/JPEG格式的图片的情况,在项目实践过程中,楼主只使用了quality参数就满足了要求,后面的subsampling参数未使用。以上仅供参考。

纠结了我好久,马赛克,我以为是pil转换的问题

结果是生成jpg,把清晰度调成97

三步走:

获得起点和终点坐标,计算距离,除以木板伸长的速度,得出长按的时间

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注