CSDN初体验,尝试完成一个自动扫雷程序

零、简介

本文主要为本人初次接触CSDN,尝试着自己去创作一些东西,虽然可能看来很简单,但这也是我学习的过程。
本次我想实现的主要是实现自动化win7扫雷的过程,我玩win7版本的扫雷也已经有上千局了,在游玩的过程中,我发现我的大部分操作和逻辑判断过程都是重复的,所以我产生了让电脑自己玩扫雷的想法,也是在此过程中来精进我的代码水平,向大神学习。

一、逻辑框架

1.1 与windows交互 包括获取截图 鼠标点击

1.2 扫雷内部算法实现

二、代码实现

2.1 windows下获取屏幕截图

在这里,我在网上首先搜索了C++的截图代码,太过繁长,并且经过我的测试,由于我的屏幕开启了文字大小缩放,所以其截图大小是有问题的,原先是1920*1080的屏幕,我的文字缩放是125%,也就是说其在系统中生成的原始图像是1536*864,所以C++代码截取了屏幕左上角开始1536*864的内容,不完整,所以弃用C++,改用Java,我发现Java的代码相当的简单,看来Java对这些接口做了相当好的封装,以下是实现屏幕获取的Java代码。

import java.awt.Rectangle;
import java.awt.Robot;
import java.awt.Toolkit;
import java.awt.image.BufferedImage;
import java.io.File;
import javax.imageio.ImageIO;

public class GetScreen {
   

    public void getScreenShot() throws Exception
    {
   
        Robot robot = new Robot();
        BufferedImage screenShot = robot.createScreenCapture(new Rectangle(Toolkit.getDefaultToolkit().getScreenSize()));
        ImageIO.write(screenShot, "JPG", new File("screen_shot.jpg"));
    }

    public static void main(String[] args) throws Exception
    {
   
        GetScreen getScreen = new GetScreen();
        getScreen.getScreenShot();
    }
}

获取屏幕截图的主要实现原理是用java.awt包下的Robot类的createScreenCapture方法。
awt(Abstract Window Toolkit)抽象视窗工具组,是Java的平台独立的视窗系统, 图形和使用者界面器件工具包。AWT是Java基础类(JFC-Java Foundation Classes)的一部分,为Java程序提供图形使用者界面(GUI)的标准API。
Robot类用于生成本机系统输入事件,主要目的是促进Java平台的自动化测试。
createScreenCapture的参数为createScreenCapture(Rectangle screenRect),表明截取screenRect大小的屏幕,在这里我们用Toolkit.getDefaultToolkit().getScreenSize()来返回屏幕的大小。
BufferedImage是一个承载图像数据的数据流
ImageIO. write方法用来写入图片,其有三个参数,第一个是图像数据的数据流,第二个是文件格式,第三个是文件名。

2.2 对图像进行预处理

2.2.1 图像切分

图像切分的基本思路就是先把上一步得到的屏幕截图切分出雷区,再根据雷区的行数和列数来单独切分出每一小块,基本实现如下:

public static int[][] splitCells(int startX, int startY, int width, int height, int hori, int verti) throws IOException {
   
    BufferedImage image = ImageIO.read(new File("screen_shot.jpg"));
    BufferedImage croppedImage = image.getSubimage(startX, startY, width, height);
    int[][] ans = new int[hori][verti];
    for (int i = 0; i < hori; i++) {
   
        for (int j = 0; j < verti; j++) {
   
            int x0 = (int) Math.round(i * (width / (double) hori));
            int x1 = (int) Math.round((i + 1) * (width / (double) hori));
            int y0 = (int) Math.round(j * (height / (double) verti));
            int y1 = (int) Math.round((j + 1) * (height / (double) verti));
            BufferedImage cellImage = croppedImage.getSubimage(x0 + 5, y0 + 5, x1 - x0 - 10, y1 - y0 - 10);
            ans[i][j] = recognize(cellImage);
            //ImageIO.write(cellImage,"JPG","img_set\\crop_test_"+Integer.toString(i*16+j)+".jpg");
        }
    }
    return ans;
}

如图所示 这就是切分出的每一个小块

splitCells函数接收六个参数,前两个分别是雷区左上角的坐标,中间两个是雷区的宽度和高度,后两个是雷区每一行的雷数和每一列的雷数,函数返回一个二维数组,表明这个雷区的状态,或者说每一个格子的数字,这就要看下一个小节,讲上面的代码中recognize函数的实现了。
在这里主要需要知道的就是BufferedImage的getSubimage方法了,该方法接收四个参数,分别是起始点的横纵坐标和图片的宽和高,根据这些参数返回原图像的一个子图像。

2.2.2 图像小块识别

下面就要讲这个程序比较难的一部分,也就是我们怎么把一个小块的图片转变成一个电脑可以识别的状态,众所周知,扫雷中的格子有这些状态,未点开的格子,点开的格子,其中有一个1~8的数字或者没有数字(这里因为是电脑玩游戏的原因,我们不需要标出雷),当然还有可能点开是雷,但这就不在我们考虑范围之内了(毕竟这就意味这游戏失败了)
在这里插入图片描述

