使用OpenCv+Arduino实现挂机自动打怪

最近在玩某网游,练级十分枯燥和缓慢,就是挂机刷刷刷,所以研究一下自动化,找了个可以原地挂机刷怪的职业,然后用OpenCv检测技能冷却,冷却好了通过串口通知Arduino按下模拟键盘按键释放技能

大概流程如下:

OpenCv定时扫描屏幕->对技能区域截图->对比预设图判断技能冷却->按键事件加入队列

消费队列->取出按键事件->通过串口通知Arduino按下按键释放技能

OpenCv代码

截图部分

public static void ShotFrame(String fileName) throws AWTException, IOException {
Integer x = Integer.valueOf(PropertiesHelper.getValue(窗口X)); Integer y = Integer.valueOf(PropertiesHelper.getValue(窗口Y)); Integer height = Integer.valueOf(PropertiesHelper.getValue(窗口高度)); Integer width = Integer.valueOf(PropertiesHelper.getValue(窗口宽度)); //创建一个robot对象
Robot robut = new Robot();
//获取屏幕分辨率
//打印屏幕分辨率
//创建该分辨率的矩形对象
Dimension act=new Dimension();
act.setSize(width,height);
Point point=new Point(x,y);
Rectangle screenRect = new Rectangle(point,act);
//根据这个矩形截图
BufferedImage bufferedImage = robut.createScreenCapture(screenRect);
//保存截图
File file = new File("D:\\maplePic\\"+fileName+".jpg");
ImageIO.write(bufferedImage, "jpg", file);
}

OpenCv比较

这里用的是bytedeco的javacv,一个封装了OpenCv方法的库,Java用原生OpenCv比较麻烦,推荐用这个

    /**
* OpenCV-4.0.0 直方图比较
*
* @return: double 两张图片的相似值
*/
public static double compareHist_2(String resource, String now) {
Mat src_1 = imread("D:\\maplePic\\"+resource+".jpg");// 图片 1
Mat src_2 = imread("D:\\maplePic\\"+now+".jpg");// 图片 2 Mat hvs_1 = new Mat();
Mat hvs_2 = new Mat();
//图片转HSV
cvtColor(src_1, hvs_1,COLOR_BGR2HSV);
cvtColor(src_2, hvs_2,COLOR_BGR2HSV); Mat hist_1 = new Mat();
Mat hist_2 = new Mat(); final int[] channels = new int[]{0, 1, 2}; final int[] histSize = new int[]{8, 8, 8};
final float[] histRange = new float[]{0f, 255f};
IntPointer intPtrChannels = new IntPointer(channels);
IntPointer intPtrHistSize = new IntPointer(histSize);
final PointerPointer<FloatPointer> ptrPtrHistRange = new PointerPointer<>(histRange, histRange, histRange);
calcHist(hvs_1, 1, intPtrChannels, new Mat(), hist_1, 3, intPtrHistSize, ptrPtrHistRange, true, false);
calcHist(hvs_2, 1, intPtrChannels, new Mat(), hist_2, 3, intPtrHistSize, ptrPtrHistRange, true, false); //图片归一化
normalize(hist_1, hist_1, 1, hist_1.rows() , NORM_MINMAX, -1, new Mat() );
normalize(hist_2, hist_2, 1, hist_2.rows() , NORM_MINMAX, -1, new Mat() ); //直方图比较
return compareHist(hist_1,hist_2,CV_COMP_CORREL);
}

串口工具类

public class SerialPortTool {

