使用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. Android添加布局和按键

    Android添加布局和按键 Android布局方式分为 1.LinearLayout (线性布局) 2.ConstraintLayout (约束布局) 3.FrameLayout (帧布局) 4.T ...

  2. 项目完成小结 - Django3.x版本 - 开发部署小结 (2)

    前言 好久没更新博客了,最近依然是在做之前博客说的这个项目:项目完成 - 基于Django3.x版本 - 开发部署小结 这项目因为前期工作出了问题,需求没确定好,导致了现在要做很多麻烦的工作,搞得大家 ...

  3. 题解0012:剪花布条(KMP)

    信奥一本通1465 KPM例题 题目链接:http://ybt.ssoier.cn:8088/problem_show.php?pid=1465 题目描述:给出花布条和小饰条(字符串),求花布条中能剪 ...

  4. 虚拟环境与django版本与视图层相关知识

    目录 虚拟环境 django版本区别 视图函数返回值 JsonResponse对象 form表单上传文件 request方法 FBV与CBV CBV源码剖析 模板语法传值 传值方式 传值范围 虚拟环境 ...

  5. 每天一个 HTTP 状态码 206

    206 Partial Content 206 Partial Content 是当客户端请求时使用了 Range 头部,服务器端回复的响应,表示只响应一部分内容. 实例 请求: GET /favor ...

  6. map计算

    map理解 参考1: https://github.com/rafaelpadilla/Object-Detection-Metrics 参考2:https://github.com/rafaelpa ...

  7. js循环调用axios异步请求,实现同步

    准备: const axios = require('axios'); // axios请求 const res = []; const arr = ["a", "b&q ...

  8. Kafka消息的压缩机制

    最近在做 AWS cost saving 的事情,对于 Kafka 消息集群,计划通过压缩消息来减少消息存储所占空间,从而达到减少 cost 的目的.本文将结合源码从 Kafka 支持的消息压缩类型. ...

  9. React history.push()无法跳转 url改变页面不渲染

    一.history.push()无法跳转参考了很多文章 研究一下生命周期 render是要有state变化才会执行 BrowserHistory只有props变化 无法触发render 如下改造环境 ...

  10. 【进阶】Spring中的注解与反射

    [进阶]Spring中的注解与反射 目录 [进阶]Spring中的注解与反射 前言 一.内置(常用)注解 1.1@Overrode 1.2@RequestMapping 1.3@RequestBody ...