web-dev-qa-db-ja.com

CMSampleBufferRefから作成されたUIImageがUIImageViewに表示されませんか?

カメラからのUIImageをリアルタイムで表示しようとしていますが、UIImageViewで画像が正しく表示されていないようです。これは、AVCaptureVideoDataOutputSampleBufferDelegateが実装する必要のあるメソッドです。

- (void)captureOutput:(AVCaptureOutput *)captureOutput 
didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer 
       fromConnection:(AVCaptureConnection *)connection
{ 
    // Create a UIImage from the sample buffer data
    UIImage *theImage = [self imageFromSampleBuffer:sampleBuffer];
//    NSLog(@"Got an image! %f %f", theImage.size.width, theImage.size.height);
//    NSLog(@"The image view is %@", imageView);
//    UIImage *theImage = [[UIImage alloc] initWithData:[NSData 
//        dataWithContentsOfURL:[NSURL 
//        URLWithString:@"http://farm4.static.flickr.com/3092/2915896504_a88b69c9de.jpg"]]];
    [self.session stopRunning];
    [imageView setImage: theImage];
}

簡単な問題を回避するには:

  • UIImagePickerControllerを使用することはオプションではありません(最終的には実際に画像を使用します)
  • ハンドラーが呼び出されていることを知っています(NSLog呼び出しが行われ、出力が表示されます)
  • IBOutlet宣言が正しく設定されていることを知っています。上記のコメントコードを使用して、単にsetImage:theImageをimageViewに送信するのではなく、Webから任意の画像を読み込むと、画像は正しく読み込まれます(NSLogへの2回目の呼び出しでnil以外のオブジェクトが報告されます)。
  • 少なくとも基本的には、imageFromSampleBuffer:から取得した画像は問題ありません。これは、NSLogがサイズを予想したサイズである360x480と報告しているためです。

私が使用しているコードは、Apple available here から最近投稿されたAVFoundationスニペットです。

特に、これは私が使用するコードであり、AVCaptureSessionオブジェクトとその仲間(私はほとんど理解していません)をセットアップし、コアビデオバッファー(つまりimageFromSampleBuffer)からUIImageオブジェクトを作成します。方法)。

最後に、UIImageによって返されたimageFromSamplerBufferを使用してdrawInRect:をプレーンなUIViewサブクラスに送信しようとすると、アプリケーションがクラッシュする可能性がありますが、使用してもクラッシュしません。上記のURLからのUIImage。クラッシュ内のデバッガーからのスタックトレースは次のとおりです(EXC_BAD_ACCESSシグナルを受け取ります)。

#0  0x34a977ee in decode_swap ()
#1  0x34a8f80e in decode_data ()
#2  0x34a8f674 in img_decode_read ()
#3  0x34a8a76e in img_interpolate_read ()
#4  0x34a63b46 in img_data_lock ()
#5  0x34a62302 in CGSImageDataLock ()
#6  0x351ab812 in ripc_AcquireImage ()
#7  0x351a8f28 in ripc_DrawImage ()
#8  0x34a620f6 in CGContextDelegateDrawImage ()
#9  0x34a61fb4 in CGContextDrawImage ()
#10 0x321fd0d0 in -[UIImage drawInRect:blendMode:alpha:] ()
#11 0x321fcc38 in -[UIImage drawInRect:] ()

編集:コードのそのビットによって返されるUIImageに関するいくつかの詳細情報があります。

説明されている方法を使用して ここ 、ピクセルに到達してそれらを印刷することができ、それらは一見問題ないように見えます(たとえば、アルファチャネルのすべての値は255です)。ただし、バッファサイズには少しずれがあります。そのURLからFlickrから取得した画像は375x500であり、その[pixelData length]は期待値である750000 = 375*500*4を提供します。ただし、imageFromSampleBuffer:から返される画像のピクセルデータのサイズは691208 = 360*480*4 + 8であるため、ピクセルデータには8バイト余分にあります。 CVPixelBufferGetDataSize自体は、この8単位の値を返します。メモリ内の整列した位置にバッファを割り当てたことが原因である可能性があると少し考えましたが、691200は256の倍数であるため、それも説明できません。このサイズの不一致は、2つのUIImage間で私が知ることができる唯一の違いであり、問​​題を引き起こしている可能性があります。それでも、バッファにextraメモリを割り当てると、EXC_BAD_ACCESS違反が発生する理由はありません。

