ヒストグラム その4: ヒストグラムの逆投影法

目的

このチュートリアルではヒストグラムの逆投影法を学びます.

理論

Michael J. Swain , Dana H. Ballard らの Indexing via color histograms という論文で提案された方法です.

簡潔に言うと何なのでしょうか? 画像の領域分割や画像中の対象物体の検出に使われます.簡単に言うと,各画素が対象物体に属している確率を表す入力画像と同じサイズで単相の画像を作成します.さらに言うと,出力画像中で対象物体のように見える部分は白く,それ以外の部分は黒くなります.これは直観的な説明です(これ以上単純に説明できません).ヒストグラム逆投影法はcamshiftアルゴリズムなどに使われます.

どのように実現するのでしょうか? 対象物体(今回では地面,選手,それ以外です)を含む画像のヒストグラムを作成します.よりよい結果を得るには,対象物体は出来る限り画像全体を占める必要があります.対象物体の色はグレースケール画像よりカラー画像を見た方が定義しやすいため,グレースケールヒストグラム(1次元ヒストグラム)よりカラーヒストグラム(2次元ヒストグラム)が好まれます.次にこのヒストグラムを画像に対して逆投影します.別の言い方をすると,各画素がグラウンドに属しているであろう確率を計算し,結果を見せます.妥当なしきい値処理と組み合わせると,画像中からグラウンドのみを検出できます.

Numpyのアルゴリズム

1. 初めに検出したい物体のカラーヒストグラム(‘M’ とします)を計算し,検出に使う画像を ‘I’ とします.

import cv2
import numpy as np
from matplotlib import pyplot as plt

#roi is the object or region of object we need to find
roi = cv2.imread('rose_red.png')
hsv = cv2.cvtColor(roi,cv2.COLOR_BGR2HSV)

#target is the image we search in
target = cv2.imread('rose.png')
hsvt = cv2.cvtColor(target,cv2.COLOR_BGR2HSV)

# Find the histograms using calcHist. Can be done with np.histogram2d also
M = cv2.calcHist([hsv],[0, 1], None, [180, 256], [0, 180, 0, 256] )
I = cv2.calcHist([hsvt],[0, 1], None, [180, 256], [0, 180, 0, 256] )

2. 比率 R = \frac{M}{I} を計算します.次に計算したRを逆投影します,つまりRをパレットとして使い各画素に対して対象物体であろう確率を計算します B(x,y) = R[h(x,y),s(x,y)] ここでhはHue,sはsaturationを表します.その後以下の条件を適用します B(x,y) = min[B(x,y), 1]

h,s,v = cv2.split(hsvt)
B = R[h.ravel(),s.ravel()]
B = np.minimum(B,1)
B = B.reshape(hsvt.shape[:2])

3. 次に円形カーネルを使ってフィルタリングします, B = D \ast B, ここでDは円形カーネルを表します.

disc = cv2.getStructuringElement(cv2.MORPH_ELLIPSE,(5,5))
cv2.filter2D(B,-1,disc,B)
B = np.uint8(B)
cv2.normalize(B,B,0,255,cv2.NORM_MINMAX)

4. 最大値を示す場所が対象物体の場所になるわけです.画像中の領域を検出したいのであれば,良い結果を得るために適したしきい値を設定してください.

ret,thresh = cv2.threshold(B,50,255,0)

以上です!!

OpenCVの逆投影法

OpenCVを使うのであれば cv2.calcBackProject() 関数を使います.指定するパラメータは cv2.calcHist() 関数とほとんど同じです.パラメータの内の一つは対象物体のヒストグラムなので,計算しなければいけません.また,対象物体のヒストグラムは関数に渡す前に正規化をしなければいけません.出力は確率を表す画像になります.個の出力に対して円形カーネルをconvolutionし,しきい値処理をします.以下にコードと結果を示します.:

import cv2
import numpy as np

roi = cv2.imread('rose_red.png')
hsv = cv2.cvtColor(roi,cv2.COLOR_BGR2HSV)

target = cv2.imread('rose.png')
hsvt = cv2.cvtColor(target,cv2.COLOR_BGR2HSV)

# calculating object histogram
roihist = cv2.calcHist([hsv],[0, 1], None, [180, 256], [0, 180, 0, 256] )

# normalize histogram and apply backprojection
cv2.normalize(roihist,roihist,0,255,cv2.NORM_MINMAX)
dst = cv2.calcBackProject([hsvt],[0,1],roihist,[0,180,0,256],1)

# Now convolute with circular disc
disc = cv2.getStructuringElement(cv2.MORPH_ELLIPSE,(5,5))
cv2.filter2D(dst,-1,disc,dst)

# threshold and binary AND
ret,thresh = cv2.threshold(dst,50,255,0)
thresh = cv2.merge((thresh,thresh,thresh))
res = cv2.bitwise_and(target,thresh)

res = np.vstack((target,thresh,res))
cv2.imwrite('res.jpg',res)

以下に結果を示します.グラウンド全体を検出するために,青い長方形で囲った領域を対象物体のヒストグラムの計算に使いました.

Histogram Backprojection in OpenCV

補足資料

  1. “Indexing via color histograms”, Swain, Michael J. , Third international conference on computer vision,1990.

課題