专栏首页QA一隅OpenCV图像识别在自动化测试中实践

OpenCV图像识别在自动化测试中实践

自动化测试使用过程中,发现很多App无法获取到控件、资源ID等内部资源,而目前主要的移动端自动化测试工具基本都是基于获取内部控件元素来进行操作。因此,传统的测试框架和工具无法满足项目组游戏自动化测试的需求。

Appium最新版本其实也集成了图像识别的元素定位方法find_element_by_image,但是需要安装依赖环境opencv4nodejs,使用方法如下:

self.driver.update_settings({"getMatchedImageResult": True})
el = self.driver.find_element_by_image('path/to/img.ong')
el.get_attribute('visual') # returns base64 encoded string

但是该内置方法识别元素准确率较低,识别速度慢。这种情况下,只能通过点击坐标代替控件操作,而如何自动获取控件坐标就成了能否实现自动化的关键。解决的方法是将开源计算机视觉库OpenCV引入Appium框架,将按钮或控件的截图作为参数输入,在屏幕中通过图像特征识别获取对应控件坐标,调用AppiumAPI实现坐标点击,然后再次调用OpenCV图像识别库,自动判断操作结果,完成自动化测试。

OpenCV

OpenCV的全称是:Open Source Computer Vision Library(开源计算机视觉库),其可以运行在Linux、WindowsMac OS操作系统上。OpenCV轻量级而且高效的实现了图像处理和计算机视觉方面的很多通用算法,算法实现由一系列C 函数和少量 C++ 类构成,同时提供了Python、Ruby、MATLA等语言的接口。

特征简介

特征就是有意义的图像区域,该区域具有独特性或易于识别性。角点与高密度区域是一个很好的特征,边缘可以将图像分为两个区域,因此可以看作很好的特征,斑点(与周围有很大区别的图像区域)也是有意义的特征。

特征检测(Feature detection)是计算机视觉和图像处理中的一个概念。它指的是使用计算机提取图像信息,决定每个图像的点是否属于一个图像特征。特征检测的结果是把图像上的点分为不同的子集,这些子集往往属于孤立的点、连续的曲线或者连续的区域。

在计算机视觉领域,兴趣点(也称关键点或特征点)的概念已经得到了广泛的应用, 包括目标识别、 图像配准、 视觉跟踪、 三维重建等。这个概念的原理是, 从图像中选取某些特征点并对图像进行局部分析,而非观察整幅图像。只要图像中有足够多可检测的兴趣点,并且这些兴趣点各不相同且特征稳定, 能被精确地定位,上述方法就十分有效。

特征匹配

匹配模式

  • 暴力(Brute-Force)匹配:一种描述符匹配算法,该方法会比较两个描述符,并产生匹配结果列表,第一个描述符的所有特征都拿来和第二个进行比较。
  • K-最近邻(knn)匹配:在所有的机器学习算法中,knn可能是最简单的。
  • FLANN匹配:FLANN具有一种内部机制,该机制可以根据数据本身选取合适的算法来处理数据集,FLANN比其他的最近邻搜索快10倍。
  • FLANN的单应性匹配,单应性指的是两幅图像中的一幅出现投影畸变时,他们还能彼此匹配。

SIFT

SIFT的全称是Scale Invariant Feature Transform,尺度不变特征变换,由加拿大教授David G.Lowe提出的。是在不同的尺度空间上查找关键点(特征点),并计算出关键点的方向。SIFT所查找到的关键点是一些十分突出、不会因光照、仿射变换和噪音等因素而变化的点,如角点、边缘点、暗区的亮点及亮区的暗点等。SIFT特征对旋转、尺度缩放、亮度变化等保持不变性,是一种非常稳定的局部特征。

特征检测的视觉不变性是一个非常重要的概念。但是要解决尺度不变性问题,难度相当大。为解决这一问题,计算机视觉界引入了尺度不变特征的概念。它的理念是, 不仅在任何尺度下拍摄的物体都能检测到一致的关键点,而且每个被检测的特征点都对应一个尺度因子。理想情况下,对于两幅图像中不同尺度的的同一个物体点, 计算得到的两个尺度因子之间的比率应该等于图像尺度的比率。

