C#でOCRを作ってみる

環境

Windows10 Pro 64bit
Visual Studio Community 2017

準備

  • こちらから「tesseract-ocr-3.02.eng.tar.gz」をダウンロードして解凍する
  • 実行ファイルと同じフォルダに入れる

tesseractの導入

Visual Studio 2017
>ツール
>Nugetパッケージマネージャー
>ソリューションのNugetパッケージの管理
で「tesseract3.0.2」をインストールする
(注:最新版でなく「3.0.2」を指定する)

コード

using System;
using System.Windows.Forms;
using System.Drawing;
using System.Drawing.Imaging;

namespace OCR
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void button1_Click(object sender, EventArgs e)
        {
            if (Clipboard.ContainsImage())
            {
                string langPath = "tessdata";

                //言語
                string lngStr = "eng";

                //画像ファイルを取得
                var img = (Bitmap)Clipboard.GetImage();

                //フォーマットの変換
                var bitmap24 = new Bitmap(img.Width, img.Height, PixelFormat.Format24bppRgb);
                using (var gr = Graphics.FromImage(bitmap24))
                {
                    gr.DrawImage(img, new Rectangle(0, 0, bitmap24.Width, bitmap24.Height));
                }

                using (var tesseract = new Tesseract.TesseractEngine(langPath, lngStr))
                {
                    // 文字を指定
                    tesseract.SetVariable("tessedit_char_whitelist", "1234567890.,");

                    // OCRの実行
                    Tesseract.Page page = tesseract.Process(bitmap24);

                    //表示
                    MessageBox.Show(page.GetText());
                }
            }
        }
    }
}

工夫した点

  • Windowsに付属する「Snipping Tool」で画面の一部をキャプチャ
  • それをClipboardにコピー
  • Clipboardから画像を取得できるようにした

苦労した点

「Format32bppRgb」から「Format24bppRgb」にフォーマットを変換するのに苦労した

C#個人的メモ(2)

  • マウスの移動だけでテキストボックスに文字を入力する
public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        int in_posi;
        int out_posi;

        private void label1_MouseLeave(object sender, EventArgs e)
        {
            out_posi = Cursor.Position.Y;
            
            if (out_posi > in_posi)
            {
                Control ac = this.ActiveControl;
                string temp = ac.Text;
                ac.Text = temp + "1";
            }
        }
        private void label1_MouseEnter(object sender, EventArgs e)
        {
            in_posi = Cursor.Position.Y;
        }
    }

C#個人的メモ(1)

  • 数字を入力したら自動的に次のテキストボックスにフォーカスを移す
  • 数字の表示する際に、数字の前にプラスマイナスの符号をつける
        private void textBox1_TextChanged(object sender, EventArgs e)
        {
            if(Regex.IsMatch(textBox1.Text, @"\d+[.]\d{1}"))
            {
                textBox2.Focus();
            }
        }
        private void textBox2_TextChanged(object sender, EventArgs e)
        {
            if (Regex.IsMatch(textBox2.Text, @"\d+[.]\d{1}"))
            {
                double pre;
                double post;
                double difference;

                double.TryParse(textBox1.Text, out pre);
                double.TryParse(textBox2.Text, out post);
                difference = post - pre;
                textBox3.Text = difference.ToString("+0.0;-0.0;0.0");

                textBox4.Focus();
            }
        }

子供のためにメモリーゲームを作る

苦労した点

  • メインフォームの移動、サイズ変更を禁止する
Main Form
FormBorderStyle:None
Start Position:CenterScreen

PictureBox
Dock:top
Size:(-),32

Label
Font:14pt, bold
  • アニメーション

画像を順番に切り替えることで解決

  • 背景が透明なpng画像をフォーム上に表示

別のuserform上に画像を表示させてメインのフォームに重ねることで解決

  • アニメーション中はクリック動作を受け付けない

Application.DoEvents()を使って解決


Form1.cs

using System;
using System.Collections.Generic;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Media;
using System.Threading;
using System.Windows.Forms;

