LoginSignup
1
3

More than 3 years have passed since last update.

C#で簡易手書き文字認識システムを作った

Posted at

C#で数字だけを認識する、手書き文字認識システムを作りました。
非常に簡易的なものです。
以下の説明は、VisualStudioを使用して作成する場合の説明になります。

TegakiLib.cs
using System;
using System.Drawing;
using System.Windows.Forms;


namespace TegakiLib
{
    public class Tegaki : PictureBox
    {
        private Bitmap canvas;
        private Graphics g;
        private Pen p;
        private bool writing = false;
        private int pointCount = 0;
        private bool isInit = true;
        private int initPositionX = 0;
        private int initPositionY = 0;

        /// <summary>
        /// up=1,down=2
        /// left=1,right=2
        /// </summary>
        private int[][,] hanbetu = new int[10][,];
        private int[] refIndex = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };

        public Action<int> exec;

        public Tegaki()
        {
            exec = (int i) => { };
            InitTegaki();
        }
        public Tegaki(Action<int> func)
        {
            exec = func;
            InitTegaki();
        }

        private void InitTegaki()
        {
            canvas = new Bitmap(500, 500);
            g = Graphics.FromImage(canvas);
            p = new Pen(Color.Cyan, 5);

            hanbetu[0] = new int[,] { { 2, 0, 0, 0 }, { 0, 2, 0, 0 }, { 1, 0, 0, 0 }, { 0, 1, 0, 0 }, { 9, 9, 0, 0 } };
            hanbetu[1] = new int[,] { { 2, 0, 0, 0 }, { 2, 0, 0, 0 }, { 2, 0, 0, 0 }, { 9, 9, 0, 0 } };
            hanbetu[2] = new int[,] { { 0, 2, 0, 0 }, { 2, 0, 0, 0 }, { 0, 2, 0, 0 }, { 9, 9, 0, 0 } };
            hanbetu[3] = new int[,] { { 0, 2, 0, 0 }, { 0, 1, 0, 0 }, { 0, 2, 0, 0 }, { 0, 1, 0, 0 }, { 9, 9, 0, 0 } };
            hanbetu[4] = new int[,] { { 0, 1, 0, 0 }, { 1, 0, 0, 0 }, { 2, 0, 0, 0 }, { 9, 9, 0, 0 } };
            hanbetu[5] = new int[,] { { 0, 1, 0, 0 }, { 2, 0, 0, 0 }, { 0, 2, 0, 0 }, { 2, 0, 0, 0 }, { 0, 1, 0, 0 }, { 9, 9, 0, 0 } };
            hanbetu[6] = new int[,] { { 0, 1, 0, 0 }, { 2, 0, 0, 0 }, { 0, 2, 0, 0 }, { 1, 0, 0, 0 }, { 0, 1, 0, 0 }, { 9, 9, 0, 0 } };
            hanbetu[7] = new int[,] { { 1, 0, 0, 0 }, { 0, 2, 0, 0 }, { 2, 0, 0, 0 }, { 9, 9, 0, 0 } };
            hanbetu[8] = new int[,] { { 0, 1, 0, 0 }, { 0, 2, 0, 0 }, { 0, 1, 0, 0 }, { 0, 2, 0, 0 }, { 1, 0, 0, 0 }, { 9, 9, 0, 0 } };
            hanbetu[9] = new int[,] { { 0, 1, 0, 0 }, { 2, 0, 0, 0 }, { 0, 2, 0, 0 }, { 1, 0, 0, 0 }, { 2, 0, 0, 0 }, { 9, 9, 0, 0 } };
        }

        protected override void OnMouseDown(MouseEventArgs e)
        {
            writing = true;
            base.OnMouseDown(e);
        }

        protected override void OnMouseLeave(EventArgs e)
        {
            drawFin();
            base.OnMouseLeave(e);
        }

        protected override void OnMouseMove(MouseEventArgs e)
        {
            mouseMoving();
            base.OnMouseMove(e);
        }

        protected override void OnMouseUp(MouseEventArgs e)
        {
            drawFin();
            base.OnMouseUp(e);
        }

        private void drawFin()
        {
            writing = false;
            isInit = true;
            int j;

            g.Clear(Color.FromArgb(64, 0, 64));
            this.Image = canvas;
            for (int i = 0; i <= 9; i++)
            {
                refIndex[i] = 0;
                for (j = 0; j < hanbetu[i].Length / 4; j++)
                {
                    hanbetu[i][j, 2] = 0;
                    hanbetu[i][j, 3] = 0;
                }
            }
        }

