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

2023-11-07

安卓自动化工具程序设计之[识别区域提取] 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("裁剪完成")


六、写在最后

  如果你觉得这篇文章对你有帮助请给文章点个赞,如果你有什么疑问或者建议,请在评论区留言。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

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

  • EditText 不显示当前输入(Android 4)

    我的 Android 应用程序包含一个EditText http developer android com reference android widget EditText html查看可以在其中键入一些短消息 单行 按键盘的DONE键
  • 在InputMethodService 外部调用InputMethodManager.setInputMethod(IBinder token, String id)。哪里可以找到代币?

    我想通过单击按钮在我的 EditText 上显示 Google 语音输入 IME 所以 根据this http android developers blogspot ru 2011 12 add voice typing to your
  • 如何使用 python 操作系统更改驱动器?

    我正在尝试更改当前目录C to Y 我试过 import os os chdir Y 但我不断收到错误消息 提示无法找到驱动器 本质上我正在寻找相当于 cd d cmd 中的命令 你确定吗Y 确实是有效的驱动器号吗 Try os chdir
  • 在 MATLAB 中创建共享库

    一位研究人员在 MATLAB 中创建了一个小型仿真 我们希望其他人也能使用它 我的计划是进行模拟 清理一些东西并将其变成一组函数 然后我打算将其编译成C库并使用SWIG https en wikipedia org wiki SWIG创建一
  • 如何使用 msgpack 进行读写?

    如何序列化 反序列化字典data with msgpack http msgpack org The Python 文档 http msgpack python readthedocs io en latest badge latest似乎
  • 管理文件字段当前 url 不正确

    在 Django 管理中 只要有 FileField 编辑页面上就会有一个 当前 框 其中包含指向当前文件的超链接 但是 此链接会附加到当前页面 url 因此会导致 404 因为不存在这样的页面 例如 http 127 0 0 1 8000
  • 如何从数据框的单元格中获取值?

    我构建了一个条件 从我的数据框中提取一行 d2 df df l ext l ext df item item df wn wn df wd 1 现在我想从特定列中获取一个值 val d2 col name 但结果 我得到一个包含一行和一列
  • 如何为工具栏上的溢出菜单中的菜单项设置字体

    我想更改项目的默认字体溢出菜单并设置自定义字体 我尝试添加一个工厂LayoutInflater并在onCreateView 方法我改变了TextView的字体 但这没有用 这是代码 在 onCreateOptionsMenu 内 getLa
  • Python 垃圾收集有时在 Jupyter Notebook 中不起作用

    我的一些 Jupyter 笔记本经常出现 RAM 不足的情况 而且我似乎无法释放不再需要的内存 这是一个例子 import gc thing Thing result thing do something thing None gc col
  • 如何在 Python 中执行相当于预处理器指令的操作?

    有没有办法在 Python 中执行以下预处理器指令 if DEBUG lt do some code gt else lt do some other code gt endif There s debug 这是编译器预处理的特殊值 if
  • Flask SQLAlchemy 与 MyPy - 模型类型错误

    我遇到了以下组合问题flask sqlalchemy and mypy 当我定义一个新的 ORM 对象时 例如 class Foo db Model pass where db是使用创建的数据库SQL炼金术应用于flask app mypy
  • Android - 保持用户登录状态

    我正在尝试使用 PHP 和 MySQLi for Android 进行登录 我不明白的是如何保持用户登录状态 我看到一个简单的教程 其中有人使用 SQLite 来保护信息 但我不知道这是否真的安全 如何保存用户信息以保持用户登录状态 谢谢
  • 在 Tensorflow 2.0 中的简单 LSTM 层之上添加 Attention

    我有一个由一个 LSTM 和两个 Dense 层组成的简单网络 如下所示 model tf keras Sequential model add layers LSTM 20 input shape train X shape 1 trai
  • Android应用程序中的模式输入

    我想知道是否有其他替代方案可以替代 Android 上平庸的 EditText 密码输入 是否有 API 或开源代码可以集成到我的应用程序中 类似于锁屏图案解锁 Intent 可能会返回哈希值 数字 字符串或代表用户输入的模式的任何内容 我
  • 如何为背景图像添加内边距

    我有一个LinearLayout其中有一个背景图像 一个 9 修补的 png 文件 如何向左和右添加填充 以使背景图像不占据整个宽度 我努力了android paddingLeft and android paddingRight 但这并没
  • 使 matplotlib 图形默认看起来像 R?

    Is there a way to make matplotlib behave identically to R or almost like R in terms of plotting defaults For example R t
  • Android进程调度

    我试图更好地理解 以便在创建 Android 应用程序 服务时确定潜在的互操作性问题对可靠性的影响 我想弄清楚进程优先级是如何确定的 服务和活动之间优先级的差异以及调度程序是否以不同方式对待它们的优先级 基本上 我试图深入了解某个活动或服务
  • 更改 Android 中突出显示文本的颜色

    我不确定这是否可能 也许有人可以纠正我 我在 Android 应用程序中有一个 EditText 视图 该视图在蓝色背景上有白色文本 当选择文本时 通过长按和编辑对话框 我希望突出显示为白色并将文本颜色更改为黑色 令人烦恼的是 似乎没有办法
  • Shap - 颜色条不显示在摘要图中

    显示summary plot时 不显示颜色条 shap summary plot shap values X train 我尝试过改变plot size 当绘图较高时 会出现颜色条 但它非常小 看起来不应该 shap summary plo
  • 同时有两个操作栏(底部和向上)?

    我需要制作两个操作栏 顺便说一下我正在使用actionBarSherlock 所以我真正需要的是在正常操作栏上放置一个 欢迎屏幕 开关 并添加两个正常的 ActionBar 操作选项 与我需要的类似的是 Gmail 和地图 如下所示 htt

