AutoX.js - openCV多分辨率找图

AutoX.js - openCV多分辨率找图

一、起因

AutoXjs 中有两个找图相关的方法 findImage 和 matchTemplate,之前一直没发现什么问题,但最近在一次测试找图时,明明大图和模板图的轮廓都清晰,却怎么也找不到图,降低阈值参数后找到的结果乱七八糟,我仔细对比图像后发现原因,竟是大图相较于模板抠图时的画面等比缩小了1.2倍,仅仅是0.2倍的整体像素差异,致使搜图失败。

于是我就去翻了AutoX 的具体实现代码,发现这部分代码已是5年前继承于 autojs 的部分代码,具体代码文件在:TemplateMatching.java

其实现原理是借助 opencv的 Imgproc.matchTemplate 方法,以下是部分代码文档:

/*** 采用图像金字塔算法快速找图** @param img             图片* @param template        模板图片* @param matchMethod     匹配算法* @param weakThreshold   弱阈值。该值用于在每一轮模板匹配中检验是否继续匹配。如果相似度小于该值,则不再继续匹配。* @param strictThreshold 强阈值。该值用于检验最终匹配结果,以及在每一轮匹配中如果相似度大于该值则直接返回匹配结果。* @param maxLevel        图像金字塔的层数* @return*/
public static List<Match> fastTemplateMatching(Mat img, Mat template, int matchMethod, float weakThreshold, float strictThreshold, int maxLevel, int limit) {TimingLogger logger = new TimingLogger(LOG_TAG, "fast_tm");

从中我们可以得知,其采用了“图像金字塔算法快速找图”的处理方式,大体流程是这样的,先将大图和模板图都进行等比缩放,比如宽高都缩小为原来的1/2,如果此时能找到满足阈值的点P1,那么在图像细节更丰富的大图中点P1一定也符合阈值。将图像先等比例缩小,表示图像的矩阵尺寸也就变小,这样执行找图时,整体的计算量减小,找图效率就会大大提高。

这里采用的金字塔算法快速找图,图像尺寸变换就像金字塔,每提高一层图像就变,宽高就缩小一半。方法中的level 参数就是控制起始找图层级的,从 level 层开始从上往下搜图,level=0,即金字塔最底层,也就是原图和模板一比一对比。例如:level=2 就代表从第3层开始往下找图,先在第3层找到 >=threshold 阈值的匹配点就加到最终结果中,剩余 < threshold 且 >=weakThreshold 的疑似匹配点就在第二层中重点关注,以此类推,直到连 >=weakThreshold 的点位也没有就结束找图。

我本以为将level参数调高就能忽略掉大图放大1.2倍的找图问题,但是level过高会导致图像挤成一团,再尝试降低 threshold,虽得到了几个结果,但都是不相干的位置,根本没法用。仔细一想,level参数只是控制找图效率的,这种快速找图的方式根本不适用于大图或模板图分辨率发生变化后的找图场景,而这种情形是很常见的,比如在你手机上开发的脚本放到平板或者别的型号手机上导致找图失败,再比如游戏场景中因双指缩放导致的画面变化。

为了适应大图分辨率可能发生变化的找图场景,我参照 AutoX 中的找图代码,重新用js实现了一版。由于我只看了找图这一部分的相关源码,项目其他的代码不熟悉,就暂时不打算通过提交 PR 为项目做贡献了,有能力的朋友可以完善此部分功能。

二、具体实现

重点部分我已加了文档说明和注释,来不及看也没关系,直接看 main() 方法中的示例,开箱即用。

importClass(org.opencv.imgproc.Imgproc);
importClass(org.opencv.core.Core);
importClass(org.opencv.core.Rect);
importClass(org.opencv.core.Mat);
importClass(org.opencv.core.Point);
importClass(org.opencv.core.Size);
importClass(org.opencv.core.CvType);
importClass(org.opencv.core.Scalar);
importClass(org.opencv.imgcodecs.Imgcodecs);/*** @param {number[]} region 是一个两个或四个元素的数组。* (region[0], region[1])表示找色区域的左上角;region[2]*region[3]表示找色区域的宽高。如果只有region只有两个元素,则找色区域为(region[0], region[1])到屏幕右下角。* 如果不指定region选项,则找色区域为整张图片。* @param {*} img* @returns {org.opencv.core.Rect}*/
function buildRegion(region, img) {if (region == undefined) {region = [];}let x = region[0] === undefined ? 0 : region[0];let y = region[1] === undefined ? 0 : region[1];let width = region[2] === undefined ? img.getWidth() - x : region[2];let height = region[3] === undefined ? img.getHeight() - y : region[3];if (x < 0 || y < 0 || x + width > img.width || y + height > img.height) {throw new Error('out of region: region = [' + [x, y, width, height] + '], image.size = [' + [img.width, img.height] + ']');}return new Rect(x, y, width, height);
}/*** @param {number} threshold 图片相似度。取值范围为0~1的浮点数。默认值为0.9* @param {number[]} region 找图区域* @param {number[]} scaleFactors 大图的宽高缩放因子,默认为 [1, 0.9, 1.1, 0.8, 1.2]* @param {number} max 找图结果最大数量,默认为5* @param {boolean} grayTransform 是否进行灰度化预处理,默认为true。* 通常情况下将图像转换为灰度图可以简化匹配过程并提高匹配的准确性,当然,如果你的匹配任务中颜色信息对匹配结果具有重要意义,* 可以跳过灰度化步骤,直接在彩色图像上进行模板匹配。*/
function MatchOptions(threshold, region, scaleFactors, max, grayTransform) {this.threshold = threshold;this.region = region;this.scaleFactors = scaleFactors;this.max = max;this.grayTransform = grayTransform;
}const defaultMatchOptions = new MatchOptions(0.9,undefined,[[1, 1],[0.9, 0.9],[1.1, 1.1],[0.8, 0.8],[1.2, 1.2]],5,true
);
// 校验参数
MatchOptions.check = function (options) {if (options == undefined) {return defaultMatchOptions;}// deep copylet newOptions = JSON.parse(JSON.stringify(options));if (newOptions.threshold == undefined) {newOptions.threshold = defaultMatchOptions.threshold;}if (newOptions.region && !Array.isArray(newOptions.region)) {throw new TypeError('region type is number[]');}if (newOptions.max == undefined) {newOptions.max = defaultMatchOptions.max;}if (newOptions.scaleFactors == undefined) {newOptions.scaleFactors = defaultMatchOptions.scaleFactors;} else if (!Array.isArray(newOptions.scaleFactors)) {throw new TypeError('scaleFactors');}for (let index = 0; index < newOptions.scaleFactors.length; index++) {let factor = newOptions.scaleFactors[index];if (Array.isArray(factor) && factor[0] > 0 && factor[1] > 0) {// nothing} else if (typeof factor === 'number') {newOptions.scaleFactors[index] = [factor, factor];} else {throw new TypeError('scaleFactors');}}if (newOptions.grayTransform === undefined) {newOptions.grayTransform = defaultMatchOptions.grayTransform;}return newOptions;
};function Match(point, similarity, scaleX, scaleY) {this.point = point;this.similarity = similarity;this.scaleX = scaleX;this.scaleY = scaleY;
}/*** 找图,在图中找出所有匹配的位置* @param {Image} img* @param {Image} template* @param {MatchOptions} options 参数见上方定义* @returns {Match[]}*/
function matchTemplate(img, template, options) {if (img == null || template == null) {throw new Error('ParamError');}options = MatchOptions.check(options);console.log('参数:', options);let largeMat = img.mat;let templateMat = template.mat;let largeGrayMat;let templateGrayMat;if (options.region) {options.region = buildRegion(options.region, img);largeMat = new Mat(largeMat, options.region);}// 灰度处理if (options.grayTransform) {largeGrayMat = new Mat();Imgproc.cvtColor(largeMat, largeGrayMat, Imgproc.COLOR_BGR2GRAY);templateGrayMat = new Mat();Imgproc.cvtColor(templateMat, templateGrayMat, Imgproc.COLOR_BGR2GRAY);}// =================================================let finalMatches = [];for (let factor of options.scaleFactors) {let [fx, fy] = factor;let resizedTemplate = new Mat();Imgproc.resize(templateGrayMat || templateMat, resizedTemplate, new Size(), fx, fy, Imgproc.INTER_LINEAR);// 执行模板匹配,标准化相关性系数匹配法let matchMat = new Mat();Imgproc.matchTemplate(largeGrayMat || largeMat, resizedTemplate, matchMat, Imgproc.TM_CCOEFF_NORMED);let currentMatches = _getAllMatch(matchMat, resizedTemplate, options.threshold, factor, options.region);console.log('缩放比:', factor, '可疑目标数:', currentMatches.length);for (let match of currentMatches) {if (finalMatches.length === 0) {finalMatches = currentMatches.slice(0, options.max);break;}if (!isOverlapping(finalMatches, match)) {finalMatches.push(match);}if (finalMatches.length >= options.max) {break;}}resizedTemplate.release();matchMat.release();if (finalMatches.length >= options.max) {break;}}largeMat !== img.mat && largeMat.release();largeGrayMat && largeGrayMat.release();templateGrayMat && templateGrayMat.release();return finalMatches;
}function _getAllMatch(tmResult, templateMat, threshold, factor, rect) {let currentMatches = [];let mmr = Core.minMaxLoc(tmResult);while (mmr.maxVal >= threshold) {// 每次取匹配结果中的最大值和位置,从而使结果按相似度指标从高到低排序let pos = mmr.maxLoc; // Pointlet value = mmr.maxVal;let start = new Point(Math.max(0, pos.x - templateMat.width() / 2), Math.max(0, pos.y - templateMat.height() / 2));let end = new Point(Math.min(tmResult.width() - 1, pos.x + templateMat.width() / 2),Math.min(tmResult.height() - 1, pos.y + templateMat.height() / 2));// 屏蔽已匹配到的区域Imgproc.rectangle(tmResult, start, end, new Scalar(0), -1);mmr = Core.minMaxLoc(tmResult);if (rect) {pos.x += rect.x;pos.y += rect.y;start.x += rect.x;start.y += rect.y;end.x += rect.x;end.y += rect.y;}let match = new Match(pos, value, factor[0], factor[1]);// 保存匹配点的大致范围,用于后续去重。设置enumerable为false相当于声明其为私有属性Object.defineProperty(match, 'matchAroundRect', { value: new Rect(start, end), writable: true, enumerable: false });currentMatches.push(match);}return currentMatches;
}/*** 判断新检测到的点位是否与之前的某个点位重合。* @param {Match[]} matches* @param {Match} newMatch* @returns {boolean}*/
function isOverlapping(matches, newMatch) {for (let existingMatch of matches) {// 也可判断两点间的距离,但是平方、开方运算不如比较范围简单高效if (existingMatch.matchAroundRect.contains(newMatch.point)) {if (newMatch.similarity > existingMatch.similarity) {existingMatch.point = newMatch.point;existingMatch.similarity = newMatch.similarity;existingMatch.scaleX = newMatch.scaleX;existingMatch.scaleY = newMatch.scaleY;existingMatch.matchAroundRect = newMatch.matchAroundRect;}return true;}}return false;
}
/*** 根据搜图结果在原图上画框* @param {Match[]} matches* @param {*} srcMat* @param {*} templateMat*/
function showMatchRectangle(matches, srcMat, templateMat) {for (let match of matches) {let start = match.point;let end = new Point(match.point.x + templateMat.width() * match.scaleX,match.point.y + templateMat.height() * match.scaleY);Imgproc.rectangle(srcMat, start, end, new Scalar(0, 0, 255), 3);}const saveName = '/sdcard/Download/temp.jpg';let img2 = images.matToImage(srcMat);images.save(img2, saveName);app.viewFile(saveName);img2.recycle();
}function main() {let largeImage = images.read('/sdcard/Download/large.jpg');let template = images.read('/sdcard/Download/template.jpg');console.log('大图尺寸:', [largeImage.getWidth(), largeImage.getHeight()]);console.log('模板尺寸:', [template.getWidth(), template.getHeight()]);let startTs = Date.now();let result = matchTemplate(largeImage, template, {threshold: 0.85,region: [100, 100],grayTransform: false,scaleFactors: [1, 0.9, 1.1, 0.8, 1.2],max: 6});console.log('找图耗时:', (Date.now() - startTs) / 1000);console.log(result);// 将结果画框展示showMatchRectangle(result, largeImage.mat, template.mat);template.recycle();largeImage.recycle();
}// 初始化openCV
runtime.getImages().initOpenCvIfNeeded();
main();

备注说明

  • 参数 threholdregionmax 跟AutoX中的一样。

  • grayTransform:是否将图像进行灰度预处理,开启可大幅提高找图效率,默认为true。
    对于模板匹配任务,通常关注的是图像的纹理和亮度变化,而不是颜色差异。因此,将图像转换为灰度图可以降低计算复杂度,减少模板匹配过程中的噪声干扰,并提高匹配的稳定性和准确性。尤其是对于一些目标图案周围颜色不确定的搜图场景,开启灰度处理后后,不管目标周围颜色如何变化,都会找到一个较高准确度的匹配点。如果你的模板小图中纹理不明显,或是一团颜色相近的色块,就得关闭该功能。

  • scaleFactors:是对小图模板的缩放因子,数组类型,默认为[1, 0.9, 1.1, 0.8, 1.2]。每一项可以是数字,表示宽高等比例缩放,也可以是长度为2的数组,表示宽、高对应的缩放比,例如:[0.9, 1, [1.1, 1.2]]

    这里重点强调一点,我没有在 openCV 里找到可以直接用于忽略图像比例差异的搜图方法,只能手动指定可能的缩放范围,依次对小图模板进行缩放后再搜图。理论上,只要你设置的(不重复)缩放因子足够多,就一定能找到目标,除非图中没有😁。

  • max参数的妙用:搜图过程中,默认在找到前 max 个符合阈值的匹配点就退出,但是可能存在一种情况,例如先在缩放比为 1.1 的情况下找到了相似度为 0.8 的点 P1,此时若还没有找够 max 个匹配点,在后续比例为 1.2 的情况下,检测到点 P1 处的相似度提高到0.9,就将原来 P1 点的信息更新为更准确的信息。理解了这一点,如果你将 max 设置的非常大,我的搜图算法就会按照 scaleFactors 中设置的全部缩放因子都检测一遍,不会提前结束,那么最终结果中所有的 Match 对象中保存的都是最佳匹配点的信息,你可以凭借最终结果中的 scaleX、scaleY 信息,动态调整 scaleFactors 参数,使其优先匹配最佳缩放比,提高后续的找图效率。

三、测试结果展示

以下是一个测试数据,模板图是一坨近乎白色的光团,在使用了5个缩放因子情况下,对比了 grayTransform 参数开启和关闭的情况,执行效率上相差了好几倍,且找图结果也不一样。

希望大家在使用时,清楚每个参数改变所产生的效果。

  • 模板小图
    在这里插入图片描述

  • 5个缩放因子下的找图结果
    在这里插入图片描述

  • 开启灰度处理
    在这里插入图片描述

  • 未开启灰度处理

持续更新链接:Android自动化-AutoX多分辨率找图

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

C语言 每日一题 11

1.使用函数求素数和 本题要求实现一个判断素数的简单函数、以及利用该函数计算给定区间内素数和的函数。 素数就是只能被1和自身整除的正整数。注意&#xff1a;1不是素数&#xff0c;2是素数。 函数接口定义&#xff1a; int prime(int p); int PrimeSum(int m, int n); 其中…

云原生环境下JAVA应用容器JVM内存如何配置?—— 筑梦之路

Docker环境下的JVM参数非定值配置 —— 筑梦之路_docker jvm设置-CSDN博客 之前简单地记录过一篇&#xff0c;这里在之前的基础上更加细化一下。 场景说明 使用Java开发且设置的JVM堆空间过小时&#xff0c;程序会出现系统内存不足OOM&#xff08;Out of Memory&#xff09;的…

Java实验二类编程实验

1.编写一个代表三角形的类&#xff08;Triangle.java&#xff09;。 其中&#xff0c;三条边a,b,c&#xff08;数据类型为double类型&#xff09;为三角形的属性&#xff0c;该类封装有求三角形的面积和周长的方法。分别针对三条边为3、4、5和7、8、9的两个三角形进行测试&…

【精】UML及软件管理工具汇总

目录 1 老七工具&#xff08;规划质量&#xff09; 1.1 因果图&#xff08;鱼骨图、石川图&#xff09; 1.2 控制图 1.3 流程图:也称过程图 1.4 核查表:又称计数表 1.5 直方图 1.6 帕累托图 1.7 散点图&#xf…

通过USM(U盘魔术大师)在PE环境下使用分区助手拷贝磁盘——无损升级硬盘

这里写自定义目录标题 背景本次使用技术步骤1、添加新硬盘2、添加PE3、开机进入BIOS&#xff0c;进入PE4、开始拷贝磁盘5、调整分区5.1 删除系统盘前的所有分区5.2 修改硬盘分区表格式为GUID5.3 新建引导分区 6、修复引导7、大功告成 背景 由于硬盘空间不够的时候就需要更换硬盘…

CCF_A 计算机视觉顶会CVPR2024投稿指南以及论文模板

目录 CVPR2024官网&#xff1a; CVPR2024投稿链接&#xff1a; CVPR2024 重要时间节点&#xff1a; CVPR2024投稿模板: WORD: LATEX : CVPR2024_AuthorGuidelines CVPR2024投稿Topics&#xff1a; CVPR2024官网&#xff1a; https://cvpr.thecvf.com/Conferences/2024CV…

【设计模式】第7节:创建型模式之“建造者模式”

Builder模式&#xff0c;中文翻译为建造者模式或者构建者模式&#xff0c;也有人叫它生成器模式。 在创建对象时&#xff0c;一般可以通过构造函数、set()方法等设置初始化参数&#xff0c;但当参数比较多&#xff0c;或者参数之间有依赖关系&#xff0c;需要进行复杂校验时&a…

Linux进程概念(2)

Linux进程概念(2) &#x1f4df;作者主页&#xff1a;慢热的陕西人 &#x1f334;专栏链接&#xff1a;Linux &#x1f4e3;欢迎各位大佬&#x1f44d;点赞&#x1f525;关注&#x1f693;收藏&#xff0c;&#x1f349;留言 本博客主要内容讲解了进程的概念&#xff0c;PCB&am…

【数据结构】树形结构所有路径复原为链表

目录 1. 树形结构可视化 2. 树形结构转为链表 此目标是要还原树形结构的所有路径。树形结构是一种常见的数据结构&#xff0c;它表示元素之间层次关系。在树形结构中&#xff0c;每个节点可能拥有一个或多个子节点&#xff0c;形成了一个分层的结构。为了还原树形结构的路径&…

OpenCV官方教程中文版 —— 图像去噪

OpenCV官方教程中文版 —— 图像去噪 前言一、原理二、OpenCV 中的图像去噪1.cv2.fastNlMeansDenoisingColored()2.cv2.fastNlMeansDenoisingMulti() 前言 目标 • 学习使用非局部平均值去噪算法去除图像中的噪音 • 学习函数 cv2.fastNlMeansDenoising()&#xff0c;cv2.fa…

贝锐向日葵亮相阿里云“云栖大会”:独创专利算法赋能全新云桌面

2023年10月31日-11月2日&#xff0c;一年一度的云栖大会如期举办&#xff0c;国产远程连接服务创领者贝锐受邀参与。活动现场&#xff0c;贝锐CTO张小峰进行了分享&#xff0c;宣布贝锐旗下国民级远程控制品牌“贝锐向日葵”与无影展开合作&#xff0c;同时全新的“云桌面”将于…

后台界面设计都有哪些关键的技巧

在大数据时代&#xff0c;越来越多的设计师接触到背景界面设计。网站的背景是网站数据库和文件的快速操作和管理系统&#xff0c;以便及时更新和调整前台内容。和大多数UI设计一样&#xff0c;背景界面设计也有自己的设计元素和规范。本文将分享和总结背景界面设计的五个关键设…

Lightdb23.4 Client 包含ecpg可执行程序及相关库文件

功能介绍 部分客户在使用Lightdb client绿色包时需要ecpg程序和ecpg相关的头文件和库文件&#xff0c;所以在Lightdb 23.4版本client绿色包中新增了ecpg的程序和相关头文件和库文件&#xff0c;以方便用户的使用。 Client包目录结构 bin目录是可执行程序和脚本&#xff0c;i…

微信聚合聊天系统的便捷功能:自动发圈,跟圈

快到双十一咯&#xff0c;很多商家和自媒体、运营人都在发圈做运营&#xff0c;所以现在发圈的频率也会比以往的多一些&#xff0c;但事情一多就会担心今天的朋友圈忘记发、漏发或者错过发圈的时间导致错过私域里的好友、客户会错过活动时间。 其实这些都是可以不用担心&#…

无需服务器内网穿透Windows下快速搭建个人WEB项目

&#x1f4d1;前言 本文主要是windows下内网穿透文章&#xff0c;如果有什么需要改进的地方还请大佬指出⛺️ 参考自&#xff1a;Windows搭建web站点&#xff1a;免费内网穿透发布至公网 &#x1f3ac;作者简介&#xff1a;大家好&#xff0c;我是青衿&#x1f947; ☁️博客首…

day47

今日内容详细 overflow溢出属性 visible 默认值&#xff0c;内容不会被修剪&#xff0c;会呈现在元素框之外 hidden 内容会被修剪&#xff0c;并且其余内容是不可见的 scroll 内容会被修剪&#xff0c;但是浏览器会显示滚动条以便查看其余内容 auto 如果内容被修剪&#xff0c…

python之pip常用指令

文章目录 pip show xxx 查看是否安装该 module

再也不用惧怕那些“流氓”软件了!卸载不能卸载软件的方法不少

保持电脑的清洁和整洁至关重要,原因有两个:电脑的健康和幸福,以及你自己。一堆不需要的软件可能会让你的机器陷入困境,变得迟钝,而一个杂乱的桌面也会对你的大脑产生同样的影响。 但清理并不总是那么容易;有时应用程序会留下不需要的痕迹,有时它们会坏掉并拒绝卸载,有…

c++ 实现二叉搜索树

二叉搜索树的概念 二叉搜索树 (BST&#xff0c;Binary Search Tree)&#xff0c;也称二叉排序树或二叉查找树。它要么是一颗空树&#xff0c;要么是满足以下性质的二叉树&#xff1a; 若它的左子树不为空&#xff0c;则左子树上所有节点的值都小于根节点的值。若它的右子树不为…

消息中间件——RabbitMQ(二)各大主流消息中间件综合对比介绍!

前言 消息队列已经逐渐成为企业IT系统内部通信的核心手段。它具有低耦合、可靠投递、广播、流量控制、最终一致性等一系列功能&#xff0c;成为异步RPC的主要手段之一。当今市面上有很多主流的消息中间件&#xff0c;如老牌的ActiveMQ、RabbitMQ&#xff0c;炙手可热的Kafka&a…