ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

UI自动化测试之图形识别:基于OpenCV的模板匹配实战

UI自动化测试之图形识别:基于OpenCV的模板匹配实战 引言当UI自动化遇到图形元素在UI自动化测试中我们经常使用XPath、CSS选择器、ID等属性来定位元素。但当面对纯图形化的界面元素——如自定义图标、游戏界面、验证码、动态生成的图表或没有标准属性的控件时传统的定位方式往往束手无策。这时图形识别技术便成为解决问题的关键。本文将深入探讨如何利用OpenCV的模板匹配技术在UI自动化测试中实现图形元素的识别、校验与交互并提供一个可直接复用的Python封装方法。一、图形识别在UI自动化中的应用场景图形识别技术特别适用于以下测试场景自定义图标/按钮应用中的非标准控件如游戏技能图标、自定义设计的按钮验证码识别自动化测试中需要处理的图形验证码仅用于测试环境游戏界面测试游戏UI多为图形元素缺乏标准DOM结构图表/图像校验验证生成的图表是否正确显示跨平台一致性检查验证不同平台上同一功能的UI表现是否一致二、OpenCV模板匹配原理简介OpenCV的模板匹配是一种在较大图像中搜索和查找模板图像位置的方法。其核心原理是将模板图像在源图像上滑动像卷积操作在每个位置计算模板与源图像对应区域的相似度找到相似度最高的位置即为匹配结果OpenCV提供了多种匹配方法如平方差匹配TM_SQDIFF、归一化平方差匹配TM_SQDIFF_NORMED、相关系数匹配TM_CCORR等其中归一化相关系数匹配TM_CCOEFF_NORMED在实际应用中最常用因为它对光照变化有一定的鲁棒性。三、图形识别封装方法详解基于参考代码我们实现了一个功能完善的图形识别封装方法import cv2 import numpy as np from typing import Optional, Tuple, List, Union def graphics_check( clsNone, file_path: Optional[Union[str, List[str]]] None, graphics_check: bool False, graphics_xy: bool False, percentage: float 0.7, delete: bool False ) - Union[bool, Tuple[int, int], None]: 图形识别的封装方法支持参考图的图形校验、相似度校验和坐标返回 参数说明 ---------- cls : object, optional 包含driver对象的测试类实例用于获取当前屏幕截图 file_path : str or list, optional 参考图路径支持单个路径或路径列表 graphics_check : bool, defaultFalse 是否进行图形存在性校验 graphics_xy : bool, defaultFalse 是否返回图形中心点坐标 percentage : float, default0.7 匹配相似度阈值范围0-1 delete : bool, defaultFalse 匹配成功后是否删除参考图用于动态生成的临时图 返回值 bool : 当graphics_checkTrue时返回图形是否存在 tuple : 当graphics_xyTrue时返回(x, y)坐标 None : 其他情况 使用场景 适合“页面上的小图标、按钮、角标”场景把对应图标按钮等截个小图传入file_path即可。 注意仅图形识别非文字识别。 会按列表顺序逐个做模板匹配首个达到percentage阈值的参考图会立刻停止继续循环。 # 依赖检查 try: import cv2 import numpy as np except ImportError as e: raise ImportError(f图形识别依赖缺失{e}。请安装pip install opencv-python numpy) if not cls or not hasattr(cls, driver): raise ValueError(需要传入包含driver对象的测试类实例) if not file_path: raise ValueError(必须提供参考图路径) # 统一处理为列表 if isinstance(file_path, str): file_paths [file_path] else: file_paths file_path # 1. 获取当前屏幕截图 screenshot_path current_screenshot.png cls.driver.save_screenshot(screenshot_path) screenshot cv2.imread(screenshot_path) if screenshot is None: raise ValueError(无法读取当前屏幕截图) # 获取屏幕尺寸用于坐标转换 screen_height, screen_width screenshot.shape[:2] for ref_path in file_paths: # 2. 读取并预处理参考图 reference cv2.imread(ref_path, cv2.IMREAD_UNCHANGED) if reference is None: print(f警告无法读取参考图 {ref_path}跳过) continue # 裁切透明/白色边缘 if reference.shape[2] 4: # RGBA图像 alpha reference[:, :, 3] coords cv2.findNonZero(alpha) else: # RGB或灰度图 gray_ref cv2.cvtColor(reference, cv2.COLOR_BGR2GRAY) _, binary cv2.threshold(gray_ref, 240, 255, cv2.THRESH_BINARY_INV) coords cv2.findNonZero(binary) if coords is not None: x, y, w, h cv2.boundingRect(coords) reference reference[y:yh, x:xw] ref_height, ref_width reference.shape[:2] # 3. 准备匹配输入 # 灰度图匹配减少颜色差异影响 gray_screenshot cv2.cvtColor(screenshot, cv2.COLOR_BGR2GRAY) if len(reference.shape) 3: if reference.shape[2] 4: # RGBA转灰度 gray_reference cv2.cvtColor(reference, cv2.COLOR_BGRA2GRAY) else: # RGB转灰度 gray_reference cv2.cvtColor(reference, cv2.COLOR_BGR2GRAY) else: gray_reference reference # 4. 多尺度模板匹配 best_match_val 0 best_match_loc None best_scale 1.0 scales [0.8, 0.9, 1.0, 1.1, 1.2] # 多尺度搜索 for scale in scales: # 缩放参考图 new_width int(ref_width * scale) new_height int(ref_height * scale) if new_width 10 or new_height 10 or new_width screen_width or new_height screen_height: continue scaled_ref cv2.resize(gray_reference, (new_width, new_height), interpolationcv2.INTER_AREA) # 执行模板匹配 result cv2.matchTemplate(gray_screenshot, scaled_ref, cv2.TM_CCOEFF_NORMED) min_val, max_val, min_loc, max_loc cv2.minMaxLoc(result) if max_val best_match_val: best_match_val max_val best_match_loc max_loc best_scale scale best_size (new_width, new_height) # 5. 判断匹配结果 if best_match_val percentage: if graphics_xy: # 计算中心点坐标模板匹配坐标系 center_x best_match_loc[0] best_size[0] // 2 center_y best_match_loc[1] best_size[1] // 2 # 坐标转换截图像素坐标系 → 当前会话窗口坐标系 # 假设driver.get_window_size()返回窗口尺寸 window_size cls.driver.get_window_size() window_width window_size[width] window_height window_size[height] # 按比例换算 converted_x int(center_x * window_width / screen_width) converted_y int(center_y * window_height / screen_height) if delete: import os os.remove(ref_path) return (converted_x, converted_y) elif graphics_check: if delete: import os os.remove(ref_path) return True # 匹配成功但不需要返回具体值 if delete: import os os.remove(ref_path) return None # 所有参考图都未匹配成功 if graphics_check: return False return None四、关键实现细节解析4.1 坐标转换机制代码中一个关键点是坐标转换模板匹配给出的坐标属于“截图像素坐标系”Appium点击使用的是当前会话窗口坐标因此这里按宽高比例做一次换算window_size cls.driver.get_window_size() window_width window_size[width] window_height window_size[height] converted_x int(center_x * window_width / screen_width) converted_y int(center_y * window_height / screen_height)这是因为OpenCV模板匹配返回的是在截图图像中的像素坐标而Appium等自动化工具操作的是设备或浏览器窗口坐标两者尺寸可能不同特别是存在设备像素比、浏览器缩放等情况。4.2 多尺度搜索策略实际应用中参考图与屏幕截图可能存在缩放差异如不同分辨率设备、浏览器缩放等。代码通过多尺度搜索解决这一问题在多个缩放比例0.8, 0.9, 1.0, 1.1, 1.2下进行匹配选择相似度最高的匹配结果避免因轻微缩放导致的匹配失败4.3 图像预处理优化为提高匹配成功率代码进行了以下预处理裁切边缘自动裁切参考图的透明或白色边缘减少无关区域干扰灰度转换在灰度图上进行匹配减少颜色细微差异的影响支持透明通道正确处理带Alpha通道的PNG图像4.4 依赖管理方法开头进行了明确的依赖检查try: import cv2 import numpy as np except ImportError as e: raise ImportError(f图形识别依赖缺失{e}。请安装pip install opencv-python numpy)这避免了调用方在后续阶段才遇到难排查的导入错误。五、实战应用示例5.1 校验图标是否存在检查购物车图标是否存在def test_cart_icon_exists(self): result graphics_check( clsself, file_pathresources/cart_icon.png, graphics_checkTrue, percentage0.8 ) assert result is True, 购物车图标未找到5.2 获取坐标并点击获取搜索按钮坐标并点击def test_click_search_button(self): coordinates graphics_check( clsself, file_pathresources/search_button.png, graphics_xyTrue, percentage0.75 ) if coordinates: x, y coordinates # 使用Appium点击 actions TouchAction(self.driver) actions.tap(xx, yy).perform() print(f成功点击搜索按钮坐标({x}, {y})) else: raise AssertionError(搜索按钮未找到)5.3 多参考图匹配尝试多个可能的按钮样式def test_find_submit_button(self): button_variants [ resources/submit_btn_blue.png, resources/submit_btn_green.png, resources/submit_btn_red.png ] result graphics_check( clsself, file_pathbutton_variants, graphics_checkTrue, percentage0.7 ) assert result is True, 所有样式的提交按钮均未找到六、最佳实践与注意事项6.1 参考图准备技巧尺寸适中参考图不宜过大或过小建议50×50到200×200像素内容明确只包含需要识别的图形部分避免多余背景格式统一使用PNG格式支持透明通道命名规范按功能命名如login_button.png、cart_icon.png6.2 阈值设置建议严格场景按钮、图标等关键元素阈值设为0.8-0.9宽松场景装饰性元素、背景图案阈值可设为0.6-0.7动态内容带轻微变形的图形适当降低阈值并配合多尺度搜索6.3 性能优化限制搜索区域如果知道图形大致位置可先截图局部区域再匹配缓存参考图多次使用的参考图可加载到内存中重复使用并行匹配多个独立图形识别可并行执行提高效率6.4 常见问题排查问题现象可能原因解决方案匹配成功率低参考图与屏幕内容差异大更新参考图、降低阈值、增加多尺度搜索范围坐标点击不准确坐标转换错误或设备像素比问题检查driver窗口尺寸、验证坐标转换公式匹配速度慢参考图过大或搜索区域太大优化参考图尺寸、限制搜索区域透明背景干扰参考图透明区域被误识别确保参考图已裁切透明边缘七、扩展与进阶7.1 结合其他识别技术图形识别可与其他技术结合使用OCR文字识别先定位图形区域再识别其中的文字颜色匹配验证图形颜色是否符合预期特征点匹配对于变形较大的图形使用SIFT、ORB等特征匹配算法7.2 集成到测试框架封装为Page Object模式class LoginPage: def __init__(self, driver): self.driver driver self.login_button_img resources/login_btn.png def click_login_button(self): 通过图形识别点击登录按钮 coords graphics_check( clsself, file_pathself.login_button_img, graphics_xyTrue, percentage0.8 ) if coords: x, y coords TouchAction(self.driver).tap(xx, yy).perform() return True return False7.3 持续集成中的使用在CI/CD流水线中将参考图作为测试资源纳入版本管理在不同分辨率设备上维护多套参考图设置合理的超时和重试机制生成图形识别报告包含匹配截图和置信度总结OpenCV模板匹配为UI自动化测试提供了强大的图形识别能力特别适合处理传统定位方式无法解决的图形化元素。本文提供的封装方法具备生产可用性支持图形存在性校验坐标获取与自动点击多参考图匹配多尺度搜索坐标自动转换在实际应用中建议根据具体场景调整阈值和搜索策略并结合其他定位方式形成混合定位方案以构建更健壮、更灵活的UI自动化测试体系。图形识别不是银弹它增加了测试对UI变化的敏感性但在面对纯图形界面、游戏、定制化应用等场景时它往往是唯一可行的自动化解决方案。
返回列表