java手写识别项目(一):初识KNN算法和GUI框架搭建

本文介绍了使用Java实现的手写识别项目,从初识KNN算法开始,详细讲解了GUI的搭建过程,包括JPanel的使用和事件监听器的实现,以及repaint函数的作用。后续将深入讨论KNN算法和项目其他部分。
摘要由CSDN通过智能技术生成

系列文章目录

迭代一:初识KNN算法和GUI框架搭建

前言

在接下来的一系列博客中,我将带领大家一起探索一个全新的手写识别项目。这个项目的核心是KNN算法,而在最初的阶段,我们暂时不涉及复杂的机器学习和神经网络,这使得它成为一个入门门槛较低、适合更多初学者的学习项目。

在第一篇博客中,我将从搭建GUI界面开始,为大家展示这个项目的基本框架。我会简要介绍KNN算法,让大家对这个算法有一个初步的了解。这篇博客旨在为后续的学习奠定基础,为大家展示整个项目的轮廓。最终的项目成果在github上也会同步。每一篇文章都是项目完成的一部分,更新过程中可能会对之前的文章进行修改


一、初识KNN算法

当涉及到KNN的实现时,Java提供了灵活的数据结构和算法库,可以使用这些库来实现KNN算法。以下是一个简单的Java代码示例,演示了如何使用KNN算法对数据集进行分类。
在示例中,假设我们有一个包含样本数据的CSV文件,每行包含特征值和标签。这里使用Weka库中的IBk类(Weka是一个用于机器学习和数据挖掘的Java库)来实现KNN。
首先,需要添加Weka库的依赖。这里假设已经添加了Weka的JAR文件到项目中。

1、KNN算法的Java实现示例

import weka.core.*;
import weka.core.converters.ConverterUtils.DataSource;
import weka.classifiers.lazy.IBk;
import java.io.File;

