安卓自动化工具程序设计之[识别区域提取] 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中的其他的模块keyboardnumpy以及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的主要内容,如果未能解决你的问题,请参考以下文章

安卓自动化工具程序设计之[识别区域提取] python + uiautomator2 + Open CV

目标检测之YOLO V1

基于颜色模型和形态学处理的车牌定位和识别matlab仿真

如何学会使用安卓自动化测试工具MonkeyRunner

数码管数字提取与识别(附源码)

[软件工具][windows]OCR指定区域图片自动识别内容重命名软件使用教程