效果演示
先看一下界面,界面做的有点简陋
大致记录一下操作的过程
1.点击训练
2.选择一个需要训练的数字
3.在白色面板上写下该训练的数字
4.写完后点击确定
5.双击白色面板,清除画的数字痕迹,重复②和3的步骤,直到训练完所有的数字6.在白色面板上写下需要识别的数字
7.点击识别,则会有一个小框弹出,显示识别出的数字,点击小框的确定按钮,双击白色面板,清除画过的痕迹
8.再一次在白色面板上写下需要识别的数字,再次点击识别,可以重复7和8步骤
注意:一次仅能识别一位数字,每画完一次数字需要再次点击识别
识别过程演示如下:
写下数字0
再点击识别按钮
写下数字4
再点击识别按钮
实现过程
一. 画出界面,加监听器
注意:
- 在这里添加的JRadioButton单选按钮组件训练和识别,两个按钮不可以同时选择,所以将这两个组件加入了ButtonGroup中去,这样同一时刻仅仅可以选择其中一个;
- 这里其实可以不用单独为每个组件添加监听器,但是为了更方便区分各自的功能,我给每个组件单独添加了监听器;
this.setTitle("数字手写识别");
this.setSize(400, 400);
this.setDefaultCloseOperation(3);
this.setResizable(false);
this.setLocationRelativeTo(null);
//设置为流式布局
this.setLayout(new FlowLayout());
//添加单选按钮组件
JRadioButton button_recognition = new JRadioButton("识别");
JRadioButton button_exercise = new JRadioButton("训练");
button_recognition.setActionCommand("识别");
button_exercise.setActionCommand("训练");
//加入到ButtonGroup中的JRadioButton只有一个可以被选中,每个JRadioButton独享一个监听器,
//当其被选中时执行监听器中的处理方法
ButtonGroup group = new ButtonGroup();
group.add(button_recognition);
group.add(button_exercise);
//在这里JRadioButton被添加了两次,一次加到了ButtonGroup中,另一次是加到了JPanel中,
//但是ButtonGroup不需要被添加
this.add(button_recognition);
this.add(button_exercise);
String[] label = {"0","1","2","3","4","5","6","7","8","9"};
jcb = new JComboBox(label);
this.add(jcb);
//添加写数字的面板
JPanel jp = new JPanel();
//jp.setLayout(new FlowLayout(FlowLayout.CENTER,0,0));
//设置添加的写数字的面板的位置和大小
//JPanel需要使用的方法为setPreferredSize()
jp.setPreferredSize(new Dimension(300,300));
jp.setBackground(Color.white);
this.add(jp);//BorderLayout.CENTER
JButton button_sure = new JButton("确定");
button_sure.setActionCommand("确定");
this.add(button_sure);
this.setVisible(true);
g = jp.getGraphics();
gD = (Graphics2D)g;
//添加监听器
Listener l = new Listener(g,jcb);
//给面板添加鼠标监听器,用于画数字的记录
JpanelListener jpListener = new JpanelListener();
jp.addMouseListener(jpListener);
jp.addMouseMotionListener(jpListener);
//识别按钮加上监听器
JButton_recognitionListener JButton_recognition = new JButton_recognitionListener();
button_recognition.addActionListener(JButton_recognition);
//训练按钮加上监听器
JButton_exerciseListener JButton_exercise = new JButton_exerciseListener();
button_exercise.addActionListener(JButton_exercise);
//给确定按钮加上监听器
JButton_sureListener JButton_sure = new JButton_sureListener();
button_sure.addActionListener(JButton_sure);
//给数字滑条加上监听器
JComboBoxListener Jcombobox = new JComboBoxListener();
jcb.addActionListener(Jcombobox);
}
二,生成数字数组number_list
//面板将写的数字生成二维数组 并且 从中判断数字
private class JpanelListener extends MouseAdapter {
public void mouseClicked(MouseEvent e) {
//双击画布,则清除画布为白色,且将数字数组清为全0
if(e.getClickCount()==2) {
gD.setColor(Color.white);
gD.fillRect(0, 0,299, 299);
for(int i = 0;i<30;i++) {
for(int j = 0;j<30;j++) {
number_list[i][j] = 0;
}
}
}
}
public void mousePressed(MouseEvent e) {
x1 = e.getX();
y1 = e.getY();
}
public void mouseDragged(MouseEvent e) {
x2 = e.getX();
y2 = e.getY();
gD.setStroke(new BasicStroke(12));
gD.setColor(Color.black);
gD.drawLine(x1, y1, x2, y2);
//1.将写入的数字转化为二维数组
for(int i = 0;i<number_list.length;i++) {
for(int j = 0;j<number_list.length;j++) {
if(x2<i*size && x2>(i-1)*size) {
if(y2<j*size && y2>(j-1)*size) {
//记录鼠标走过的区域
number_list[i][j] =1;
}
}
}
}
x1 = x2;
y1 = y2;
}
}
三,将生成的数字数组写入对应的txt文件中去
注意:
- 这里文件名必须保证不重复,故文件命名时加入了时间System.currentTimeMillis()
//确定按钮,之后将数字生成的二维数组写入文件中去
private class JButton_sureListener implements ActionListener{
@Override
public void actionPerformed(ActionEvent e) {
if(e.getActionCommand().equals("确定")) {
//如果用户写好数字之后,点击确定,打印出数组并写入文件
// for(int i = 0;i<number_list.length;i++) {
// for(int j = 0;j<number_list.length;j++) {
// System.out.print(number_list[j][i]+" ");
// }
// System.out.println("");
// }
//给文件起名,为了不重复,在其中添加时间进行命名
File file = new File("D:\\Java需要\\handing\\"+num+"--"+System.currentTimeMillis()+".txt");
try {
FileWriter out = new FileWriter(file);
System.out.println("----------------点击训练按钮了");
for(int i = 0;i<30;i++) {
for(int j = 0;j<30;j++) {
//public void write(int c:,写入单个字符,参数:c - 指定要写入字符的 int。
out.write(number_list[i][j]);
}
}
out.flush();
out.close();
} catch (IOException e1) {
e1.printStackTrace();
}
}
}
}
四,识别过程第一步,将上一步txt文件中的内容取出存入数组number_list_read中去
程序片段:
//识别下,应该先读取训练完成后数字文件中的内容
File file = new File("D:\\Java需要\\handing\\");
//得到目录下所有文件名
//list():返回一个字符串数组,这些字符串指定此抽象路径名表示的目录中的文件和目录。
String[] filelist = file.list();
for(int i=0;i<filelist.length;i++) {
try {
FileReader in = new FileReader("D:\\Java需要\\handing\\"+filelist[i]);
try {
//选用了public int read():读取单个字符,返回:读取的字符,如果已到达流的末尾,则返回 -1
int b = in.read();
int m = 0;
int n = 0;
while(b!=-1) {//如果读取的文件还没有到达文件尾
//取出来的数字存入number_list_read数组中去
number_list_read[m][n] = b;
n++;//按行读
if(n == 30) {
n = 0;
m++;//按列读
}
//读取下一个字符
b = in.read();
}
//读取文件结束
//关闭文件
in.close();
五,将读取的每个数组txt文件中的内容,与当前识别状态下面板上画出的数字数组对比,算出欧氏距离存入数组distance中,并记录下对应文件代表的数字
注意
- 在第五步之前,已经通过训练写下了0~9十个数字并且生成了对应的若干个txt文件,当前在面板上写下了需要识别的数字并且已经生成了对应的数字数组number_list
- 点击识别按钮,就会进行第五步需要的操作,也就是将目录中的若干个txt文件中的内容和当前的number_list进行比较,算出相应的若干个欧式距离,存入数组distance中去,并且记录下目录中的若干个txt文件所代表的数字0~9,存入数组number_recognize中去
//获得数字文件名字的首字符,Integer.parseInt方法用于将String类型转换为int类型
int number = Integer.parseInt(filelist[i].substring(0, 1));
//计算距离
int d = 0;
//遍历所有点,算出所有欧式距离之和
for(int p = 0;p<30;p++) {
for(int q = 0;q<30;q++) {
d += Math.abs(number_list_read[p][q] - number_list[p][q]);
}
}
//将训练后的每个数字文件中的内容与识别写入的数字数组计算欧式距离
distance.add(d);
//存入数字文件中代表的数字
number_recognize.add(number);
六,将算出的欧式距离从小到大排序,选取最短的距离和作为识别的结果
//接着将所有求出的欧式距离进行从小到大的排序
for(int i = 0;i<distance.size()-1;i++) {
for(int j = 0;j<distance.size()-1-i;j++) {
if(distance.get(j)>distance.get(j+1)) {
//交换j和j+1两个位置的值
int temp = distance.get(j);
distance.set(j, distance.get(j+1));
distance.set(j+1,temp);
//还有代表的数字number_recognize也需要发生变化
int tempnum = number_recognize.get(j);
number_recognize.set(j, number_recognize.get(j+1));
number_recognize.set(j+1,tempnum);
}
}
}
//排好序之后,选取欧式距离最短的
int result = number_recognize.get(0);
JOptionPane.showMessageDialog(null, "识别出的数字是:"+result);
源代码
package Handing;
import java.awt.BasicStroke;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import javax.swing.ButtonGroup;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JRadioButton;
public class MainFrame extends JFrame{
public static void main(String[] args) {
MainFrame mf = new MainFrame();
mf.initUI();
}
Graphics g;
Graphics2D gD;
private static volatile int num;
private int x1;
private int y1;
private int x2;
private int y2;
private static int size = 10;
private static volatile int number_list[][] = new int[30][30];
private static volatile int number_list_read[][] = new int[30][30];
//存放计算的欧式距离
private ArrayList<Integer> distance = new ArrayList<Integer>();
private ArrayList<Integer> number_recognize = new ArrayList<Integer>();
JComboBox jcb;
private void initUI() {
this.setTitle("数字手写识别");
this.setSize(400, 400);
this.setDefaultCloseOperation(3);
this.setResizable(false);
this.setLocationRelativeTo(null);
//设置为流式布局
this.setLayout(new FlowLayout());
//添加单选按钮组件
JRadioButton button_recognition = new JRadioButton("识别");
JRadioButton button_exercise = new JRadioButton("训练");
button_recognition.setActionCommand("识别");
button_exercise.setActionCommand("训练");
//加入到ButtonGroup中的JRadioButton只有一个可以被选中,每个JRadioButton独享一个监听器,
//当其被选中时执行监听器中的处理方法
ButtonGroup group = new ButtonGroup();
group.add(button_recognition);
group.add(button_exercise);
//在这里JRadioButton被添加了两次,一次加到了ButtonGroup中,另一次是加到了JPanel中,
//但是ButtonGroup不需要被添加
this.add(button_recognition);
this.add(button_exercise);
String[] label = {"0","1","2","3","4","5","6","7","8","9"};
jcb = new JComboBox(label);
this.add(jcb);
//添加写数字的面板
JPanel jp = new JPanel();
//设置添加的写数字的面板的位置和大小
//JPanel需要使用的方法为setPreferredSize()
jp.setPreferredSize(new Dimension(300,300));
jp.setBackground(Color.white);
this.add(jp);//BorderLayout.CENTER
JButton button_sure = new JButton("确定");
button_sure.setActionCommand("确定");
this.add(button_sure);
this.setVisible(true);
g = jp.getGraphics();
gD = (Graphics2D)g;
//添加监听器
Listener l = new Listener(g,jcb);
//给面板添加鼠标监听器,用于画数字的记录
JpanelListener jpListener = new JpanelListener();
jp.addMouseListener(jpListener);
jp.addMouseMotionListener(jpListener);
//识别按钮加上监听器
JButton_recognitionListener JButton_recognition = new JButton_recognitionListener();
button_recognition.addActionListener(JButton_recognition);
//训练按钮加上监听器
JButton_exerciseListener JButton_exercise = new JButton_exerciseListener();
button_exercise.addActionListener(JButton_exercise);
//给确定按钮加上监听器
JButton_sureListener JButton_sure = new JButton_sureListener();
button_sure.addActionListener(JButton_sure);
//给数字滑条加上监听器
JComboBoxListener Jcombobox = new JComboBoxListener();
jcb.addActionListener(Jcombobox);
}
//--------------------------------------------------------------------
//给数字滑条加上监听器,便于监听用户点击训练的的是哪个数字
private class JComboBoxListener implements ActionListener{
@Override
public void actionPerformed(ActionEvent e) {
num = jcb.getSelectedIndex();
}
}
//--------------------------------------------------------------
//确定按钮监听,点击后将用户所画的数字生成的二维数组写入文件中
private class JButton_sureListener implements ActionListener{
@Override
public void actionPerformed(ActionEvent e) {
if(e.getActionCommand().equals("确定")) {
//如果用户写好数字之后,点击确定,可以打印出数组查看后写入文件
// for(int i = 0;i<number_list.length;i++) {
// for(int j = 0;j<number_list.length;j++) {
// System.out.print(number_list[j][i]+" ");
// }
// System.out.println("");
// }
//给文件起名,为了不重复,在其中添加时间进行命名
File file = new File("D:\\Java需要\\handing\\"+num+"--"+System.currentTimeMillis()+".txt");
try {
FileWriter out = new FileWriter(file);
for(int i = 0;i<30;i++) {
for(int j = 0;j<30;j++) {
//public void write(int c:,写入单个字符,参数:c - 指定要写入字符的 int
out.write(number_list[i][j]);
}
}
out.flush();
out.close();
} catch (IOException e1) {
e1.printStackTrace();
}
}
}
}
//---------------------------------------------------------------
//训练下应该进行的操作,实际上没有写具体用处,可去掉该监听
private class JButton_exerciseListener implements ActionListener{
@Override
public void actionPerformed(ActionEvent e) {
//训练下应该监听训练的数字
}
}
//------------------------------------------------------------------
//识别按钮下应该进行的操作,操作是先在面板上写下数字,生成了一个数组文件,然后将该数组文件与训练中的数字文件求欧式距离,选取最小者
private class JButton_recognitionListener implements ActionListener{
@Override
public void actionPerformed(ActionEvent e) {
distance.clear();
number_recognize.clear();
//识别下,应该先读取训练完成后数字文件中的内容
File file = new File("D:\\Java需要\\handing\\");
//得到目录下所有文件名
//list():返回一个字符串数组,这些字符串指定此抽象路径名表示的目录中的文件和目录。
String[] filelist = file.list();
for(int i=0;i<filelist.length;i++) {
try {
FileReader in = new FileReader("D:\\Java需要\\handing\\"+filelist[i]);
try {
//选用了public int read():读取单个字符,返回:读取的字符,如果已到达流的末尾,则返回 -1
int b = in.read();
int m = 0;
int n = 0;
while(b!=-1) {//如果读取的文件还没有到达文件尾
//取出来的数字存入number_list_read数组中去
number_list_read[m][n] = b;
n++;//按行读
if(n == 30) {
n = 0;
m++;//按列读
}
//读取下一个字符
b = in.read();
}
//读取文件结束
//关闭文件
in.close();
//获得数字文件名字的首字符,Integer.parseInt方法用于将String类型转换为int类型
int number = Integer.parseInt(filelist[i].substring(0, 1));
//计算距离
int d = 0;
//遍历所有点,算出所有欧式距离之和
for(int p = 0;p<30;p++) {
for(int q = 0;q<30;q++) {
d += Math.abs(number_list_read[p][q] - number_list[p][q]);
}
}
//将训练后的每个数字文件中的内容与识别写入的数字数组计算欧式距离
distance.add(d);
//存入数字文件中代表的数字
number_recognize.add(number);
} catch (IOException e1) {
e1.printStackTrace();
}
} catch (FileNotFoundException e1) {
e1.printStackTrace();
}
}
//接着将所有求出的欧式距离进行从小到大排序
for(int i = 0;i<distance.size()-1;i++) {
for(int j = 0;j<distance.size()-1-i;j++) {
if(distance.get(j)>distance.get(j+1)) {
//交换j和j+1两个位置的值
int temp = distance.get(j);
distance.set(j, distance.get(j+1));
distance.set(j+1,temp);
//还有代表的数字number_recognize也需要发生变化
int tempnum = number_recognize.get(j);
number_recognize.set(j, number_recognize.get(j+1));
number_recognize.set(j+1,tempnum);
}
}
}
//排好序之后,选取欧式距离最短的作为识别结果
int result = number_recognize.get(0);
JOptionPane.showMessageDialog(null, "识别出的数字是:"+result);
}
}
//-----------------------------------------------------------------
//面板将写的数字生成二维数组 并且 从中判断数字
private class JpanelListener extends MouseAdapter {
public void mouseClicked(MouseEvent e) {
//双击画布,则清除画布为白色,且将数组清为全0
if(e.getClickCount()==2) {
gD.setColor(Color.white);
gD.fillRect(0, 0,299, 299);
for(int i = 0;i<30;i++) {
for(int j = 0;j<30;j++) {
number_list[i][j] = 0;
}
}
}
}
public void mousePressed(MouseEvent e) {
x1 = e.getX();
y1 = e.getY();
}
public void mouseDragged(MouseEvent e) {
x2 = e.getX();
y2 = e.getY();
gD.setStroke(new BasicStroke(12));
gD.setColor(Color.black);
gD.drawLine(x1, y1, x2, y2);
//将写入的数字转化为二维数组
for(int i = 0;i<number_list.length;i++) {
for(int j = 0;j<number_list.length;j++) {
if(x2<i*size && x2>(i-1)*size) {
if(y2<j*size && y2>(j-1)*size) {
//记录鼠标走过的区域
number_list[i][j] =1;
}
}
}
}
x1 = x2;
y1 = y2;
}
public void mouseReleased(MouseEvent e) {}
}
}