Android无障碍自动化结合opencv实现支付宝能量自动收集

Android无障碍服务可以操作元素,手势模拟,实现基本的控制。opencv可以进行图像识别。两者结合在一起即可实现支付宝能量自动收集。opencv用于识别能量,无障碍服务用于模拟手势,即点击能量。

当然这两者结合不单单只能实现这些,还能做很多自动化的程序,如芭芭农场自动施肥、蚂蚁庄园等等的自动化,甚至游戏的自动化也没问题。

下面简单介绍下核心的实现逻辑

核心步骤

  1. 准确识别多个能量球位置
  2. 准确点击能量球位置

opencv识别能量球

OpenCV是一个可用于开发实时的图像处理、计算机视觉以及模式识别可商用的开源库-opencv介绍

思路

使用opencv怎么识别能量球呢?

使用opencv的模板匹配。即,将能量球单独裁剪出来作为模板,再将其与屏幕图像进行匹配,筛选匹配分值最高的结果即获取能量球在屏幕中的位置。

实现

1. 项目集成opencv-android版
复制代码
dependencies {
 implementation 'org.opencv:opencv:4.9.0'
}

最新版本可查看官方集成教程

2. 截取能量球图像作为模板
3. 截取屏幕图像
4. 使用opencv模板匹配获取所有能量球位置

opencv模板匹配api

复制代码
Imgproc.matchTemplate(image, templ, result, method, mask)

参数解释:
image屏幕图像,即步骤3中截取的屏幕图像
templ模板图像,即步骤2中截图的能量球图像
result匹配结果容器,用于存储匹配的结果
mask掩膜,用于指定模板中哪些位置需要匹配,哪些不需要匹配

其中参数mask掩膜是匹配准确度的关键点

掩膜图像是根据模板生成的一张黑白图像,其中黑色为不需要匹配的区域

模板图像与生成的掩膜图像对比

模板图像 掩模图像

其中文字也是我们不需要匹配的,因为里面的文字会变化,所以中间加了一块黑色矩形用于指定匹配忽略区域

对于掩膜的创建方法这里不介绍了,所有代码都已经开放在我的自动化开源库Assists里,想直接看代码这里:https://github.com/ven-coder/Assists

参数准备好就可以进行匹配了,下面是完整代码(kotlin代码)

复制代码
    /**
     * 模板匹配能量球
     */
    fun match() {
        try {
            val path = System.getProperty("user.dir") + "\\lib\\x64\\opencv_java490.dll"
            System.load(path)
            val temp = System.getProperty("user.dir") + "\\images\\temp.jpg"
            val image = System.getProperty("user.dir") + "\\images\\image.png"
            //模板图像
            val img = Imgcodecs.imread(image)
            //屏幕图像
            val templ = Imgcodecs.imread(temp)
            //掩膜图像
            val mask = createMask(templ)
            // 创建结果矩阵
            val resultCols: Int = img.cols() - templ.cols() + 1
            val resultRows: Int = img.rows() - templ.rows() + 1
            val result = Mat(resultRows, resultCols, CvType.CV_32FC1)
            // 进行模板匹配
            Imgproc.matchTemplate(img, templ, result, Imgproc.TM_CCORR_NORMED, mask)
            // 遍历结果矩阵,找到所有匹配超过阈值的位置
            val threshold = 0.98 // 阈值,根据实际情况调整
            var count = 0
            var countValue = 0
            for (y in 0 until result.rows()) {
                for (x in 0 until result.cols()) {
                    countValue++
                    val matchValue = result[y, x]
                    if (matchValue[0] >= threshold) {
                        count++
                        // 找到一个匹配位置
                        val matchLoc = Point(x.toDouble(), y.toDouble())
                        // 绘制矩形框
                        Imgproc.rectangle(img, matchLoc, Point(matchLoc.x + templ.cols(), matchLoc.y + templ.rows()), Scalar( 85.0, 85.0,205.0,), 2, Imgproc.LINE_AA, 0)
                    }
                }
            }
            // 显示结果
            Imgproc.resize(img, img, Size(img.cols() / 2.0, img.rows() / 2.0)) // 可选:调整显示大小
            HighGui.imshow("Matched Result: $count", img)
            HighGui.waitKey(0)
        } catch (e: Throwable) {
            e.printStackTrace()
        }
    }

    /**
     * 创建掩膜
     */
    fun createMask(source: Mat): Mat {
        // 转换为 HSV 颜色空间
        val hsvImage = Mat()
        Imgproc.cvtColor(source, hsvImage, Imgproc.COLOR_BGR2HSV)


        // 定义绿色的颜色范围
        val lowerGreen = Scalar(35.0, 100.0, 100.0)
        val upperGreen = Scalar(85.0, 255.0, 255.0)


        // 创建掩膜
        val mask = Mat()
        Core.inRange(hsvImage, lowerGreen, upperGreen, mask)

        // 忽略"27g"文字
        // 你可以使用形态学操作去掉文字部分,或者手动确定文字的位置并将其设置为黑色(0)。
        // 假设文字位于圆形中心,可以手动遮盖这个区域
        // Rect(中心位置x, 中心位置y, 宽度, 高度)
        val width = 80
        val height = 60
        val textRect = Rect(source.width() / 2 - width / 2, source.height() / 2 - height / 2, width, height) // 假设的"27g"文字位置和大小
        Imgproc.rectangle(mask, textRect, Scalar(0.0), -1)
        Imgproc.rectangle(mask, Rect((source.width() / 2 - width / 2) + 10, (source.height() / 2 - height / 2) + height, 40, 25), Scalar(255.0), -1)
        return mask
    }

匹配结果

点击能量球

准确得到能量球位置之后就好办了,使用我的开源库Assists开启无障碍服务后调用gestureClick(x: Float, y: Float)点击能量球位置即可

复制代码
//it.x + temp3.width() / 2,坐标加上模板大小的一半即点击中间位置
Assists.gestureClick((it.x + temp3.width() / 2).toFloat(), (it.y + temp3.height() / 2).toFloat())

最终效果

以上所有代码都在我的开源库Assists示例里了,需要的自取即可。

觉得有帮助顺便可以start一下,满足以下一下老夫虚荣心憋

相关推荐
xiangpanf4 小时前
Laravel 10.x重磅升级:五大核心特性解析
android
robotx7 小时前
安卓线程相关
android
消失的旧时光-19437 小时前
Android 面试高频:JSON 文件、大数据存储与断电安全(从原理到工程实践)
android·面试·json
dalancon8 小时前
VSYNC 信号流程分析 (Android 14)
android
dalancon8 小时前
VSYNC 信号完整流程2
android
dalancon8 小时前
SurfaceFlinger 上帧后 releaseBuffer 完整流程分析
android
用户69371750013849 小时前
不卷AI速度,我卷自己的从容——北京程序员手记
android·前端·人工智能
程序员Android10 小时前
Android 刷新一帧流程trace拆解
android
墨狂之逸才11 小时前
解决 Android/Gradle 编译报错:Comparison method violates its general contract!
android
阿明的小蝴蝶11 小时前
记一次Gradle环境的编译问题与解决
android·前端·gradle