SIFT(Scale Invariant Feature Transform),尺度不变特征变换。SIFT特征对旋转、尺度缩放、亮度变化等保持不变性,是一种非常稳定的局部特征。

匹配阈值

为了排除因为图像遮挡和背景混乱而产生的无匹配关系的关键点,SIFT的作者Lowe提出了比较最近邻距离与次近邻距离的SIFT匹配方式:取一幅图像中的一个SIFT关键点,并找出其与另一幅图像中欧式距离最近的前两个关键点,在这两个关键点中,如果最近的距离除以次近的距离得到的比率ratio少于某个阈值T,则接受这一对匹配点。

因为对于错误匹配,由于特征空间的高维性,相似的距离可能有大量其他的错误匹配,从而它的ratio值比较高。显然降低这个比例阈值TSIFT匹配点数目会减少,但更加稳定,反之亦然。

Lowe推荐ratio的阈值为0.8,但作者对大量任意存在尺度、旋转和亮度变化的两幅图片进行匹配,结果表明ratio取值在0. 4~0. 6 之间最佳,小于0. 4的很少有匹配点,大于0. 6的则存在大量错误匹配点,所以建议ratio的取值原则如下:

  • ratio=0.4:对于准确度要求高的匹配;
  • ratio=0.6:对于匹配点数目要求比较多的匹配;
  • ratio=0.5:一般情况下。

SURF

Speeded Up Robust Features。加速版的SIFT

SURF的流程和SIFT比较类似,这些改进体现在以下几个方面:

特征点检测是基于Hessian矩阵,依据Hessian矩阵行列式的极值来定位特征点的位置。并且将Hession特征计算与高斯平滑结合在一起,两个操作通过近似处理得到一个核模板。在构建尺度空间时,使用box filter与源图像卷积,而不是使用DoG算子。 SURF使用一阶Haar小波在x、y两个方向的响应作为构建特征向量的分布信息。

ORB

ORBORiented Brief的简称,是brief算法的改进版。ORB算法比SIFT算法快100倍,比SURF算法快10倍。在计算机视觉领域有种说法,ORB算法的综合性能在各种测评里较其他特征提取算法是最好的。

ORB算法是brief算法的改进,那么我们先说一下brief算法有什么缺点。 BRIEF的优点在于其速度,其缺点是:

  • 不具备旋转不变性
  • 对噪声敏感
  • 不具备尺度不变性

FAST

FAST(加速分割测试获得特征, Features from Accelerated Segment Test) 。这种算子专门用来快速检测兴趣点, 只需要对比几个像素,就可以判断是否为关键点。

Harris检测器的情况一样, FAST算法源于对构成角点的定义。FAST对角点的定义基于候选特征点周围的图像强度值。以某个点为中心作一个圆, 根据圆上的像素值判断该点是否为关键点。如果存在这样一段圆弧, 它的连续长度超过周长的3/4, 并且它上面所有像素的强度值都与圆心的强度值明显不同(全部更黑或更亮) , 那么就认定这是一个关键点。

用这个算法检测兴趣点的速度非常快, 因此十分适合需要优先考虑速度的应用。这些应用包括实时视觉跟踪、 目标识别等, 它们需要在实 时视频流中跟踪或匹配多个点。

Harris角点

在图像中搜索有价值的特征点时,使用角点是一种不错的方法。角点是很容易在图像中定位的局部特征, 并且大量存在于人造物体中(例如墙壁、 门、 窗户、 桌子等产生的角点)。

角点的价值在于它是两条边缘线的接合点, 是一种二维特征,可以被精确地定位(即使是子像素级精度)。与此相反的是位于均匀区域或物体轮廓上的点以及在同一物体的不同图像上很难重复精确定位的点。 Harris特征检测是检测角点的经典方法。

