Mac在Python项目中通过opencv模版匹配定位不到图片

news2024/9/23 15:26:20

起因

原本一行代码的事情,但是在Mac上总能出现意外,如下

box = pyautogui.locateOnScreen('obsidian.png')  
print(box)  
pyautogui.moveTo(box[0],box[1])

上面的代码用来定位图片在屏幕中的位置,然后移动鼠标到定位到的屏幕位置坐标。

意外的结果如下:
在这里插入图片描述

排除的情况:

  • 图片相对路径是一定没错的(左边目录结构同级)
  • 截图真实存在屏幕中

看了很多人(window用户)定位图片简简单单一行代码搞定,为什么到我这里就无限报错呢,所以模版匹配(定位图片)到底是怎么回事?

理解pyautogui定位原理

locateOnScreen源码
在这里插入图片描述

Note:(本质只有两件事情)

  • screenshot(region=None)截屏
  • locate携带截屏数据和目标图片数据以及多值字典定位图片

截屏源码
在这里插入图片描述

因为pillow版本大于6并且没有指定region所以pyautogui截屏本质是调用底层库ImageGrab得到的。

# pyautogui 截屏
screenshotIm = screenshot(region=None)

# 本质等于
im = ImageGrab.grab()

定位源码
在这里插入图片描述

Note:

  • 默认就是底层调用opencv进行定位
  • 如果没有指定grayscale,默认就是灰度图
  • 核心定位方法为cv2.matchTemplateopencv的模版匹配

所以pyautogui定位图片的调用本质是opencv的模版匹配那么opencv的模版匹配原理是什么呢?

理解opencv模版匹配

理解计算机中的图片

我们知道颜色是由三原色RGB(红色Red,绿色Green,蓝色Blue)组成,一张图片就是由给定的像素点组成,通常我们说图片大小为100*100就是横着存在100个像素点,竖着存在100个像素点,而每个像素点记录一个RGB值,当像素越密集,我们看到的图片就越清晰。

在这里插入图片描述

因此对于彩色图而言,如果图为100*100意味着存在矩阵R[],矩阵G[],矩阵B[]大小为100行100列,分别记录每个像素点的二进制表示,也就是0~255。

在这里插入图片描述

由于二进制可以进行十六进制转换,因此一个像素点通常也可以表示为两种模式:

  • RGB:比如RGB(255, 255, 255)
  • 十六进制:比如0XFFFFFF

如果图片是灰度模式,则不需要三个通道RGB记录,只需要亮度即可,也就是说在灰度图中,记录一张图片的信息只需要每个像素点的亮度值表示即可!即一个二维数组

在这里插入图片描述

总结:在计算机中图片由很多的像素点组成,其中每个像素点在计算机中以二进制形式进行存储,可以是彩色模式,也可以是灰度模式。

理解模版匹配

给定一个图片,如何在屏幕中(源图)中定位给定图片的位置呢?这就是模版匹配。

我们先看一下屏幕的表示

0,0       X increases -->
+---------------------------+
|                           | Y increases
|                           |     |
|   1920 x 1080 screen      |     |
|                           |     V
|                           |
|                           |
+---------------------------+ 1919, 1079

在屏幕中我们以左上角为坐标轴原点(0, 0),分辨率(1920*1080)表示在横轴X上存在1920个像素点(pixel),在纵轴Y上存在1080个像素点。

那我要查找某个图片,图片肯定涵盖多个像素点,比如我们截屏(100*100),我们称这个为盒子(box),匹配一张图片最起码需要两张大小相同的图进行对比,那我们就需要在屏幕上截取大小相同的图然后进行像素点的差值比较,最后计算整个像素的差异,就可以得到匹配的相关性数据了。

比如原图大小(100*100)即10000个像素点,需要查找的图(10*10)即100个像素点,那么就需要从坐标原点(0,0)截取长宽为10的一个盒子和原图进行比较,怎么比较呢?就是每个像素点依次做差值,这样就可以判断像素颜色是否相近,最后得到100个像素点的差值,最后进行计算得到一个相关性系数,范围(0~1)。
在这里插入图片描述

