cBlog

Tips for you.

合志清一先生の超解像技術をPythonで実装してみた

なぜかって? 私はただアップコンバート手法を実装したいんだ。

 

※筆者は、合志先生やその研究グループ等の関係者ではありません。掲載している実装例は[1]を参考にしたものであり、推測や間違いが含まれていると考えられます。したがって、結果は本来の性能に至らない可能性があります。実際の性能に関しては原著を参照してください。

※本記事が他者の権利を侵害している場合、記事の取り下げや修正に応じますのでご連絡ください。

 

趣味研究の結果です。できる限りニュートラルに書きたいと思います。

 

本技術は、3年以上前に発表されています。

eetimes.jp

最近はスマホにも搭載されたようです。

k-tai.impress.co.jp

 

原理

原理はシンプルです。ブロック図を参照してください。

f:id:cruller:20160306202232p:plain

これを見るとアンシャープマスキングじゃないかと思うかもしれません。違いはNLF (Non-Linear Function)部にあります。非線形関数には元々、y = x^3が提案され、その後一般化されてy = x^2が提案されているようです。変わった理由は、y = x^3だと出力がバカらしいほどになることと、エイリアシングが過剰に起きるからだと思います。今回使ったのもy = x^2です。

 

筆者の主張によると、

\displaystyle \cos^2\omega n = \frac{1 + \cos 2\omega n}{2}

\displaystyle \sin^2\omega n = \frac{1 - \cos 2\omega n}{2}

ですから、2倍の空間周波数(\omega)成分を作り出せるということが基本のようです。高周波成分(エッジやテクスチャ)をHPF (High-Pass Filter)で取り出し、非線形処理で原画像のナイキスト周波数以上の領域にコピペするといったところでしょうか。復元すべき画像の高周波成分は低周波成分の相似になっているという仮定をしていると思うのですが、その理論的根拠は薄いんじゃないかと。

 

ここで、周波数成分について整理しておきます。ディジタル画像(各位置の輝度を表す)を2次元FFTすると、周波数スペクトラム(各周波数成分の輝度を表す)になります。FFTした周波数スペクトラムは周期的です。2倍にアップサンプリングした画像のFFTを見ればわかりやすいと思います。

f:id:cruller:20160304224823p:plain

中央を面積が1/4の四角で区切ったとき、周りも同じ模様の繰り返しになっていると思います。この四角の境界が原画像のナイキスト周波数です。その外側は繰り返しなので特に意味は持ちません。対して、画像の縁はこの画像でのナイキスト周波数です。この画像の外も繰り返しています。ポリリズムです。

 

要は、通常の拡大処理は原画像のナイキスト周波数までしか成分を持たないので、ナイキスト周波数までの成分をうまく作り出せれば、元から高い解像度であったような画像を作り出せるということです。(原画像と拡大後の画像、2つの意味でナイキスト周波数が出てくるので、混乱しないように今書きました。)結果でもFFTを載せるので、このことを意識して見ていただければと思います。

 

 

実装

Python 3でのソースを載せます。

nlsp.py
# Originally proposed in [1]
# Implemented by cruller (http://yaritakunai.hatenablog.com/)
# [1] S. Gohshi and I. Echizen, "Limitations of Super Resolution Image
# Reconstruction and How to Overcome Them for a Single Image," Proc. SIGMAP
# 2013, Reykjavik, Iceland, pp. 71-78, July 2013.

import sys
from PIL import Image
import numpy as np
from scipy import ndimage

if len(sys.argv) != 3:
    print('Usage: python nlsp.py input output')
    sys.exit(1)

th = 121  # Threshold for Limiter [0..255]
alpha = 0.5  # Blend factor of the harmonics

img_in = Image.open(sys.argv[1])

# Upscaling
size = (img_in.width*2, img_in.height*2)
img_2x = img_in.resize(size, resample=Image.LANCZOS)
x = np.array(img_2x)

# High-Pass Filter
# Low-Pass Filter
lpf = np.array([[1, 2, 1],
                [2, 4, 2],
                [1, 2, 1]])/16  # Gaussian

# Convolve
x_lpf = np.empty(x.shape, dtype='uint8')
x_lpf[:, :, 0] = ndimage.convolve(x[:, :, 0], lpf)
x_lpf[:, :, 1] = ndimage.convolve(x[:, :, 1], lpf)
x_lpf[:, :, 2] = ndimage.convolve(x[:, :, 2], lpf)

# Subtractor: [-255, 255]
x_hpf = np.int16(x) - x_lpf
# /High-Pass Filter

# Non-Linear Function: [-65025, 65025]
x_nlf = np.sign(x_hpf)*np.int32(x_hpf)**2