opencv-python特征匹配

运行环境

  • Mac OS 10.14.6
  • Python 3.7
  • opencv-python-4.5.1

Opencv-Python安装可以使用如下命令

pip3 install opencv-python

关键点

OpenCV实现的特征检测接口包括detect()、compute()detectAndCompute()函数,分别实现特征检测、特征描述,以及检测并描述一步到位。

import numpy as np
import cv2 as cv
import matplotlib.pyplot as plt
import pprint
import os

#以灰度模式读取图像
img1 = cv.imread('img/vip_tab.PNG',cv.IMREAD_GRAYSCALE)          
img2 = cv.imread('img/ipad_index.PNG',cv.IMREAD_GRAYSCALE)  

# 创建SITF对象
sift = cv.SIFT_create()

# 使用SITF找到关键点和特征描述
kp1, des1 = sift.detectAndCompute(img1,None)
kp2, des2 = sift.detectAndCompute(img2,None)


#画出特征点
img=cv.drawKeypoints(img2,kp2,img2)
cv.imshow('SIFT key points',img)
cv.imwrite('sift_keypoint.jpg',img)
cv.waitKey()

运行之后可以看到绘制的特征点详情如下图所示:

特征匹配

Brute Force匹配和FLANN匹配是Opencv二维特征点匹配常见的两种办法,分别对应BFMatcher(BruteForceMatcher)FlannBasedMatcher

二者的区别在于BFMatcher总是尝试所有可能的匹配,从而使得它总能够找到最佳匹配,这也是Brute Force(暴力法)的原始含义。而FlannBasedMatcherFLANN的含义是Fast Library forApproximate NearestNeighbors,从字面意思可知它是一种近似法,算法更快但是找到的是最近邻近似匹配。

所以当我们需要找到一个相对好的匹配但是不需要最佳匹配的时候往往使用FlannBasedMatcher。当然也可以通过调整FlannBasedMatcher的参数来提高匹配的精度或者提高算法速度,但是相应地算法速度或者算法精度会受到影响。

# FLANN 匹配算法参数
FLANN_INDEX_KDTREE = 1

index_params = dict(algorithm = FLANN_INDEX_KDTREE, trees = 5) #第一个参数指定算法
search_params = dict(checks=50) #指定应递归遍历索引中的树的次数


# flann特征匹配
flann = cv.FlannBasedMatcher(index_params,search_params)
matches = flann.knnMatch(des1,des2,k=2)


# 初始化匹配模板表
matchesMask = [[0,0] for i in range(len(matches))]


good=[]

# 匹配阈值
for i,(m,n) in enumerate(matches):
    if m.distance < 0.5*n.distance:
        good.append(m)
        matchesMask[i]=[1,0]

对于基于FLANN的匹配器,我们需要传递两个字典,这些字典指定要使用的算法,其相关参数等。第一个是IndexParams,表示指定的算法。第二个字典是SearchParams,它指定了索引里的树应该被递归遍历的次数。更高的值带来更高的准确率。但是也花更多时间,如果你想改变值,search_params = dict(checks=100)

矩阵变换

MIN_MATCH_COUNT=10

