任意のカラー画像をモノクロ2階調風画像に変換する学習モデル

# 概要

ソースコードは[**GitHub**](https://github.com/ka10ryu1/duotone)にある。
学習とテストに使ったのも[**GitHub**](https://github.com/ka10ryu1/ImageDataAll)にある。

**入力画像**

<img src="https://qiita-image-store.s3.amazonaws.com/0/227399/3836d181-ba48-b683-c7dc-3d8976a8d158.jpeg" width="640px">

**粗いドット**

<img src="https://qiita-image-store.s3.amazonaws.com/0/227399/15a886a8-1d55-8cdd-e084-7034ab2c23aa.jpeg" width="320px">

**細かいドット**

<img src="https://qiita-image-store.s3.amazonaws.com/0/227399/b3ee5d43-891c-84cd-5563-6d0041395b7f.jpeg" width="640px">

上記の結果は、過学習を起こしたモデルから生成されている。赤が粗いドットのloss、緑が細かいドットのloss。

<img src="https://qiita-image-store.s3.amazonaws.com/0/227399/ca7bef1d-1d9e-902e-9a24-0e40e971c04f.png" width="640px">



# 動作環境

- **Ubuntu** 16.04.3 LTS
- **Python** 3.5.2
- **chainer** 3.2
- **numpy** 1.13.3
- **cupy** 2.2
- **opencv-python** 3.4.0.12

# 実行速度

粗いドットと細かいドットの実行時間はネットワークが全く同じなので、どちらも同じ。

- CPU：core-i7 7700K
- GPU：GTX 1060 6G

|  CPU/GPU | 実行時間 [s] |
|--------:|---------:|
| CPU     |  5.31
| GPU     |  0.48

#  詳細

基本的には「[**高圧縮のJPG画像のノイズを除去する学習モデル**](https://qiita.com/ka10ryu1/items/5dc38a32847ad6f2b0e8)」と同じだが、使用したネットワークはDeep Image Priorの簡易版みたいなやつ。Deep Image Priorについては[**ここ**](https://qiita.com/peisuke/items/4f37a74eeb16401d2a10)が詳しい。

使用しているデータセットは自前で作成したやつで、モノクロ2階調風画像を生成するのはGIMPを利用している。GIMPによる作成方法は「[**GIMPで新聞印刷風（モノクロ2階調）の画像を生成する**](https://qiita.com/ka10ryu1/items/e5cb253fd2d2d977120f)」に詳しく書いたので詳細は省く。

## 入力データと正解データ

細かいドットの画像を学習させるときに使用した入力データ（上）と正解データ（下）

![npz2jpg_3.jpg](https://qiita-image-store.s3.amazonaws.com/0/227399/090853ff-f77a-7683-97e9-283d95b798ea.jpeg)



## ネットワーク

ネットワークは以下に示すように、`DownSampleBlock`と`UpSampleBlock`に分かれている。

```python
class JC_DDUU(Chain):
    def __init__(self, n_unit=128, n_out=1, rate=4,
                 layer=3, actfun_1=F.relu, actfun_2=F.sigmoid,
                 dropout=0.0, view=False):
        """
        [in] n_unit:    中間層のユニット数
        [in] n_out:     出力チャンネル
        [in] layer:     中間層の数
        [in] actfun_1: 活性化関数（Layer A用）
        [in] actfun_2: 活性化関数（Layer B用）
        """

        unit1 = n_unit
        unit2 = n_unit * 2
        unit4 = n_unit * 4
        unit8 = n_unit * 8

        super(JC_DDUU, self).__init__()
        with self.init_scope():
            # D: n_unit, ksize, stride, pad,
            #    actfun=None, dropout=0, wd=0.02
            self.block1a = DownSanpleBlock(unit1, 5, 2, 2, actfun_1, dropout)
            self.block1b = DownSanpleBlock(unit2, 5, 2, 2, actfun_1, dropout)
            self.block1c = DownSanpleBlock(unit4, 5, 2, 2, actfun_1, dropout)
            self.block1d = DownSanpleBlock(unit8, 5, 2, 2, actfun_1, dropout)
            self.block1e = DownSanpleBlock(unit8, 3, 1, 1, actfun_1, dropout)

            # U: n_unit_1, n_unit_2, ksize, stride, pad,
            #    actfun=None, dropout=0, rate=2
            self.block2a = UpSampleBlock(
                unit4, unit1, 5, 1, 2, actfun_2, dropout)
            self.block2b = UpSampleBlock(
                unit4, unit1, 5, 1, 2, actfun_2, dropout)
            self.block2c = UpSampleBlock(
                unit4, unit1, 5, 1, 2, actfun_2, dropout)
            self.block2d = UpSampleBlock(
                unit4, unit1, 5, 1, 2, actfun_2, dropout)

            self.blockN = UpSampleBlock(rate**2, 1, 5, 1, 2, actfun_2, 0, rate)

        self.layer = layer
        self.view = view

    def block(self, f, x):
        if self.view:
            print(x.shape)

        return f(x)

    def __call__(self, x):
        hc = []

        ha = self.block(self.block1a, x)
        hb = self.block(self.block1b, ha)
        hc = self.block(self.block1c, hb)
        hd = self.block(self.block1d, hc)
        he = self.block(self.block1e, hd)

        h = self.block(self.block2a, F.concat([hd, he]))
        h = self.block(self.block2b, F.concat([hc, h]))
        h = self.block(self.block2c, F.concat([hb, h]))
        h = self.block(self.block2d, F.concat([ha, h]))
        y = self.block(self.blockN, h)

        if self.view:
            print(y.shape)
            exit()

        else:
            return y
```

### `DownSanpleBlock`クラス

画像を縮小していくクラス

```python
class DownSanpleBlock(Chain):
    def __init__(self, n_unit, ksize, stride, pad,
                 actfun=None, dropout=0, wd=0.02):

        super(DownSanpleBlock, self).__init__()
        with self.init_scope():
            self.cnv = L.Convolution2D(
                None, n_unit, ksize=ksize, stride=stride, pad=pad, initialW=I.Normal(wd)
            )
            self.brn = L.BatchRenormalization(n_unit)

        self.actfun = actfun
        self.dropout_ratio = dropout

    def __call__(self, x):
        h = self.actfun(self.brn(self.cnv(x)))
        if self.dropout_ratio > 0:
            h = F.dropout(h, self.dropout_ratio)

        return h
```

### `UpSampleBlock`クラス

画像を拡大していくクラス

```python

class UpSampleBlock(Chain):
    def __init__(self, n_unit_1, n_unit_2, ksize, stride, pad,
                 actfun=None, dropout=0.0, wd=0.02, rate=2):

        super(UpSampleBlock, self).__init__()
        with self.init_scope():
            self.cnv = L.Convolution2D(
                None, n_unit_1, ksize=ksize, stride=stride, pad=pad, initialW=I.Normal(wd)
            )
            self.brn = L.BatchRenormalization(n_unit_2)

        self.actfun = actfun
        self.dropout_ratio = dropout
        self.rate = rate

    def __call__(self, x):
        h = self.actfun(self.brn(self.PS(self.cnv(x), self.rate)))
        if self.dropout_ratio > 0:
            h = F.dropout(h, self.dropout_ratio)

        return h

    def PS(self, h, r=2):
        """
        "P"ixcel"S"huffler
        Deconvolutionの高速版
        """

        batchsize, in_ch, in_h, in_w = h.shape
        out_ch = int(in_ch / (r ** 2))
        out_h = in_h * r
        out_w = in_w * r
        out = F.reshape(h, (batchsize, r, r, out_ch, in_h, in_w))
        out = F.transpose(out, (0, 3, 4, 1, 5, 2))
        out = F.reshape(out, (batchsize, out_ch, out_h, out_w))
        return out
```

以上