安卓自动化工具程序设计之[识别区域提取] python + uiautomator2 + Open CV
Posted 紫夜君
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了安卓自动化工具程序设计之[识别区域提取] python + uiautomator2 + Open CV相关的知识,希望对你有一定的参考价值。
安卓自动化工具程序设计之[识别区域提取] python + uiautomator2 + Open CV
一、设计需求
在安卓自动化控制中我们经常有需要用到精确控制的场景,比如点击控制时,如果让程序在特定的场景精确的点击某个位置而不出错。在这种场景中就需要让程序知道什么时候点击什么地方。
例如:目前最常见的做法,就是利用图像识别技术。首先将需要点击位置附近的图像信息(暂且称之为key)提前出来,然后在下一次进入该场景时,通过模板匹配、特征点识别等识别方式(识别方式千千万,想怎么做全看你)。获取该图像对应在屏幕的位置,再点击这个位置点,就能够实现精确的点击控制。
想必这个时候,你已经发现了,既然点击一个点就需要一个key,那要实现一个完整的自动化程序,肯定会有各种点击不同位置的情况,那是不是需要大量的key?
没错,这就是为什么我设计这个工具程序。
单纯的就是为了快速的提取大量的key,用以应对各种不同场景下的点击控制。
二、所需工具
由于我的自动控制脚本,主要是基于Python + uiautomator2 + Open CV,因此这三个工具必不可少,同时还涉及到python中的其他的模块keyboard,numpy以及os。
如果你还没有这些模块,可以在安装python之后,在cmd中使用以下命令进行快速安装:
1.pip install uiautomator2
2.pip install opencv-python
3.pip install keyboard
4.pip install numpy
os用于控制系统命令,通常不需要另外安装。
三、程序设计过程与思路
我一直认为写代码的乐趣,在于你到问题之后,思考如何解决问题。思考和解决的过程,才是最快乐的。
当然,因为每个人的思路都是不一样的,我将我的思路记录下来之后,而你恰好有更好点子能够解决问题,岂不美哉?这也正是开源的魅力。
如果你对这个过程没有兴趣,你也可以拉倒文章的底部,直接copy我的源代码去使用。
快速转跳到脚本源码
1.首先确认设计方案
在设计这个脚本之处,我想过不用任何脚本,直接用电脑上的截图工具,然后将模拟器中的图像信息截取下来。但是你模拟器在电脑上运行,可以用到PC的截图工具,手机怎么办?手机截取一张图像,在手机上裁剪完,然后回头一次性发到电脑上?那这也太慢了吧。其实剔除效率的原因,最后实施的时候,你也会发现,由于模拟器和手机分辨率的原因,直接截取的图像往往会出现偏大,偏小或者图像偏模糊的问题,导致在图像识别时,出现无法匹配到有效区域,或者匹配后概率偏低的问题。所以这个方法并不可靠。
之后我想到,可以从源图像中,也就是每次需要比对的背景图像中提取所需要的图像,由于背景图像不会变,从背景文件中提取的图像数据,能保证key图像和背景图像的分辨率相同,以及对应位置的长宽大小相同。这也正是我使用的方案。
2.编写程序
2.1)导入相关模块,并连接设备
import uiautomator2 as u2
import cv2 as cv
import numpy as np
import keyboard
import os
os.system("adb connect 127.0.0.1:7555")
d = u2.connect("127.0.0.1:7555") # USB控制设备端口号
由于我使用的是网易模拟器,所以连接号对应的是网易模拟器的设备号。其他模拟器以及手机的连接方法我在
安卓游戏自动化控制实验!超详细!小白也能一学就会!(Python + uiautomator2 + Open CV)(一)
一文中有介绍,有需要自行参考。
2.2)获取背景
targetImg = d.screenshot(format='opencv')
利用 uiautomator2 的截屏功能,截取屏幕,并转换为opencv格式,便于我们后续调用opencv相关接口进行处理。
2.3)加载事件捕获函数
cv.setMouseCallback("Please check the required area (y: confirm the check, n: Re-cutting, esc:quit)", mouse_event, targetImg)
由于我们需要快速从图像中选取一块特定区域,所以我们可以设计一个类似于拖拉框选的功能,因此我们需要加载一个鼠标事件捕获函数,用于实现框选功能。
2.4)鼠标事件回调函数的设计
RoiX1 = -1
RoiY1 = -1
RoiX2 = -1
RoiY2 = -1
MouseDownFlag = False
Canvas = cv.imdecode(np.zeros(1, np.uint8), cv.IMREAD_COLOR)
TempCanvas = Canvas
def mouse_event(event, x, y, flags, param):
global RoiX1, RoiY1, RoiX2, RoiY2, Canvas, TempCanvas, MouseDownFlag
if event == cv.EVENT_LBUTTONDOWN:
MouseDownFlag = True
RoiX1 = x
RoiY1 = y
Canvas = param.copy()
putText = "(%d,%d)" % (RoiX1, RoiY1) # 设置坐标显示格式
cv.circle(Canvas, (RoiX1, RoiY1), 2, (0, 255, 0), thickness=-1)
cv.putText(Canvas, putText, (RoiX1 + 10, RoiY1 - 10), cv.FONT_HERSHEY_PLAIN, 1, (0, 0, 255), thickness=1)
TempCanvas = Canvas.copy()
elif event == cv.EVENT_MOUSEMOVE:
if not MouseDownFlag:
return
RoiX2 = x
RoiY2 = y
dx = RoiX2 - RoiX1
dy = RoiY2 - RoiY1
if dx > 0 and dy > 0:
Canvas = TempCanvas.copy() # 消除重影
cv.rectangle(Canvas, (RoiX1, RoiY1), (RoiX2, RoiY2), (0, 0, 255), 1)
elif event == cv.EVENT_LBUTTONUP:
RoiX2 = x
RoiY2 = y
putText = "(%d,%d)" % (RoiX2, RoiY2) # 设置坐标显示格式
cv.circle(Canvas, (RoiX2, RoiY2), 2, (0, 255, 0), thickness=-1)
cv.putText(Canvas, putText, (RoiX2 - 15, RoiY2 + 15), cv.FONT_HERSHEY_PLAIN, 1, (0, 0, 255), thickness=1)
MouseDownFlag = False
首先,我们将鼠标按下的点定义为P1(x1,y1),鼠标拉动的过程,产生的点为P2(x2,y2),以两点绘制矩形,最终鼠标松开时,该动作结束。
由于我需要不断地更新拉动过程中的图像,所以我需要引入一些全局变量,在鼠标事件中产生数据,并在另一个函数中进行显示。如下所示:
2.5)数据处理函数
Canvas = targetImg.copy()
flag = False
while True:
cv.imshow("Please check the required area (y: confirm the check, n: Re-cutting, esc:quit)", Canvas)
cv.waitKey(1)
if keyboard.is_pressed('y'):
cv.destroyAllWindows()
print("(%d,%d),(%d,%d)" % (RoiX1, RoiY1, RoiX2, RoiY2))
if RoiX1 < 0 or RoiY1 < 0 or RoiX2 < 0 or RoiY2 < 0:
print("截取错误,重新截取")
flag = True
break
roiImg = targetImg[RoiY1:RoiY2, RoiX1:RoiX2]
cv.namedWindow("Image", cv.WINDOW_AUTOSIZE)
cv.imshow("Image", roiImg)
cv.waitKey(1)
img_name = input("请输入保存的图像名称(默认格式jpg):\\n")
cv.imwrite("../imgKey/" + img_name + ".jpg", roiImg)
cv.destroyAllWindows()
print("图像已保存!")
flag = True
break
elif keyboard.is_pressed('n'):
cv.destroyAllWindows()
print("重新裁剪")
flag = True
break
elif keyboard.is_pressed('esc'):
break
if not flag:
break
在该函数中,不断循环显示Canvas中的数据,由于我在鼠标事件中对Canvas数据不断进行复制,两者向结合,就能形成动态显示绘制矩形的过程。
之后我们利用判断键盘按下的不同值,执行不同功能,从而实现裁剪完成,重新裁剪,退出程序的功能。
在裁剪完成之后,我们通过对键盘输入内容的捕获,实现对图片保存到不同名词。
介绍完大致思路之后,我们需要对程序进行一点点优化,完成脚本的设计。此处省略。代码贴在文章的结尾。
快速转跳到脚本源码
四、工具使用讲解
1.准备工作
首先在python工程目录下创建两个文件夹,imgKey(用于存放key图片),tools(用于存放本工具程序),如图所示:
2.识别区域提取流程
脚本启动后,出现和模拟器当前窗口一样的画面,用于截取key图像
之后在图像窗口中,框选出需要提取的画面,如下图所示。
框选完成之后,使用键盘按下y键将完成截取(n键取消本次截取),之后会显示截取的图像,并要求在命令栏输出需要保存的图像名称,如下图所示。
我这里随便输入图像的名称,如 test
之后就能够在imgKey文件夹下,看到我们保存的图像了。
截取完成之后会自动开启下一次截图,当然如果你想要退出继续截图,按下Esc键即可
五、程序源码
# MakeImgKey.py
# 本脚本用来制作按键图标
import uiautomator2 as u2
import cv2 as cv
import numpy as np
import keyboard
import os
####################
RoiX1 = -1
RoiY1 = -1
RoiX2 = -1
RoiY2 = -1
MouseDownFlag = False
Canvas = cv.imdecode(np.zeros(1, np.uint8), cv.IMREAD_COLOR)
TempCanvas = Canvas
########################
def mouse_event(event, x, y, flags, param):
global RoiX1, RoiY1, RoiX2, RoiY2, Canvas, TempCanvas, MouseDownFlag
if event == cv.EVENT_LBUTTONDOWN:
MouseDownFlag = True
RoiX1 = x
RoiY1 = y
Canvas = param.copy()
putText = "(%d,%d)" % (RoiX1, RoiY1) # 设置坐标显示格式
cv.circle(Canvas, (RoiX1, RoiY1), 2, (0, 255, 0), thickness=-1)
cv.putText(Canvas, putText, (RoiX1 + 10, RoiY1 - 10), cv.FONT_HERSHEY_PLAIN, 1, (0, 0, 255), thickness=1)
TempCanvas = Canvas.copy()
elif event == cv.EVENT_MOUSEMOVE:
if not MouseDownFlag:
return
RoiX2 = x
RoiY2 = y
dx = RoiX2 - RoiX1
dy = RoiY2 - RoiY1
if dx > 0 and dy > 0:
Canvas = TempCanvas.copy() # 消除重影
cv.rectangle(Canvas, (RoiX1, RoiY1), (RoiX2, RoiY2), (0, 0, 255), 1)
elif event == cv.EVENT_LBUTTONUP:
RoiX2 = x
RoiY2 = y
putText = "(%d,%d)" % (RoiX2, RoiY2) # 设置坐标显示格式
cv.circle(Canvas, (RoiX2, RoiY2), 2, (0, 255, 0), thickness=-1)
cv.putText(Canvas, putText, (RoiX2 - 15, RoiY2 + 15), cv.FONT_HERSHEY_PLAIN, 1, (0, 0, 255), thickness=1)
MouseDownFlag = False
def draw_roi(devices):
global Canvas
while True:
targetImg = devices.screenshot(format='opencv')
cv.namedWindow("Please check the required area (y: confirm the check, n: Re-cutting, esc:quit)",
cv.WINDOW_AUTOSIZE)
cv.setMouseCallback("Please check the required area (y: confirm the check, n: Re-cutting, esc:quit)",
mouse_event, targetImg)
Canvas = targetImg.copy()
flag = False
while True:
cv.imshow("Please check the required area (y: confirm the check, n: Re-cutting, esc:quit)", Canvas)
cv.waitKey(1)
if keyboard.is_pressed('y'):
cv.destroyAllWindows()
print("(%d,%d),(%d,%d)" % (RoiX1, RoiY1, RoiX2, RoiY2))
if RoiX1 < 0 or RoiY1 < 0 or RoiX2 < 0 or RoiY2 < 0:
print("截取错误,重新截取")
flag = True
break
roiImg = targetImg[RoiY1:RoiY2, RoiX1:RoiX2]
cv.namedWindow("Image", cv.WINDOW_AUTOSIZE)
cv.imshow("Image", roiImg)
cv.waitKey(1)
img_name = input("请输入保存的图像名称(默认格式jpg):\\n")
cv.imwrite("../imgKey/" + img_name + ".jpg", roiImg)
cv.destroyAllWindows()
print("图像已保存!")
flag = True
break
elif keyboard.is_pressed('n'):
cv.destroyAllWindows()
print("重新裁剪")
flag = True
break
elif keyboard.is_pressed('esc'):
break
if not flag:
break
if __name__ == '__main__':
os.system("adb connect 127.0.0.1:7555")
d = u2.connect("127.0.0.1:7555") # USB控制设备端口号
draw_roi(d)
print("裁剪完成")
六、写在最后
如果你觉得这篇文章对你有帮助请给文章点个赞,如果你有什么疑问或者建议,请在评论区留言。
以上是关于安卓自动化工具程序设计之[识别区域提取] python + uiautomator2 + Open CV的主要内容,如果未能解决你的问题,请参考以下文章