#获取转换矩阵
if len(good)>MIN_MATCH_COUNT:
    #获取关键点坐标
    src_pts = np.float32([ kp1[m.queryIdx].pt for m in good ]).reshape(-1,1,2)
    dst_pts = np.float32([ kp2[m.trainIdx].pt for m in good ]).reshape(-1,1,2)

    #获取变换矩阵,M就是变化矩阵
    M, mask = cv.findHomography(src_pts, dst_pts, cv.RANSAC,5.0)
    matchesMask = mask.ravel().tolist()

    #获得原图像高和宽
    h,w = img1.shape

    #使用得到的变换矩阵对原图像的四个角进行变换,获得在目标上对应的坐标
    pts = np.float32([ [0,0],[0,h-1],[w-1,h-1],[w-1,0] ]).reshape(-1,1,2)
    dst = cv.perspectiveTransform(pts,M)

    
    #提取坐标点
    cordinate_x1=get_Middle_Str(str(dst[2]),'[[',']]').split()[0].split('.')[0]
    cordinate_y1=get_Middle_Str(str(dst[2]),'[[',']]').split()[1].split('.')[0]


    cordinate_x2 = get_Middle_Str(str(dst[0]), '[[', ']]').split()[0].split('.')[0]
    cordinate_y2 = get_Middle_Str(str(dst[0]), '[[', ']]').split()[1].split('.')[0]


    mid_cordinate_x=(int(cordinate_x1)-int(cordinate_x2))/2+int(cordinate_x2)
    mid_cordinate_y=(int(cordinate_y1)-int(cordinate_y2))/2+int(cordinate_y2)
    
    print(mid_cordinate_x,mid_cordinate_y)



    #原图像还原为灰度
    img2 = cv.polylines(img2,[np.int32(dst)],True,255,10, cv.LINE_AA)
else:
    print( "Not enough matches are found - {}/{}".format(len(good), MIN_MATCH_COUNT) )
    matchesMask = None


#打印图像轮廓
draw_params = dict(matchColor=(0, 255, 0),
                   # draw matches in green color
                   singlePointColor=None,
                   matchesMask=matchesMask,  # draw only inliers
                   flags=2)

img3 = cv.drawMatches(img1, kp1, img2, kp2, good, None, **draw_params)
plt.imshow(img3, 'gray'), plt.show()

运行之后可以看到匹配的轮廓图,同时可以计算匹配的中心点坐标。

最后我们将整个过程封装成一个方法即可,方便在自动化项目中接入调试。

import numpy as np
import cv2 as cv
import matplotlib.pyplot as plt


def get_Middle_Str(content, startStr, endStr):
    """
    根据字符串首尾字符来获取指定字符
    :param content: 字符内容
    :param startStr: 开始字符
    :param endStr: 结束字符
    :return:
    """
    startIndex = content.index(startStr)
    if startIndex >= 0:
        startIndex += len(startStr)
    endIndex = content.index(endStr)
    return content[startIndex:endIndex]

