手写识别KNN(java)

起初以为手写识别仅与机器学习有关,做了这个才知道有KNN算法。

KNN:

概念摘自百度

邻近算法,或者说K最近邻(kNN,k-NearestNeighbor)分类算法是数据挖掘分类技术中最简单的方法之一。所谓K最近邻,就是k个最近的邻居的意思,说的是每个样本都可以用它最接近的k个邻居来代表。

kNN算法的核心思想是如果一个样本在特征空间中的k个最相邻的样本中的大多数属于某一个类别,则该样本也属于这个类别,并具有这个类别上样本的特性。该方法在确定分类决策上只依据最邻近的一个或者几个样本的类别来决定待分样本所属的类别。 kNN方法在类别决策时,只与极少量的相邻样本有关。由于kNN方法主要靠周围有限的邻近的样本,而不是靠判别类域的方法来确定所属类别的,因此对于类域的交叉或重叠较多的待分样本集来说,kNN方法较其他方法更为适合。

 

为了判断距离最近,引入欧氏距离余弦距离。(可以自己再查查)

欧氏距离:

将两点之间距离公式拓展到N维,即抽象出来两物体距离为两点间距离公式的延展....emmm公式打不出来了

余弦距离

个人觉得余弦距离与其说是距离不如理解为趋势,余弦距离是空间两个向量夹角的余弦值来作为比较依据。

IO流

按操作数据类型分为字符流和字节流

按流分为输入流和输出流

字符流处理文本数据,字节流处理媒体数据

FileInputStream  FileOutputStream  纯文本输入输出最为方便,这里的输入输出参考于程序

流使用后一定关闭

思路

1.数据存储

数据存储分两类:“学习”样本数据的存储,识别样本的存储;

在我点击“学习”按钮后,在界面画数字“1”,它会被截图保存,然后转为灰度,保存在二维数组中,输出到文本文档。

在我点击“识别”按钮后,在界面画数字“1”,它会被截图保存,然后转为灰度,保存在二维数组中,输出到文本文档。

2.knn,计算欧氏距离

我们需要从学习文档中得到所有的数组数据,在与得到的一组测试数据比较,欧氏距离最小的极为同类。输出就好

源代码(1.0版本,它的问题是每次识别前都要录入新的样本)



import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Graphics;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import java.awt.AWTException;

import java.awt.Rectangle;

import java.awt.Robot;

import java.awt.image.BufferedImage;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;

import javax.imageio.ImageIO;
public class DrawFrame extends JFrame{

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		DrawFrame h=new DrawFrame();
		h.show();
		
		
	}

	public void show(){
		JFrame jf=new JFrame();
		jf.setTitle("手写识别");
		jf.setSize(400,400);
		jf.getContentPane().setBackground(Color.white);
		//jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		jf.setDefaultCloseOperation(3);
	
		// 居中显示
		//jf.setLocationRelativeTo(null);
		JPanel p1=new JPanel();
		JPanel p=new JPanel();
		jf.add(p1, BorderLayout.CENTER);
		jf.add(p, BorderLayout.NORTH);
		p.setPreferredSize(new Dimension(400, 100));
		p.setBackground(Color.white);
		p1.setPreferredSize(new Dimension(400, 300));
		p1.setBackground(Color.black);
		p1.setLayout(new FlowLayout(10, 10, 30));
		String[] s={"识别","调整"};
		HwrListener mouse=new HwrListener();
		for (int i = 0; i < s.length; ++i) {
			JButton jbt=new JButton(s[i]);
			p.add(jbt);
			jbt.addActionListener(mouse);
		}
		jf.setVisible(true);
		Graphics gr = p1.getGraphics();
		mouse.Set(gr,p1,p);
		jf.addMouseMotionListener(mouse);
		jf.addMouseListener(mouse);
		p1.addMouseMotionListener(mouse);
		p1.addMouseListener(mouse);
	}
	
}


import java.awt.AWTException;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.Robot;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.geom.Line2D;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.awt.AWTException;
import java.awt.Rectangle;
import java.awt.Robot;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.imageio.ImageIO;
import javax.swing.JOptionPane;
import javax.swing.JPanel;

