SSブログ

QTKit Captureを使ってみる - その9 [プログラミング - QTKit Capture]

仕事で疲れて一晩何もしないでいると、その前何をやっていたか忘れてしまう。QTKit Capture?なんやったっけそれ?
う、あかん。「愛した数式」の博士は数論だけは忘れないのに、僕は忘れると何もなくなってしまう。せめてなんとか「死んでもラッパを離しませんでした」ぐらいには。
前回、QTCaptureDecompressedVideoOutputの出力を、Core Imageを使ってQTCaptureViewより表示品質を上げる実験。具体的なコードを書いてみる。

具体的なコード

NSViewのサブクラスを作って、InterfaceBuilderでウィンドウに貼付けておく。このサブクラスのdrawRect:の中でCIContextを描画するコードを書く。例えばこんな風に。

- (void)drawRect:(NSRect)rect
{
    if (context == nil)
        [self setupContext];
    if (currentImage) {
        id  result;
        [blurFilter setValue:currentImage forKey:@"inputImage"];
        [sharpFilter setValue:[blurFilter valueForKey:@"outputImage"]
                                 forKey:@"inputImage"];
        result = [sharpFilter valueForKey:@"outputImage"];
        CGRect	frame = NSRectToCGRect([self frame]);
        [context drawImage:result
                    inRect:frame
                  fromRect:[currentImage extent]];   //1
        [currentImage release];
        currentImage = nil;
    }
}

- (void)setupContext
{
    context = [[[NSGraphicsContext currentContext] CIContext] retain];
    blurFilter = [[CIFilter filterWithName:@"CIMotionBlur"] retain];
    [blurFilter setDefaults];
    [blurFilter setValue:[NSNumber numberWithFloat:1.0f] forKey:@"inputRadius"];
    [blurFilter setValue:[NSNumber numberWithFloat:3.1415f * 0.5f]
                  forKey:@"inputAngle"];
    sharpFilter = [[CIFilter filterWithName:@"CIUnsharpMask"] retain];
    [sharpFilter setDefaults];
    [sharpFilter setValue:[NSNumber numberWithFloat:1.0f] forKey:@"inputRadius"];
    [sharpFilter setValue:[NSNumber numberWithFloat:0.25f]
                   forKey:@"inputIntensity"];
}
ここでcontext、blurFilter、sharpFilter、それとcurrentImageはインスタンス変数として持っている。CIContextはdrawRect:の中で作らなければいけない(NSGraphicsContextのcurrentContextを使っている)のでcontext変数がnilかどうかを判断して初期化するようなコードになってしまう。かっこわる。

ガイドには「CIContextはウィンドウ単位で作れ」、「CIContextは使い回せ(描画のたびに作るな)」と書いてある。この場合、ウィンドウにはこのNSViewのサブクラスしか無いので、これでかまわない。

setupContextの中で二つのフィルタを設定している。「モーションブラー」と「アンシャープマスク」フィルタ。これでデインターレースをやろうというわけ。

モーションブラーで縦方向(inputAngleにπ/2を指定)にブラー(ぼかす)したあと、アンシャープマスクで画像のエッジを立てるという屋上屋というか、穴をほってそれをまた自分で埋めるようなことをしてみた。

フィルタの設定としてはdrawRect:の二つ目のif文の中で

  1. 渡されてきたCIImage(currentImageインスタンス変数が保持している)をモーションブラーのinputImageに設定
  2. アンシャープマスクのinputImageにモーションブラーのoutputImageを設定
  3. アンシャープマスクのoutputImageをresultというidのauto変数で受ける
  4. resultをCIContextに渡して描画する
ということをしている。 なんか、雰囲気はCIContextを意識しなければいけないこと以外、NSViewに直接描画するのと全然変わらない。

僕が最初ハマったのはdrawRect:の中の(1)のところ。Source Imageの領域(CGRect)として

[result extent]
のようにフィルタを適用した結果のCIImageのCGRectを指定すると
Infinite source rectangle to drawImage method
というエラーがコンソールに出力されて何も表示されない。大きさは明示的に指定しないといけないらしい。もし、フィルタの中に解像度を変えるフィルタがあればそれを考慮した大きさを指定する必要がある。AppKitに較べるとちょっとめんどう。

結果

表示のNSViewのサブクラスの大きさを640x480に設定して
  1. QTCautpureView
  2. CIImageのdrawInRect:fromRect:fraction:メソッド
  3. Core ImageのCIMotionBlurとCIUnsharpMaskのフィルタ経由