def get_image_element_point(src_path,dst_path):
    """
    获取图像目标的坐标点
    :param src_path: 原图像
    :param dst_path: 目标识别图像
    :return: 目标元素的中心坐标点
    """

    print('src_path:%s,dst_path:%s'%(src_path,dst_path))

    #以灰度模式读取图像
    src_img = cv.imread(src_path,cv.IMREAD_GRAYSCALE)
    dst_img = cv.imread(dst_path,cv.IMREAD_GRAYSCALE)

    # 创建SITF对象
    sift = cv.SIFT_create()

    # 使用SITF找到关键点和特征描述
    kp1, des1 = sift.detectAndCompute(src_img,None)
    kp2, des2 = sift.detectAndCompute(dst_img,None)


    # FLANN 匹配算法参数
    FLANN_INDEX_KDTREE = 1

    index_params = dict(algorithm = FLANN_INDEX_KDTREE, trees = 5) #第一个参数指定算法
    search_params = dict(checks=50) #指定应递归遍历索引中的树的次数


    # flann特征匹配
    flann = cv.FlannBasedMatcher(index_params,search_params)
    matches = flann.knnMatch(des1,des2,k=2)


    # 初始化匹配模板表
    matchesMask = [[0,0] for i in range(len(matches))]

    good=[]

    # 匹配阈值
    for i,(m,n) in enumerate(matches):
        if m.distance < 0.5*n.distance:
            good.append(m)
            matchesMask[i]=[1,0]


    MIN_MATCH_COUNT=10


    #获取转换矩阵
    if len(good)>MIN_MATCH_COUNT:
        #获取关键点坐标
        src_pts = np.float32([ kp1[m.queryIdx].pt for m in good ]).reshape(-1,1,2)
        dst_pts = np.float32([ kp2[m.trainIdx].pt for m in good ]).reshape(-1,1,2)

        #获取变换矩阵,M就是变化矩阵
        M, mask = cv.findHomography(src_pts, dst_pts, cv.RANSAC,5.0)
        matchesMask = mask.ravel().tolist()

        #获得原图像高和宽
        h,w = src_img.shape

        #使用得到的变换矩阵对原图像的四个角进行变换,获得在目标上对应的坐标
        pts = np.float32([ [0,0],[0,h-1],[w-1,h-1],[w-1,0] ]).reshape(-1,1,2)
        dst = cv.perspectiveTransform(pts,M)


        #提取坐标点
        cordinate_x1=get_Middle_Str(str(dst[2]),'[[',']]').split()[0].split('.')[0]
        cordinate_y1=get_Middle_Str(str(dst[2]),'[[',']]').split()[1].split('.')[0]


        cordinate_x2 = get_Middle_Str(str(dst[0]), '[[', ']]').split()[0].split('.')[0]
        cordinate_y2 = get_Middle_Str(str(dst[0]), '[[', ']]').split()[1].split('.')[0]

        #提取目标元素中心坐标点
        mid_cordinate_x=(int(cordinate_x1)-int(cordinate_x2))/2+int(cordinate_x2)
        mid_cordinate_y=(int(cordinate_y1)-int(cordinate_y2))/2+int(cordinate_y2)



        # print(mid_cordinate_x/2,mid_cordinate_y/2)



        #原图像还原为灰度
        img2 = cv.polylines(dst_img,[np.int32(dst)],True,255,10, cv.LINE_AA)

        ############打印图像轮廓#################
        draw_params = dict(matchColor=(0, 255, 0),
                           # draw matches in green color
                           singlePointColor=None,
                           matchesMask=matchesMask,  # draw only inliers
                           flags=2)

        img3 = cv.drawMatches(src_img, kp1, dst_img, kp2, good, None, **draw_params)
        plt.imshow(img3, 'gray'), plt.show()

        return int(mid_cordinate_x/2),int(mid_cordinate_y/2)

    else:
        print( "Not enough matches are found - {}/{}".format(len(good), MIN_MATCH_COUNT) )
        matchesMask = None
        
if __name__ == '__main__':
    img1 = './img/login_btn.PNG'
    img2 = './img/dst_image.PNG'
    get_image_element_point(img1,img2)

小结

虽然Opencv图像识别功能非常强大,但是也存在不足之处,比如识别的图片在被识别的图像中有多个相同元素时会无法准确识别到期望的目标,如下图所示,期望匹配是登录界面的“登录”按钮,但是却识别到“邮箱登录”的标题位置了。

对于这种情况我们可能需要进行模型训练来提高识别的准确度。