namespace CardGame
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
            this.BackColor = Color.FromArgb(255, 255, 100);
            pictureBox0.Image = Image.FromFile("image1_10.jpg");
            pictureBox1.Image = Image.FromFile("image2_10.jpg");
            pictureBox2.Image = Image.FromFile("image3_10.jpg");
            pictureBox3.Image = Image.FromFile("image4_10.jpg");
            pictureBox4.Image = Image.FromFile("image5_10.jpg");
            pictureBox5.Image = Image.FromFile("image6_10.jpg");
            for (int i = 0; i < 6; i++)
            {
                Controls["pictureBox" + i.ToString()].Click += pic_Click;
            }
        }

        SoundPlayer ok = new SoundPlayer("ok.wav");

        int[] elems = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 };
        List<string> list = new List<string>();

        string[] image = new string[6];

        int hantei = -1;
        int atari;

        private void pic_Click(object sender, EventArgs e)
        {

            if (((PictureBox)sender).Image == null)
            {
                //すべてのカードを無効にする
                for (int i = 0; i < 6; i++)
                {
                    Controls["pictureBox" + i.ToString()].Enabled = false;
                }

                int click_num = Int32.Parse(String.Join("", ((PictureBox)sender).Name.Where(Char.IsDigit)));

                for (int i = 6; i < 11; i++)

                {
                    ((PictureBox)sender).Image = Image.FromFile(image[click_num] + i.ToString() + ".jpg");
                    ((PictureBox)sender).Refresh();
                    Thread.Sleep(100);
                }

                if (hantei == -1)
                {
                    hantei = click_num;
                }
                else
                {
                    if (image[hantei] == image[click_num])
                    {
                        //当たり
                        rock();
                    }
                    else
                    {
                        SystemSounds.Beep.Play();
                        Thread.Sleep(500);

                        //はずれ
                        for (int i = 1; i < 5; i++)
                        {
                            ((PictureBox)Controls["pictureBox" + hantei.ToString()]).Image = Image.FromFile(image[hantei] + i.ToString() + ".jpg");
                            ((PictureBox)Controls["pictureBox" + hantei.ToString()]).Refresh();
                            Thread.Sleep(100);
                        }
                        ((PictureBox)Controls["pictureBox" + hantei.ToString()]).Image = null;
                        ((PictureBox)Controls["pictureBox" + hantei.ToString()]).Refresh();

                        for (int i = 1; i < 5; i++)
                        {
                            ((PictureBox)Controls["pictureBox" + click_num.ToString()]).Image = Image.FromFile(image[click_num] + i.ToString() + ".jpg");
                            ((PictureBox)Controls["pictureBox" + click_num.ToString()]).Refresh();
                            Thread.Sleep(100);
                        }
                        ((PictureBox)Controls["pictureBox" + click_num.ToString()]).Image = null;
                        ((PictureBox)Controls["pictureBox" + click_num.ToString()]).Refresh();

                        hantei = -1;
                    }
                }
                Application.DoEvents();
                //すべてのカードを有効にする
                for (int i = 0; i < 6; i++)
                {
                    Controls["pictureBox" + i.ToString()].Enabled = true;
                }
            }
        }
        private void button1_Click(object sender, EventArgs e)
        {
            //初期化

            //1~12までの数字から3つを選ぶ
            int[] elems2 = elems.OrderBy(i => Guid.NewGuid()).ToArray();
            list.Clear();
            list.Add("image" + elems2[0].ToString() + "_");
            list.Add("image" + elems2[0].ToString() + "_");
            list.Add("image" + elems2[1].ToString() + "_");
            list.Add("image" + elems2[1].ToString() + "_");
            list.Add("image" + elems2[2].ToString() + "_");
            list.Add("image" + elems2[2].ToString() + "_");
            string[] ary2 = list.OrderBy(i => Guid.NewGuid()).ToArray();

            hantei = -1;
            atari = 0;

            image[0] = ary2[0];
            image[1] = ary2[1];
            image[2] = ary2[2];
            image[3] = ary2[3];
            image[4] = ary2[4];
            image[5] = ary2[5];

            pictureBox0.Enabled = true;
            pictureBox1.Enabled = true;
            pictureBox2.Enabled = true;
            pictureBox3.Enabled = true;
            pictureBox4.Enabled = true;
            pictureBox5.Enabled = true;

            pictureBox0.Image = null;
            pictureBox1.Image = null;
            pictureBox2.Image = null;
            pictureBox3.Image = null;
            pictureBox4.Image = null;
            pictureBox5.Image = null;
        }
        private void rock()
        {
            //当たりの処理
            hantei = -1;
            atari += 1;
            if (atari < 3)
            {
                ok.Play();
            }
            else
            {
                this.BackColor = Color.FromArgb(255, 255, 255);
                Form2 f2 = new Form2();
                f2.ShowDialog();
                this.BackColor = Color.FromArgb(255, 255, 100);
            }
        }
        private void label1_Click(object sender, EventArgs e)
        {
            Application.Exit();
        }
    }
}

