基于ollama与VLM的自动化测试元素定位实践
1. 项目背景与核心价值
最近在自动化测试和RPA领域,视觉语言模型(VLM)结合本地大语言模型(LLM)的方案越来越受到关注。这个项目探索了如何利用ollama部署的本地VLM模型,实现屏幕界面元素的精准识别和定位。传统基于DOM结构的元素定位方式在面对老旧系统、游戏界面或自定义UI组件时常常失效,而纯视觉方案又缺乏语义理解能力。VLM正好填补了这个空白——它能同时理解图像内容和自然语言指令。
我在实际项目中发现,当需要自动化操作一个没有API接口的桌面应用时,最头疼的就是如何稳定地定位那些动态生成的按钮和控件。通过ollama运行开源VLM模型,我们可以在本地环境实现:用自然语言描述要查找的界面元素(比如"登录按钮"或"提交表单"),模型会直接返回该元素在屏幕中的像素级坐标。这种方法不依赖任何UI框架元数据,对任何可见的界面元素都有效。
2. 技术方案选型
2.1 为什么选择ollama
ollama作为本地化LLM运行环境有几个不可替代的优势:
- 完全离线运行,适合处理敏感的屏幕图像数据
- 支持量化模型,8GB内存的笔记本就能流畅运行7B参数的VLM
- 统一的模型管理接口,方便切换不同视觉语言模型
- 对多模态输入的原生支持(如图像+文本的prompt)
对比云端方案,本地部署避免了截图上传的隐私风险,也减少了网络延迟。我在测试中发现,对于需要实时交互的场景(如游戏自动化),本地推理的响应速度比API调用快3-5倍。
2.2 VLM模型选择
经过实测对比,推荐以下几个在ollama上表现优秀的开源VLM模型:
- llava-1.5-7b-q4:轻量级但识别准确率高,适合大多数GUI元素
- bakllava-1:对图标类元素识别更精准
- cogvlm-17b:处理复杂界面时理解能力更强,但需要更高配置
重要提示:模型不是越大越好。对于简单的按钮定位任务,7B参数模型在保持90%+准确率的同时,推理速度是34B模型的6倍。
3. 完整实现步骤
3.1 环境准备
首先安装ollama并拉取VLM模型:
curl -fsSL https://ollama.com/install.sh | sh ollama pull llava:1.5-7b-q4安装必要的Python依赖:
pip install pillow opencv-python pyautogui numpy3.2 屏幕捕获与预处理
创建屏幕捕获工具类:
import pyautogui import cv2 import numpy as np class ScreenCapturer: @staticmethod def get_screenshot(): # 获取屏幕截图并转换为RGB格式 screenshot = pyautogui.screenshot() return cv2.cvtColor(np.array(screenshot), cv2.COLOR_RGB2BGR) @staticmethod def save_temp_image(img, path='temp_screen.png'): cv2.imwrite(path, img) return path图像预处理的关键技巧:
- 将截图分辨率调整为模型训练尺寸(通常为336x336或448x448)
- 保持原始宽高比进行padding,避免元素变形
- 对暗色界面适当提高gamma值(1.2-1.5)
3.3 ollama接口调用
实现VLM查询封装:
import subprocess import json import time class VLMController: def __init__(self, model_name="llava:1.5-7b-q4"): self.model = model_name def query_element_position(self, image_path, prompt): full_prompt = f"""请精确识别图片中'{prompt}'的位置。 只返回JSON格式的边界框坐标,格式如: {{"x1": 100, "y1": 200, "x2": 300, "y2": 400}}""" cmd = [ "ollama", "run", self.model, "--image", image_path, full_prompt ] start_time = time.time() result = subprocess.run(cmd, capture_output=True, text=True) elapsed = time.time() - start_time try: return json.loads(result.stdout.strip()), elapsed except json.JSONDecodeError: print(f"解析失败,原始输出:{result.stdout}") return None, elapsed3.4 坐标后处理
获取原始坐标后需要转换为屏幕绝对坐标:
def convert_to_screen_coordinates(bbox, original_size, model_input_size=336): """ 将模型输出的相对坐标转换为屏幕绝对坐标 bbox: {"x1": 100, "y1": 200, "x2": 300, "y2": 400} original_size: (width, height) 原始截图尺寸 """ scale_x = original_size[0] / model_input_size scale_y = original_size[1] / model_input_size return { "x1": int(bbox["x1"] * scale_x), "y1": int(bbox["y1"] * scale_y), "x2": int(bbox["x2"] * scale_x), "y2": int(bbox["y2"] * scale_y) }4. 实战应用示例
4.1 识别Chrome刷新按钮
def locate_chrome_refresh_button(): capturer = ScreenCapturer() vlm = VLMController() # 获取屏幕截图 original_img = capturer.get_screenshot() original_size = (original_img.shape[1], original_img.shape[0]) # 保存临时图像 temp_path = capturer.save_temp_image(original_img) # 查询元素位置 bbox, time_cost = vlm.query_element_position( temp_path, "Chrome浏览器的刷新按钮" ) if bbox: screen_bbox = convert_to_screen_coordinates(bbox, original_size) print(f"定位成功,耗时{time_cost:.2f}s 坐标:{screen_bbox}") return screen_bbox else: print("定位失败") return None4.2 自动化登录操作
def auto_login(username, password): # 定位用户名输入框 username_field = locate_element("用户名输入框") pyautogui.click( (username_field["x1"] + username_field["x2"]) // 2, (username_field["y1"] + username_field["y2"]) // 2 ) pyautogui.write(username) # 定位密码输入框 password_field = locate_element("密码输入框") pyautogui.click( (password_field["x1"] + password_field["x2"]) // 2, (password_field["y1"] + password_field["y2"]) // 2 ) pyautogui.write(password) # 点击登录按钮 login_btn = locate_element("登录按钮") pyautogui.click( (login_btn["x1"] + login_btn["x2"]) // 2, (login_btn["y1"] + login_btn["y2"]) // 2 )5. 性能优化技巧
5.1 加速推理的实用方法
区域截屏:只截取屏幕相关区域而非全屏,减少输入尺寸
# 只截取屏幕中央800x600区域 region = (screen_width//2-400, screen_height//2-300, 800, 600) screenshot = pyautogui.screenshot(region=region)prompt工程:精确的prompt能显著提高识别准确率
- 坏prompt:"找按钮"
- 好prompt:"识别蓝色矩形、带有'提交'文字的按钮"
温度参数调整:通过--temperature 0.1减少随机性
ollama run llava:1.5-7b-q4 --temperature 0.1
5.2 缓存策略
对静态界面元素建立坐标缓存:
from functools import lru_cache @lru_cache(maxsize=50) def locate_element_cached(prompt, screen_hash): return locate_element(prompt)6. 常见问题排查
6.1 识别准确率低
症状:返回的坐标明显错误或找不到元素解决方案:
- 检查截图质量 - 确保图像清晰无模糊
- 优化prompt - 加入更多视觉特征描述
- 尝试不同的VLM模型 - bakllava对图标识别更好
6.2 响应速度慢
症状:单次查询超过5秒优化方案:
- 使用量化程度更高的模型(如q4改为q3)
- 限制截图区域而非全屏捕获
- 升级硬件 - 给ollama分配更多内存
6.3 坐标偏移问题
症状:点击位置总是有偏移校正方法:
- 检查DPI缩放设置 - 特别是4K屏幕
- 添加校准偏移量:
def apply_calibration(bbox, x_offset=0, y_offset=0): return { "x1": bbox["x1"] + x_offset, "y1": bbox["y1"] + y_offset, "x2": bbox["x2"] + x_offset, "y2": bbox["y2"] + y_offset }
7. 进阶应用方向
7.1 动态元素跟踪
结合OpenCV实现实时元素追踪:
while True: element = locate_element("移动的目标物体") if element: center_x = (element["x1"] + element["x2"]) // 2 center_y = (element["y1"] + element["y2"]) // 2 pyautogui.moveTo(center_x, center_y) time.sleep(0.1)7.2 多显示器支持
扩展屏幕坐标处理:
def get_active_screen_region(): """获取当前活动显示器的区域""" monitors = pyautogui.getAllScreens() primary = [m for m in monitors if m.is_primary][0] return ( primary.left, primary.top, primary.width, primary.height )7.3 与RPA工具集成
通过HTTP服务暴露接口供UiPath等工具调用:
from flask import Flask, request, jsonify app = Flask(__name__) vlm = VLMController() @app.route('/locate', methods=['POST']) def locate_api(): data = request.json img_path = save_base64_image(data['image']) bbox, _ = vlm.query_element_position(img_path, data['prompt']) return jsonify(bbox)在实际项目中,我发现这套方案特别适合处理这些场景:
- 老旧ERP系统的自动化测试
- 游戏内的UI自动化操作
- 跨平台应用的功能测试
- 快速原型开发时的界面自动化
最后分享一个实用技巧:当需要连续定位多个相似元素时(如表格行),可以在prompt中指定序号:"请识别第3个'删除'按钮"。这比单独截取每个区域效率高得多。