一开始我会以为这很容易,毕竟我们面对的都是固定的图像,不需要像识别手写数字那样使用神经网络之类的方法,但我在写算法的时候发现了问题,首先,每个格子的颜色不尽相同,这里可以看到win7扫雷在实现的时候为图像添加了光效,这就使得格子的亮度从左上角向右下角递减,我本来的想法是对格子的每个状态截几张图求其平均值生成一张模板图然后进行对比,现在看来可能不大行,首先是有一个亮度的区别,然后因为这个图片比较小,所以如果有一两个像素的位移会对结果有较大的影响。
在这里插入图片描述
我的想法是首先把数字和非数字的区别出来,我们可以观察到,数字图像和非数字图像的一个最明显的区别是非数字图像的整体颜色较为平均,而数字图像因为有色块,颜色有较大差异,所以我考虑用方差的思路,这里不是把颜色求平均再求方差,而是求这个像素与上一个像素之间的差异,因为这样可以使得差异更明显,而且我们不用选取所有的点,这样可以减小计算量,代码如下:

public static int imageVarience(BufferedImage image){
   
    int height=image.getHeight();
    int width=image.getWidth();
    int varience=0;
    int color = image.getRGB(2, 2);
    int preB = color & 0xff;
    int preG = (color & 0xff00) >> 8;
    int preR = (color & 0xff0000) >> 16;
    for(int i=2;i<width-2;i++){
   
        for (int j=2;j<height-2;j++){
   
            color=image.getRGB(i,j);
            int curB = color & 0xff;
            int curG = (color & 0xff00) >> 8;
            int curR = (color & 0xff0000) >> 16;
            varience+=(curB-preB)*(curB-preB)+(curG-preG)*(curG-preG)+(curR-preR)*(curR-preR);
            j+=2;
        }
        i+=2;
    }
    return varience;
}

在这里的代码中涉及到了BufferedImage的数据存储模式,我们可以将一个BufferedImage image数据其看作为是一个unsigned int image[width][height]数据,也就是无符号二维数组,每一个像素点BufferedImage用32bits来存储,分别为AAAAAAAA RRRRRRRR GGGGGGGG BBBBBBBB,其中A为Alpha,为不透明度,RGB为红绿蓝,BufferedImage的getRGB方法会返回这32bits中的低24位,然后稍加位运算即可获得RGB值。
这些图片的方差值如下:

我们可以看到,非数字图片和数字图片的值还是有很大差别的,所以我选取了600000作为了两者的分界线。
再然后就是区分未点开的格子和点开的空白格子了,观察不难发现,它们很明显的区别就是一个呈现明显的蓝色 而另一个呈现白色,也就是说一个的Blue值会明显偏高,而另一个会比较相同,另外经测试发现,再两者中Red的值都是三原色中最低的,所以我们将Blue-Red值作为区分其是白还是蓝的标准。经测试图片数据如下,每一行都是一张图片的数据,取其平均值。
在这里插入图片描述
左边是蓝色的,右边是白色的,可以看到区别还是不小,我们取60作为其的临界值。
计算其图片色差的代码如下:

 public static int calColorDiff(BufferedImage image){
   
    int height = image.getHeight();
    int width = image.getWidth();
    int diff = 0;
    int cnt=0;
    for (int i = 2; i < width - 2; i++) {
   
        for (int j = 2; j < height - 2; j++) {
   
            int color = image.getRGB(i, j);
            int Blue = color & 0xff;
            int Red = (color & 0xff0000) >> 16;
            diff+=Blue-Red;
            cnt++;
            j += 2;
        }
        i += 2;
    }
    return diff/cnt;
}

接下来就是处理数字的部分了,我的想法和上面处理判断图像是否均匀的思路一致,因为每个数字都有自己的形状,我们可以对每一个数字设定一个其自己的点集,对于这个点集,只有正确的数字才能匹配上,判断是否匹配上的方法还是使用方差,如果匹配上,因为这个数字在它的点集上的颜色基本一致,所以其方差会很小,而别的数字方差会很大,经测试,效果还不错,唯一的缺陷就是我们需要自己去慢慢弄点集。在这里我也学习了java里ArrayList的使用,其使用方法类似于C++STL库中的vector,一样需要在尖括号中输入这个List里元素的类别,ArrayList里add方法类似于vector的push_back方法,其他方法暂时没有用到。

2.2.3 代码汇总

下面是2.2部分的代码汇总:

import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;

public class ImageProcess {
   
    private static final ArrayList<ArrayList<Point>> AAP =initAAP();

