目的
本章では、
理論
通常、私たちは一定サイズの画像を扱う。しかし、場合によっては(同じ)画像を異なる解像度で扱う必要がある。例えば、画像内で顔のような何かを探索する際、その対象がその画像内でどのサイズで存在するかは分からない。その場合、同じ画像を異なる解像度で集合として作成し、それらすべての中で対象を探索する必要がある。これらの異なる解像度の画像の集合は画像ピラミッドと呼ばれる(最高解像度の画像を一番下に、最低解像度の画像を一番上にしてスタックに積むと、ピラミッドのように見えるからである)。
画像ピラミッドには2種類ある。1) ガウシアンピラミッド と 2) ラプラシアンピラミッド である
ガウシアンピラミッドの上位レベル(低解像度)は、下位レベル(高解像度)の画像において連続する行と列を除去することで形成される。そして上位レベルの各ピクセルは、下位レベルの5ピクセルからの寄与にガウシアン重みを掛けて形成される。これにより、\(M \times N\) の画像は \(M/2 \times N/2\) の画像になる。したがって面積は元の面積の4分の1に縮小される。これはオクターブと呼ばれる。ピラミッドを上に進む(すなわち解像度が下がる)につれて同じパターンが続く。同様に、拡大する際には各レベルで面積が4倍になる。ガウシアンピラミッドは cv.pyrDown() と cv.pyrUp() 関数を使って求めることができる。
assert img is not None, "file could not be read, check with os.path.exists()"
Mat imread(const String &filename, int flags=IMREAD_COLOR_BGR)
Loads an image from a file.
void pyrDown(InputArray src, OutputArray dst, const Size &dstsize=Size(), int borderType=BORDER_DEFAULT)
Blurs an image and downsamples it.
以下は画像ピラミッドの4つのレベルである。
image
ここで cv.pyrUp() 関数を使って画像ピラミッドを下っていける。
void pyrUp(InputArray src, OutputArray dst, const Size &dstsize=Size(), int borderType=BORDER_DEFAULT)
Upsamples an image and then blurs it.
覚えておくこと、higher_reso2はhigher_resoと等しくない。解像度を下げると情報が失われるためである。以下の画像は、前の例で得た最小の画像から作成したピラミッドを3レベル下ったものである。これを元画像と比較してみよう:
image
ラプラシアンピラミッドはガウシアンピラミッドから形成される。そのための専用の関数はない。ラプラシアンピラミッドの画像はエッジ画像のようなものである。その要素のほとんどはゼロである。これらは画像圧縮に使われる。ラプラシアンピラミッドのあるレベルは、ガウシアンピラミッドのそのレベルと、ガウシアンピラミッドの1つ上のレベルを拡大したものとの差分によって形成される。ラプラシアンの3つのレベルは以下のようになる(内容を見やすくするためにコントラストを調整している):
image
ピラミッドを用いた画像ブレンディング
ピラミッドの応用の一つに画像ブレンディングがある。例えば画像スティッチングでは、2枚の画像を重ね合わせる必要があるが、画像間の不連続性のためにうまく見えないことがある。そのような場合、ピラミッドを用いた画像ブレンディングを使うと、画像に多くのデータを残すことなくシームレスなブレンディングが得られる。この古典的な例の一つが、オレンジとリンゴという2つの果物のブレンディングである。何を言っているのか理解するために、今すぐ結果を見てみよう:
image
まず追加リソースの最初の参考文献を確認してほしい。そこには画像ブレンディングやラプラシアンピラミッドなどの図解付きの詳細がすべて載っている。簡単に言えば、次のように行う:
- リンゴとオレンジの2枚の画像を読み込む
- リンゴとオレンジのガウシアンピラミッドを求める(この例ではレベル数は6)
- ガウシアンピラミッドから、それらのラプラシアンピラミッドを求める
- 次に、ラプラシアンピラミッドの各レベルで、リンゴの左半分とオレンジの右半分を結合する
- 最後に、この結合した画像ピラミッドから、元の画像を再構成する。
以下が完全なコードである。(簡潔さのため、各ステップを個別に行っているが、これはより多くのメモリを消費する可能性がある。必要に応じて最適化できる。)
import cv2 as cv
import numpy as np,sys
assert A is not None, "file could not be read, check with os.path.exists()"
assert B is not None, "file could not be read, check with os.path.exists()"
G = A.copy()
gpA = [G]
for i in range(6):
gpA.append(G)
G = B.copy()
gpB = [G]
for i in range(6):
gpB.append(G)
lpA = [gpA[5]]
for i in range(5,0,-1):
lpA.append(L)
lpB = [gpB[5]]
for i in range(5,0,-1):
lpB.append(L)
LS = []
for la,lb in zip(lpA,lpB):
rows,cols,dpt = la.shape
ls = np.hstack((la[:,0:cols//2], lb[:,cols//2:]))
LS.append(ls)
ls_ = LS[0]
for i in range(1,6):
real = np.hstack((A[:,:cols//2],B[:,cols//2:]))
void add(InputArray src1, InputArray src2, OutputArray dst, InputArray mask=noArray(), int dtype=-1)
Calculates the per-element sum of two arrays or an array and a scalar.
void subtract(InputArray src1, InputArray src2, OutputArray dst, InputArray mask=noArray(), int dtype=-1)
Calculates the per-element difference between two arrays or array and a scalar.
bool imwrite(const String &filename, InputArray img, const std::vector< int > ¶ms=std::vector< int >())
Saves an image to a specified file.
追加リソース
- 画像ブレンディング