【画像拡大】Cifar10画像をいろいろな手法でUpconversionして遊んでみた♬

今回は画像拡大について、調べたのでまとめておこうと思う。
多くの手法が提案されているが、ここではOpencvのcv2.resize, PILのImage.resize, 、Raisr、およびWaifu2xを試してみた。
やってみるとそれぞれなかなかの精度が出ているので、それぞれの手法でCifar10の画像を拡大して遊んでみた。
そして、まゆゆの画像(128x128)を４K画像(4096x4096)までアップしてみたら、大きなまゆゆが出現した。
なお、多くのアプリがノイズ除去の機能も持っているが、ここでは画像拡大についてのみ記載する。

【参考】
・①[【画像処理】バイリニア補間法の原理・計算式](https://algorithm.joho.info/image-processing/bi-linear-interpolation/)
・②[Python, Pillowで画像を一括リサイズ（拡大・縮小）@note.nkmk.me](https://note.nkmk.me/python-pillow-image-resize/)
・③[A Python implementation of RAISR](https://github.com/movehand/raisr)
・④[【Python】MacBookAirのローカル環境でPython版のwaifu2xを試してみた@歩いたら休め](https://kiito.hatenablog.com/entry/2015/05/30/181700)
###簡単な理論
ここでは簡単に理論を記載する。
つまり、画像を拡大すると、一つのピクセルがそれを取り巻くいくつかのピクセルに増殖する。その増殖したピクセルのRGB値をいくつにするかという問題である。
これは、一次元ならいわゆる補完問題であり、その近傍の値をフィッティングする曲線上の点を振り分ければよいことがすぐわかる。
一番簡単な補完曲線は直線近似であり、単なる平均ということになる。
※RGBの場合は、それぞれに分解して補完曲線を求めればよい
二次元だとこれが補完二次曲面になる。この二次曲面上の点を補完値とすればよい。
したがって、思考するのは極めて容易である。
しかし、この補完二次曲面を求める方法は単純とは言えない。それはそのエリアを取り巻く４点だけでその補完値の周りの局面が決定するわけではなく、より精度を上げようとすればさらにその周りの点まで拡大して曲面を求める必要が出てくるが、これは時に難しい問題となる。
ということで、一番簡単な場合は、周りの４つの点からそのエリアの内部の点の補完値を求めることである。これは周りの４点の画素値が$I(x,y)$, $I(x+1,y)$, $I(x,y+1)$, $I(x+1,y+1)$で囲まれたエリア内の任意の点の画素値は以下の式で求められる。参考①より

```math
I(x+dx, y+dy)=(1-dx)(1-dy)I(x,y)+(1-dx)dyI(x,y+1)+dx(1-dy)I(x+1,y)+dxdyI(x+1,y+1)
```
さらに詳細な解説が以下の参考にあります。
【参考】
・[ImageMagick リサイズ補間アルゴリズム](https://qiita.com/yoya/items/b1590de289b623f18639)
・[画素の補間（Nearest neighbor,Bilinear,Bicubic）@画像処理ソリューション](http://imagingsolution.blog.fc2.com/blog-entry-142.html)

###やったこと
・Opencvによるresize
・PILによるresize
・RAISRによるresize
・Waifu2xによるresize
・Cifar10データの（256,256,3）への変換
###・Opencvによるresize
以下は、'./results/*.jpg'のjpgファイルを読み込みます。

```py
# -*- coding: utf-8 -*-
import cv2
import os
import glob

def main():
    files = glob.glob('./results/*.jpg')
    for f in files:
        # 入力画像の読み込み
        img = cv2.imread(f)
```
以下はwidth, heightをそれぞれ2倍にしています。補完はCUBICで実施しています。
ほかにもLINEARなどが選べますが、CUBICが綺麗でした。

```py
        dst1 = cv2.resize(img, (int(img.shape[1]*2), int(img.shape[0]*2)), interpolation=cv2.INTER_CUBIC) #interpolation = cv2.INTER_CUBIC  #cv2.INTER_LINEAR
        # 結果を出力
        ftitle, fext = os.path.splitext(f)
        cv2.imwrite(ftitle+ '_color' + fext, dst1)
if __name__ == "__main__":
    main()
```
【参考】
[Geometric Image Transformations@OpenCV](https://docs.opencv.org/2.4/modules/imgproc/doc/geometric_transformations.html#resize)
より以下が使えます。
interpolation method:
◦INTER_NEAREST - a nearest-neighbor interpolation
◦INTER_LINEAR - a bilinear interpolation (used by default)
◦INTER_AREA - resampling using pixel area relation. It may be a preferred method for image decimation, as it gives moire’-free results. But when the image is zoomed, it is similar to the INTER_NEAREST method.
◦INTER_CUBIC - a bicubic interpolation over 4x4 pixel neighborhood
◦INTER_LANCZOS4 - a Lanczos interpolation over 8x8 pixel neighborhood

###・PILによるresize
コードとしては上記とほぼ同じです。

```py 
# -*- coding: utf-8 -*-
import os
import glob
from PIL import Image

files = glob.glob('./caps_figures/X_train/*.jpg')
for f in files:
    #入力画像の読み込み
    img = Image.open(f)
```
以下の部分だけが異なりますが、ほぼ似ています。
こちらもLANCZOSが使えます。

```py
    img_resize = img.resize((int(img.width*2), int(img.height*2)), Image.BICUBIC) #- NEAREST- BOX- BILINEAR- HAMMING- BICUBIC- LANCZOS
    ftitle, fext = os.path.splitext(f)
    img_resize.save(ftitle + '_double' + fext)
```
###・RAISRによるresize
使い方は、
１．上記GithubからZipファイルをダウンロードして解凍。
２．trainというDirに綺麗な画像を入れておく
３．２の状態で`>python train.py`再学習は `>python train.py -q q.p -v v.p`
    *In the training stage, the program virtually downscales the high  resolution images. The program then trains the model using the downscaled version images and the original HR images.*
**学習場面では、プログラムがTrainに入れたハイレゾの画像をダウンスケールする。このダウンスケールした画像とオリジナルなハイレゾ画像を使ってモデルを学習する。**
４．画像生成は`>python test.py`というコマンドを実行すると、'test'というDirに置いたファイルに基いて'results'というDirに生成される。
５．これらのディレクトリ'train'、'test'、そして'results'はtrain.py及び'test.py'でハードコーディングされているので、そこを変更すれば変更できる
また、出力ファイルも当初はbmpとなっているが、jpgやpngなどに変更できる。
使えるファイルは、以下のとおり
('.bmp', '.dib', '.png', '.jpg', '.jpeg', '.pbm', '.pgm', '.ppm', '.tif', '.tiff'))
出力部分は以下のとおり

```py
cv2.imwrite('results/' + os.path.splitext(os.path.basename(image))[0] + '_result.jpg', cv2.cvtColor(result, cv2.COLOR_RGB2BGR)) #'_result.bmp'
```
コードは以下のリンクのとおり
[raisr/train.py](https://github.com/movehand/raisr/blob/master/train.py)
[raisr/test.py](https://github.com/movehand/raisr/blob/master/test.py)
６．当初エラーをはいて止まってしまったが、以下を変更したら無事に動きました。以下の参考を見るとoptionalになっており、ここでは利用していないので指定は不要。

```py
LR = transform.resize(grayorigin, (floor((height+1)/2),floor((width+1)/2)), mode='reflect')  #, anti_aliasing=False)
```
【参考】
・[skimage.transform.resize](http://scikit-image.org/docs/dev/api/skimage.transform.html#resize)
###・Waifu2xによるresize
これはもう動かしただけです。動かし方は以下のコマンドで動きました。

```
python waifu2x.py infile outfile scale2.0x_model.json
```
コードはおまけの掲載のとおりで、下記の参考②から入手しています。
また、コードに記載されていますが、一応
#### Model export script: https://mrcn.st/t/export_model.lua (needs a working waifu2x install)
とのことなので、以下参考③のtoolsからexport_model.luaをダウンロードしています。
このPython版だと、精度は上記のRAISRより悪い感じがしました。そして時間は一番かかるような気がします。
###結果
ここまでの経過だと、速度的な観点と精度もOpenCVやPILは健闘していると思います。RAISRもそれなりにはスムースで綺麗な絵がでるので使えそうです。
ということで、Cifar10のデータを（320,320,3）に拡大しました。
元画像
![809.jpg](https://qiita-image-store.s3.amazonaws.com/0/233744/739b0c90-d6af-f7f7-c219-99ae12f641a2.jpeg)
CV2.resize(cubic)
(320,320,3)
![809.jpg](https://qiita-image-store.s3.amazonaws.com/0/233744/7bb68d7e-2604-a7f1-289f-6d3d40a66027.jpeg)
(256,256,3)
![809.jpg](https://qiita-image-store.s3.amazonaws.com/0/233744/823a2fa1-836d-c0b2-2761-a444275e0dc3.jpeg)
Image.resize(cubic)
![809_double.jpg](https://qiita-image-store.s3.amazonaws.com/0/233744/52b872b8-5c59-ff6f-1b4a-2bc759414bf2.jpeg)

普通にResizeすると
CV2.resize(linear)
![809.jpg](https://qiita-image-store.s3.amazonaws.com/0/233744/6fa39454-1a05-b204-9dde-fa794d65f931.jpeg)
Raisr;10倍ができなかった（２倍の繰り返しで実施）
![809_result_result_result.jpg](https://qiita-image-store.s3.amazonaws.com/0/233744/bc3e4d27-0ca6-52d3-6566-38d5db8ad8ce.jpeg)
Waifu2x;他と同様一度に10x10倍しているが悪い、modelの最適化が必要なようだ
![809_waifu2x.jpg](https://qiita-image-store.s3.amazonaws.com/0/233744/7de00410-69d1-8f39-71d1-4a17ef3cd972.jpeg)
ペイントブラシでresize
![809_resize_bypaint.jpg](https://qiita-image-store.s3.amazonaws.com/0/233744/64d764e0-e955-62d5-8dbc-3dc306bc6153.jpeg)
###・Cifar10データの（256,256,3）への変換
以下のとおり、実施した。案外綺麗な画像で驚いている。
X_train　50000枚（256，256，3）
![Cifar10_image.jpg](https://qiita-image-store.s3.amazonaws.com/0/233744/a95be832-4727-a2cc-ebf4-9ab935d0ca6e.jpeg)
###まとめ
・いろいろな手法で画像拡大をやってみた
・CV2がスピードもあり使いやすかった
・cv2でCifar10の60000枚を(256,256,3)に変換した

・Cifar10の拡大画像が綺麗なので、別途自動分類していろいろ使おう
・このRAISRの発想はいろいろ試せそうだ
・この分野にDLを分かりやすく利用したいと思う
###おまけ
【参考】
・①[nagadomi/waifu2x](https://github.com/nagadomi/waifu2x)
・②[cl-waifu2x/tools/waifu2x.py](https://github.com/marcan/cl-waifu2x/blob/master/tools/waifu2x.py)
・③[marcan/cl-waifu2x/tools/](https://github.com/marcan/cl-waifu2x/tree/master/tools)

```py
# waifu2x.py by @marcan42 based on https://github.com/nagadomi/waifu2x
# MIT license, see https://github.com/nagadomi/waifu2x/blob/master/LICENSE
import json, sys, numpy as np
from scipy import misc, signal
from PIL import Image
# Model export script: https://mrcn.st/t/export_model.lua (needs a working waifu2x install)
infile, outfile, modelpath = sys.argv[1:]
model = json.load(open(modelpath))
im = Image.open(infile).convert("YCbCr")
im = misc.fromimage(im.resize((2*im.size[0], 2*im.size[1]), resample=Image.NEAREST)).astype("float32")
planes = [np.pad(im[:,:,0], len(model), "edge") / 255.0]
count = sum(step["nInputPlane"] * step["nOutputPlane"] for step in model)
progress = 0
for step in model:
    assert step["nInputPlane"] == len(planes)
    assert step["nOutputPlane"] == len(step["weight"]) == len(step["bias"])
    o_planes = []
    for bias, weights in zip(step["bias"], step["weight"]):
        partial = None
        for ip, kernel in zip(planes, weights):
            p = signal.convolve2d(ip, np.float32(kernel), "valid")
            if partial is None:
                partial = p
            else:
                partial += p
            progress += 1
            sys.stderr.write("\r%.1f%%..." % (100 * progress / float(count)))
        partial += np.float32(bias)
        o_planes.append(partial)
    planes = [np.maximum(p, 0) + 0.1 * np.minimum(p, 0) for p in o_planes]

assert len(planes) == 1
im[:,:,0] = np.clip(planes[0], 0, 1) * 255
misc.toimage(im, mode="YCbCr").convert("RGB").save(outfile)
sys.stderr.write("Done\n")
```