ご協力いただきありがとうございます。さらに情報が必要な場合はお知らせください。

23

同じ問題がありました...しかし、この古い投稿を見つけました。CGImageRefを作成する方法は機能します。

http://forum.unity3d.com/viewtopic.php?p=300819

実用的なサンプルは次のとおりです。

app has a member UIImage theImage;

// Delegate routine that is called when a sample buffer was written
- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer  fromConnection:(AVCaptureConnection *)connection
{
        //... just an example of how to get an image out of this ...

    CGImageRef cgImage = [self imageFromSampleBuffer:sampleBuffer];
    theImage.image =     [UIImage imageWithCGImage: cgImage ];
    CGImageRelease( cgImage );
}

- (CGImageRef) imageFromSampleBuffer:(CMSampleBufferRef) sampleBuffer // Create a CGImageRef from sample buffer data
{
    CVImageBufferRef imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer); 
    CVPixelBufferLockBaseAddress(imageBuffer,0);        // Lock the image buffer 

    uint8_t *baseAddress = (uint8_t *)CVPixelBufferGetBaseAddressOfPlane(imageBuffer, 0);   // Get information of the image 
    size_t bytesPerRow = CVPixelBufferGetBytesPerRow(imageBuffer); 
    size_t width = CVPixelBufferGetWidth(imageBuffer); 
    size_t height = CVPixelBufferGetHeight(imageBuffer); 
    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); 

    CGContextRef newContext = CGBitmapContextCreate(baseAddress, width, height, 8, bytesPerRow, colorSpace, kCGBitmapByteOrder32Little | kCGImageAlphaPremultipliedFirst); 
    CGImageRef newImage = CGBitmapContextCreateImage(newContext); 
    CGContextRelease(newContext); 

    CGColorSpaceRelease(colorSpace); 
    CVPixelBufferUnlockBaseAddress(imageBuffer,0); 
    /* CVBufferRelease(imageBuffer); */  // do not call this!

    return newImage;
}
40
Ken Pletzer

ビデオフレームのライブキャプチャは、AppleのテクニカルQ&AQA1702で十分に説明されています。

https://developer.Apple.com/library/ios/#qa/qa1702/_index.html

10
matt

正しい出力フォーマットを設定することも重要です。デフォルトのフォーマット設定を使用すると、画像のキャプチャに問題が発生しました。そのはず:

[videoDataOutput setVideoSettings:[NSDictionary dictionaryWithObject:[NSNumber numberWithUnsignedInt:kCVPixelFormatType_32BGRA] forKey:(NSString*)kCVPixelBufferPixelFormatTypeKey]];
7
Vladimir

Ben Loulierは、これを行う方法について 良い記事 を持っています。

私は 彼のサンプルアプリ を出発点として使用していますが、それは私のために働いています。 imageFromSamplerBuffer関数をCGBitmapContextCreateでCGImageRefを作成するものに置き換えることに加えて、彼は出力サンプルバッファーデリゲートを設定するときにメインディスパッチキューを(dispatch_get_main_queue()経由で)使用しています。これはシリアルキューが必要であり、メインキューはシリアルキューではないことを理解しているため、これは最善の解決策ではありません。したがって、フレームを正しい順序で取得することは保証されていませんが、これまでのところうまくいくようです:)

5
sroske

もう1つ注意すべき点は、メインスレッドでUIImageViewを実際に更新しているかどうかです。更新していない場合は、変更が反映されない可能性があります。

captureOutput:didOutputSampleBuffer:fromConnectionデリゲートメソッドは、バックグラウンドスレッドで呼び出されることがよくあります。だからあなたはこれをしたい:

- (void)captureOutput:(AVCaptureOutput *)captureOutput
didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer
       fromConnection:(AVCaptureConnection *)connection
{   
    CFRetain(sampleBuffer);

    [[NSOperationQueue mainQueue] addOperationWithBlock:^{

        //Now we're definitely on the main thread, so update the imageView:
        UIImage *capturedImage = [self imageFromSampleBuffer:sampleBuffer];

        //Display the image currently being captured:
        imageView.image = capturedImage;

        CFRelease(sampleBuffer);
    }];
}
1
Eric