[python] くずし字 (kuzushiji) でまっちゃん

# はじめに

現在、[人文学データセンター](http://codh.rois.ac.jp)がホストとなりkaggleにて、くずし字の認識[コンペ](https://www.kaggle.com/c/kuzushiji-recognition)が開催されています。今回は、センターから公開されているくずし字のデータセットを使って、まっちゃんを描画します。
![images.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/330669/1c28846e-d8d2-411e-9e9f-0a16b7770e08.jpeg) まっちゃん



本記事は、同じくkaggleに公開されている下記カーネルを参考にさせていただきました。
・[Kuzushiji-MNIST Cat](https://www.kaggle.com/anokas/kuzushiji-mnist-cat)

# データセット
データは、[こちら](http://codh.rois.ac.jp/kmnist/index.html.ja#license)からダウンロードできます。
>『KMNISTデータセット』（CODH作成） 『日本古典籍くずし字データセット』（国文研ほか所蔵）を翻案 doi:10.20676/00000341

今回は、**Kuzushiji-49**内の**k49-train-imgs.npz**を使用します。
![kmnist.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/330669/c18cd553-17bb-225e-920a-5c0617ee56d1.png)


# 環境
ローカルにデータをダウンロードして、jupyter notebook にて実行します。
カレントフォルダに、上記の "k49-train-img.npz" および、まっちゃん画像 "matchan.jpg" を用意します。

macOS Mojave
・python 3.6.5
・numpy 1.16.3
・pandas 0.24.2
・mlcrate 0.2.0
・skimage 0.15.0
・matplotlib 3.0.3

# 実装
### ライブラリのインポート
```python:
import numpy as np
import pandas as pd
import mlcrate as mlc

from skimage.io import imread, imsave
from skimage.transform import resize

import matplotlib.pyplot as plt
%matplotlib inline
```
### 元画像の読み込み
次に、元画像を読み込み、白黒画像（グレースケール化）します。

```python:
image = imread('matchan.jpg') # => image.shape = (252, 200, 3)

# グレースケール化
image = image[:,:,0]
plt.imshow(img, cmap='gray')
```
![download.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/330669/c441efcf-aa91-004c-cf7b-b2a7196451ce.png)

### データセット読み込み
くずし字データセットを読み込み、各文字の画像におけるグレーレベル（白黒の比率）を見てみる。また、各文字とそのグレーレベルを辞書化。

```python:
imgs_train = np.load('k49-train-img.npz')['arr_0']
means = (255 - imgs_train).mean(axis=1).mean(axis=1)
plt.hist(means, bins=50, log=True)

# 辞書化
from collections import defaultdict
character_bins = defaultdict(list)
for char, mean in zip(imgs_train, means):
    character_bins[int(mean)].append(char)
```
![download-1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/330669/14c387e1-2cc5-b999-6bbe-a79e1d34458a.png)
このグラフから、このデータセットには、グレーレベルが低い（約90以下）画像は無いことがわかる。

### 元画像をリスケール
```python:
# 各ピクセルが文字に置き換えられるので、これに28x28が乗算される。
OUTPUT_RESOLUTION = (96, 75) 

# データセットには、グレーレベルが低い画像が無いため、各ピクセルを(120, 245)にリスケールする。
img_small = (resize(img, OUTPUT_RESOLUTION, preserve_range=True) / 2) + 120 

plt.hist(img_small.flatten(), bins=50, log=True)
plt.title('Gray level')
plt.show()
plt.imshow(img_small, cmap='gray')
```

![download-4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/330669/e18a4b16-ca22-e9f2-7dcf-686db7663b51.png)
![download-5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/330669/075a6220-354e-97e1-a785-fe17af603b42.png)

### 新しい画像を作成
上記、まっちゃん画像の全てのピクセルをループして、くずし字画像から同じグレーレベルのものをランダムに選択し、新しい画像の同じ位置に配置していく。

```python:
# 新しい空の画像
new_img = np.zeros((img_small.shape[0]*28,
                    img_small.shape[1]*28),
                    dtype=np.unit8)

# ループ
ix = 0
iy = 0
for iy in range(img_small.shape[0]):
    for ix in range(img_small.shape[1]):
        level = int(img_small[iy, ix])
        charbin = character_bins[level]
        if len(charbin) == 0:
            charbin = character_bins[level + 1]
        if len(charbin) == 0:
            charbin = character_bins[level - 1]
        char = 255 - charbin[np.random.choice(np.arange(len(charbin)))]
        new_img[iy*28:(iy+1)*28, ix*28:(ix+1)*28] = char

# カレントディレクトリに画像の保存
imsave('kuzushiji_matchan.jpg', new_img)
```

# くずし字まっちゃん

上記、設定だと解像度も高いので、拡大するとくずし字がはっきりわかる。
![download copy.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/330669/80ae9b63-7823-e429-036f-0f45672a4400.png)