本文分享自微信公众号 - QA一隅(sutune2020),作者:Sutune

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2021-05-25

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • Docker在自动化测试中的实践应用

    Docker作为一个开源的应用容器引擎,设计思想来自于集装箱,集装箱解决了什么问题?在一艘大船上,可以把货物规整的摆放起来。并且各种各样的货物被集装箱标准化了,...

    ITester软件测试小栈
  • 18本纸质书:OpenCV、Python和机器学习,总有一本适合你

    这次主页君蒙电子工业出版社赞助,为大家准备了6个三本:包含OpenCV类书籍四本,机器学习类书籍两本,每本书送出三份,一共十八个名额。这六种书籍都是干货满满的...

    企鹅号小编
  • 我为什么要写《OpenCV Android 开发实战》这本书

    2015年我出版了个人第一本关于图像处理方面的书籍《Java图像处理-编程技巧与应用实践》,这本书主要是从理论与编码上面详细阐述了图像处理基础算法以...

    OpenCV学堂
  • 历时七个月整理出来的《OpenCV4系统化学习路线图》

    OpenCV4.0发布以来,其依靠良好的接口代码、系统级别的优化、更加通用易学的函数调用,集成OpenVINO与tensorflow、caffe等模型加速推断、...

    AI算法与图像处理
  • 历时七个月整理出来的《OpenCV4系统化学习路线图》

    OpenCV4.0发布以来,其依靠良好的接口代码、系统级别的优化、更加通用易学的函数调用,集成OpenVINO与tensorflow、caffe等模型加速推断、...

    磐创AI
  • 没有高速摄像机,我是这样测试app页面加载时间

    对于安卓app专项性能测试中的页面加载/启动时间测试,对于测试方法的选择主要考虑技术,成本和测试项;对于启动时间/页面加载时间目前主要测试方法有五种,第一种是通...

    厦门-安仔
  • 基于图像识别的自动化

    导语 在客户端自动化中,如果需要对UI进行操作,控件识别和操作是最基础的能力。在windows标准控件中,我们可以通过FindWindow来找到窗口,FindW...

    1007261
  • OpenCV框架介绍

    概述 OpenCV是一个开放源代码的计算机视觉应用平台,由英特尔公司研发中心俄罗斯团队发起该项目,开源BSD证书,OpenCV的目标是实现实时计算机视觉,,是一...

    OpenCV学堂
  • UI 自动化测试在有赞的实践

    UI 自动化是质量保障的一种重要手段,我们从分层测试金字塔模型可以看出,质量保障更多的应该依靠底层的单元测试和接口集成测试,UI 自动化测试占比是非常小的一部分...

    有赞coder
  • OpenCV入门及应用案例:手把手教你做DNN图像分类

    导读:本文将介绍OpenCV的源码结构、OpenCV深度学习应用的典型流程,以及深度学习和OpenCV DNN(Deep Neural Networks,深度神...

    华章科技
  • 大伽带你入门OpenCV Python计算机视觉

    【OpenCV学堂】原创文章作者 贾志刚 推出 OpenCV Python系列视频教程,全套视频教程基于OpenCV Python语言API讲述,简单易学,内容...

    OpenCV学堂
  • 如何学习计算OpenCV

    如何学习OpenCV 一:学习OpenCV三个阶段 人工智能带火了计算机视觉的人才需求,作为计算机视觉应用开发框架OpenCV也越来越受到欢迎,市场需求大增,...

    OpenCV学堂
  • 【腾讯TMQ】看图测试指南:图像识别在测试中的应用

    也许我们使用过Uiautomator或Monkey来进行系统的测试。但在使用过程中总出现用Uiautomator没法识别、用Monkey无法法复现等问题……本文...

    腾讯移动品质中心TMQ
  • OpenCV4系统化学习路线图与教程

    OpenCV4.0发布以来,其依靠良好的接口代码、系统级别的优化、更加通用易学的函数调用,集成OpenVINO与tensorflow、caffe等模型加速推断、...

    磐创AI
  • OpenCV4系统化学习路线图与教程

    OpenCV4.0发布以来,其依靠良好的接口代码、系统级别的优化、更加通用易学的函数调用,集成OpenVINO与tensorflow、caffe等模型加速推断、...

    计算机视觉研究院
  • 用Python在25行以下代码实现人脸识别

    在本文中,我们将看到一种使用Python和开放源码库开始人脸识别的非常简单的方法。

    Python知识大全
  • 独家 | 利用OpenCV和深度学习来实现人类活动识别(附链接)

    我们实现的人类活动识别模型可以识别超过400类活动,其中准确率在78.4-94.5%之间(取决于任务类别)。 比如,活动类别的可包括:

    数据派THU
  • 轻松学pytorch-构建卷积神经网络

    大家好,这个是我的pytorch学习笔记第三篇,主要是使用pytorch来构建一个简单的卷积神经网络,完成mnist手写字符识别。涉及到主要知识点是如何使用to...

    OpenCV学堂
  • Differences between OpenCV JavaCV and OpenCV4Android

    本文将介绍OpenCV,JavaCV以及OpenCV for Android(以下简称OpenCV4Android)之间的区别,并以一个人脸识别的Android...

    宅男潇涧

扫码关注云+社区

领取腾讯云代金券