如果原图大小为(W, H)需要查找的目标图大小为(w, h),那么在X轴方向就需要移动(W-w+1)次,也就是这么多像素点,同理Y轴方向为(H-h+1)次,所以针对上面的情况,进行匹配将会得到结果大小为91*91的数组结果,其中每个元素代表区块的匹配程度,显然我们只需要最接近的那个区块,也就是结果中R[0,0,…,0,1]中最后的一个数字。

代码验证
在这里插入图片描述

Note:

  • 图片对比需要保证两张图片大小相同,即像素点一样才具有可比性。
  • 因此原图中查找的基本思路就是从原点开始逐个像素点移动得到长宽一样的图进行比较。
  • 匹配的思路是每个像素点的差值大小记为相关性,在每个位置都能得到一个相关系数,因此匹配的结果大小一定是(W-w+1)*(H-h+1)

为什么100*100的原图查找10*10X轴到90就可以了?*

因为再移动就出原图边界了,这样得到的原图块大小和目标图不一致,无法比较! 因为我们得到这些数据后,只需要记录左上角坐标(90, 90)即可。

opencv模版匹配方法的使用

文档链接

基本定义

cv.matchTemplate(image, templ, method[, result[, mask]]) ->result

其中参数:

  • image:表示原图数据,如果彩色则是三维数组,如果灰度图则为二维数组
  • templ:需要查找的图数据,同上,图片需要数组表示
  • method:模版匹配的算法,就是做差值时候怎么得到相关系数的算法
    • cv.TM_SQDIFF
    • cv.TM_SQDIFF_NORMED
    • cv.TM_CCORR
    • cv.TM_CCORR_NORMED
    • cv.TM_CCOEFF
    • cv.TM_CCOEFF_NORMED (pyautogui底层默认的算法)

使用

# 屏幕截屏, 默认是RGBA模式,也可以直接用pyautogui.screenshot()底层就是下面代码
screen = ImageGrab.grab()  
# 加载图片为数组数据,指定灰度图,也可通过 Image.open('obsidian.png') 加载图片,但是同上是图片对象,非数组RGBA模式  
target_img = cv2.imread('obsidian.png', cv2.IMREAD_GRAYSCALE)  
  
# 将屏幕图片转为数组,并且模式为灰度图  
screen_img = cv2.cvtColor(np.array(screen), cv2.COLOR_RGBA2GRAY)  
  
result = cv2.matchTemplate(screen_img, target_img, cv2.TM_CCOEFF_NORMED)  
# 从所有的相关系数结果中找到最大最小值,以及坐在的坐标位置  
min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(result)  
  
print(min_val, max_val, min_loc, max_loc) 

# 结果
# -0.6216521859169006 0.637797474861145 (37, 10) (22, 102)

可以看到最大相关系数只有0.63,这个图片是我的图标默认存在的,这里是存在问题的

分析问题

分辨率对查找的影响

要知道我们使用pyautogui的目的是定位图片,获取图片在屏幕中所在的坐标位置,这里涉及三个东西:

  • 目标图
  • 原图(屏幕截图)
  • 屏幕坐标位置

已知我的屏幕大小为1440*900,那么给定原图为2880*1800,那么我查找目标图时候怎么得到在屏幕中的坐标位置?比如目标刚好在右下角,难道得到坐标(2800,1720)?显然这么走鼠标都要点击到屏幕外边去了。

在这里插入图片描述

# 从所有的相关系数结果中找到最大最小值,以及坐在的坐标位置  
min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(result)

这行代码返回的max_loc对应上图就是(20,12)显然这是超出屏幕大小的,因此使用这个坐标点击屏幕肯定不行,一种思路是等比例计算在屏幕中的位置,即上图的坐标(10,6)

