画像処理をしたよ

昔とった杵柄で,画像処理なぞしてみた.
成果は近日中に某所で公開される予定.

そこで,numpyで表現されているRGBを効率良くHSVに変換するコードを書いたので公開しておく.

前提
行列imのim[y, x, 0], im[y, x, 1], im[y, x, 2]にそれぞれ座標y, xに対応するR, G, B値が入っている.

さて,RGBからHSVへの変換はナイーブにやろうとするとループが必要となる.
しかし,numpyはPythonなのでループを回してしまうと非常に重い.
例えば,単純な行列の足し算C←A + Bについて,

for i in xrange(A.shape[0]):
  for j in xrange(A.shape[1]):
    C[i, j] = A[i, j] + B[i, j]

などとやろうものなら,とんでもない負荷になる.
けど,numpyではこの例はC = A + Bのように書けて,これを使うとPythonインタプリタを通さずにループを回してくれるから効率が良い.こういうふうにnumpyでネイティブ処理される表記を使うことが非常に重要だ.
今回はRGB→HSV変換をなんとかしてnumpyに高速にやってもらえる方法で書いた.

    im = im.astype(float) / 256.0

    imV = im.max(2)
    imS = (imV - im.min(2)) / (imV + EPSILON)

    maxchidx = im.argmax(2)

    imH = zeros((im.shape[0], im.shape[1]))
    imH += (maxchidx==0) * ((im[::,::,1] - im[::,::,2]) * 60.0 / (imS + EPSILON))
    imH += (maxchidx==1) * (120.0 + (im[::,::,2] - im[::,::,0]) * 60.0 / (imS + EPSILON))
    imH += (maxchidx==2) * (240.0 + (im[::,::,0] - im[::,::,1]) * 60.0 / (imS + EPSILON))
    imH[isinf(imH)] = 0.0
    imH = imH % 360.0
    imH /= 360.0

定義通りV値 (imV)はRGBの最大値,つまり[y, x, c]の順でインデックスがついてる行列のcの軸でmaxを取ってやれば良い,ということでmax(2).
imSはV値とRGBの最小値の差をV値で割ったもの.Vが0の時は0になって欲しいので微少量足している. (EPSILONはグローバル変数として定義した)

問題はH値.V == Rの時とV == Gの時と,V == Bの時で場合分けがいる.
今回はnumpyのbool行列を使って場合分けを実現した.
最初に0行列を用意しておいて,(maxchidx==0)行列とV==Rの時のimHの値を格納した行列を乗算したものを足し,(maxchidx==1)行列とV==Gの時のimHの値を格納した行列を乗算したものを足し,(maxchidx==2)行列とV==Bの時のimHの値を格納した行列を乗算したものを足す.

このアプローチではCで書くのに比べてimHの計算に3倍計算が必要になる.V == Rの時,本来使われることのないV == Gの場合の値や,V == Bの場合の値も計算してるからだ.
(さらに,Cで書けばim.max(2)とim.argmax(2)を同時にできる)

けど,それでもPythonでループやるのより10倍くらい速くなる.こういうのをバッドノウハウと言うのだろう.
僕は良くしらないが,MATLABの世界の人もこういうバッドノウハウを蓄えていると聞く.

もちろん,本来はCでガリガリ書くのが一番効率的なのだけど,ちょっと今回はコンパイラが動かせるかどうか分からなかったので...