    /**
* 查找电脑上所有可用 com 端口
*
* @return 可用端口名称列表,没有时 列表为空
*/
public static final ArrayList<String> findSystemAllComPort() {
/**
* getPortIdentifiers:获得电脑主板当前所有可用串口
*/
Enumeration<CommPortIdentifier> portList = CommPortIdentifier.getPortIdentifiers();
ArrayList<String> portNameList = new ArrayList<>(); /**
* 将可用串口名添加到 List 列表
*/
while (portList.hasMoreElements()) {
String portName = portList.nextElement().getName();//名称如 COM1、COM2....
portNameList.add(portName);
}
return portNameList;
} /**
* 打开电脑上指定的串口
*
* @param portName 端口名称,如 COM1,为 null 时,默认使用电脑中能用的端口中的第一个
* @param b 波特率(baudrate),如 9600
* @param d 数据位(datebits),如 SerialPort.DATABITS_8 = 8
* @param s 停止位(stopbits),如 SerialPort.STOPBITS_1 = 1
* @param p 校验位 (parity),如 SerialPort.PARITY_NONE = 0
* @return 打开的串口对象,打开失败时,返回 null
*/
public static final SerialPort openComPort(String portName, int b, int d, int s, int p) {
CommPort commPort = null;
try {
//当没有传入可用的 com 口时,默认使用电脑中可用的 com 口中的第一个
if (portName == null || "".equals(portName)) {
List<String> comPortList = findSystemAllComPort();
if (comPortList != null && comPortList.size() > 0) {
portName = comPortList.get(0);
}
}
// System.out.println("开始打开串口:portName=" + portName + ",baudrate=" + b + ",datebits=" + d + ",stopbits=" + s + ",parity=" + p);
//通过端口名称识别指定 COM 端口
CommPortIdentifier portIdentifier = CommPortIdentifier.getPortIdentifier(portName);
/**
* open(String TheOwner, int i):打开端口
* TheOwner 自定义一个端口名称,随便自定义即可
* i:打开的端口的超时时间,单位毫秒,超时则抛出异常:PortInUseException if in use.
* 如果此时串口已经被占用,则抛出异常:gnu.io.PortInUseException: Unknown Application
*/
commPort = portIdentifier.open(portName, 5000);
/**
* 判断端口是不是串口
* public abstract class SerialPort extends CommPort
*/
if (commPort instanceof SerialPort) {
SerialPort serialPort = (SerialPort) commPort;
/**
* 设置串口参数:setSerialPortParams( int b, int d, int s, int p )
* b:波特率(baudrate)
* d:数据位(datebits),SerialPort 支持 5,6,7,8
* s:停止位(stopbits),SerialPort 支持 1,2,3
* p:校验位 (parity),SerialPort 支持 0,1,2,3,4
* 如果参数设置错误,则抛出异常:gnu.io.UnsupportedCommOperationException: Invalid Parameter
* 此时必须关闭串口,否则下次 portIdentifier.open 时会打不开串口,因为已经被占用
*/
serialPort.setSerialPortParams(b, d, s, p);
// System.out.println("打开串口 " + portName + " 成功...");
return serialPort;
} else {
System.out.println("当前端口 " + commPort.getName() + " 不是串口...");
}
} catch (NoSuchPortException e) {
e.printStackTrace();
} catch (PortInUseException e) {
System.out.println("串口 " + portName + " 已经被占用,请先解除占用...");
e.printStackTrace();
} catch (UnsupportedCommOperationException e) {
System.out.println("串口参数设置错误,关闭串口,数据位[5-8]、停止位[1-3]、验证位[0-4]...");
e.printStackTrace();
if (commPort != null) {//此时必须关闭串口,否则下次 portIdentifier.open 时会打不开串口,因为已经被占用
commPort.close();
}
}
System.out.println("打开串口 " + portName + " 失败...");
return null;
} /**
* 往串口发送数据
*
* @param serialPort 串口对象
* @param order 待发送数据
*/
public static void sendDataToComPort(SerialPort serialPort, byte[] orders) {
OutputStream outputStream = null;
try {
if (serialPort != null) {
outputStream = serialPort.getOutputStream();
outputStream.write(orders);
outputStream.flush();
// System.out.println("往串口 " + serialPort.getName() + " 发送数据:" + Arrays.toString(orders) + " 完成...");
} else {
System.out.println("gnu.io.SerialPort 为null,取消数据发送...");
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (outputStream != null) {
try {
outputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
} /**
* 往串口发送数据
*
* @param serialPort 串口对象
*/
public static String readDataToComPort(SerialPort serialPort) { try (InputStream inputStream = serialPort.getInputStream(); ByteArrayOutputStream byteArrayOutputStream=new ByteArrayOutputStream()) {
byte[] a=new byte[1024];
while (inputStream.read(a)>0){
byteArrayOutputStream.write(a);
}
return byteArrayOutputStream.toString();
} catch (IOException e) {
e.printStackTrace();
}
return "";
} /**
* 关闭串口
*
* @param serialport 待关闭的串口对象
*/
public static void closeComPort(SerialPort serialPort) {
if (serialPort != null) {
serialPort.close();
// System.out.println("关闭串口 " + serialPort.getName());
}
} /**
* 16进制字符串转十进制字节数组
* 这是常用的方法,如某些硬件的通信指令就是提供的16进制字符串,发送时需要转为字节数组再进行发送
*
* @param strSource 16进制字符串,如 "455A432F5600",每两位对应字节数组中的一个10进制元素
* 默认会去除参数字符串中的空格,所以参数 "45 5A 43 2F 56 00" 也是可以的
* @return 十进制字节数组, 如 [69, 90, 67, 47, 86, 0]
*/
public static byte[] hexString2Bytes(String strSource) {
if (strSource == null || "".equals(strSource.trim())) {
System.out.println("hexString2Bytes 参数为空,放弃转换.");
return null;
}
strSource = strSource.replace(" ", "");
int l = strSource.length() / 2;
byte[] ret = new byte[l];
for (int i = 0; i < l; i++) {
ret[i] = Integer.valueOf(strSource.substring(i * 2, i * 2 + 2), 16).byteValue();
}
return ret;
} public static void main(String[] args) {
// //发送普通数据
byte[] bytes = new byte[]{0x31};
SerialPort serialPort = SerialPortTool.openComPort("COM5", 9600, 8, 1, 0);
SerialPortTool.sendDataToComPort(serialPort, bytes);
String s = SerialPortTool.readDataToComPort(serialPort);
System.out.println(s);
SerialPortTool.closeComPort(serialPort);
}
}

主流程函数

 public static void main(String[] args) {
//每秒一次
Timer timer = new Timer();
TimerTask timerTask = new TimerTask() {
@Override
public void run() {
try {
lhbrother();
} catch (IOException e) {
e.printStackTrace();
} catch (AWTException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
// 第一次任务延迟时间
long delay = 2000; // 任务执行频率
long period = 1000; // 开始调度
timer.schedule(timerTask, delay, period); }
public static void lhbrother() throws IOException, AWTException, InterruptedException {
//截图生成结果
ScreenShotUtil.Shot1("n1");
if(OpenCvUtil.compareHist_2("n1","1")>0.90){
System.out.println("1冷却完毕");
Random random=new Random();
Thread.sleep(random.nextInt(500)+500);
//这里发送的是ascii码,Arduino读取串口后直接KeyBorad.Press读到的内容即可。
//因为ascii码没有上下左右等方向键,所以如果需要控制上下走动,可以使用一个特殊的ascii码,然后在Arduino代码里特殊判断下
byte[] bytes = new byte[]{0x31};
SerialPort serialPort = SerialPortTool.openComPort("COM5", 9600, 8, 1, 0);
SerialPortTool.sendDataToComPort(serialPort, bytes);
String s = SerialPortTool.readDataToComPort(serialPort);
System.out.println(s);
SerialPortTool.closeComPort(serialPort);
return ;
}
}

使用OpenCv+Arduino实现挂机自动打怪的更多相关文章

  1. airTest 实战之 -- 【征途】自动打怪回城卖物品

    airTest是一个跨平台的.基于图像识别的UI自动化测试框架,适用于游戏和App,支持平台有Windows.Android和iOS 官方文档: http://airtest.netease.com/ ...

  2. Opencv分水岭算法——watershed自动图像分割用法

    分水岭算法是一种图像区域分割法,在分割的过程中,它会把跟临近像素间的相似性作为重要的参考依据,从而将在空间位置上相近并且灰度值相近的像素点互相连接起来构成一个封闭的轮廓,封闭性是分水岭算法的一个重要特 ...

  3. 自动打怪 c#

    其中思路很简单,单线程的一个乱七八糟的游戏 预计会更新背包,背包这个估计会用一个vector来存 图形的话,我得催催我的美工大人,她会帮我弄吧,哇哈哈 界面: namespace auttompk { ...

  4. 一个人独立开发 3D 游戏引擎可能吗?

    作者:孙志超链接:https://www.zhihu.com/question/24733255/answer/42000966来源:知乎著作权归作者所有,转载请联系作者获得授权. 当然可以,但难道有 ...

  5. 使用OpenCV/python进行双目测距

    在做SLAM时,希望用到深度图来辅助生成场景,所以要构建立体视觉,在这里使用OpenCV的Stereo库和python来进行双目立体视觉的图像处理. 立体标定 应用标定数据 转换成深度图 标定 在开始 ...

  6. OpenCV 例子代码的讲解、简介及库的安装 .

    转载请标明是引用于 http://blog.csdn.net/chenyujing1234 欢迎大家提出意见,一起讨论! 一.OpenCV介绍: OpenCV是由Intel性能基元(IPP)团队主持, ...

  7. (4opencv)OpenCV PR 成功的收获和感悟

     2018-09-12,第一次对OpenCV PR成功 https://github.com/opencv/opencv/pull/12206 <find innercircle of cont ...

  8. 基于标记的分水岭分割算法/OpenCV中距离变换

    Opencv分水岭算法——watershed自动图像分割用法 OpenCV距离变换distanceTransform应用 图像分割作为图像识别的基础,在图像处理中占有重要地位,通常需要在进行图像分割算 ...

  9. 使用OpenCV和Python构建自己的车辆检测模型

    概述 你对智慧城市的想法感到兴奋吗?如果是的话,你会喜欢这个关于建立你自己的车辆检测系统的教程的 在深入实现部分之前,我们将首先了解如何检测视频中的移动目标 我们将使用OpenCV和Python构建自 ...

随机推荐

  1. Java语言的词法分析器的Java实现

    一.实验目的 1. 学会针对DFA转换图实现相应的高级语言源程序. 2. 深刻领会状态转换图的含义,逐步理解有限自动机. 3. 掌握手工生成词法分析器的方法,了解词法分析器的内部工作原理. 二.实验内 ...

  2. React 与 Hooks 如何使用 TypeScript 书写类型?

    React 与 Hooks 如何使用 TypeScript 书写类型? 本文写于 2020 年 9 月 20 日 函数组件与 TS 对于 Hooks 来说是不支持使用 class 组件的. 如何在函数 ...

  3. 关于position的relative和absolute分别是相对于谁进行定位的

    position:absolute; 他的意思是绝对定位,他是参照浏览器的左上角,配合TOP.RIGHT.BOTTOM.LEFT(下面简称TRBL)进行定位,在没有设定TRBL,默认依据父级的做标原始 ...

  4. 117_PowerQuery使用ODBC访问带密码的Access

    博客:www.jiaopengzi.com 焦棚子的文章目录 请点击下载附件 一. 有朋友在问pq访问带密码的access的时候会报错,导致无法访问(如下图): 1.选择更多 图1 2.选择Acces ...

  5. 使用 Vite 插件开发构建 Tampermonkey 用户脚本

    起因 一直以来,我都是直接在浏览器 Tampermonkey 扩展页面直接新建用户脚本来开发的: 对于一些简单的脚本,这没有什么问题,即改即看.但当代码多了以后问题就来了,自带编辑器开发体验确实不太舒 ...

  6. 参与 2022 第二季度 Flutter 开发者调查

    2022 Google I/O 大会正式落下帷幕,Flutter 作为 14 个开发者产品和平台中的一款,吸引了来自全球的很多开发者们的关注.随着全国很多地方已经进入夏季,Flutter 今年第二季度 ...

  7. SQL中如何修改数据库名、表名、列名?

    文章目录 1.SQL中如何修改数据库的名字? 2.SQL中如何修改表的名字? 3.SQL中如何修改列的名字? 4.SQL中如何修改列的数据类型?(未完成,待续) 1.SQL中如何修改数据库名? 语法 ...

  8. SpringCloud 简介

    目录 什么是微服务? 初识 SpringCloud SpringCloud VS Dubbo 什么是微服务? <互联网系统架构演变> "微服务"一词源于 Martin ...

  9. 【算法】Floyd算法

    什么是Floyd Floyd用于求最短路程.举个栗子,给你一张图,让你求出点[1]到点[5]的最短路程,你会怎么求? (画图工具:CS Academy) 如上图,有向边分别是 1->2  1-& ...

  10. 基于.NetCore开发博客项目 StarBlog - (11) 实现访问统计

    系列文章 基于.NetCore开发博客项目 StarBlog - (1) 为什么需要自己写一个博客? 基于.NetCore开发博客项目 StarBlog - (2) 环境准备和创建项目 基于.NetC ...