再看下面的问题
在这里插入图片描述

按照理论,查找的图片应该位于屏幕之外的位置,可是这里得到的结果却是完全错的,并且最大相关系数也才0.63,这是因为,原图比例等比例放大了一倍,但是查找的目标图并没有,这意味着,在计算机中存储的数值完全是不相关的!

我们可以通过放大目标图去匹配如下:

target_img = cv2.resize(target_img, (width*2, height*2))

结果如下:
在这里插入图片描述

可以看到相关系数和坐标都看似正常了,通过pyautogui移动鼠标位置基本正确。

解决问题

上面我们已经知道,截屏的尺寸问题会影响对图片的查找,这里的本质其实是:我的目标图要在原图的基础上截取! 如果我不使用Image.grab()截屏而是直接手动截图存储然后查找就可以了。

方式一:手动截屏

# 加载目标图为数组数据  
target_img = cv2.imread('obsidian.png', cv2.IMREAD_GRAYSCALE)  
# 加载原图为数组数据  
screen_img = cv2.imread('screenshot.png', cv2.IMREAD_GRAYSCALE)  
  
result = cv2.matchTemplate(target_img, screen_img, cv2.TM_CCOEFF_NORMED)  
min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(result)  
print(min_val, max_val, min_loc, max_loc)

结果图
在这里插入图片描述

可以看到最大相关系数为0.89,坐标也基本正确。

方式二:调整截屏大小

# 加载目标图为数组数据  
target_img = cv2.imread('obsidian.png', cv2.IMREAD_GRAYSCALE)  
# 截屏默认为RGBA  
screen = ImageGrab.grab()  
# numpy将图片转为数组数据  
screen_tmp = np.array(screen)  
# 将RGBA通道转为灰度图  
screen_img = cv2.cvtColor(screen_tmp, cv2.COLOR_RGBA2GRAY)  
print("截屏大小:" + str(screen_img.shape))  
# 调整截屏大小为屏幕分辨率大小  
width, height = pyautogui.size()  
screen_img = cv2.resize(screen_img, (width, height))  
print("调整后截屏大小:" + str(screen_img.shape))  
  
result = cv2.matchTemplate(screen_img, target_img, cv2.TM_CCOEFF_NORMED)  
min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(result)  
print(min_val, max_val, min_loc, max_loc)

结果图
在这里插入图片描述

Note:

  • 一定要确保通道转换正确,RGBA转GRAY
  • 查看截图的图片模式可以通过Image.grab().mode查看

方式三:调整目标图大小

# 加载目标图为数组数据  
target_img = cv2.imread('obsidian.png', cv2.IMREAD_GRAYSCALE)  
  
# 截屏默认为RGBA  
screen = ImageGrab.grab()  
# numpy将图片转为数组数据  
screen_tmp = np.array(screen)  
# 将RGBA通道转为灰度图  
screen_img = cv2.cvtColor(screen_tmp, cv2.COLOR_RGBA2GRAY)  
  
# 等比例调整目标图大小  
width_screen, height_screen = pyautogui.size()  
height_original, width_original = screen_img.shape[:2]  
# 计算调整比例  
rate_width = width_original // width_screen  
rate_height = height_original // height_screen  
print(rate_width, rate_height)  
  
height, width = target_img.shape[:2]  
target_img = cv2.resize(target_img, (width * rate_width, height * rate_height))  
  
result = cv2.matchTemplate(screen_img, target_img, cv2.TM_CCOEFF_NORMED)  
min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(result)  
  
# 坐标等比例换算  
min_loc = min_loc[0] // rate_width, min_loc[1] // rate_width  
max_loc = max_loc[0] // rate_height, max_loc[1] // rate_height  
print(min_val, max_val, min_loc, max_loc)

结果图
在这里插入图片描述


总结: 出现问题的根因在于底层库截屏得到的屏幕图片分辨率和屏幕不一致导致的,解决办法也就是根据这个思路进行,推荐resize调整截屏大小,然后再进行定位图片就可以了。

