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

苦労した点

  • メインフォームの移動、サイズ変更を禁止する
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」を使ったほうが楽

MXNet 備忘録(3)

「mx.io.NDArrayIter」の仕様が変わったらしい
github.com

Change the timing of shuffling. Previously, it shuffles only during the initialization, which didn't meet training needs.
・Changes
shuffle when calling the reset.

「shuffle」について

テスト用コード

import mxnet as mx

A = []

for i in range(6):
    a = mx.nd.array([i])
    a = mx.nd.expand_dims(a, axis=0)
    A.append(a)

batch_size = 3

train_data = mx.io.NDArrayIter(data=[mx.nd.concat(*A, dim=0)], 
                             batch_size=batch_size, 
                             shuffle=True)

epoch = 2
for i in range(1, epoch+1):
    print('epoch %d'%i)
    train_data.reset()
    for batch in train_data:
        print(batch.data[0])

結果

epoch 1

[[5.]
 [4.]
 [1.]]
<NDArray 3x1 @cpu(0)>

[[3.]
 [2.]
 [0.]]
<NDArray 3x1 @cpu(0)>
epoch 2

[[0.]
 [3.]
 [4.]]
<NDArray 3x1 @cpu(0)>

[[1.]
 [5.]
 [2.]]
<NDArray 3x1 @cpu(0)>

reset毎にshuffleが実行される

「last_batch_handle」について

  • 「last_batch_handle='discard'」

テスト用コード

import mxnet as mx

A = []

for i in range(5):
    a = mx.nd.array([i])
    a = mx.nd.expand_dims(a, axis=0)
    A.append(a)

batch_size = 3

train_data = mx.io.NDArrayIter(data=[mx.nd.concat(*A, dim=0)], 
                             batch_size=batch_size, 
                             shuffle=False,
                             last_batch_handle='discard')

epoch = 2
for i in range(1, epoch+1):
    print('epoch %d'%i)
    train_data.reset()
    for batch in train_data:
        print(batch.data[0])

結果

epoch 1

[[0.]
 [1.]
 [2.]]
<NDArray 3x1 @cpu(0)>
epoch 2

[[0.]
 [1.]
 [2.]]
<NDArray 3x1 @cpu(0)>

切り捨てられる

  • 「last_batch_handle='pad'」(デフォルト)

テスト用コード

import mxnet as mx

A = []

for i in range(5):
    a = mx.nd.array([i])
    a = mx.nd.expand_dims(a, axis=0)
    A.append(a)

batch_size = 3

train_data = mx.io.NDArrayIter(data=[mx.nd.concat(*A, dim=0)], 
                             batch_size=batch_size, 
                             shuffle=False,
                             last_batch_handle='pad')

epoch = 2
for i in range(1, epoch+1):
    print('epoch %d'%i)
    train_data.reset()
    for batch in train_data:
        print(batch.data[0])

結果

epoch 1

[[0.]
 [1.]
 [2.]]
<NDArray 3x1 @cpu(0)>

[[3.]
 [4.]
 [0.]]
<NDArray 3x1 @cpu(0)>
epoch 2

[[0.]
 [1.]
 [2.]]
<NDArray 3x1 @cpu(0)>

[[3.]
 [4.]
 [0.]]
<NDArray 3x1 @cpu(0)>

最初に戻ってPaddingされる

  • 「last_batch_handle='roll_over'」

テスト用コード

import mxnet as mx

A = []

for i in range(5):
    a = mx.nd.array([i])
    a = mx.nd.expand_dims(a, axis=0)
    A.append(a)

batch_size = 3

train_data = mx.io.NDArrayIter(data=[mx.nd.concat(*A, dim=0)], 
                             batch_size=batch_size, 
                             shuffle=False,
                             last_batch_handle='roll_over')

epoch = 2
for i in range(1, epoch+1):
    print('epoch %d'%i)
    train_data.reset()
    for batch in train_data:
        print(batch.data[0])

結果

epoch 1

[[0.]
 [1.]
 [2.]]
<NDArray 3x1 @cpu(0)>
epoch 2

[[3.]
 [4.]
 [0.]]
<NDArray 3x1 @cpu(0)>

[[1.]
 [2.]
 [3.]]
<NDArray 3x1 @cpu(0)>

次のエポックに持ち越される

WindowsでPytorchのCycleGANを使ってみる

はじめに