public class HwrListener extends MouseAdapter implements MouseListener, ActionListener, MouseMotionListener {
	private String Str;
	private String ff = null;// F是txt;ff是string图片
	private Graphics gr;
	private JPanel p;
	private JPanel p1;
	private int x1, x3, y3;
	private int y1;
	private int x4;
	private int y4, m = 0;
	private Graphics2D g;
	private Color[][] s = new Color[400][400];
	private int[][] tem = new int[400][400];
	private int[] str = new int[100];
	private int h, count;// 记录
	private int w, ans;
	private Color[][] c = new Color[400][400];
	//RGB2 rgb = new RGB2();
	private String f;// txt
	private int[][] st= new int[400][400];//图片坐标;
	KNN knn=new KNN();
	public void Set(Graphics gr, JPanel p1, JPanel p) {
		this.gr = gr;
		this.p = p;
		this.p1 = p1;
	}
	public void actionPerformed(ActionEvent e) {
		if (e.getActionCommand() != null) {
			Str = e.getActionCommand();
		}
		if ("调整".equals(Str)) {
			g = (Graphics2D) gr;
			g.setColor(Color.white);
			// System.out.println("vmjdckds");
			g.setStroke(new BasicStroke(10));
			//JOptionPane.showMessageDialog(null, "请按序输出5个0和5个1");
			try {
				knn.Train();
			} catch (IOException e1) {
				// TODO Auto-generated catch block
				e1.printStackTrace();
			}
			m = 0;// 调整
			
		}
		if ("识别".equals(Str)) {
			g = (Graphics2D) gr;
			g.setColor(Color.white);
			m = 1;
			g.setStroke(new BasicStroke(10));
		}
	}

	public void mousePressed(MouseEvent e) {
		// System.out.print("按下");
		if (m == 0) {// 调整
			x1 = e.getX();
			y1 = e.getY();
			System.out.println(x1 + "   " + y1);
		}
		if (m == 1) {// 识别
			x1 = e.getX();
			y1 = e.getY();
			System.out.println(x1 + "   " + y1);
		}
	}

	public void mouseReleased(MouseEvent e) {
		// System.out.print("释放");
		g = (Graphics2D) gr;
		g.setColor(Color.white);
		try {
			this.text();
		} catch (AWTException e1) {
			// TODO Auto-generated catch block
			e1.printStackTrace();
		} catch (IOException e1) {
			// TODO Auto-generated catch block
			e1.printStackTrace();
		}
		g.setColor(Color.BLACK);
		g.fillRect(0, 0, 500, 500);
		count++;

	}

	private void text() throws AWTException, IOException {// 截图
		ff = "F:\\text\\" + count + "text.png";
		File file = new File(ff);
		//rgb.setFF(ff);
		BufferedImage bufferedImage = (new Robot()).createScreenCapture(new Rectangle(0,0, 400, 400));
		ImageIO.write(bufferedImage, "png", file);
		for (int i = 0; i <400; i++) {
			for (int j =0; j <400; j++) {
				/*getRGB(i,j)获取的该点的颜色值是ARGB,
				而在实际应用中使用的是RGB,所以需要将ARGB转化成RGB,类型转换
				即bufImg.getRGB(i, j) & 0xFFFFFF。*/
				int pixel = bufferedImage.getRGB(j, i);
				int r = (pixel & 0xff0000) >> 16;				                        int g = (pixel & 0xff00) >> 8;
				int b = (pixel & 0xff);
				float gray = r * 0.3f + g * 0.59f + b * 0.11f;
				if (gray == 0) {                                        					st[i][j]=0;//图片坐标[i][j] = 0;
				} else if (gray == 255) {
				
					st[i][j] = 1;
				}
				System.out.print(st[i][j]);
			}
			System.out.println();
		}
		
		
		if(m==0){//调整
			f = count + "train"+".txt";
			File fil=new File("F:\\TEXT\\"+f);
			System.out.println("F:\\TEXT\\"+f);
			FileOutputStream out=new FileOutputStream(fil);
			for(int i=0;i<st.length;++i){
				for(int j=0;j<st[i].length;++j){
					out.write((byte)st[i][j]);
					out.flush();
				}
			}
			knn.setF(f);
			knn.Train();
			//count++;
		//	System.out.println("tiaozheng");
		}else if(m==1){//识别
			//knn.Train();
			f = "1work.txt";
			File fil=new File("F:\\TEXT\\"+f);
			System.out.println("F:\\TEXT\\"+f);
			FileOutputStream out=new FileOutputStream(fil);
			for(int i=0;i<st.length;++i){
				for(int j=0;j<st[i].length;++j){
					out.write((byte)st[i][j]);
					out.flush();
				}
			}
			//knn.setF2(f);
			//knn.Train();
			ans=knn.Max();
			//System.out.println("shibie");
			JOptionPane.showMessageDialog(null, "该数字是" + ans);
		}
		
	}
	public void mouseClicked(MouseEvent e) {
		System.out.print("点击");
		x3 = e.getX();
		y3 = e.getY();
		System.out.println(x3 + " x3 " + y3 + " y3 ");
	}