随机推荐

  • 求Sn=1!+2!+3!+4!+5!+…+n!之值,其中n是一个数字(n不超过20)。

    这个是一个比较坑的题 但也是一个极其能查缺补漏的题 题目描述 求Sn 1 2 3 4 5 n 之值 其中n是一个数字 n不超过20 输入 n 输出 Sn的值 样例输入 5 样例输出 153 在这里插入代码片 乍一看很简单 一下就打好了 但开
  • C++ 结构体转json

    FdogSerialize FdogSerialize是一个用于C 序列化的开源库 采用非入侵方式 无需在原有结构体上进行修改 目前支持基础类型 基础类型数组 结构体 以及vector list map等数据类型的序列化 支持JSON和XM
  • SHA-256算法实现

    SHA 256 算法输入报文的最大长度不超过2 64 bit 输入按512 bit 分组进行处理 产生 的输出是一个256 bit 的报文摘要 该算法处理包括以下几步 STEP1 附加填充比特 对报文进行填充使报文长度与448 模512 同
  • JSR303校验的全局错误处理

    实现一个全局处理类 并对异常进行判断处理 方法有如下几种 1 实现HandlerExceptionResolver接口 实现其中的resolveException 方法 public class GlobalExceptionResolve
  • 自上而下的企业级数据分析应用 更好地满足业务部门需求

    2016年5月26日 由亦策软件和Qlik原厂联合主办的 汽车行业大数据沙龙 在上海巴黎春天新世界酒店召开 与会嘉宾踏着小雨纷至而来 一起围绕着汽车行业目前数据分析的瓶颈 企业发展的局限 以及对未来的构想建设等话题展开了精彩的分享和热切的探
  • ubuntu网卡设置

    UBUNTU网卡配置 主机名修改 hostnamectl set hostname ubuntu1804 cat etc hostname 网卡改名 修改配置文件为下面形式 vi etc default grub GRUB CMDLINE
  • Python面向对象基础练习——设计一个名为 MyRectangle 的矩形类来表示矩形

    设计一个名为 MyRectangle 的矩形类来表示矩形 这个类包含 1 左上角顶点的坐标 x y 2 宽度和高度 width height 3 构造方法 传入 x y width height 如果 x y 不传则默认是 0 如果 wid
  • NFS极品飞车21WIN11闪退解决办法,个人心得

    1 自己遇到的现象 系统升级win11后 之前能运行的NFS21打不开 黑屏后闪退 或者游戏中闪退 重新打开几次都不行 后台直接死 什么垃圾游戏 但是我就要玩 我自己游戏打开时是窗口化的 过10秒左右闪退 进程直接消失 多次打开可能会进 但
  • L2-3 完全二叉树的层序遍历 (25分) 2020 天梯赛

    L2 3 完全二叉树的层序遍历 25分 一个二叉树 如果每一个层的结点数都达到最大值 则这个二叉树就是完美二叉树 对于深度为 D 的 有 N 个结点的二叉树 若其结点对应于相同深度完美二叉树的层序遍历的前 N 个结点 这样的树就是完全二叉树
  • Makefile的基本用法

    1 Makefile的基本概念 目标 目标顶格写 后面是冒号 冒号后面是依赖 依赖 依赖是用来产生目标的原材料 命令 命令前面一定是Tab 不能是顶格 也不能说多个空格 命令就是要生成那个目标需要做的动作 2 示例代码 led bin st
  • Node.js 连接 MongoDB

    在 Node js 中连接 MongoDB 数据库需要使用第三方模块 mongodb 首先需要安装 mongodb 模块 你可以使用 npm 命令来安装 npm install mongodb 接着可以使用以下代码来连接 MongoDB 数
  • Qt 可视化Ui设计

    QMainWindow 是主窗口类 主窗口类具有主菜单栏 工具栏和状态栏 类似于一般的应用程序的主窗口 QWidget是所有具有可视界面类的基类 选择QWidget创建的界面对各种界面组件都可以支持 QDialog是对话框类 可建立一个基于
  • 两种方法(JS方法和Vue方法)实现页面渲染

    一 需求 根据数据渲染如下页面 二 JS方法 div class box w div class box hd h3 精品推荐 h3 a href 查看全部 a div div class box bd ul class clearfix
  • JavaScript之ES6规范中let的巧用(经典案例讲解)

    html部分 ul li 天 li li 地 li li 人 li li 和 li ul ul li dyklk li ul 要求 再点击某个li标签的时候 弹框输出其对应的顺序号 注意 本文js代码部分全为原生写法 错误写法 var oL
  • 发布 VectorTraits v1.0,它是 C# 下增强SIMD向量运算的类库

    发布 VectorTraits v1 0 它是C 下增强SIMD向量运算的类库 VectorTraits SIMD Vector type traits methods SIMD向量类型的特征方法 NuGet https www nuget
  • 未能检测服务器连接失败,被控链接失败处理检查方法

    检查方法 1 重要 提示服务器连接失败 一般是防火墙问题或者主控与被控直接的通信问题 机器防火墙放行端口22333 1433 如有的服务器商的机器 需要在安全组里面放行端口 或者关闭防火墙 取消安全组 2 检查进程是否异常 右击任务栏 启动
  • C语言面试常撕的几个str代码-【strcpy】【memcpy】【strcmp】【memcpy】【strcat】

    一 字符串拷贝strcpy 代码 char strcpy char des const char src assert des NULL src NULL char p des while p src 0 return des 要注意的点
  • 什么是Scrum?如何实施Scrum(敏捷开发)以及敏捷工具

    什么是Scrum Scrum是一个敏捷开发框架 它是一个增量的 迭代的开发过程 它被广泛应用于敏捷软件开发 在Scrum中 开发过程由若干个短的迭代周期组成 每个迭代周期称为一个Sprint 那么Scrum如何实施呢 Scrum实施过程可分
  • idea使用分享

    ideaVim 配置文件 Source your vimrc source vimrc Suggested options Show a few lines of context around the cursor Note that th
  • 安卓自动化工具程序设计之[识别区域提取] python + uiautomator2 + Open CV

    安卓自动化工具程序设计之 识别区域提取 python uiautomator2 Open CV 一 设计需求 二 所需工具 三 程序设计过程与思路 四 工具使用讲解 五 程序源码 六 写在最后 一 设计需求 在安卓自动化控制中我们经常有需要