基于图像识别的自动化

导语

在客户端自动化中,如果需要对UI进行操作,控件识别和操作是最基础的能力。在windows标准控件中,我们可以通过FindWindow来找到窗口,FindWindowEx来找到子窗口和按钮,在selenium测试web页面,我们通过find_element_by_xpath、find_element_by_css_selector、find_element_by_id等等来找到页面元素。但是,在大多数应用程序中使用的都是非标准的控件,无法通过FindWindowEx来找到某个按钮,也无法通过某个ID来找到某个输入框。一般来说,应用程序必须嵌入一定的SDK或者开发时支持一定的“后门”,自动化程序才能根据相应的协议来识别控件。但是没有这些条件怎么办呢?这时候就要使用一些trick了。

首先了解到的的是 sikuli,根据截图来做自动化,截一截图就能写个自动化脚本,岂不是很爽?

图1 :根据截图编写的王者荣耀登录 sikuli 脚本

Sikuli 有很多优点,例如

  • 基于 jython,可使用 python 语法来写脚本
  • 良好的可视化脚本编辑器
  • 可手动调整匹配度和操作偏移位置

那么问题来了,为什么要手动调整匹配度?

根源是是因为 sikuli 是基于图片像素级对比的,而在实践中,像素级对比往往存在很多缺陷,图片文件必须与屏幕上的呈现完全一致才能匹配上,实践中必须手动调低匹配度才能匹配到元素,但又不能太低,太低则会匹配多个元素,所以如果使用 sikuli 一定会存在不停地调整图片的匹配度的烦恼。

几种像素级匹配不适用的场景:

  • 图像稍有缩放,无法匹配
  • 图像稍有变形,无法匹配
  • 图像有细微改动,匹配率降低

图2:Sikuli 手动调节匹配度

另外,虽然 sikuli 虽然是开源项目,但由于对 Java 方面了解不多,想要修改匹配方法或者增加一些功能比较困难,于是萌生了自己实现基于图像对比的自动化的想法。想要实现这个想法,必须要解决两个问题:

1、 匹配问题:即在一张大图中找到一张小图 2、 操作问题:即封装各种操作,包括各种鼠标操作和键盘操作

匹配问题

一、 图片 Hash

首先查到的是根据图片的 hash 来查找相似图片。各种 hash 算法的原理都很相似。比如 ahash(average hash):

1、 先将图片缩放至 88(或 3232) 2、 将图片灰度化 3、 计算所有点的颜色深度均值 4、 用 0 表示小于均值,用 1 表示高于均值,得到一个元素都是 0 或 1 的 hash。如 010101001000…010100。 5、 计算两个矩阵汉明距离(元素相同是 0,不同是 1),值越大越不相似,越小越相似。

其它类似的 hash 算法还有 phash,whash,dhash,差别就在于不是直接计算平均灰度值来得到 hash,而是通过一定的变化后再计算。

Python 中已经有人写好了相关的库 imagehash,直接使用即可。

图3:使用 phash 计算两张图的差异

唯一的问题是性能。比较一次就要 100 多毫秒,想要在一张 100*100 的图片中查找一张 1010 的图片,如果每次移动一个像素则比较次数为 9090,也就是说 810 秒才能找到最佳匹配,显然性能比要求差了几个数量级。尝试了其它几种 hash 算法,速度都在这个数量级上。看来各种 hash 算法还是比较适合用于以图搜相似图,或者是以缩略图搜原图,而不适用于在大图中找小图。

二、 模板匹配

模板匹配matchTemplate)是一种最具代表性的图像识别方法。在大图(待识别图像 T) 滑动小图(模板 I) 进行匹配,滑动的意思是每次从左向右或者从上向下移动 1 个像素,最终找到最佳匹配。

图4:模板匹配原理图

Opencv 中支持多种匹配算法:

a.平方差匹配 method=CV_TM_SQDIFF

b.标准平方差匹配 method=CV_TM_SQDIFF_NORMED

c.相关匹配 method=CV_TM_CCORR

d.标准相关匹配 method=CV_TM_CCORR_NORMED

e.相关匹配 method=CV_TM_CCOEFF

其中

f.标准相关匹配 method=CV_TM_CCOEFF_NORMED

从 a 到 f 算法是越来越复杂,得到的结果是越来越好,计算量会越来越大,但在实际测试中发现,这几个匹配算法速度差别不大,如果没有特殊需求,一般直接使用CV_TM_CCOEFF_NORMED即可。使用时唯一需要注意的是 TM_SQDIFF 和 TM_SQDIFF_NORMED 计算结果越小越匹配,其它的都是越大越匹配。

模板匹配的缺陷:

a.首先模板匹配是直接使用的在大图中切割和小图一样大小的图像来进行比较的,匹配算法 也是固定位置对应固定位置进行计算,所以 T 和 I 的方向必须一致。

b.更重要的是,模板匹配一定会返回一个最佳匹配。模板匹配的返回值都是-1 到 1,根据模板匹配的返回值,很难确定匹配度是多少。所以,使用模板匹配,你不能确定目标图像中是否存在模板图像。

在一群牛中找到了一只羊的"最佳匹配"

