SSブログ

OpenCVとCAEmitterLayerを使った魔法の杖効果 [OpenCV関係]

昨日、こんなのを作って遊んでいた。iMacのFaceTimeカメラのリアルタイム画像の上に、いわゆる魔法の杖効果(Magic Wand Effect)をオーバーラップする。こんなの。


これはOpenCVにあるLucas-Kanade法によるオプティカルフローで動きを検出して、それに対してOS Xの2次元パーティクルシステムであるCAEmitterLayerでパーティクルを飛ばしたもの。

「プリキュア、ドレスアップ!」

やっぱりおっさんでは絵的に醜い。バックも殺風景だし、YouTubeの圧縮のせいもあってなんかほこりが立ってるみたい。アプリのウィンドウの上ではもっときれいなんだけど。もうちょっと圧縮率の低いムービーをここに置いておくのでダウンロードしてみて欲しい。H.264圧縮のQuickTime形式、280x240の18秒間で2.7MB。

うーん、しかし、プリキュアの変身シーンよりもパーティクルの量は多いのに、なんか地味だな。Glitter(キラキラッ、ちゅうやつ)が無いからかな。CAEmitterLayerにそういうpropertyはないな。どうするればいいのかな。

もともとこないだからやってるポリゴンレンダラに組み込みたくて、顔認識や特徴点抽出を勉強していた。SURFを使いたかったけど、実際にOpenCVに実装されたSURFでやってみると処理がかなり重かったので、次善の策としてLK法によるオプティカルフローを使うことにした。LK法も特徴点を追いかけるという目的ではSURFなんかと一致するし、特徴点として「かどっこ」を選び出すという意味では同じことをしていると言える。SURFでは特徴量を多次元に記述するので、離れたところでも対応が見つけやすいが、LK法では比較的近いという制限がつく。

実際になにに使いたいか、というのはできあがってからのお楽しみとして、昨日OpenCVに実装されたLK法を使ったオプティカルフロー検出のcvCalcOpticalFlowPyrLK()を使ってみた。前も書いたけど僕はマルチプラットフォームには興味がないので、OS X専用で、したがってhighguiを使わず、計算処理だけをOpenCVにやらせることにしている。

ただ動かすだけではつまらないので、フローベクトルがわかりやすいように、またなにか魅力的に見えるようにキラキラ(パーティクル)を表示することにした。OS Xには(そしてもちろんiOSにも)CAEmitterLayerという2次元パーティクルシステム表示用のオブジェクトがある。名前からわかるようにCore Animationフレームワークの一部で、CALayerのサブクラスである。

CAEmitterLayerで生成できるパーティクルは3次元ではなく、パーティクルそのものは2次元座標しか持っていなくて、それを3次元に投影することで、擬似的な2.5次元表示とでも言うものになっている。Core Animationの一部であることからわかるようにこれは本来、視認性を高めるための視覚効果という位置づけなので、厳密な3次元展開やフォトリアリスティックな表示を狙ったものではなくて、負荷が軽く簡単に使えるということを目指している、というように思える。

まず、FaceTimeカメラの画像をCALayerを使って表示するNSViewのサブクラスを定義する。
@interface CSParticleFlowView : NSView {
    CGImageRef  image;
    BOOL        mirrored;
}

- (void)setOpticalFlow:(CSOpticalFlow *)opticalFlow;
- (void)setPreviewLayer:(AVCaptureVideoPreviewLayer *)previewLayer;
NSViewにCALayerを持たせて、その一番下のサブレイヤとしてAVCaptureVideoPreviewLayerを置いて、その上にパーティクル用のCAEmitterLayerを重ねて表示する。imageはCAEmitterCellのcontent用。

CSParticleFlowViewに渡されるCSOpticalFlowというオブジェクトはcvCalcOpticalFlowPyrLK()をラップするもので、カメラからのひとつ前のフレームと現在のフレームとの間のオプティカルフローを、cvCalcOpticalFlowPyrLK()で計算して、フレームごとのフローベクトルの配列を保持している。誤検出を排除するための工夫をふたつほど導入してあって、ナマのcvCalcOpticalFlowPyrLK()よりはフローベクトルは減っているが、見てわかるように誤検出はほとんど無くなっている。

CAEmitterLayerはそのまま使うのではなくて、今回そのサブクラスを作った。
@interface CSTemporalEmitter : CAEmitterLayer {
    CGFloat     xstep;
    CGFloat     ystep;
}

- (void)setLayerLifetime:(double)layerLifetime;
- (void)moveFrom:(NSPoint)start toward:(NSPoint)toward;
@end
これはなにかというと、layerLifetime秒過ぎたときに自分自身をsuperLayerから取り除いてreleaseするという時限装置つきのもの。フローベクトルひとつにこのレイヤひとつが対応して、パーティクルを表示する。フローベクトルの方向にパーティクルが動いてその周辺にちらばるようにした。フローベクトルが長い、つまり動きが速いときはパーティクルの量を増やしたりしている。ちなみにCAEmitterLayerにlifetimeというpropertyがあるが、それはCAEmitterCellの寿命の係数になっている。今回は疑似3次元は使わずフラットな2次元表現にした。

CAEmitterLayerの使い方がイマイチ良くわからないので、こうしたけど、CSTemporalEmitterのオブジェクトが大量に作られることになるので、あまり効率は良くない。もっと賢い方法があるかもしれないけど、今回はお遊びなのでこれでよしとする。まあ、いつもお遊びではあるんだけど。

あとは、CAEmitterLayerのたくさんあるパラメータを適当に設定して、いかにもそれらしいパーティクルにするだけ。パーティクルのもと(CAEmitterCellのオブジェクトのcontent)は16x16ピクセルの十字星型をとりあえず作って設定した。YouTubeでは圧縮のせいでよくわからないけど、十字が飛びながらくるくるまわっている。

これを動かすとCPUの負荷は僕のiMac(Late2012、12.5")では100%ちょっとで結構重い。しかも仮想メモリをいきなり2GB近く消費する。CALayerを作っては捨てするせいらしい。これでちゃんと動くものにするなら、ここは考え直さないといけない。


nice!(0)  コメント(0)  トラックバック(0) 

nice! 0

コメント 0

コメントを書く

お名前:
URL:
コメント:
画像認証:
下の画像に表示されている文字を入力してください。

トラックバック 0

献立08/25献立08/26 ブログトップ

この広告は前回の更新から一定期間経過したブログに表示されています。更新すると自動で解除されます。