読者です 読者をやめる 読者になる 読者になる

君は誰だ(画像処理)

画像処理の修行中

画像処理入門:第1話-画像の縦横2倍拡大処理-

本記事はKogakuin Univ Advent Calendar 2016の21日目の記事です。
www.adventar.org
本記事では画像を縦横2倍に拡大するアルゴリズムpythonを用いて解説する。簡単な内容である為、何の役にも立たないかもしれないが、許して欲しい。

はじめに

画像拡大は各プログラム言語のライブラリのimresizeなどの関数で実装されているだろう。しかしながら、それらの関数には不正確な結果を導き出す物も少なくない。その為、趣味ならともかく、研究における実験画像作成時に拡大を行う時には自作のプログラムを用いるのが望ましい。いつもはMATLABを用いているが、MATLABを使用できる環境がある人は限られる為、今回はpythonを用いて作成した。(MATLAB版も加えて後日更新するかもしれない)整数倍の拡大では元からある画素の間に新たな画素を補間する。
f:id:AKSKA:20161221181625j:plain
使用ライブラリ

from PIL import Image;[f:id:AKSKA:20161221231727j:plain]
import numpy as np
from scipy import signal

実験画像の取得

使用画像は好きな画像を用いるといいが、今回は画像処理研究で最も有名な人物画像の1枚であるLenaの画像を用いる。以下のリンクはLenaを初め、画像処理に用いられる有名な画像のデータベースである。
SIPI Image Database
早速、画像を読み込む。

#read image
I = np.array(Image.open('image/Lenna.tiff'));

f:id:AKSKA:20161221061305j:plain

輝度変換

colorよりもgrayの方が問題が単純(正直変わらないが)である為、今回はgrayscaleに画像を変換、8bit量子化し、用いることにする。変換式はBT.709を採用した。

#rgb to grayscale(BT.709)
y = np.uint8(0.2126 * I[:, :, 0] + 0.7152 * I[:, :, 1] + 0.0722 * I[:, :, 2])
#output grayscale
Image.fromarray(np.uint8(y)).save('gray.bmp')

f:id:AKSKA:20161221231727j:plain

単純拡大

ここでいう単純拡大とは補間は行わず、原画像に存在する画素と0が交互となる画像を生成する。単純拡大を行い、空いている画素を補間することが最も明確で単純な2x2拡大アルゴリズムである。拡大して見れば分かり易いが、黒の格子と原画像の画素で画像が構成されている。プログラムはもっと効率の良い書き方があると思うが、思いつかなかった。

#make simple expand2x2 image
expandImage = np.array(sum(zip(np.array((sum(zip(y, np.zeros((len(y), len(y[0])))), ()))).transpose(), np.zeros((len(y[0]), len(y)*2))), ())).transpose()
#output simple expand2x2 image
Image.fromarray(np.uint8(y)).save('gray.bmp')

f:id:AKSKA:20161221062133j:plain

画素補間

画素補間には有名なアルゴリズムであるbilinear補間とLanczos3補間の2種についてプログラムを作成した。画素補間は単純拡大画像に対する畳み込みによって行うことができ、今回作成したアルゴリズムにおいても同様に行うことができる。基本的には1次元のフィルタを行と列方向にそれぞれ畳み込む。また、畳み込み時に画像サイズが変わらないようにpaddingを行う。今回はフィルタを2次元にしてまとめてたたみ込んでいる。



bilinear補間:近傍4画素を距離に応じて重み付けするアルゴリズムである。2倍拡大時には[1/2, 1, 1/2]のフィルタで定義される。

#make LPF(bilinear)
LPF1 = np.array([1/2, 1, 1/2]).reshape(3, 1)
LPF1 = LPF1 * LPF1.transpose()
#padding
bilinear = np.pad(expandImage, (1, 1), "reflect")
#convolution(interpolation)
bilinear = signal.convolve2d(bilinear, LPF1, 'valid')
#output bilinear expand2x2 image
Image.fromarray(np.uint8(bilinear)).save('bilinear.bmp')

f:id:AKSKA:20161221212617j:plain



Lanczos3補間:近傍36画素についてLanczos kernelを用いて重み付けするアルゴリズムである。Lanczos3では下記数式にn=3を代入した数式を使用する。また、0, 1については誤差をなくす為、別途代入をした。
f:id:AKSKA:20161221220228j:plain

#make LPF(Lanczos3)
x = np.arange(-2.5, 3, 0.5)
LPF2 = (np.sin(abs(x) * np.pi) / (x * np.pi) * np.sin(abs(x) * np.pi / 3) / (x * np.pi / 3)).reshape(11, 1)
LPF2[1::2] = 0; LPF2 = LPF2 / sum(LPF2); LPF2[5] = 1
LPF2 = LPF2 * LPF2.transpose()
#padding
lanczos3 = np.pad(expandImage, (5, 5), "reflect")
#convolution(interpolation)
lanczos3 = signal.convolve2d(lanczos3, LPF2, 'valid')
#output lanczos3 expand2x2 image
Image.fromarray(np.uint8(lanczos3)).save('lanczos3.bmp')

f:id:AKSKA:20161221212644j:plain



bilinearは余り精度は高くないが、Lanczos3は良い精度の補間を行うことで知られている。多分この2種のアルゴリズムであれば目視で優劣がわかると思うので確認して欲しい。

最後に

今回は整数倍拡大を扱ったが、現実では実数倍拡大を行う場合は多い。距離に応じた適応型フィルタを用いるだけだが、またいつか記事を書くかもしれない。また、本記事で間違っている箇所や、質問、その他コメントは大歓迎である。多分返事をする。

advent calendarの皆様へ
簡単な記事しか書けなくてすみません。

以上