Form2.cs

using System;
using System.Drawing;
using System.Media;
using System.Threading;
using System.Windows.Forms;
namespace CardGame
{
    public partial class Form2 : Form
    {
        public Form2()
        {
            InitializeComponent();
        }

        SoundPlayer yattane = new SoundPlayer("yattane.wav");

        private void Form2_Shown(object sender, EventArgs e)
        {
            yattane.Play();

            for (int i = 1; i < 15; i++)
            {
                pictureBox1.Image = Image.FromFile("a3_trans" + i.ToString() + ".png");
                pictureBox1.Refresh();
                Thread.Sleep(60);
            }
            Thread.Sleep(2000);
            this.Dispose();
        }
    }
}

MSRNで超解像(MXNet)

  • MXNetの学習済みモデルはこちらからダウンロード可能

github.com

サンプル画像のダウンロード

こちら』からダウンロードして「dog.jpg」の名前で保存
https://s3.amazonaws.com/onnx-mxnet/examples/super_res_input.jpg

実行スクリプト

import numpy as np
import mxnet as mx
from mxnet import image

ctx = mx.cpu()

img = image.imread('dog.jpg')
img = img.astype(np.float32)/255
img = mx.nd.transpose(img, (2,0,1))
img = mx.nd.expand_dims(img, axis=0)

sym, arg_params, aux_params = mx.model.load_checkpoint('MSRN_4x', 0)
model = mx.mod.Module(symbol=sym, label_names=None, context=ctx)
model.bind(for_training=False, data_shapes=[('data', img.shape)])

model.set_params(arg_params, aux_params)

from collections import namedtuple
Batch = namedtuple('Batch', ['data'])

model.forward(Batch([img]), is_train=False)
prob = model.get_outputs()[0].asnumpy()
prob = np.squeeze(prob)

from PIL import Image
prob = (prob.transpose(1,2,0)*255).astype(np.uint8)
img = Image.fromarray(prob)
img.save('MSRN_4x.jpg')

結果の表示

いままでで一番きれいなような気がする。
f:id:touch-sp:20181121183247j:plain

カードをめくるアニメーションを作る

  • 回転している画像を作成する
from PIL import Image
import numpy as np
import cv2

img = cv2.imread('sss10.jpg')
pts0 = np.float32([[256,256],[256,0],[0,0],[0,256]])
pts1 = np.float32([[192,236],[192,20],[64,0],[64,256]])
pts2 = np.float32(([160,216],[160,40],[96,0],[96,256]))
pts3 = np.float32([[160,256],[160,0],[96,40],[96,216]])
pts4 = np.float32(([192,256],[192,0],[64,20],[64,236]))

M1 = cv2.getPerspectiveTransform(pts0,pts1)
M2 = cv2.getPerspectiveTransform(pts0,pts2)
M8 = cv2.getPerspectiveTransform(pts0,pts3)
M9 = cv2.getPerspectiveTransform(pts0,pts4)