        private void mouseMoving()
        {
            if (!writing) return;

            int up = 1, down = 2, left = 1, right = 2;
            int ud = 10, lr = 10;
            int writtenNumber = 0;
            bool m = false;

            Point point = this.PointToClient(Control.MousePosition);

            g.DrawRectangle(p, point.X, point.Y, 5, 5);
            this.Image = canvas;

            if (isInit)
            {
                initPositionX = point.X;
                initPositionY = point.Y;
                isInit = false;
            }
            pointCount++;
            if (pointCount >= 5)
            {
                if ((point.X - initPositionX) >= 30)
                {
                    lr = right;
                    isInit = true;
                }
                if ((point.X - initPositionX) <= -30)
                {
                    lr = left;
                    isInit = true;
                }
                if ((point.Y - initPositionY) >= 30)
                {
                    ud = down;
                    isInit = true;
                }
                if ((point.Y - initPositionY) <= -30)
                {
                    ud = up;
                    isInit = true;
                }
                pointCount = 0;
                //isInit = true;

                for (int i = 0; i <= 9; i++)
                {
                    if (hanbetu[i][refIndex[i], 0] == ud || hanbetu[i][refIndex[i], 1] == lr)
                    {
                        hanbetu[i][refIndex[i], 2] = point.X;
                        hanbetu[i][refIndex[i], 3] = point.Y;

                        refIndex[i]++;
                        if (hanbetu[i][refIndex[i], 0] == 9 || hanbetu[i][refIndex[i], 1] == 9)
                        {
                            switch (i)
                            {
                                case 0:
                                    if ((hanbetu[0][0, 2] < hanbetu[0][2, 2]) && (hanbetu[0][1, 3] > hanbetu[0][3, 3]) &&
                                       (hanbetu[0][0, 3] - 10 > hanbetu[0][3, 3]))
                                    {
                                        writtenNumber = 0;
                                        m = !m;
                                    }
                                    break;
                                case 1:
                                    if (((hanbetu[1][0, 2] + 50 >= hanbetu[1][2, 2]) && (hanbetu[1][0, 2] - 50 <= hanbetu[1][2, 2])) &&
                                            (hanbetu[4][2, 2] == 0) && (hanbetu[4][2, 3] == 0) &&
                                            (hanbetu[9][4, 2] == 0) && (hanbetu[9][4, 3] == 0) &&
                                            (hanbetu[7][0, 2] == 0) && (hanbetu[7][0, 3] == 0))
                                    {
                                        writtenNumber = 1;
                                        m = !m;
                                    }
                                    break;
                                case 2:
                                    writtenNumber = 2;
                                    m = !m;
                                    break;
                                case 3:
                                    if ((hanbetu[3][0, 3] < hanbetu[3][1, 3]) && (hanbetu[3][2, 3] < hanbetu[3][3, 3]))
                                    {
                                        writtenNumber = 3;
                                        m = !m;
                                    }
                                    break;
                                case 4:
                                    if ((hanbetu[4][1, 3] < hanbetu[4][0, 3]) && (hanbetu[4][1, 2] < hanbetu[4][2, 2]))
                                    {
                                        writtenNumber = 4;
                                        m = !m;
                                    }
                                    break;
                                case 5:
                                    writtenNumber = 5;
                                    m = !m;
                                    break;
                                case 6:
                                    if ((hanbetu[6][1, 2] < hanbetu[6][0, 2]) &&
                                       (hanbetu[6][1, 2] < hanbetu[6][2, 2]) &&
                                       (hanbetu[6][1, 2] < hanbetu[6][3, 2]) &&
                                       (hanbetu[6][1, 2] < hanbetu[6][4, 2]) &&
                                       (hanbetu[6][0, 3] + 30 < hanbetu[6][4, 3]))
                                    {
                                        writtenNumber = 6;
                                        m = !m;
                                    }
                                    break;
                                case 7:
                                    if ((hanbetu[9][3, 2] == 0) && (hanbetu[9][3, 3] == 0))
                                    {
                                        writtenNumber = 7;
                                        m = !m;
                                    }
                                    break;
                                case 8:
                                    if ((hanbetu[8][0, 3] < hanbetu[8][1, 3]) && (hanbetu[8][2, 3] > hanbetu[8][3, 3]))
                                    {
                                        writtenNumber = 8;
                                        m = !m;
                                    }
                                    break;
                                case 9:
                                    if (hanbetu[9][0, 3] < hanbetu[9][2, 3])
                                    {
                                        writtenNumber = 9;
                                        m = !m;
                                    }
                                    break;
                            }
                            if(m) exec(writtenNumber);
                        }
                    }
                }
            }
        }
    }
}

PictureBoxクラスの派生Tegakiクラスです。
上のソースコードをそのままコピーして、DLLにもできますし、プロジェクトの一部にしてもかまいません。
フォームのデザイン画面を開いてみると、ツールボックスにTegakiが追加されます。

使い方(プログラム例)

00001.png

Tegakiのサイズは500✕500のみ対応しています。プロパティでサイズを500✕500にしてください。
背景色は、何でもいいですが、勝手にRGB(64,0,64)に変わってしまいます。(手抜きですが・・・)
他にもラベルを2つ追加しました。

Form2.cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace TegakiNumber
{
    public partial class Form2 : Form
    {
        int j = 0;
        public Form2()
        {
            InitializeComponent();
            tegaki1.exec = exec;
        }

        private void exec(int i)
        {
            label1.Text = "現在の判別値" + i.ToString();
            j = i;
        }

        private void tegaki1_MouseUp(object sender, MouseEventArgs e)
        {
            label2.Text = "確定した判別値" + j.ToString();
        }
    }
}

フォームのソースコードです。
コンストラクターで、
tegaki1.exec = exec;
とありますが、Tegakiクラスの中でpublic Action<int> exec;と定義されています。
これはint型を1つ引数にとって、戻り値を返さない関数を意味しています。

つまりTegaki.execForm2.exec(int i)を渡しています。
この関数は、手書き入力中に、今現在判別している数字が変化したら実行されます。
その数字が引数iに渡されます。
label1は、入力中の現在の判別値を表示します。

この簡易文字認識システムは、マウスクリックからマウスが離されるまでの軌跡で判断します。
よって、label2は、tegaki1MouseUpイベントで、確定した判別値を返します。

使い方(数字の書き方)

先ほど説明したように、マウスクリックからマウスが離されるまでの軌跡で判断します。
つまり、一筆書きが基本です。
「4」は、
00002.png
の順に書きます。
「5」は、
00003.png
と書きます。
「7」は、
00004.png
最初の縦棒は必須です。

実際の実行中の画面

00006.png

「3」を書いてる途中の画面です。まだ途中なので、現在の判別値が「2」と判定されています。

00005.png

「6」を書いたときの画面です。(マウスクリックを離すと手書き文字が消えるので、確定した判別値が前回のままになっています。)

以上、数字だけに対応した簡易的な手書き文字認識システムでした。

1
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
3