使用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. c++:-4

    上一节学习了C++的数组,指针和字符串,c++:-3.本节学习C++的继承与派生: 继承 继承和派生的关系 继承与派生是同一过程从不同的角度看 保持已有类的特性而构造新类的过程称为继承 在已有类的基础 ...

  2. Git 日志提交规范

    Commit messages的基本语法 当前业界应用的比较广泛的是 Angular Git Commit Guidelines 具体格式为: <type>: <subject> ...

  3. Tomcat配置安装

    1.tomcat是什么? 是由JAVA开发的开源且免费,主要是用于web服务器,是中间件.主要用于中小型企业 2.tomcat安装 安装jdk,可以rpm或者官网安装,安装完配置java环境变量,打开 ...

  4. Cubieboard安装系统

    2013年买的一个小玩意. 一.硬件 1.1 相关资料 http://www.cubieforums.com http://cubie.cc 1.2 cubieboard1 1.3 无线网卡 水星 M ...

  5. K8S 使用Kubeadm搭建单个Master节点的Kubernetes(K8S)~本文仅用于测试学习

    01.集群规划 系统版本:CentOS Linux release 7.6.1810 (Core) 软件版本:kubeadm.kubernetes-1.15.docker-ce-18.09 硬件要求: ...

  6. 用python解决打标签时将xml文件的标签名打错

    用python解决打标签时将xml文件的标签名打错 问题描述:再进行达标签时将magnetic_tile的标签名错误的打成了magnetic_title,又不想一张一张的修改 出现问题的xml文件 & ...

  7. 一款开源的跨平台实时web应用框架——DotNetify

    今天给大家介绍一个开源的轻量级跨平台实时HTML+C#.NET Web应用程序开发框架--DotNetify,允许你在C#.NET后端上创建具有React.React Native.Vue或Blazo ...

  8. 以字节跳动内部 Data Catalog 架构升级为例聊业务系统的性能优化

    背景 字节跳动 Data Catalog 产品早期,是基于 LinkedIn Wherehows 进行二次改造,产品早期只支持 Hive 一种数据源.后续为了支持业务发展,做了很多修修补补的工作,系统 ...

  9. GDKOI 2021 Day2 TG 总结

    又是爆炸的一天,炸多了本蒟蒻已经习以为常 但今天比昨天整整高了 40 分!!!!却还是没有 100 今天本蒟蒻本想模仿奆佬的打字速度,结果思路混乱让我无法开始 T1 不是吧怎么是期望 dp ,期望值怎 ...

  10. [自制操作系统] 第04回 完善MBR

    目录 一.前景回顾 二.改写MBR 三.实现loader 一.前景回顾 在之前我们说到,MBR的作用便是加载操作系统内核到指定位置.而MBR需要通过读取硬盘来获得操作系统内核.在上一回我们已经讲解了硬 ...