public class KNNExample {
    public static void main(String[] args) {
        try {
            // 加载数据集
            DataSource source = new DataSource("path/to/your/data.arff"); // 替换为你的数据集文件路径
            Instances data = source.getDataSet();
            
            // 设置类别属性(假设标签在最后一列)
            data.setClassIndex(data.numAttributes() - 1);
            
            // 构建KNN分类器
            IBk knn = new IBk();
            knn.buildClassifier(data);
            
            // 创建一个测试样本(这里假设有一组特征值)
            Instance testInstance = new DenseInstance(data.numAttributes());
            testInstance.setDataset(data);
            testInstance.setValue(0, 1.5); // 设置第一个特征值
            // 设置其他特征值...
            
            // 进行分类预测
            double predictedClass = knn.classifyInstance(testInstance);
            String predictedClassName = data.classAttribute().value((int) predictedClass);
            
            System.out.println("预测类别: " + predictedClassName);
            
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

这个例子中,需要替换path/to/your/data.csv为你的数据集文件路径,并且设置测试样本的特征值。该代码加载CSV数据集,构建KNN分类器,然后使用测试样本进行分类预测,并输出预测结果。

二、项目整体结构

根据目前对这个项目的整体认知和把握,这个项目可以分成4个java文件分别是

  1. Main.java (用于构建GUI)
  2. Listener.java (用于实现鼠标监听器和图形的绘制和调用FileOp中的方法)
  3. KNN.java (用于实现KNN的核心算法)
  4. FileOp.java (用于实现与文件操作相关的识别和训练的方法)

当深入了解一个项目时,将重点放在GUI构建和事件处理上是很合理的。Main.java和Listener.java确实承担了构建GUI和用户交互的重要角色。在这篇文章中,GUI的构建和事件处理是核心所在。在后续的文章中,剩下几个文件的内容和功能进行详细的解释。

三、GUI的搭建

1、源代码:

import javax.swing.*;
import java.awt.*;

public class Main extends JPanel {

    /**
     * Generates a function comment for the given function body in a Markdown code block
     * with the correct language syntax.
     *
     * @param args the command line arguments
     */
    public static void main(String[] args) {
        Main main = new Main();
        main.initUI();
    }

    /**
     * Initializes the user interface for the application.
     */
    public void initUI() {
        // Create a new JFrame
        JFrame frame = new JFrame();
        frame.setTitle("手写识别");
        frame.setSize(410, 460);
        frame.setResizable(false);
        frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        frame.setLocationRelativeTo(null);

        // Use a BorderLayout for the main panel
        this.setLayout(new BorderLayout());

        // Create a panel for buttons using GridLayout
        JPanel componentsPanel = new JPanel(new GridLayout(1, 4));
        JButton recognizeButton = new JButton("识别");
        JButton trainButton = new JButton("训练");
        JButton clearButton = new JButton("重置");
        recognizeButton.setPreferredSize(new Dimension(100, 30));
        trainButton.setPreferredSize(new Dimension(100, 30));
        clearButton.setPreferredSize(new Dimension(100, 30));
        componentsPanel.add(recognizeButton);
        componentsPanel.add(trainButton);
        componentsPanel.add(clearButton);
        JComboBox<String> comboBox = new JComboBox<>(new String[]{"0", "1", "2", "3", "4", "5", "6", "7", "8", "9"});
        componentsPanel.add(comboBox);

        // Create a panel for both buttonPanel in the same row using FlowLayout
        JPanel rowPanel = new JPanel(new FlowLayout());
        rowPanel.add(componentsPanel);

        // Set the preferred size and background color of the main panel
        this.setPreferredSize(new Dimension(400, 400));
        this.setBackground(Color.WHITE);

        // Add components to the main panel using BorderLayout regions
        this.add(rowPanel, BorderLayout.NORTH);

        // Add the main panel to the frame
        frame.add(this);

        // Make the frame visible
        frame.setVisible(true);

        // Create a new listener and register it with the buttons and main panel
        Listener listener = new Listener(this);
        recognizeButton.addActionListener(listener);
        trainButton.addActionListener(listener);
        clearButton.addActionListener(listener);
        comboBox.addActionListener(listener);
        this.addMouseListener(listener);
        this.addMouseMotionListener(listener);
    }
}

基于Swing库构建了一个简单的GUI界面,提供了识别和训练两个按钮,一个数字选择的下拉列表,并预留了一个黑色背景的绘图区域。通过事件监听器,可以响应用户的操作并触发相应的事件处理。

2、需要注意

在编写过程中,我也遇到了一些问题,下面分享给大家,需要注意的一些十分细节的地方。下面我们从头开始梳理

(1)关于为什么要继承自JPanel

在以前的诸多与GUI有关的项目中,我们的UI通常都是继承自JFrame,这就要说明一下JFrame与JPanel的区别。首先,我先演示一下继承自JFrame和继承自JPanel的区别是什么:
JFrame与JPanel的区别,这里简单的说一下,下面是GPT3.5的回答:

  • 当涉及到图形用户界面(GUI)编程时:
  • JFrame是用于创建应用程序窗口的主要容器,代表整个应用窗口。
  • JPanel是一个轻量级容器,用于组织和管理其他GUI组件,比如按钮、文本框等。
  • JFrame是顶层窗口,通常代表应用程序的主窗口。
  • JPanel是一个子容器,可以嵌套在JFrame或其他JPanel中,用于更灵活地组织UI布局。
  • JFrame通常是应用程序的整体窗口,而JPanel通常用于划分和组织界面的不同部分。

关于具体的区别,我会单独出一期blog来为大家介绍,以便大家在后续项目的编写
当继承自JFrame,运行后,会出现“adding a window to a container”报错,
Main继承自JFrame
这是因为其中Window代表了一个顶级窗口,而Container是可以包含其他组件的对象,不应该将Window添加到Container中,而JFrame刚好继承自WindowJPanel刚好继承自Container,所以会产生报错,我们需要的是一个用于承载和组织UI组件的画板。

(2)关于setPreferredSize()函数的运用

在这个界面中,为了设定组件区域和下面的绘图区域的大小,我在这里运用了setPreferredSize()函数,它的作用是修改组件的大小,它接受一个 Dimension 对象作为参数,该对象定义了组件的宽度和高度。它可以配合setResizable(false)函数来使用,这个函数的作用为用户将无法通过鼠标拖拽边框来改变窗口的大小。

四、实现手写

这部分处理起来相对比较简单,代码量较少,但是有很多地方容易出错。完成手绘的代码将放在Listener中,手绘的动作由mouseDragged函数完成。此外,Listener中还有对按下按钮后的函数调用的控制。由于我想将 FileOp.java 中实现与文件操作相关的识别和训练的方法,所以在编写FileOp.java时候还需要传进Listener的参数,需要增加部分关联,这个先放一放。下面是实现的代码。

1、Listener类的实现

import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;

public class Listener extends MouseAdapter implements ActionListener {
    private final Graphics2D g;
    private final Main panel;
    private final int[][] pixel = new int[40][40];

    public Listener(Main panel) {
        g = (Graphics2D) panel.getGraphics();
        this.panel = panel;
    }


    /**
     * Handles the action events triggered by the buttons.
     *
     * @param e the ActionEvent object representing the action event
     */
    @Override
    public void actionPerformed(ActionEvent e) {
        // Checks if the action command is "识别" (Recognition)
        if (e.getActionCommand().equals("识别")) {
            // Performs recognition
            FileOp.performRecognition();
            // Repaints the panel
            panel.repaint();
        }
        // Checks if the action command is "训练" (Training)
        if (e.getActionCommand().equals("训练")) {
            // Performs training
            FileOp.performTraining();
            // Repaints the panel
            panel.repaint();
        }
        // Checks if the action command is "重置" (Reset)
        if (e.getActionCommand().equals("重置")) {
            // Resets the state
            resetPixels();
            // Repaints the panel
            panel.repaint();
        }
    }

    /**
     * Resets the pixel array by setting all elements to zero.
     */
    private void resetPixels() {
        for (int i = 0; i < 40; i++) {
            for (int j = 0; j < 40; j++) {
                pixel[i][j] = 0;
            }
        }

        System.out.println("重置");
    }

    /**
     * Resets the pixels and prints a message when the mouse is pressed.
     *
     * @param e the MouseEvent object representing the mouse press event
     */
    @Override
    public void mousePressed(MouseEvent e) {
        // Reset the pixels
        resetPixels();

        // Print a message
        System.out.println("鼠标按下");
    }

    /**
     * This method is called when the mouse is dragged.
     *
     * @param e The MouseEvent object containing information about the mouse event.
     */
    @Override
    public void mouseDragged(MouseEvent e) {
        // Set the color to black
        g.setColor(Color.BLACK);

        // Get the x and y coordinates of the mouse event
        int x = e.getX();
        int y = e.getY();

        // Fill a rectangle with the color black at the specified coordinates
        g.fillRect(x, y, 10, 10);

        // Update the pixel array to indicate that the pixels within the rectangle are filled
        for (int i = x; i < x + 20; i++) {
            for (int j = y; j < y + 20; j++) {
                pixel[i / 10][j / 10] = 1;
            }
        }
    }

    /**
     * Called when the mouse button is released.
     *
     * @param e the MouseEvent object containing information about the event
     */
    @Override
    public void mouseReleased(MouseEvent e) {
        System.out.println("鼠标释放");
    }
}

2、继承MouseAdapter的用途

这里我选择了继承MouseAdapter,它的作用是我可以选择性地重写你感兴趣的方法,而不需要实现接口中的所有方法。上述代码中,我已经重写了 mousePressedmouseDraggedmouseReleased 方法,而忽略了 MouseListenerMouseMotionListener 提供的其他方法。这无疑提供了便利性。

3、探究repaint函数的作用

repaint() 方法是用于请求重新绘制组件的 Java Swing 方法。在上述代码中,repaint() 方法被用来更新用户界面的显示,特别是在执行了识别、训练或重置操作后。
在代码中的 actionPerformed() 方法中,每当点击了"识别"、"训练"或"重置"按钮时,相应的操作被执行完毕后,panel.repaint() 被调用。这会触发 panel(即用户界面的主要绘图面板)进行重新绘制,以反映对应操作所带来的变化。
因此,repaint() 的作用是告诉 Swing 库需要重新绘制组件,以确保用户界面的更新和呈现能够反映最新的状态或更改。——GPT 3.5
以上是ChatGPT回答的内容,我觉得十分清晰(偷懒)。以后我会单独出一期,分析一下repaint函数的源代码。

总结

这篇文章是java手写识别项目的第一期,主要是大框架的搭建,没有涉及比较复杂的算法,但是有不少细节需要注意。在后续的文章中,我将深入探讨KNN算法的实现和其他文件的功能,会进一步丰富这个项目。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

钻石程序的金锄

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值