github.com

  • 今回はWindowsでhorse2zebraのデモのみ行った。
  • Linux or macOSが前提と書かれているがWindowsでも動く(ただしデータのダウンロードに少し骨が折れる)
  • あらかじめこのGitHubページからZIPファイルをダウンロードして解凍しておく。

環境

Windows10 Pro 64bit
NVIDIA GeForce GTX1080
CUDA9.2
cudnn7.2.1
Python3.6.6(venv使用)

バージョン確認(pip freeze)

  • 「torch」「torchvision」のインストールはこちらを参照
  • 「dominate」「scipy」「visdom」をpipで追加インストールした
  • データのダウンロードにMXNetを使用したので「mxnet-cu92」がインストールされている
  • venvを使用しているので「pylint」がインストールされている
astroid==2.0.4
certifi==2018.10.15
chardet==3.0.4
colorama==0.4.0
dominate==2.3.4
graphviz==0.8.4
idna==2.6
isort==4.3.4
lazy-object-proxy==1.3.1
mccabe==0.6.1
mxnet-cu92==1.3.1b20180927
numpy==1.14.6
Pillow==5.3.0
pylint==2.1.1
pyzmq==17.1.2
requests==2.18.4
scipy==1.1.0
six==1.11.0
torch==0.4.1
torchfile==0.1.0
torchvision==0.2.1
tornado==5.1.1
typed-ast==1.1.0
urllib3==1.22
visdom==0.1.8.5
websocket-client==0.53.0
wrapt==1.10.11

データのダウンロード

  • 自分はMXNetを使用したがなんでも良い
from mxnet.gluon import utils

dataset = 'horse2zebra'

url = 'http://efrosgans.eecs.berkeley.edu/cyclegan/pretrained_models/%s.pth' % (dataset)
utils.download(url)

URL='https://people.eecs.berkeley.edu/~taesung_park/CycleGAN/datasets/%s.zip' % (dataset)
utils.download(URL)
  • ダウンロードされた「horse2zebra.pth」は「latest_net_G.pth」に名前を変更して「./checkpoints/horse2zebra_pretrained/latest_net_G.pth」に保存(存在しないフォルダは手動で作る)
  • ダウンロードされた「horse2zebra.zip」を手動で解凍する

実行

python test.py --dataroot horse2zebra/testA --name horse2zebra_pretrained --model test

CPUのみの場合には「--gpu_ids -1」をつける

GluonNLPを使ってみる

はじめに

  • 以前にGloveやFastTextを試してみた。

touch-sp.hatenablog.com
touch-sp.hatenablog.com

  • 今回はGluonNLPでGloveの学習済みモデルを使ってみた。

環境

Windows7 64bit
Python 3.6.6
GPUなし

バージョン確認(pip freeze)

astroid==2.0.4
certifi==2018.10.15
chardet==3.0.4
colorama==0.4.0
gluonnlp==0.4.0
graphviz==0.8.4
idna==2.6
isort==4.3.4
lazy-object-proxy==1.3.1
mccabe==0.6.1
mxnet==1.3.1b20181018
numpy==1.14.6
pylint==2.1.1
requests==2.18.4
six==1.11.0
typed-ast==1.1.0
urllib3==1.22
wrapt==1.10.11

実行スクリプト

import mxnet as mx
import gluonnlp as nlp

def norm_vecs_by_row(x):
    return x / mx.nd.sqrt(mx.nd.sum(x * x, axis=1) + 1E-10).reshape((-1,1))

def get_knn(vocab, k, word_vec):
    word_vec = word_vec.reshape((-1, 1))
    vocab_vecs = norm_vecs_by_row(vocab.embedding.idx_to_vec)
    dot_prod = mx.nd.dot(vocab_vecs, word_vec)
    indices = mx.nd.topk(dot_prod.reshape((-1, )), k=k, ret_typ='indices')
    indices = [int(i.asscalar()) for i in indices]
    return vocab.to_tokens(indices)

glove = nlp.embedding.create('glove', source='glove.6B.100d')

vocab = nlp.Vocab(nlp.data.Counter(glove.idx_to_token))
vocab.set_embedding(glove)

word_embedding = vocab.embedding['bad', 'worst', 'big']
word_vec = word_embedding[1] - word_embedding[0] + word_embedding[2]

get_knn(vocab, 3, word_vec)

学習済みモデルの種類

  • 「glove」「word2vec」「fasttext」それぞれ用意されている
  • 以下で確認できる
gluonnlp.embedding.list_sources('glove')
gluonnlp.embedding.list_sources('word2vec')
gluonnlp.embedding.list_sources('fasttext')