SSブログ

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

QTKit Captureのお勉強の続き。さっさとやらないと忘れてしまう。
い、いかん、ああ、もう忘れそう。
ボケとらんとQTCaptureDecompressedVideoOutputを使って、プレビューの表示品質を上げる、具体的なコードを書いてみる。

具体的なコード

QTCaptureSessionを作るところまでは一緒。QTCaptureViewのかわりにQTCaptureDecompressedVideoOutputのインスタンスを作って繋ぐ。

    decomOutput = [[QTCaptureDecompressedVideoOutput alloc] init];
    [decomOutput setDelegate:self];
    [captureSession addOutput:decomOutput error:error];
例のごとくエラー処理は省く。
それからデリゲートメソッドを実装する。
- (void)captureOutput:(QTCaptureOutput *)captureOutput
  didOutputVideoFrame:(CVImageBufferRef)videoFrame
     withSampleBuffer:(QTSampleBuffer *)sampleBuffer
       fromConnection:(QTCaptureConnection *)connection
{
    @synchronized (self) {
        CVBufferRetain(videoFrame);
        CIImage *image = [CIImage imageWithCVImageBuffer:videoFrame];
        [previewer setCIImage:image];
        CVBufferRelease(videoFrame);
    }
}
Appleのガイドにはこのデリゲートメソッドはmain threadで呼ばれるとは限らないので@synchronizedの中でやれ、と書いてある。でもどれがthread safeでどれがthread safeでないかよくわかってないので全部を入れてしまった。こんなに@synchronaizedの中である必要は無いはず。videoFrameのretain - releaseのタイミングもこんなに外側である必要はないはずだけど、考え出すと悩むのでとりあえず動くことを優先する。

ここでのpreviewerはNSViewのサブクラスで、QTCaptureViewのかわりにウィンドウに貼ってあり、

- (void)setCIImage:(CIImage *)image;
というメソッドを持っているとする。このメソッドは引数のCIImageをインスタンス変数として保持するだけ。
ダメなコード
そのあと、previewerは自分を描画するdrawRect:の中でごく普通にNSImageを描画するのと同じように
- (void)drawRect:(NSRect)rect
{
    if (image) {
        [image drawInRect:[self frame]
                 fromRect:NSZeroRect
                operation:NSCompositeCopy
                 fraction:1.0f];
        [image release];
    }
}
などとすればいいということになる。ところがこのコードはなぜか動作しない。CIImageのdrawInRect:云々のメソッドはNSImageの同名のメソッドとは動作が違うみたいで、NSImageのReferenceにはfromRectの後の引数にNSZeroRectを渡すと全領域が描画されるとあるけど、CIImageにはその記述がない。どうやらそれが問題らしい。

動くコードその1

これを

- (void)drawRect:(NSRect)rect
{
    if (image) {
        NSCIImageRep *imageRep = [NSCIImageRep imageRepWithCIImage:image];
        NSImage      *im = [[NSImage alloc] initWithSize:[imageRep size]];
        [im addRepresentation:imageRep];
        
        [im drawInRect:[self frame]
              fromRect:NSZeroRect
             operation:NSCompositeCopy
              fraction:1.0f];
        [im release];
        [image release];
    }
}
こうすれば動いた。つまりCIImageからNSCIImageRep(要するにビットマップ)を作ってそれをRepresentationにするNSImageを描画する。Still Motionはこのやり方を使っている。このNSImageをムービーに継ぎ足して行くことでスティルモーション(ストップモーション)を作っている。実質的なオーバーヘッドはそれほど大きくないけど(CIImageやNSImageは単なる入れ物のはずなので)、無駄が多いように見える。

動くコードその2

またNSZeroRectではなく、ソースイメージとしてのCIImageのCGRectをちゃんと指定して、

- (void)drawRect:(NSRect)rect
{
    if (currentImage) {
        NSRect	extent = NSRectFromCGRect([image extent]);
        [image drawInRect:[self frame]
                 fromRect:extent
                operation:NSCompositeCopy
                 fraction:1.0f];
        [image release];
        image = nil;
	}
}
とする。これでも動いた(NSRectFromCGRect()と言う関数を見つけて使っているけど、NSRectとCGRectって全く同じ構造体なんだよな。なんか無駄)。ひとつ前のNSImageを作るコードよりはちょっとオーバーヘッドは少ないはず(といっても多分ビットマップは使い回されているので大勢に影響はない)。やはりCIImageのdrawInRect:云々のメソッドはNSZeroRectを受けなくて(というより、その通り解釈する)全画面であっても大きさを指定する必要がある、ということらしい。

このコードを実際に僕のiMacで実行してみると、QTCaptureViewにくらべると

  • ブロックノイズは減る
  • 色がしゃっきりする
で、QTCaptureViewにくらべて表示品質はかなり上がる。

ところがDVストリームの場合、

  • 大きな表示サイズでは1秒に2~3回ぐらいフレーム落ちする
  • インターレースの横ギザギザがめちゃめちゃ目立つ
IIDCではもともと30フレーム/秒転送されていない場合が多いし、ノンイターレースなのでそもそも問題にならない。一方でDVではQTCaptureViewの場合と違って非常に見辛い。QTCaptureViewはインターレース→ノンインターレース変換が気にならないくらいボケてた、ということみたい。というよりどうせプレビューなんだからそんなところに力を注ぐより他をちゃんとやれ、ちゅうことやね。

IIDCはいいとしてDVのこのふたつの問題を改善する方法はあるのか?
もうちょっとだけ突っ込んでみよう...


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

nice! 0

コメント 0

コメントを書く

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

トラックバック 0

献立09/01献立09/02 ブログトップ

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