画像ピラミッド

目的

このチュートリアルでは
  • 画像ピラミッドについて学びます.
  • “Orapple” という新種の果物を作成するために画像ピラミッドを使います.
  • 以下の関数の使い方を学びます: cv2.pyrUp(), cv2.pyrDown()

理論

通常私達は一定サイズの画像を使います.しかし,同一画像を様々な解像度で処理をする必要がある時もあります.例えば画像中で何かを探すとき(例えば顔),画像中にどのような大きさで現れるか分かりません.そのような状況では様々な解像度の画像を用意し,全画像に対して物体検出を試みます.これらの異なる解像度を持つ画像の集合を画像ピラミッド(最大解像度の画像を下に,最小解像度の画像を上に積むとピラミッドのようになるからです)と呼びます.

画像ピラミッドには 1) ガウシアンピラミッドと 2) ラプラシアンピラミッドの2種類があります.

ガウシアンピラミッドを構築するには,まず初めに低レベル(高解像度)画像の連続している行と列を削除して高レベル(低解像度)画像を作ります.次に高レベル画像中の各画素に周囲5画素の画素値にガウシアンで重み付けした値を設定します.このようにすることで,解像度が M \times N の画像の解像度を M/2 \times N/2 に変更します.この変更によって画像の解像度が1/4に削減されます.これはOctaveと呼ばれます.同様の処理をピラミッド中で高レベル方向に向かって(低解像度方向に向かって)続けます.同じように拡張する時は,レベル毎に解像度が4倍になります.ガウシアンピラミッドを得るには cv2.pyrDown()cv2.pyrUp() 関数を使います. cv2.pyrDown() 関数を使うと画像をボカしダウンサンプルします.

higher_reso = cv2.imread('messi5.jpg')
lower_reso = cv2.pyrDown(higher_reso)

以下に画像ピラミッドの四つのレベルを示します.

Gaussian Pyramid

今度は cv2.pyrUp() 関数を使ってアップサンプルをします.

higher_reso2 = cv2.pyrUp(lower_reso)

一度ダウンサンプリングによって解像度を下げたため, higher_reso2higher_reso と同じ画像にはならない点を覚えておいてください.以下の画像は先ほどの例で得た最小の画像から作成したレベルを3段階落とした画像です.原画像と比較してみてください:

Gaussian Pyramid

ラプラシアンピラミッドはガウシアンピラミッドから作成します.このための関数は用意されていません.ラプラシアンピラミッドはエッジ画像のようなものです.大半の画素は0になるので,画像圧縮に使われます.ラプラシアンピラミッド中の1レベルは,ガウシアンピラミッド中の同一レベルの画像と,その上位レベルの画像をアップサンプルした画像の差分画像になります.ラプラシアンぴらみっとの3段階の画像を以下に示します(各画像のコントラストはエッジが見易いように調整しています):

Laplacian Pyramid

ピラミッドを使った画像のブレンディング

画像ピラミッドのアプリケーションの一つとして画像のブレンディングが挙げられます.例えば画像のスティッチング(stitching)では2枚の画像を同時に保持しておく必要がありますが,画像間の不連続性が原因で良く見えません.そのような状況ではピラミッドを使った画像のブレンディングによって滑らかな画像の結合ができます.昔からある例の一つがオレンジとリンゴの画像のブレンディングです.以下の結果を観れば何が言いたいか分かってもらえると思います:

Pyramid Blending

補足資料の最初の文献を見てください.画像のブレンディングやラプラシアンピラミッドなどの詳細に関する図入りの説明が載っています.簡単に書くと以下のような処理になります:

  1. オレンジとリンゴの2枚の画像を読み込む
  2. 各画像のガウシアンピラミッドを計算(ピラミッドのレベルは6)
  3. ガウシアンピラミッドからラプラシアンピラミッドを計算
  4. ラプラシアンピラミッド中の各レベルでリンゴの左半分とオレンジの右半分の画像をつける
  5. 結合した画像ピラミッドから元の画像を再構成する

以下にこのブレンディングを実行するためのコードを示します(簡単のために各処理を独立して行っているためより多くのメモリを使用します.必要であれば最適化してみてください).

import cv2
import numpy as np,sys

A = cv2.imread('apple.jpg')
B = cv2.imread('orange.jpg')

# generate Gaussian pyramid for A
G = A.copy()
gpA = [G]
for i in xrange(6):
    G = cv2.pyrDown(G)
    gpA.append(G)

# generate Gaussian pyramid for B
G = B.copy()
gpB = [G]
for i in xrange(6):
    G = cv2.pyrDown(G)
    gpB.append(G)

# generate Laplacian Pyramid for A
lpA = [gpA[5]]
for i in xrange(5,0,-1):
    GE = cv2.pyrUp(gpA[i])
    L = cv2.subtract(gpA[i-1],GE)
    lpA.append(L)

# generate Laplacian Pyramid for B
lpB = [gpB[5]]
for i in xrange(5,0,-1):
    GE = cv2.pyrUp(gpB[i])
    L = cv2.subtract(gpB[i-1],GE)
    lpB.append(L)

# Now add left and right halves of images in each level
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)

# now reconstruct
ls_ = LS[0]
for i in xrange(1,6):
    ls_ = cv2.pyrUp(ls_)
    ls_ = cv2.add(ls_, LS[i])

# image with direct connecting each half
real = np.hstack((A[:,:cols/2],B[:,cols/2:]))

cv2.imwrite('Pyramid_blending2.jpg',ls_)
cv2.imwrite('Direct_blending.jpg',real)

課題