    public static int[][] splitCells(int startX, int startY, int width, int height, int hori, int verti) throws IOException {
   
        BufferedImage image = ImageIO.read(new File("screen_shot.jpg"));
        BufferedImage croppedImage = image.getSubimage(startX, startY, width, height);
        int[][] ans = new int[verti][hori];
        for (int i = 0; i < verti; i++) {
   
            for (int j = 0; j < hori; j++) {
   
                int x0 = (int) Math.round(j * (width / (double) hori));
                int x1 = (int) Math.round((j + 1) * (width / (double) hori));
                int y0 = (int) Math.round(i * (height / (double) verti));
                int y1 = (int) Math.round((i + 1) * (height / (double) verti));
                BufferedImage cellImage = croppedImage.getSubimage(x0 + 5, y0 + 5, x1 - x0 - 10, y1 - y0 - 10);
                System.out.print(recognize(cellImage)+" ");
                ans[i][j] = recognize(cellImage);
            }
            System.out.println();
        }
        return ans;
    }

    public static int imageVarience(BufferedImage image) {
   
        int height = image.getHeight();
        int width = image.getWidth();
        int varience = 0;
        int color = image.getRGB(2, 2);
        int preB = color & 0xff;
        int preG = (color & 0xff00) >> 8;
        int preR = (color & 0xff0000) >> 16;
        for (int i = 2; i < width - 2; i++) {
   
            for (int j = 2; j < height - 2; j++) {
   
                color = image.getRGB(i, j);
                int curB = color & 0xff;
                int curG = (color & 0xff00) >> 8;
                int curR = (color & 0xff0000) >> 16;
                varience += (curB - preB) * (curB - preB) + (curG - preG) * (curG - preG) + (curR - preR) * (curR - preR);
                j += 2;
            }
            i += 2;
        }
        return varience;
    }

    public static int calColorDiff(BufferedImage image) {
   
        int height = image.getHeight();
        int width = image.getWidth();
        int diff = 0;
        int cnt = 0;
        for (int i = 2; i < width - 2; i++) {
   
            for (int j = 2; j < height - 2; j++) {
   
                int color = image.getRGB(i, j);
                int Blue = color & 0xff;
                int Red = (c
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
为了实现这个功能,您需要使用Delphi编写一个程序,该程序可以自动模拟用户在CSDN网站上的操作来完成签到。 以下是一些基本的步骤: 1. 打开CSDN网站并登录您的账户。 2. 寻找签到按钮并单击它。 3. 等待签到过程完成,并确保签到成功。 为了实现上述步骤,您需要使用Delphi编写一个程序,该程序可以自动模拟用户在CSDN网站上的操作。以下是一些示例代码,可以帮助您开始: 首先,您需要使用TWebBrowser组件来加载CSDN网站并模拟用户登录: ```delphi procedure TForm1.Button1Click(Sender: TObject); begin WebBrowser1.Navigate('https://passport.csdn.net/login'); end; procedure TForm1.WebBrowser1DocumentComplete(ASender: TObject; const pDisp: IDispatch; const URL: OleVariant); var Doc: IHTMLDocument2; Login: IHTMLElement; Username: IHTMLElement; Password: IHTMLElement; begin Doc := WebBrowser1.Document as IHTMLDocument2; // 如果登录页面已加载完毕 if (Pos('login', URL) > 0) and (not FLoggedIn) then begin // 填写用户名和密码 Username := Doc.all.item('username', 0) as IHTMLElement; Password := Doc.all.item('password', 0) as IHTMLElement; Username.value := 'your_username'; Password.value := 'your_password'; // 单击登录按钮 Login := Doc.all.item('loginBtn', 0) as IHTMLElement; Login.click; end; end; ``` 在用户登录后,您需要加载签到页面并模拟单击签到按钮: ```delphi procedure TForm1.Button2Click(Sender: TObject); begin WebBrowser1.Navigate('https://me.csdn.net/robot/signin'); end; procedure TForm1.WebBrowser1DocumentComplete(ASender: TObject; const pDisp: IDispatch; const URL: OleVariant); var Doc: IHTMLDocument2; SignIn: IHTMLElement; begin Doc := WebBrowser1.Document as IHTMLDocument2; // 如果签到页面已加载完毕 if (Pos('robot/signin', URL) > 0) then begin // 单击签到按钮 SignIn := Doc.all.item('btn-signIn', 0) as IHTMLElement; SignIn.click; end; end; ``` 最后,您需要等待签到过程完成并检查签到是否成功: ```delphi procedure TForm1.WebBrowser1DocumentComplete(ASender: TObject; const pDisp: IDispatch; const URL: OleVariant); var Doc: IHTMLDocument2; Success: Boolean; begin Doc := WebBrowser1.Document as IHTMLDocument2; // 如果签到页面已加载完毕 if (Pos('robot/signin', URL) > 0) then begin // 单击签到按钮 SignIn := Doc.all.item('btn-signIn', 0) as IHTMLElement; SignIn.click; // 等待签到过程完成 // 这里可以使用Sleep函数来等待一段时间 Sleep(5000); // 检查签到是否成功 Success := (Pos('已签到', Doc.body.innerText) > 0); if Success then ShowMessage('签到成功!') else ShowMessage('签到失败!'); end; end; ``` 请注意,以上代码只是示例代码,并且可能需要根据您的具体情况进行修改。此外,您可能需要使用其他组件和技术来实现自动签到功能。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值