SVGとPNGのデータセット ①　(塩尻MLもくもく会#09)

#SVGとPNGのデータセット ①　([塩尻MLもくもく会#09](https://shiomoku.connpass.com/event/101457/))

ラスタ形式からベクタ形式を推論する研究が、様々な分野で行われているようです。

* [Learning to Simplify: Fully Convolutional Networks for Rough Sketch Cleanup](http://www.f.waseda.jp/hfs/SimoSerraSIGGRAPH2016.pdf)
* [Semantic Segmentation for Line Drawing Vectorization Using
Neural Networks](http://www.byungsoo.me/project/vectornet/paper.pdf)
* [Raster-to-Vector: Revisiting Floorplan Transformation](http://openaccess.thecvf.com/content_ICCV_2017/papers/Liu_Raster-To-Vector_Revisiting_Floorplan_ICCV_2017_paper.pdf)
* [SvgAI — Training artificial intelligent agent to use SVG editor](https://ieeexplore.ieee.org/document/8323672/)

ラスタからベクタへの学習が簡単に試せるデータセットがあったら面白いので、MNISTのサンプルを少し弄るだけで使えそうなものを作りたいと思います。

###PythonでSVGを出力する
参考 : [SVGをお手軽に出力するならQtが最強のライブラリではないか？](https://qiita.com/pashango2/items/b9f61ab06c78a3e2ccdd)
こちらを参考にさせて頂きました。
PythonでのSVG描画は[svgwrite](https://pypi.org/project/svgwrite/)が有名ですが今回はこちらの方法を利用しました。

まずSVGを描画するための要素を`numpy.random`でランダムに生成します。
今回は、
　__0から9までの数字__
　__14から84までのサイズ__
　__15種類のフォント__
でランダムに2つ選び、
上下左右に位置をずらして、横に2枚並べて描画します。

```gen_svg.py
from __future__ import unicode_literals, print_function, absolute_import
import os
import sys
import numpy as np
import PySide
from PySide.QtCore import *
from PySide.QtGui import *
from PySide.QtSvg import *
from PIL import Image

app = QApplication(sys.argv)
fontlist = ['Axis', 'Arial', 'Bahnscrift', 'Cambria', 'Century', 'Consolas', 'Courier', 'Georgia', 'Lucida', 'Myriad', 'Rockwell', 'Segoe', 'Symbol', 'Tahoma', 'TimesNewRoman']

def random_elements(n):
    rd_text = np.random.randint(0, 9, (n, 2))
    rd_size = np.random.randint(14, 84, (n, 2))
    rd_font = np.random.choice(fontlist, n*2).reshape(n, 2)
    rd_posi_x1 = np.random.randint(7, 35, (n, 1))
    rd_posi_y1 = np.random.randint(14, 63, (n, 1))
    rd_posi_w1 = np.random.randint(16, 56, (n, 1))
    rd_posi_h1 = np.random.randint(16, 56, (n, 1))
    rd_posi_x2 = np.random.randint(49, 70, (n, 1))
    rd_posi_y2 = np.random.randint(14, 63, (n, 1))
    rd_posi_w2 = np.random.randint(16, 56, (n, 1))
    rd_posi_h2 = np.random.randint(16, 56, (n, 1))
    rd_stack = np.concatenate((rd_text, rd_size, rd_font, rd_posi_x1, rd_posi_y1, rd_posi_w1, rd_posi_h1, rd_posi_x2, rd_posi_y2, rd_posi_w2, rd_posi_h2), axis=1)
    return rd_stack
```

このパラメーターをPySideに渡してSVGを描画・保存をします。

```gen_svg.py
def draw_svg(rd, n, width, dir_svg):
    for i in range(n):
        svg_gen = QSvgGenerator()
        svg_gen.setFileName(dir_svg + str(i) + ".svg")
        svg_gen.setSize(QSize(width, width))
        svg_gen.setViewBox(QRect(0, 0, width, width))

        painter = QPainter()
        painter.begin(svg_gen)

        text1 = str(rd[i,0])
        text2 = str(rd[i,1])
        size1 = int(rd[i,2])
        size2 = int(rd[i,3])
        font1 = str(rd[i,4])
        font2 = str(rd[i,5])
        posi1 = np.array(rd[i,6:10], dtype=int)
        posi2 = np.array(rd[i,10:14], dtype=int)

        x1, y1, w1, h1 = posi1
        x2, y2, w2, h2 = posi2

        rect1 = QRect(x1, y1, w1, h1)
        painter.setPen(Qt.green)
        painter.setFont(QFont(font1, size1))
        painter.drawText(rect1, Qt.AlignCenter, text1)

        rect2 = QRect(x2, y2, w2, h2)
        painter.setPen(Qt.blue)
        painter.setFont(QFont(font2, size2))
        painter.drawText(rect2, Qt.AlignCenter, text2)
        painter.end()
```

###SVGをPNGに変換する
参考 : [PythonでSVGをPNGに変換する](https://code.i-harness.com/ja/q/648bae)
こちらにあるPySideを用いてPNGへ変換する方法を参考にしました。
SVGの変換ライブラリは[CairoSVG](https://cairosvg.org/)が有名ですが、インストールが上手くいかなかったためPySideの機能を使いました。

```gen_svg.py
def svg2png(n, width, dir_svg, dir_png):
    for i in range(n):
        file_svg = dir_svg + str(i) + ".svg"
        file_png = dir_png + str(i) + ".png"
        r = QSvgRenderer(file_svg)
        i = QImage(width, width, QImage.Format_ARGB32)
        p = QPainter(i)
        p.fillRect(0, 0, width, width, QColor(0, 0, 0))
        #p.eraseRect(0, 0, width, width)    
        r.render(p)
        i.save(file_png)
        p.end()
```
###PNGをnumpy配列に変換する
PILでPNGを読み込んで、`numpy.asarray`でnumpy配列に変換します。

```gen_svg.py
def png2numpy(n, dir_png):
    image_list = []
    for i in range(n):
        file_png = dir_png + str(i) + ".png"
        pil = Image.open(file_png)
        num = np.asarray(pil)
        image_list.append(num)
    image_numpy = np.array(image_list)
    return image_numpy
```
###1つの関数にまとめる
`random_elements`
`draw_svg`
`svg2png`
`png2numpy`
以上の4つをまとめて、`generate_datasets`という関数にします。

```gen_svg.py
def generate_datasets(n, width, dir_svg, dir_png):
    rd = random_elements(n)
    draw_svg(rd, n, width, dir_svg)
    svg2png(n, width, dir_svg, dir_png)
    image_numpy = png2numpy(n, dir_png)
    return image_numpy, rd
```
###訓練用データセット
訓練用に600枚のデータセットを作成します。
同じディレクトリにtrainフォルダ、その下のsvgフォルダとpngフォルダを`os.mkdir()`で作ります。
そこを作業ディレクトリにしてSVGとPNGが生成されます。
PNGはnumpy配列に変換され`train_image`に代入されます。
`train_signal`はSVGの描画に用いたパラメーターです。これを教師データにします。
`width`は画像サイズを決めるパラメーターです。

```gen_svg.py
n = 600
width = 112
os.mkdir("./train")
os.mkdir("./train/svg")
os.mkdir("./train/png")
dir_svg = "train/svg/"
dir_png = "train/png/"
train_image, train_signal = generate_datasets(n, width, dir_svg, dir_png)
```
###テスト用データセット
テストデータセットをtestフォルダに100枚作成します。
`test_image` `test_signal`に生成されたnumpy配列を代入します。

```gen_svg.py
n = 100
width = 112
os.mkdir("./test")
os.mkdir("./test/svg")
os.mkdir("./test/png")
dir_svg = "test/svg/"
dir_png = "test/png/"
test_image, test_signal = generate_datasets(n, width, dir_svg, dir_png)
```

###numpy配列の保存
`numpy.save("ファイル名", 配列名)` で `.npy`の拡張子に保存します。

```gen_svg.py
np.save("train_image", train_image)
np.save("train_signal", train_signal)
np.save("test_image", test_image)
np.save("test_image", test_signal)
```

***
pngフォルダに以下のようなpngファイルが生成されました。
![svg01.png](https://qiita-image-store.s3.amazonaws.com/0/248554/2aee63e8-a511-6995-9e15-087215097820.png)
***


今回は以上になります。
次回では、このnumpy配列を使ってMNISTのサンプルコードを多少変えただけのモデルで学習を行いたいと思います。