【问题标题】:UIImage created from CMSampleBufferRef not displayed in UIImageView?从 CMSampleBufferRef 创建的 UIImage 未显示在 UIImageView 中?
【发布时间】:2011-03-19 08:45:04
【问题描述】:

我正在尝试实时显示来自相机的 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 声明。如果我使用上面注释的代码从 Web 加载任意图像,而不是简单地将 setImage:theImage 发送到 imageView,则图像会正确加载(并且对 NSLog 的第二次调用会报告一个非 nil 对象)。
  • 至少在基本范围内,我从imageFromSampleBuffer: 获得的图像很好,因为 NSLog 报告的大小为 360x480,这是我预期的大小。

我使用的代码是 Apple 最近发布的AVFoundation sn-p 可用的here

特别是,我使用的代码设置了AVCaptureSession 对象和朋友(我对此了解甚少),并从核心视频缓冲区创建 UIImage 对象(即imageFromSampleBuffer 方法)。

最后,如果我尝试将drawInRect: 发送到带有UIImageimageFromSamplerBuffer 返回的普通 UIView 子类,我可以让应用程序崩溃,而如果我使用来自的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 的更多信息。

使用here 描述的方法,我可以得到像素并打印它们,它们乍一看还不错(例如,Alpha 通道中的每个值都是 255)。但是,缓冲区大小略有不同。我从该 URL 从 Flickr 获得的图像是 375x500,它的[pixelData length] 给了我750000 = 375*500*4,这是预期值。但是,从imageFromSampleBuffer: 返回的图像的像素数据大小为691208 = 360*480*4 + 8,因此像素数据中有8 个额外的字节。 CVPixelBufferGetDataSize 本身返回这个 off-by-8 值。我想了一会儿,这可能是由于在内存中对齐的位置分配缓冲区,但 691200 是 256 的倍数,所以这也不能解释。这种大小差异是我能分辨出的两个 UIImage 之间的唯一区别,它可能会导致麻烦。尽管如此,没有理由为缓冲区分配 额外 内存会导致 EXC_BAD_ACCESS 违规。

非常感谢您的帮助,如果您需要更多信息,请告诉我。

【问题讨论】:

  • 我遇到了同样的问题。令人恼火的是,是苹果自己为我们提供了自定义的“imageFromSampleBuffer”功能。
  • 更奇怪的是,图片内容本身是正确的。我正在通过套接字将原始像素推送到另一台机器,它正是以正确格式从相机捕获的视频帧。

标签: iphone uiimageview uiimage ios4 avfoundation


【解决方案1】:

我遇到了同样的问题......但我发现了这个旧帖子,它创建 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;
}

【讨论】:

  • 很好 - 我会在完成另一件事后立即尝试,我会尽快回复您。
  • 所以,当谈到 iOS API 时,我完全是个白痴,我不知道如何将像素推送到实际的 CGImage 中。这似乎是通过memcpy(mImageData, ...) 调用实现的,但实际上mImageData 并未在该sn-p 的任何地方声明。你知道我如何使用 iOS api 访问这样的指针吗?
  • 啊,是的,我什至不需要那个 - CGImageRef 已经创建好了。感谢那。您介意编辑您的答案以内联添加那段代码以供将来在 StackOverflow 中参考吗?无论如何,我都会给你赏金,但我认为这将是最有帮助的。
  • 由于某种原因,我得到 : CGBitmapContextCreateImage: invalid context 0x0。有什么想法吗?
  • 请注意。通过 CGContextRef 传输像素不会传输样本缓冲区元数据(即 EXIF 数据)。
【解决方案2】:

Apple 的技术问答 QA1702 现在很好地解释了视频帧的实时捕获:

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

【讨论】:

  • 这是一个很好的例子。但是,在 iPad3 上构建图像确实需要大约两分钟,非常奇怪。
  • 好吧,我是骗子。 performSelectorOnMainThread 用于设置 UIImageView 提供接近即时的结果。
【解决方案3】:

设置正确的输出格式也很重要。使用默认格式设置时,我遇到了图像捕获问题。应该是:

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

【讨论】:

    【解决方案4】:

    Ben Loulier 有一个 good write up 告诉你如何做到这一点。

    我使用his example app 作为起点,它对我有用。除了将 imageFromSamplerBuffer 函数替换为使用 CGBitmapContextCreate 创建 CGImageRef 的函数外,他还在设置输出样本缓冲区委托时使用主调度队列(通过 dispatch_get_main_queue())。这不是最好的解决方案,因为它需要一个串行队列,据我所知,主队列不是串行队列。因此,虽然不能保证您以正确的顺序获取帧,但到目前为止它似乎对我有用:)

    【讨论】:

    • 这工作,即使没有不同的队列。我现在怀疑 Apple 在他们的代码 sn-p 中调用 CGDataProviderCreateWithData,这是代码中唯一剩下的区别。
    【解决方案5】:

    另一件需要注意的事情是您是否真的在主线程上更新了您的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);
        }];
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多