dst1 = cv2.warpPerspective(img,M1,(256,256),borderValue=(255,255,255))
dst2 = cv2.warpPerspective(img,M2,(256,256),borderValue=(255,255,255))
dst8 = cv2.warpPerspective(img,M8,(256,256),borderValue=(255,255,255))
dst9 = cv2.warpPerspective(img,M9,(256,256),borderValue=(255,255,255))

img = Image.new('RGB', (256, 256), (0, 0, 0))
img.save('sss5.jpg')
img = np.array(img)

M3 = cv2.getPerspectiveTransform(pts0,pts3)
M4 = cv2.getPerspectiveTransform(pts0,pts4)
M6 = cv2.getPerspectiveTransform(pts0,pts1)
M7 = cv2.getPerspectiveTransform(pts0,pts2)

dst3 = cv2.warpPerspective(img,M3,(256,256),borderValue=(255,255,255))
dst4 = cv2.warpPerspective(img,M4,(256,256),borderValue=(255,255,255))
dst6 = cv2.warpPerspective(img,M6,(256,256),borderValue=(255,255,255))
dst7 = cv2.warpPerspective(img,M7,(256,256),borderValue=(255,255,255))

cv2.imwrite('sss1.jpg', dst1)
cv2.imwrite('sss2.jpg', dst2)
cv2.imwrite('sss3.jpg', dst3)
cv2.imwrite('sss4.jpg', dst4)
cv2.imwrite('sss6.jpg', dst6)
cv2.imwrite('sss7.jpg', dst7)
cv2.imwrite('sss8.jpg', dst8)
cv2.imwrite('sss9.jpg', dst9)
  • GIFとして保存
from PIL import Image

images = []

for i in range(10):
    images.append(Image.open('sss%d.jpg'%i))

images[0].save('out.gif', save_all=True, append_images=images[1:], duration=250)
  • 結果

f:id:touch-sp:20181119172236g:plain

  • 追記

Visual Studio(C#)のユーザーフォームアプリケーションにおいてpictureBox上にGIFを表示させると無限に動く。
それを回避するためには単純に画像を順番に表示すればよい。

        bool ura1 = true;

        private void pictureBox1_Click(object sender, EventArgs e)
        {
            if (ura1)
            {
                for (int i = 6; i < 11; i++)
                {
                    pictureBox1.Image = Image.FromFile("sss" + i.ToString() + ".jpg");
                    pictureBox1.Refresh();
                    Thread.Sleep(100);
                }
                ura1 = false;
            }
            else
            {
                for (int i = 1; i < 6; i++)
                {
                    pictureBox1.Image = Image.FromFile("sss" + i.ToString() + ".jpg");
                    pictureBox1.Refresh();
                    Thread.Sleep(100);
                }
                ura1 = true;
            }

Pythonで簡単なスクレイピング

import requests, bs4
import argparse
import os
from mxnet.gluon import utils

parser = argparse.ArgumentParser()
parser.add_argument('--ID', help='棋士番号', type=int, required=True)
args = parser.parse_args()

num = args.ID
print(str(num))

utils.download('https://www.shogi.or.jp/images/player/pro/%d.jpg' %num)
res = requests.get('https://www.shogi.or.jp/player/pro/%d.html'%num)
res.encoding = res.apparent_encoding
soup = bs4.BeautifulSoup(res.text, "html.parser")

elems_jp = soup.select('.nameTtl .jp')
print(elems_jp[0].text)

elems_en = soup.select('.nameTtl .en')
print(elems_en[0].text)

player =  str(num) + ',' + elems_jp[0].text + ',' + elems_en[0].text

if not os.path.isfile('players.txt'):
    with open('players.txt', mode='w') as f:
        f.write(player)
else:
    with open('players.txt', mode='a') as f:
        f.write('\n' + player)
  • MXNetがインストールされていたら画像のダウンロードは「requests」より「gluon.utils」を使ったほうが楽