	public void mouseDragged(MouseEvent e) {
		if (m == 0) {
			x4 = e.getX();
			y4 = e.getY();
			g = (Graphics2D) gr;
			g.setColor(Color.white);
			g.drawLine(x1, y1, x4, y4);
			x1 = x4;
			y1 = y4;
		}
		if (m == 1) {
			x4 = e.getX();
			y4 = e.getY();
			g = (Graphics2D) gr;
			g.drawLine(x1, y1, x4, y4);
			x1 = x4;
			y1 = y4;
		}
	}

}

为了防止存文件名被覆盖,我定义了存一次文件增加1的变量count,将测试与样本分名保存“1work.txt”和"study.txt"

新的文件路径就表示为

String f=f = "1work.txt";
            File fil=new File("F:\\TEXT\\"+f);

取数据的时候也要保证文件名的统一。



import java.awt.SystemTray;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;

import javax.imageio.stream.FileCacheImageInputStream;

public class KNN {
	private byte[][] train = new byte[400][400 * 400];// 训练数组
	private byte[] text = new byte[400 * 400];// 实验数组
	private byte[] trainn = new byte[400 * 400];
	private byte[] tt = new byte[400 * 400];
	byte[] temp = new byte[400 * 400];
	private int l;
	public int ans;
	private int[] sums = new int[100];
	private File file;
	private String trainname = ".studytxt";
	private String tryname = ".1worktxt";
	private String f,f2;
	private String[] fil;
	// 训练集
	private int fileCount = 0;

	public int Max() throws IOException {
		//Train();
		FileInputStream ins = new FileInputStream(new File("F:\\TEXT\\1work.txt"));
		ins.read(text);
		ins.close();
		
		for (int i = 0; i < 100; ++i) {
			//敲重点
				int sum = 0;
			for (int j = 0; j <400 * 400; ++j) {
				//for (int k = 0; k < 200 * 200; ++k) {
					
						// 字节是存储空间,存啥是啥
						temp = train[i];
						sum += Math.abs((text[j] - temp[j])) * Math.abs((text[j] - temp[j]));
						sums[i] = sum;
						
				}
		}
				int min = sums[1];
				int mini = 0;
				//sums[6]=0;
				int h=sums.length;
				for (int m = 1; m <h; ++m) {
					System.out.println("sums"+m+sums[m]);
					if (sums[m] <min) {
						min = sums[m];
						mini = m;
					}
				}
				
				if (mini < 0.5 * 100) {
					ans = 0;
				} else if (mini >= 0.5 * 100) {
					ans = 1;
		}
				System.out.println("ans="+ans);
		return ans;
		
	}

	public void setF(String f) {
		this.f = f;
	}

	public void Train() throws IOException {
		File file = new File("F:\\TEXT\\" + f);
		//l = 40;
		// for(int i=0;i<l;++i){
		// fil[i]=files[i].toString();
		 System.out.println("训练,.........."+f);

		// for (int j = 0; j <l; ++j) {
		FileInputStream inss = new FileInputStream(file);
		inss.read(train[fileCount]);

	}

为了直接实现识别的功能,我在此基础上做了改动,但400*400的界面,50一组的样本还是不够。emmm.....

做它的时候挺痛苦的,输入样本也是,乐趣就是发现灰度和二值很有意思。


\Rightarrow