画像を手早く高画質にする

# TL;DR
手元にある画像を手っ取り早く高画質にするために、
手軽な方法を調べ、部分的に書き換えて二倍速にした。

# はじめに
ちょうど[こんな記事](https://medium.com/@crosssceneofwindff/srgan%E3%82%92%E7%94%A8%E3%81%84%E3%81%9F%E8%B6%85%E8%A7%A3%E5%83%8F-cf7fac787729)を見つけ、頑張れば良い感じの画像が手に入るであろうことは想像が付いていたので
まずは手軽にやる方法を探していました。
そして、見つけたのが[RAISRを実装したリポジトリ](https://github.com/movehand/raisr)です。
結果の画像を見る限り、割と良い感じだったのでこれを採用して見た訳ですが、
前処理として使うには少々時間がかかり過ぎていたので[multiprocessing](https://docs.python.jp/3/library/multiprocessing.html)で処理時間を短くしておきました。
[RAISRの元論文](https://arxiv.org/pdf/1606.01299.pdf)もあるので、自分でゼロから始める人は、そちらを参考にして下さい。
リポジトリに載っていた結果の画像はこちらです。
![WomanOrigin](https://user-images.githubusercontent.com/12198424/27002908-28a69cf2-4e1f-11e7-954d-1135950cd760.png "WomanOrigin")![WomanRAISR](https://user-images.githubusercontent.com/12198424/27002910-28ae90f6-4e1f-11e7-83e5-3e63ca07b308.png "WomanRAISR")

![LineOrigin](https://user-images.githubusercontent.com/12198424/27002925-81cf7f88-4e1f-11e7-8a75-4975db1d37b8.png "LineOrigin")![LineRAISR](https://user-images.githubusercontent.com/12198424/27002926-81d126b2-4e1f-11e7-8814-f2fce323caac.png "LineRAISR")

![somethingOrigin](https://user-images.githubusercontent.com/12198424/27002932-a39f6cc2-4e1f-11e7-9dea-3aa79a9d9764.png "somethingOrigin")![somethingRAISR](https://user-images.githubusercontent.com/12198424/27002933-a3a248ac-4e1f-11e7-9c81-807d3a5eac90.png "somethingRAISR")


![TigerOrigin](https://user-images.githubusercontent.com/12198424/27002942-c9ba697a-4e1f-11e7-8743-d41be65c09d3.png "TigerOrigin")![TigerRAISR](https://user-images.githubusercontent.com/12198424/27002943-c9bcefd8-4e1f-11e7-9e55-bf5d93f17a9c.png "TigerRAISR")

ギザギザしていた部分が、綺麗になっています。

# やったこと
[test.pyで時間の掛かる処理をしていた部分](https://github.com/movehand/raisr/blob/master/test.py#L73:L91)を並列化しただけです。
元のコードで、何が不味いのかは一目見て分かることでしょう。

```python
for row in range(margin, heightHR-margin):
    for col in range(margin, widthHR-margin):
        if round(operationcount*100/totaloperations) != round((operationcount+1)*100/totaloperations):
            print('\r|', end='')
            print('#' * round((operationcount+1)*100/totaloperations/2), end='')
            print(' ' * (50 - round((operationcount+1)*100/totaloperations/2)), end='')
            print('|  ' + str(round((operationcount+1)*100/totaloperations)) + '%', end='')
            sys.stdout.flush()
        operationcount += 1
        # Get patch
        patch = upscaledLR[row-patchmargin:row+patchmargin+1, col-patchmargin:col+patchmargin+1]
        patch = patch.ravel()
        # Get gradient block
        gradientblock = upscaledLR[row-gradientmargin:row+gradientmargin+1, col-gradientmargin:col+gradientmargin+1]
        # Calculate hashkey
        angle, strength, coherence = hashkey(gradientblock, Qangle, weighting)
        # Get pixel type
        pixeltype = ((row-margin) % R) * R + ((col-margin) % R)
        predictHR[row-margin,col-margin] = patch.dot(h[angle,strength,coherence,pixeltype])
```
- 独立に計算しても問題のないものに対してシングルスレッドで処理を実行している
- pythonでforの二重ループを使っている

ぱっと見ですが、これを書き直すだけで一気に早くなりそうな気がします。
まずは、forの中身を関数化して抜き出します。
> argwrapper

という関数はmultiprocessingで関数に引数を複数与えるために使います。

```python
import multiprocessing
def argwrapper(args):
    return args[0](*args[1:])
```
```python

def calculateValues(numOfRows, upscaledLR, row, col):
    if round(row * 100 / numOfRows) \
            != round((row + 1) * 100 / numOfRows):
        print('|{sharps}{spaces}|  {percent}%'.format(**{
            'sharps': '#' * round((row + 1) *
                                  100 / numOfRows / 2),
            'spaces': ' ' * (50 - round((row + 1) *
                                        100 / numOfRows / 2)),
            'percent': round((row + 1) * 100 / numOfRows)
        }), end='\r')
    # Get patch
    patch = upscaledLR[row - patchmargin: row + patchmargin + 1,
                       col - patchmargin: col + patchmargin + 1]
    patch = patch.ravel()
    # Get gradient block
    gradientblock = upscaledLR[row - gradientmargin: row + gradientmargin + 1,
                               col - gradientmargin: col + gradientmargin + 1]
    # Calculate hashkey
    angle, strength, coherence = hashkey(gradientblock, Qangle, weighting)
    # Get pixel type
    pixeltype = ((row - margin) % R) * R + ((col - margin) % R)
    return {
        "row": row - margin,
        "col": col - margin,
        "value": patch.dot(h[angle, strength, coherence, pixeltype])
    }
```
そして本題のmultiprocessingの部分です。
先ほど作成した関数の名前を第一引数にし、それ以降に関数の引数を列挙していくだけです。
RAISRの実装だと、行列の更新が必要だったので、最後にまとめて更新を行うことにしました。

```python
poo = multiprocessing.Pool()
funcArgs = [(calculateValues, heightHR - margin,
                upscaledLR, row, col)
            for row in range(margin, heightHR - margin)
            for col in range(margin, widthHR - margin)]
results = poo.map(argwrapper, funcArgs)
poo.close()
poo.join()
for result in results:
    predictHR[result["row"], result["col"]] = result["value"]
```

multiprocessingを使うとこれだけで並列化ができてしまいます。
簡単ですね。
プログレスバーの部分はあまり気を使っていなかったため、少し雑な感じですが
本質ではないので、無視しています。

# 結果の確認
[とある画像](https://www.google.co.jp/url?sa=i&rct=j&q=&esrc=s&source=images&cd=&cad=rja&uact=8&ved=2ahUKEwiV34foq4PeAhUDbbwKHbtCB5AQjRx6BAgBEAU&url=https%3A%2F%2Fanimeanime.jp%2Farticle%2F2018%2F07%2F06%2F38576.html&psig=AOvVaw2LIAq2B7nrkes72ri-Eax5&ust=1539517515115298)で、実行速度を比較して見ました。
まずは拾ってきたコードでの実行結果です。

- 315.3537690639496 [s]
- 309.78351306915283 [s]
- 360.8852210044861 [s]

次に、multiprocessingで並列化させたコードでの実行結果です。

- 138.09882402420044 [s]
- 138.4847867488861 [s]
- 140.33899188041687 [s]

4coreでやっているとは言え、流石に4倍速にはなりませんでしたが、2~3倍程度には速くなっていそうです。
画像の方も、問題なく綺麗になっていそうです。
![スクリーンショット 2018-10-13 20.53.49.png](https://qiita-image-store.s3.amazonaws.com/0/239586/19a0dfac-fc40-d561-1181-1a961ce2459b.png)
![スクリーンショット 2018-10-13 20.54.08.png](https://qiita-image-store.s3.amazonaws.com/0/239586/6dbed97e-4a01-5609-c155-a56520306e81.png)


[速さにこだわりの持てそうな画像](https://www.google.co.jp/url?sa=i&rct=j&q=&esrc=s&source=images&cd=&cad=rja&uact=8&ved=2ahUKEwitx5CIrIPeAhVCS7wKHWAMAXUQjRx6BAgBEAU&url=http%3A%2F%2Fseiga.nicovideo.jp%2Fseiga%2Fim1255989&psig=AOvVaw1LdLSgPKq8jh-uVFWIGUH9&ust=1539517576292545)でやって見た場合も以下の様な感じでした。

- 拾ってきたコード
  - 269.8946466445923 [s]
  - 293.7288279533386 [s]
  - 278.0401508808136 [s]
- 並列化したコード
  - 139.08394598960876 [s]
  - 133.12141489982605 [s]
  - 138.94682788848877 [s]


# まとめ
手軽に2倍速に出来た。
でも、もっと速くなって欲しい。