三、 特征识别

人眼在识别物体时,会根据图像的局部特征来判断整体,比如图像的边缘轮廓、角、斑点等等。在 维基百科中可以查到,针对不同的特征形态有很多不同的特征检测算法。

维基百科中的特征检测

最著名的特征检测算法莫过于 SIFT 和 SURF 了。

SIFT 特征点检测

使用以下代码(来源:opencv-python-tutroals)可以画出一张图片的 SIFT 特征点。

import cv2
import numpy as np
img = cv2.imread('home.jpg')
gray= cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
sift = cv2.SIFT()
kp = sift.detect(gray,None)
img=cv2.drawKeypoints(gray,kp,flags=cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS)
cv2.imwrite('sift_keypoints.jpg',img)

使用 OPENCV 画出的 SIFT 特征点

SIFT 特征点包含很多属性:坐标,邻域范围,方向,关键点强度的响应等。

SURF 特征点计算更快,速度比 SIFT 快 10 倍。SURF 使用方法与 SIFT 基本一致,但在实践中发现 SURF 找到的特征点比 SIFT 少太多,因而没有采用。

四、特征匹配

Opencv 提供两种匹配器

BFMatcher(Brute Force)和 FlannBasedMatcher(Fast Library forApproximate Nearest Neighbors)。区别在于:

  • BFMatcher 暴力查找所有可能,找到最佳匹配。
  • FlannBasedMatcher 快速查找相对较好的匹配,但不一定是最佳。

为了获得更好的效果,我们使用 BFMatcher。

另外,常用且有效的消除错配的措施有两种:

1、第一个是 Lowe(SIFT 作者)提出的:

取一幅图像中的一个 SIFT 关键点,并找出其与另一幅图像中欧式距离最近的前两个关键点,在这两个关键点中,如果最近的距离除以次近的距离得到的比率 ratio 少于某个阈值 T,则接受这一对匹配点。

这个阈值 T 小,最后得到的匹配点就越少,质量越高。阈值越高,匹配点就越多,质量就会越差。因为使用上不涉及尺寸、旋转、亮度等的变化,实践中使用 Lowe 推荐的 0.8 是合适的。

2、第二种方法是 RANSAC(随机抽样一致性)去噪。

随机抽样一致性(RANSAC) 算法,可以在一组包含"外点"的数据集中,采用不断迭代的方法,寻找最优参数模型,不符合最优模型的点,被定义为"外点"。具体用法可以查看 Python cv2.RANSAC Examples

使用 RANSAC 去噪

经过过滤,最终可以得到一组匹配点对,数量为 S。如果小图的特征点数量是 A,那么我们可以认为匹配度是 ratio = S/A,ratio 高于一个阈值,我们即可认为是匹配到了。

好了,终于解决了第一个问题:匹配问题。总结一下,使用模板匹配 matchTemplate可找到最佳匹配位置,前提是图片方向一致。使用 SIFT 特征点匹配 去噪算法,可找到两张图的特征点匹配度。事实上,在一般的自动化项目中,图片方向是一致的,模板匹配是适用的。我使用的是模板匹配 SIFT 特征点匹配来实现的,并没有用到 RANSAC,原因在于模板匹配已经找到了最佳区域,大图中的最佳区域与小图进行特征对比即可,对比区域限制了,RANSAC 不会找到更多的"外点"。

操作问题

操作问题的实现并不复杂,都是按照业务需求对 win32api 的一些封装。但为了达到"傻瓜式"编写用例脚本的目的,应尽量封装得好用:

1、 实现 sikuli 的常用操作和 sikuli 没有的常用操作,包括但不限于:

  • exist
  • hover
  • wait
  • waitVanish#等待消失
  • 各种点击
  • 长按
  • 滑动
  • 键盘操作

2、操作可以 offset 偏移 3、尽量减少脚本使用 if 判断另外为了提升效率,我把操作区域限定在一个窗口区域内,由于没找到 python hook 窗口消息的方法,所以使用了比较挫的实现方法:通过一个线程不断更新窗口区域。目前已实现的窗口操作类结构如下:

Class WindowRegion():
    wait_timeout = 5*60#默认超时时间
    def __init__(self,window_handle):
    def _update_window(self):#更新窗口并不断截图
    def click(target,offset= (0,0)):
    def right_click(target,offset= (0,0)):
    def type(self,target,text):#键盘输入
    def wait(self,target,timeout = wait_timeout):
    def waitVanish(self,target,timeout = wait_timeout):#等待消失
    def hover(self,target,offset = (0,0)):
    def exist(self,target,timeout = 3):
    def wait_and_click(target,timeout = wait_timeout,offset = (0,0)):
    def click_if_exists(self,target,timeout = wait_timeout):
    def randomclick(self,timeout = 30):
    def add_img_path(self, path):

大致脚本代码如下:

handle = win32gui.FindWindow(None,r"腾讯手游助手")
assert win32gui.IsWindow(handle)
ActiveWindow(handle)
r = WindowRegion(handle)
r.add_img_path(r"I:gameautotestsgame")
r.add_img_path(r"I:gameautotestsgameqq")