を同時に表示させてスクリーンショットを撮って較べてみた(Core ImageはGPUを使って表示しているはずなのにスクリーンショットってなんで撮れるの?)。 QTCaptureDeviceは他のアプリが使っていてもオープンできるみたい。

まず、QTCaptureViewの一部分。
0904QTCView.jpg
結構ボケてるけど、背景ではもともとのDV圧縮の色相ノイズもある。

次に、CIImageでNSViewに描いたさっきと同じ部分。
0904CIImage.jpg
画面上で静止しているところはしゃっきりしてるけどインターレースノイズでまくり。まあ、当然。それにそういうシーンを選んだし。

最後に、Core ImageのCIMotionBlurフィルタとCIUnsharpMaskフィルタをかけて表示した同じ部分。
0904CoreImage.jpg
インターレースノイズは明らかに減ってるけど、結局TQCaptureViewの絵とあまり変わらん。

それぞれのレイテンシも微妙に違っているみたいで、同じDVソースを同時に表示させているにもかかわらず、シーンがちょっとずれている。詳しく見てみると、目がしょぼしょぼしてくるけど、CIImageが一番レイテンシが小さそう(一番早く表示されている。例えばスクリーンショットのペダルを踏む足の位置の違い)に見える。ふーん、そんなもんかなあ。起動のタイミングもあるのかも知れない。正確に測らないとはっきりしたことは言えなさそう。

Core Imageのブラーとアンシャープのフィルタを使っていることでインターレースのギザギザは減るけど、当然その分ボケてる。アンシャープをかけすぎるとエッジが不自然になるのでやっぱりダメみたい。デインターレースの方式はいくつかあるけど、ちゃんとしたやり方だと動きのある絵と無い絵とでやり方を変える(Motion compensation)とかするらしく、単純な縦方向ブラーではボケるのが当たり前。しょうがない。これ以上やるなら自分でフィルタを作るしかない。

CPUのロードは640x480の大きさでそれぞれ表示させたとき、アクティビティモニタの数字でだいたい

QTCaptureView25%
CIImage50%
Core Imageフィルタ2種50%
となった(±10%ぐらい)。やっぱりQTCaptureViewは軽い、ということ。でもCore ImageもフィルタをかけているのにCIImageと同じ程度ということはちょっとはましということか。

Core Imageのガイドの「Core Image and the GPU」には

 Up until now OpenGL, the industry standard for high performance 2D and 3D graphics, has been the primary gateway to the graphics processing unit (GPU). If you wanted to use the GPU for image processing, you needed to know OpenGL Shading Language. Core Image changes all that. With Core Image, you don’t need to know the details of OpenGL to harness the power of the GPU for image processing. Core Image handles OpenGL buffers and state management for you automatically. If for some reason a GPU is not available, Core Image uses a CPU fallback to ensure that your application runs. Core Image operations are opaque to you; your software just works.
 
 「上へこれまで、OpenGL(高性能2Dと3Dグラフィックスのための業界標準)はグラフィックス処理装置(GPU)への主要な出入口でした。あなたが画像処理のためにGPUを使いたいならば、あなたはOpenGL Shading Languageを知っている必要がありました。Core Imageは、そのようなものすべてを変えます。Core Imageで、あなたは画像処理のためにGPUの勢力を利用するためにOpenGLの詳細を知る必要はありません。Core Imageは、自動的にあなたのためにOpenGLバッファと州の管理を取り扱います。何かの理由でGPUが利用できないならば、Core Imageはあなたのアプリケーションが動作することを確実とするためにCPU頼れるものを使います。Core Image活動は、あなたを通さないです;あなたのソフトウェアは、ちょうど機能します。(yahooテキスト翻訳の結果)」

とある。機械翻訳もたいしたもんだ。中学生が辞書をひきひき訳した程度に見えるな。GPUを使っているかCPU代替機能を使っているかはopaqueだと言っている。逆に言えば必ずGPUを使うようにするにはOpenGLを使え、ということなのか。

その一つ手前のCore Videoを使う、という手もあるけど、苦労して結局Core Imageと同じ結果になるような気もする。

さて、Core Videoもやってみるかどうか。すぐやらないと忘れるけど、とりあえずもう寝る。


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

nice! 0

コメント 0

コメントを書く

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

トラックバック 0

献立09/04QTKit Captureを使ってみる .. ブログトップ

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