关于为什么出现Mac截图分辨率放大一倍的解释,可以参考这条issue

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2117209.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

IT管理:我与IT的故事9-数字化转型7步思考行动法

四书即论语、孟子、大学、中庸,又称四子书,为历代儒学子首要研习之书。南宋朱熹取《礼记》之大学、中庸篇,分章注释,与论语、孟子合为“四书”。 四书及其注释包涵孔子弟子及再传弟子、孟子、程子、朱熹等,其编撰时长达…

代码随想录Day 38|背包问题完结,题目322.零钱兑换、279.完全平方数、139,单词拆分数

提示:DDU,供自己复习使用。欢迎大家前来讨论~ 文章目录 动态规划part06题目题目一:322. 零钱兑换解题思路:题目二:279.完全平方数题目三:139.单词拆分数解题思路:背包问题 多重背包(…

基于 YOLOv5 的积水检测系统:打造高效智能的智慧城市应用

在城市发展中,积水问题日益严重,特别是在大雨过后,积水往往会影响交通甚至威胁人们的安全。通过现代计算机视觉技术,我们能够智能化地检测和识别积水区域,减少潜在危险。本文将介绍如何使用 YOLOv5 和 PyQt5 搭建一个积…

Unity使用自定义类型作为字典Key的最佳实践与避坑指南

自定义类型作为字典Key的最佳实践与避坑指南文章首发 问题背景 首先提一下之前项目开发时遇到的一个将自定义类型作为Dictionary键的坑。 项目中,我们有两个业务类BusinessA和BusinessB,因为某个需求,我们需要将这两个类建立一个映射关系&…

游泳馆收银系统源码解析之手牌管理--SAAS本地化及未来之窗行业应用跨平台架构

一、代码 if(手牌状态 "空"){结算界面 "";未来之窗_人工智能_通用页面_尺寸(title"游泳馆",收费,500,300);}else{未来之窗_人工智能_通用页面_尺寸(title"游泳馆",退款,1200,500);} 二、阿雪技术观 拥抱开源与共享,见…

探索图论中的关键算法(Java 实现)

“日出东海落西山 愁也一天 喜也一天 遇事不钻牛角尖” 文章目录 前言文章有误敬请斧正 不胜感恩!||Day031. 最短路径算法Dijkstra算法Java 实现: Bellman-Ford算法Java 实现: 2. 最小生成树算法Prim算法Java 实现: Kruskal算法Ja…

AI基础 L9 Local Search II 局部搜索

Local Beam search 对于当前的所有k个状态,生成它们的所有可能后继状态。 检查生成的后继状态中是否有任何状态是解决方案。 如果所有后继状态都不是解决方案,则从所有后继状态中选择k个最佳状态。 当达到预设的迭代次数或满足某个终止条件时&#x…

读软件设计的要素05概念的特性

1. 概念的特性 1.1. 专一性原则(specificity principle)认为概念与目的应该一一对应 1.1.1. 专一性原则已被证明是概念设计中最有用的原则之一 1.1.2. 一个概念最多只能满足一个目的 1.2. 很少有没有目的的概念 1.2.1. 如果本应隐藏的用户机制被暴露,可能会产生…

通信工程学习:什么是2ASK/BASK二进制振幅键控

2ASK/BASK:二进制振幅键控 2ASK/BASK二进制振幅键控是一种数字调制技术,其全称是二进制振幅键控(Binary Amplitude Shift Keying)。该技术通过改变载波的振幅来传递二进制数字信息,而载波的频率和相位则保持不变。以下…

RISC-V (九)抢占式多任务

主要的思想:借用定时器中断实现。设置定时器寄存器,系统自动触发定时器中断时会跳到trap handler这个函数里。借用这个函数做上下文的切换,从而实现了抢占式多任务。 定时器中断:跳到trap handler函数,同时系统自动将…

清华计算几何--凸Polygon的相交问题

凸Polygon和相交定义 本节只讨论凸Polygon的问题,不涉及凹Polygon. 相交包含了边相交和完全包含。 凸Polygon相交的两个问题 Detection(检测) 判断两个凸Polygon是否相交,至于相交部分是什么不关心. Construction(构造) 求出两个凸Polygon具体相交…

Linux_kernel移植rootfs10

一、动态更改内核 1、low level(静态修改) 【1】将led_drv.c拷贝到kernel/drivers/char/目录中 【2】修改当前目录下的Makefile文件 obj-y led_drv.o #将新添加的驱动文件加入到Makefile文件中 【3】退回kernel目录,执行make uImage …

熬夜后补救措施

人体的肝功能问题 直接体现在体态和容颜上 伤肝 三大坏行为 熬夜后补救 *补充养b族、口、、锌、硒 加强代谢 能力 (1)另外熬夜后一定要多喝水 提升身体代谢能力 (2)谷肤甘肽清肝 肝脏排毒,减轻负拒 (3)水飞前含量高点 (4)熬夜出更多油 容易长痘 需要清…

标准库标头 <filesystem> (C++17)学习之文件类型

本篇介绍filesystem文件库的文件类型API。 文件类型 is_block_file (C17) 检查给定的路径是否表示块设备 (函数) is_character_file (C17) 检查给定的路径是否表示字符设备 (函数) is_directory (C17) 检查给定的路径是否表示一个目录 (函数) is_empty (C17) 检查给定的路径是…

STM32G474之使用DAC1和DAC2测试模拟比较器

STM32G474使用DAC1和DAC2的输出作为比较器输入,测试模拟比较器,方法如下: PA1的附加功能为COMP1_INP,无需映射,直接将它配置为模拟功能,就可以使用了。 将COMP1_OUT引脚映射到PA0; 采用DAC2_OUT1输出电压给…

【大疆 SDR 图传 P1 】 功能拆解,通信功能剖析

大疆 SDR 图传 P1 拆解视频P1 SoC1、哲酷2、小米3、大疆(文章主角) 一、为什么说SDR技术1、sdr 软件无线电2、影视博主的测评方法3、第一个说自己SDR的还是这个老登 二、大疆的图传发展历程1、FPGA AD93632、 P1 自研1、2个DSP和一个CPU A72、音频子系统…

SpringMVC;MVC模式;Spring环境搭建;

一,介绍MVC模式: MVC模式: 1.M:model 模型,业务模型和数据模型. 2.C:controller 控制器 3.V:view 视图 优点: 使用控制器C把视图V和业务模型M分离,从而使同一个程序可以使用不同的表现形式 使用场景: 中大型项目 核心: 控制器 二…

828华为云征文 | 基于Docker与Jenkins实现自动化部署

需要了解 本文章主要讲述在 华为云Flexus X 实例上使用docker快速部署持续集成工具 Jenkins,通过插件来自动化CI/CD过程中的各种琐碎功能。选择合适的云服务器: 本文采用的是 华为云服务器 Flexus X 实例(推荐使用)连接方式&#…

【自动驾驶】决策规划算法 | 数学基础(三)直角坐标与自然坐标转换Ⅰ

写在前面: 🌟 欢迎光临 清流君 的博客小天地,这里是我分享技术与心得的温馨角落。📝 个人主页:清流君_CSDN博客,期待与您一同探索 移动机器人 领域的无限可能。 🔍 本文系 清流君 原创之作&…

【有啥问啥】数字孪生(Digital Twin)技术在人工智能中的应用

数字孪生技术在人工智能中的应用 在当今的数字化转型过程中,“数字孪生”技术逐渐成为热门话题,并且在各个行业中展现出巨大的潜力。作为一种新兴技术,数字孪生(Digital Twin)不仅仅是物理对象的虚拟复制品&#xff0…