r.click_if_exists("playwithqqfriends.png",timeout = 10)
r.click_if_exists("icon.png",timeout = 10)
if r.exists("playwithqqfriends.png",30):#如需要登录就登录一把
        r.wait_and_click("playwithqqfriends.png")
        r.wait("qqlogintitle.png")
        r.type("qqnumber.png",121200406)
        r.type("qqpwd.png","xxxxxx")
        r.click("qqloginbutton.png")
if r.exists(r"userprot.png",timeout = 10):
        r.click(r"accept.png")
r.wait_and_click(r"startgame.png")
r.wait("closebutton.png")
while r.exists("getprice.png",timeout = 10):
        r.click("getprice.png")
        r.click_if_exists("yes.png",timeout = 10)
while r.exists("closebutton.png",timeout = 10):
        r.click("closebutton.png")

操作问题虽然实现起来并不困难,但如果想要使脚本更加健壮必须要在实践中不断优化。基于 UI 的自动化稳定性经常会遇到各种问题:比如窗口被遮挡、UI 发生变化、窗口被隐藏等等。基于图像对比的脚本更是如此,比如执行一次点击后,希望知道是否操作成功,也是非常困难的一件事情,需要脚本层去解决。所以我认为基于图像的自动化比较适用场景为:

1、 UI 比较稳定

2、 操作流程比较简单

3、或者弱业务流程的自动化,如随便点击测试 后记

虽然模板匹配 特征点识别相似的图片,但依靠某种算法的特征点还是太薄弱了,能否依赖机器学习的方式,让机器自动识别 button、radio、input area 等等,让机器拥有一定的泛化能力?让机器拥有学习的能力,可以自动地操作界面,根据反馈来识别操作控件,最终达到完全自动地测试程序的 UI,到时候才是真正的"自动化"测试吧。

附:图像对比的 python 代码

运行环境:Python2.7 opencv2.4(opencv3.X 版本去除了 SIFT 等代码,需要另外安装库才支持,非常麻烦),opencv2.4 安装方法:http://opencv.org/releases.html下载指定版本,解压后拷贝\build\python\2.7\x86\cv2.pyd 至 C:Python27Libsite-packages 中即可

参考资料:

SikuliX IDE 安裝與簡易使用

SIFT 特征提取分析

opencv python tutroals

Python cv2.RANSAC Examples

附件:

原创声明,本文系作者授权云+社区发表,未经许可,不得转载。

如有侵权,请联系 yunjia_community@tencent.com 删除。

发表于

1007261的专栏

1 篇文章1 人订阅

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏ATYUN订阅号

使用Java部署训练好的Keras深度学习模型

Keras库为深度学习提供了一个相对简单的接口,使神经网络可以被大众使用。然而,我们面临的挑战之一是将Keras的探索模型转化为产品模型。Keras是用Pyth...

64040
来自专栏大数据挖掘DT机器学习

R语言进行中文分词,并对6W条微博聚类

由于时间较紧,且人手不够,不能采用分类方法,主要是没有时间人工分类一部分生成训练集……所以只能用聚类方法,聚类最简单的方法无外乎:K-means与层次聚类。 尝...

40050
来自专栏AI研习社

Github 项目推荐 | 用 PyTorch 实现全局/局部一致图像补全

本库用 PyTorch 实现了全局/局部一致图像补全(Globally and Locally Consistent Image Completion )。

20320
来自专栏AI科技评论

实战 | BERT fine-tune 终极实践教程

AI科技评论按:从 11 月初开始,google-research 就陆续开源了 BERT 的各个版本。google 此次开源的 BERT 是通过 tensor...

70350
来自专栏人工智能LeadAI

从学习 Paddle 开始学习深度学习

优点 灵活性:PaddlePaddle支持广泛的神经网络结构和优化算法,很容易配置复杂的模型,如基于注意力(Attention)机制或复杂的内存(Memory)...

36830
来自专栏机器之心

教程 | 如何用百度深度学习框架PaddlePaddle做数据预处理

34960
来自专栏儿童编程

Python基础语法导图汇总(补充篇)

这一段间经过对Python的学习,在之前两次汇总后,又对遗漏的部分及新接触的内容用思维导图的形式进行了汇总。

18330
来自专栏生信技能树

把这个R包大卸八块

本来应该这是一个很正常的学习过程,之前总结了一篇博文Bioconductor的质谱蛋白组学数据分析,对蛋白组学定量那块比较感兴趣,正好看到一个R包-MSstat...

38160
来自专栏CSDN技术头条

Hadoop 2.0 上深度学习的解决方案

波士顿的数据科学团队正在利用尖端工具和算法来优化商业活动,且这些商业活动是基于对用户数据中的深刻透析。数据科学大量使用机器算法,可以帮助我们在数据中识别和利用模...

29480
来自专栏磐创AI技术团队的专栏

Tensorflow 免费中文视频教程,开源代码,免费书籍.

Free-Tensorflow Tensorflow 免费中文视频教程,开源代码,免费书籍. ? 官方教程 官方介绍 https://tensorflow.go...

41660

扫码关注云+社区

领取腾讯云代金券