# Limiter: [-255.0, 255.0]
c = (255 - th)/(65025 - th)
x_lmt = np.empty(x_nlf.shape)
# x_nlf < -th
x_lmt[x_nlf < -th] = c*(x_nlf[x_nlf < -th] + th) - th
# -th <= x_nlf <= th
ix = np.where((-th <= x_nlf) & (x_nlf <= th))
x_lmt[ix] = x_nlf[ix]
# th < x_nlf
x_lmt[th < x_nlf] = c*(x_nlf[th < x_nlf] - th) + th

# Adder: [-255, 510]
x_add = x + np.int16(np.around(alpha*x_lmt))

# Print clipped%
clip = x_add[x_add < 0].size + x_add[x_add > 255].size
print('Clipped: {:.2f}%'.format(clip/x_add.size*100))

# Clipping: [0, 255]
y = np.uint8(x_add)
y[x_add < 0] = 0
y[x_add > 255] = 255
img_out = Image.fromarray(y)

img_out.save(sys.argv[2])

 

LMT (Limiter)の挙動が不明でした。そこで、オーディオのリミッターよろしく以下のようなハードリミッターを設計してみました。

f:id:cruller:20160304224407p:plain

ここでも波形が歪むけど… まあいいだろ。

 

Clipped%が小さくなるようにパラメータを調整すればいいかもしれません。

 

 

結果

Lena画像やRen画像等を入力してみました。結果の位置関係は以下のようになっています。

Original Lanczos (Pillow)
NLSP(本技術) waifu2x

 

比較手法にwaifu2x [2]を持ってくるのはぶち上げすぎだろと自分でも思うのですが、サクッと出力がもらえる手法として思いついたのがこれだったので。条件は拡大率2倍で、waifu2xの設定はpopup_miku.jpg [3]以外写真、ノイズ除去なしです。

 

lena.png

f:id:cruller:20160304001745p:plain

f:id:cruller:20160304001906p:plain

 

ren.png

f:id:cruller:20160304002007p:plain

f:id:cruller:20160304002118p:plain

 

mandrill.tiff

f:id:cruller:20160304002300p:plain

f:id:cruller:20160304002416p:plain

 

popup_miku.jpg

f:id:cruller:20160304002505p:plain

f:id:cruller:20160304002616p:plain

 

lena.png

NLSPの結果で一番良かったのはこの画像なんじゃないかと。スキャンされた紙の質感、肌質が表れていると思います。ただ、帽子の糸感、青いモフモフは、くっきりしていますが強調しすぎだと思います。エッジの粒子感も気になります。waifu2xは誰にとっても違和感ないんじゃ?

 

周波数領域で見ると、NLSPもwaifu2xも同じ感じかな?

 

ren.png

正直、どの結果も残念です。元々ブラーがかった画像ではありますが、解像感が上がりません。もっと肌質が復元されることを期待していたのですが。NLSPは、髪や目に違いがわかりますが、口元に偽の輪郭が生じています。周波数領域では、NLSPとwaifu2x、第一象限がよく似ています。

 

mandrill.tiff

NLSPはどうも線が太くなりがちです。それと、後に書きますがエッジ画像を重ねる都合上明るくなってしまうんですよね。waifu2xの周波数スペクトラムは全域にわたって良好ですね。

 

popup_miku.jpg

今までは3次元画像(被写体が3次元の2次元画像)でしたが、waifu2xを持ち出すなら2次元画像についても比較しないとと持ってきました。結果は、waifu2xの圧勝ですね。線の細やかさといい、塗りの自然さといい。NLSPはジャギっているし、原理上仕方ないですがJPEGノイズを強調してしまっています。

 

何このwaifu2xの2次元画像に対する強さ。周波数領域でもエイリアシングが見られません。

 

 

結果全体では、目の光を不自然ととるかどうかで印象は変わるかと。

 

 

雑感

「重ーよカス」、ごめん。

Pythonすごい。短く書ける。

LPFもっと急峻な特性にすべきだった?

ADDでは、エッジが明るくなるのは抑えられるが全体が暗くなるので加重平均は選ばなかった。

Clippingの処理適当…

RGB毎にLMTとかClippingしてたら色相変わるよな。エッジの色が変なのたぶんそのせい。

超解像の結果ってどう評価したらいいんだろう? 縮小画像を拡大する方法では縮小アルゴリズムの影響を受けてしまうから。

waifu2x周波数領域で見ても優秀。

単純にNLSPばかにするのは違うと思う。TVセット(低演算資源で低遅延が求められる)で処理するのは主にベースバンド信号。同じ距離の画素参照でも、同一ライン間と異なるライン間ではわけが違う。フレーム間参照してたらもっと時間がかかるし、メモリも要る。はじめからTVセットを相手にしているみたいなので、waifu2xとかMadVRとは背景が違うと思う。

 

 

次は、うまくいったらオリジナル(全くのではないけど)の手法実装したいと思います。

 

 

参考

[1] http://www.nl-superresolution.com/white_papers.html(主に1.、2.、5.が参考になる)

[2] http://waifu2x.udp.jp

[3] "For Creators," http://piapro.net/en_for_creators.html