• Autojs 利用OpenCV识别棋子之天天象棋你马没了


    本例子通过代码像你介绍利用OpenCV实现霍尔找圆的方法定位棋子位置
    通过autojs脚本实现自动点击棋子 开源地址

    https://github.com/Liberations/TtxqYourHorseIsGone/blob/master/main.js
    AutoXJs
    https://github.com/kkevsekk1/AutoX/releases
    
    • 1
    • 2
    • 3
    auto()
    //安卓版本高于Android 9
    if (device.sdkInt > 28) {
        //等待截屏权限申请并同意
        threads.start(function () {
            packageName('com.android.systemui').text('立即开始').waitFor()
            text('立即开始').click()
        });
    }
    //申请截屏权限
    if (!requestScreenCapture()) {
        toast("请求截图失败")
        exit()
    }
    http.__okhttp__.setTimeout(40 * 1000)//低版本需要加这个
    // 创建空的棋盘
    // 创建空的棋盘
    function createEmptyChessboard() {
        var temp = [];
        // 创建一个大小为 10x9 的二维数组,并用空字符串初始化
        for (var i = 0; i < 10; i++) {
            temp[i] = Array(9).fill("O");
        }
    
        return temp;
    }
    
    
    // 打印二维数组
    function printChessboard(chessboard) {
        for (var i = 0; i < chessboard.length; i++) {
            var row = chessboard[i].join(" ");
            log(row);
        }
    }
    
    
    //复制一份新的数组
    function copyArray(array) {
        var newArray = [];
    
        for (var i = 0; i < array.length; i++) {
            var innerArray = array[i];
            var newInnerArray = innerArray.slice(); // 复制内部数组
            newArray.push(newInnerArray);
        }
    
        return newArray;
    }
    
    //将[row,col] 转成字母
    function coordinatesToAlgebraic(coordinates) {
        var row = coordinates[0]
        var col = coordinates[1]
        if (isRed) {
            row = 9 - row;
            col = col + 'a'.charCodeAt(0);
            return String.fromCharCode(col) + row.toString();
        } else {
            col = 8 - col + 'a'.charCodeAt(0);
            return String.fromCharCode(col) + row.toString();
        }
    
    
    }
    
    
    //将字母转成 [row,col]
    function algebraicToCoordinates(algebraic) {
        var col = algebraic.charCodeAt(0) - 'a'.charCodeAt(0);
        var row = 9 - parseInt(algebraic.substring(1));
        if (isRed) {
            //b9 c7 (0,1) (2,2)
            return [row, col];
        } else {
            //b9 c7  (9,7) (7,6)
            row = 9 - row
            col = 8 - col
            return [row, col];
        }
    }
    
    
    //二维数组旋转180度
    function rotateArray180(array) {
        var rotated = [];
        for (var i = array.length - 1; i >= 0; i--) {
            var row = array[i];
            var rotatedRow = row.slice().reverse();
            rotated.push(rotatedRow);
        }
        return rotated;
    }
    
    //大小写翻转
    function transformString(str) {
        let transformedString = "";
    
        for (let i = 0; i < str.length; i++) {
            let char = str.charAt(i);
            if (char === char.toUpperCase()) {
                transformedString += char.toLowerCase();
            } else {
                transformedString += char.toUpperCase();
            }
        }
    
        return transformedString;
    }
    
    
    
    //释放图片资源
    function releaseSouce() {
        // if (img != null) {
        //     img.recycle()
        //     img = null
        // }
        if (board_img != null) {
            board_img.recycle()
            img = null
        }
    }
    
    
    //通过row col去点击相应的坐标
    function clickChess(row, col) {
        var clickStartX = (2 * col + 1) * chess_piece_radius + boardLeft;
        var clickStartY = (2 * row + 1) * chess_piece_radius + boardTop;
        //log("点击位置clickStartX " + clickStartX + " clickStartY " + clickStartY)
        click(clickStartX, clickStartY);
    }
    
    
    
    
    //所有的操作都用这个控制
    //初始的象棋布局
    var defaultChessBoard = [
        ['r', 'n', 'b', 'a', 'k', 'a', 'b', 'n', 'r'],
        ['O', 'O', 'O', 'O', 'O', 'O', 'O', 'O', 'O'],
        ['O', 'c', 'O', 'O', 'O', 'O', 'O', 'c', 'O'],
        ['p', 'O', 'p', 'O', 'p', 'O', 'p', 'O', 'p'],
        ['O', 'O', 'O', 'O', 'O', 'O', 'O', 'O', 'O'],
        ['O', 'O', 'O', 'O', 'O', 'O', 'O', 'O', 'O'],
        ['P', 'O', 'P', 'O', 'P', 'O', 'P', 'O', 'P'],
        ['O', 'C', 'O', 'O', 'O', 'O', 'O', 'C', 'O'],
        ['O', 'O', 'O', 'O', 'O', 'O', 'O', 'O', 'O'],
        ['R', 'N', 'B', 'A', 'K', 'A', 'B', 'N', 'R']
    ]
    
    //初始化圆的位置
    var defaultCircleBoard = [
        ['B', 'B', 'B', 'B', 'B', 'B', 'B', 'B', 'B'],
        ['O', 'O', 'O', 'O', 'O', 'O', 'O', 'O', 'O'],
        ['O', 'B', 'O', 'O', 'O', 'O', 'O', 'B', 'O'],
        ['B', 'O', 'B', 'O', 'B', 'O', 'B', 'O', 'B'],
        ['O', 'O', 'O', 'O', 'O', 'O', 'O', 'O', 'O'],
        ['O', 'O', 'O', 'O', 'O', 'O', 'O', 'O', 'O'],
        ['R', 'O', 'R', 'O', 'R', 'O', 'R', 'O', 'R'],
        ['O', 'R', 'O', 'O', 'O', 'O', 'O', 'R', 'O'],
        ['O', 'O', 'O', 'O', 'O', 'O', 'O', 'O', 'O'],
        ['R', 'R', 'R', 'R', 'R', 'R', 'R', 'R', 'R']
    ]
    
    var defaultBlackCircleBoard = [
        ['R', 'R', 'R', 'R', 'R', 'R', 'R', 'R', 'R'],
        ['O', 'O', 'O', 'O', 'O', 'O', 'O', 'O', 'O'],
        ['O', 'R', 'O', 'O', 'O', 'O', 'O', 'R', 'O'],
        ['R', 'O', 'R', 'O', 'R', 'O', 'R', 'O', 'R'],
        ['O', 'O', 'O', 'O', 'O', 'O', 'O', 'O', 'O'],
        ['O', 'O', 'O', 'O', 'O', 'O', 'O', 'O', 'O'],
        ['B', 'O', 'B', 'O', 'B', 'O', 'B', 'O', 'B'],
        ['O', 'B', 'O', 'O', 'O', 'O', 'O', 'B', 'O'],
        ['O', 'O', 'O', 'O', 'O', 'O', 'O', 'O', 'O'],
        ['B', 'B', 'B', 'B', 'B', 'B', 'B', 'B', 'B']
    ]
    
    var isRed = null
    var isSart = true
    var isMyStep = false //标志是否我方下棋
    var stepCount = 0 //累计步数
    var fen = ""
    var img = null //每次的截图
    var board_img = null //裁剪出来的棋盘
    var storage = storages.create("chessStorage");
    var boardWidth = 1010 //棋 盘宽度
    var boardHeight = 1140  //棋盘高度
    var boardTop = 626 //棋盘上边距
    var boardLeft = 34 //棋盘左边距
    var speekOn = false //棋盘左边距
    var chess_piece_radius = 0 //棋子半径
    var chess_piece_width = 0 //棋子宽度
    var moves = [] //当前移动的fen码
    var chessBoard = null
    var circleBoard = []
    var intervalId = 0
    var 开始 = images.read("开始.png")
    var 关闭 = images.read("关闭.png")
    var 确定 = images.read("确定.png")
    var 叉叉 = images.read("叉叉.jpg")
    var 对战助手 = images.read("对战助手.jpg")
    var 确定退出 = images.read("确定退出.jpg")
    
    //删除截图
    var imgDir = "img";
    files.removeDir(imgDir)
    files.create(imgDir + "/")
    
    function main() {
        initStart()
        isSart = true
        log('开始新的对局')
        while (isSart) {
            try {
                sleep(2000)
                findChess()
            } catch (e) {
                log("发生异常" + e)
            }
            //isSart = false
        }
    
    }
    
    function initStart() {
        chessBoard = copyArray(defaultChessBoard)
        circleBoard = copyArray(defaultCircleBoard)
        isRed = null
        isSart = true
        isMyStep = false //标志是否我方下棋
        stepCount = 0 //累计步数
        fen = ""
        moves = []
        lastMove = ""
        if (intervalId != 0) {
            clearInterval(intervalId)
            intervalId = 0
        }
    }
    
    
    function findChess() {
        img = images.captureScreen()
        if (!img) {
            toastLog('截图失败')
            return
        }
        var pos4 = images.findImage(img, 对战助手)
        if (pos4) {
            speek("发现对战助手按钮")
            click(pos4.x, pos4.y)
            //重新初始化
            initStart()
            return
        }
        //log("开始findChess")
        var pos = images.findImage(img, 关闭)
        if (pos) {
            speek("发现关闭对局结束")
            click(pos.x, pos.y)
            sleep(1000)
            click(device.width / 2, device.height / 2)
            //重新初始化
            initStart()
            return
        }
    
        var pos1 = images.findImage(img, 开始)
        if (pos1) {
            speek("发现开始按钮")
            click(pos1.x, pos1.y)
            //重新初始化
            initStart()
            return
        }
    
        var pos2 = images.findImage(img, 确定)
        if (pos2) {
            speek("发现确定按钮")
            click(pos2.x, pos2.y)
            //重新初始化
            initStart()
            return
        }
    
        var pos3 = images.findImage(img, 叉叉)
        if (pos3) {
            speek("发现叉叉按钮")
            click(pos3.x, pos3.y)
            //重新初始化
            initStart()
            return
        }
    
        chess_piece_width = boardWidth / 9
        chess_piece_radius = chess_piece_width / 2
        board_img = images.clip(img, boardLeft, boardTop, boardWidth, boardHeight)
    
        if (isRed == null) {
            var col = 4
            var row = 0
            var centerX = (2 * col + 1) * chess_piece_radius;
            var centerY = (2 * row + 1) * chess_piece_radius;
            var left = centerX - chess_piece_radius
            var top = centerY - chess_piece_radius
            let cutImg = images.clip(board_img, left, top, chess_piece_radius * 2, chess_piece_radius * 2)
    
            let containRed = images.findColor(cutImg, "#953c28", {
                threshold: 4
            })
            let containBlack = images.findColor(cutImg, "#3a3938", {
                threshold: 4
            })
    
            if (containRed) {
                circleBoard = null
                circleBoard = copyArray(defaultBlackCircleBoard)
                isRed = false
            } else if (containBlack) {
                circleBoard = null
                circleBoard = copyArray(defaultCircleBoard)
                isRed = true
            } else {
                log("没找到帅啥也没有")
            }
            speek("我方为" + (isRed ? "红方" : "黑方"))
    
    
        }
    
        var tempchessboard = createEmptyChessboard()
        var count = 0;
        // 找圆
        min_radius = chess_piece_width / 4
        max_radius = chess_piece_width / 3
        // 灰度化图片
        let gray = images.grayscale(board_img);
        // 找圆
        let arr = images.findCircles(gray, {
            dp: 1,
            minDst: 30,
            param1: 50,
            param2: 30,
            minRadius: parseInt(min_radius),
            maxRadius: parseInt(max_radius),
        });
        gray.recycle()
        for (let i = 0; i < arr.length; i++) {
            let circle = arr[i];
            let x = circle.x; // 圆心 x 坐标
            let y = circle.y; // 圆心 y 坐标
            let radius = circle.radius; // 圆的半径
            // 裁剪图像
            let row = parseInt(y / chess_piece_width)
            let col = parseInt(x / chess_piece_width)
            var left = x - radius;
            var top = y - radius;
            let cutImg = images.clip(board_img, left, top, radius * 2, radius * 2)
            let containRed = images.findColor(cutImg, "#953c28", {
                threshold: 4
            })
            let containBlack = images.findColor(cutImg, "#3a3938", {
                threshold: 4
            })
            if (containRed) {
                count++
                tempchessboard[row][col] = "R"
            } else if (containBlack) {
                count++
                tempchessboard[row][col] = "B"
            } else {
                tempchessboard[row][col] = "O"
            }
            cutImg.recycle()
    
        }
    
        //log('总共找到圆' + count)
        if (stepCount == 0 && count != 32) {
            log(arr.length + '数量不够' + count)
            initStart()
            printChessboard(tempchessboard)
            releaseSouce()
            return
        }
    
        if (stepCount < 999) {
            var pao1start = tempchessboard[7][1]
            var pao1Mid = tempchessboard[2][1]
            var pao1end = tempchessboard[0][1]
    
            var pao2start = tempchessboard[7][7]
            var pao2Mid = tempchessboard[2][7]
            var pao2end = tempchessboard[0][7]
            if (pao1start != "O" && pao1end != "O" && pao1Mid != "O") {
                speek("发现炮1")
                clickChess(7, 1)
                sleep(50)
                clickChess(0, 1)
                speek("你马没了")
                sleep(2000)
                stepCount++
                return
            }
            if (pao2start != "O" && pao2end != "O" && pao2Mid != "O") {
                speek("发现炮2")
                clickChess(7, 7)
                sleep(50)
                clickChess(0, 7)
                speek("你马没了")
                sleep(2000)
                stepCount++
                return
            }
            return
        }
    }
    
    function speek(str) {
        if (true) {
            TTS.speak(str, TextToSpeech.QUEUE_ADD, null);//自定义语音合成内容
        } else {
            log(str)
        }
    
    }
    
    //调用系统语音合成
    importPackage(android.speech.tts);
    importClass(java.util.Locale);
    if (TTS != undefined) { TTS.stop(); TTS.shutdown(); }
    var TTS = new TextToSpeech(context, function (status) {
        if (status != TextToSpeech.SUCCESS) {
            toast("初始化TTS失败");
        }
        var r = TTS.setLanguage(Locale.CHINA);
        if (r < 0) {
            toast("不支持该语言: " + r);
            exit();
        }
        log("TTS初始化成功");
    });
    sleep(500);
    speek("开始辅助")
    // 创建悬浮窗
    var window = floaty.window(
        
            
            

    主要函数利用autojs提供的 findCircles函数

    images.findCircles(gray, options)
    gray {Image} 灰度图片
    options {Object} 选项包括:
    region {Array} 找圆区域。是一个两个或四个元素的数组。(region[0], region[1])表示找圆区域的左上角;region[2]*region[3]表示找圆区域的宽高。如果只有region只有两个元素,则找圆区域为(region[0], region[1])到图片右下角。如果不指定region选项,则找圆区域为整张图片。
    dp {number} dp是累加面与原始图像相比的分辨率的反比参数,dp=2时累计面分辨率是元素图像的一半,宽高都缩减为原来的一半,dp=1时,两者相同。默认为1。
    minDst {number} minDist定义了两个圆心之间的最小距离。默认为图片高度的八分之一。
    param1 {number} param1是Canny边缘检测的高阈值,低阈值被自动置为高阈值的一半。默认为100,范围为0-255。
    param2 {number} param2是累加平面对是否是圆的判定阈值,默认为100。
    minRadius {number} 定义了检测到的圆的半径的最小值,默认为0。
    maxRadius {number} 定义了检测到的圆的半径的最大值,0为不限制最大值,默认为0。
    return {Array}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    在图片中寻找圆(做霍夫圆变换)。找到时返回找到的所有圆{x,y,radius}的数组,找不到时返回null。

    一个寻找圆的例子:

    // 请求截图
    requestScreenCapture();
    // 截图
    let img = captureScreen();
    // 灰度化图片
    let gray = images.grayscale(img);
    // 找圆
    let arr = findCircles(gray, {
        dp: 1,
        minDst: 80,
        param1: 100,
        param2: 100,
        minRadius: 50,
        maxRadius: 80,
    });
    // 回收图片
    gray.recycle();
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    具体看代码 可以在这个基础上实现手机版自动连线功能!
    在这里插入图片描述

  • 相关阅读:
    React中的dispatch()
    【单片机入门】(四)应用层软件开发的单片机学习之路-----ESP32开发板PWM控制电机以及中断的使用
    8/23 网络流、最大流+hungary算法
    【Hot100】LeetCode—84. 柱状图中最大的矩形
    每日五道java面试题之springMVC篇(三)
    (2022,DALL·E2,CLIP,Diffusion,AR)使用 CLIP 潜在空间的分层文本条件图像生成
    python扩展包安装失败解决方案
    征服地球极限,中国极地科考与登峰事业的“御寒”之旅
    OneFlow v0.8.0正式发布
    Maven-DskipTests和-Dmaven.test.skip=true的区别
  • 原文地址:https://blog.csdn.net/qq910689331/article/details/134008267