【问题标题】:How to get the Y component from CMSampleBuffer resulted from the AVCaptureSession?如何从 AVCaptureSession 产生的 CMSampleBuffer 中获取 Y 分量?
【发布时间】:2010-11-03 09:06:46
【问题描述】:

您好,我正在尝试使用 AVCaptureSession 从 iphone 相机访问原始数据。我遵循 Apple (link here) 提供的指南。

samplebuffer 中的原始数据是 YUV 格式(我对原始视频帧格式是否正确??),如何从存储在 samplebuffer 中的原始数据中直接获取 Y 分量的数据。

【问题讨论】:

  • Brad Larson 和 Codo 在这个问题上帮助了我很多。结合他们的答案,我终于可以达到我的目标了。非常感谢布拉德·拉森和科多!

标签: iphone stream avcapturesession


【解决方案1】:

在设置返回原始相机帧的 AVCaptureVideoDataOutput 时,您可以使用如下代码设置帧的格式:

[videoOutput setVideoSettings:[NSDictionary dictionaryWithObject:[NSNumber numberWithInt:kCVPixelFormatType_32BGRA] forKey:(id)kCVPixelBufferPixelFormatTypeKey]];

在这种情况下,指定了 BGRA 像素格式(我使用它来匹配 OpenGL ES 纹理的颜色格式)。该格式中的每个像素都有一个字节,依次代表蓝色、绿色、红色和 alpha。这样做可以很容易地提取颜色分量,但您确实需要从相机原生 YUV 颜色空间进行转换,从而牺牲了一点性能。

其他受支持的色彩空间是kCVPixelFormatType_420YpCbCr8BiPlanarVideoRangekCVPixelFormatType_420YpCbCr8BiPlanarFullRange(在较新的设备上)和kCVPixelFormatType_422YpCbCr8(在 iPhone 3G 上)。 VideoRangeFullRange 后缀仅表示返回的字节是在 16 - 235 (对于 Y)和 16 - 240(对于 UV)还是完整的 0 - 255(对于每个组件)之间返回。

我相信 AVCaptureVideoDataOutput 实例使用的默认色彩空间是 YUV 4:2:0 平面色彩空间(iPhone 3G 除外,它是 YUV 4:2:2 交错的)。这意味着视频帧中包含两个图像数据平面,首先是 Y 平面。对于生成图像中的每个像素,该像素的 Y 值都有一个字节。

你可以通过在你的委托回调中实现这样的东西来获得这个原始的 Y 数据:

- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection
{
    CVImageBufferRef pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);
    CVPixelBufferLockBaseAddress(pixelBuffer, 0);

    unsigned char *rawPixelBase = (unsigned char *)CVPixelBufferGetBaseAddress(pixelBuffer);

    // Do something with the raw pixels here

    CVPixelBufferUnlockBaseAddress(pixelBuffer, 0);
}

然后,您可以找出图像上每个 X、Y 坐标在帧数据中的位置,并拉出与该坐标处的 Y 分量相对应的字节。

来自WWDC 2010 的 Apple FindMyiCone 示例(可与视频一起访问)展示了如何处理来自每一帧的原始 BGRA 数据。我还创建了一个示例应用程序,您可以下载here 的代码,它使用来自iPhone 摄像头的实时视频执行color-based object tracking。两者都展示了如何处理原始像素数据,但这些都不适用于 YUV 颜色空间。

【讨论】:

  • @brad Larson :kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange(iphone4的默认)和YUV 420是否一样??
  • @Asta - 正如我上面提到的,iPhone 4 上的kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange 是 YUV 4:2:0 平面色彩空间。
  • 我还有一个问题。我的编解码器只接受 YUV420 格式,但 420YpCbCr8BiPlanarVideoRange(双平面)格式的 Y 数据(亮度)和 CbCr 数据(色度或颜色信息)位于两个独立的内存区域,称为飞机,我怎样才能发送到我的编解码器?有什么方法可以转换单个平面?我是否必须使用任何 spl 转换
  • @Asta - 如果您的编解码器需要交错的 YUV 数据,您可能需要使用 Accelerate 框架或自定义着色器自行交错。但是,在不违反 NDA 的情况下,您可能希望阅读一些关于 iOS 5.0 的发行说明。
  • @MeetDoshi - 该示例应用程序已被我的 GPUImage 框架中的 ColorObjectTracking 示例所取代:github.com/BradLarson/GPUImage/tree/master/examples/iOS/…
【解决方案2】:

除了 Brad 的回答和您自己的代码之外,您还需要考虑以下几点:

由于您的图像有两个独立的平面,函数 CVPixelBufferGetBaseAddress 不会返回平面的基地址,而是返回附加数据结构的基地址。这可能是由于当前的实现,您获得的地址足够接近第一个平面,以便您可以看到图像。但这就是它移动并在左上角有垃圾的原因。接收第一架飞机的正确方法是:

unsigned char *rowBase = CVPixelBufferGetBaseAddressOfPlane(pixelBuffer, 0);

图像中的一行可能比图像的宽度长(由于四舍五入)。这就是为什么有单独的函数来获取每行的宽度和字节数。你目前没有这个问题。但这可能会随着 iOS 的下一个版本而改变。所以你的代码应该是:

int bufferHeight = CVPixelBufferGetHeight(pixelBuffer);
int bufferWidth = CVPixelBufferGetWidth(pixelBuffer);
int bytesPerRow = CVPixelBufferGetBytesPerRowOfPlane(pixelBuffer, 0);
int size = bufferHeight * bytesPerRow ;

unsigned char *pixel = (unsigned char*)malloc(size);

unsigned char *rowBase = CVPixelBufferGetBaseAddressOfPlane(pixelBuffer, 0);
memcpy (pixel, rowBase, size);

还请注意,您的代码在 iPhone 3G 上会严重失败。

【讨论】:

  • 不应该是CVPixelBufferGetHeightOfPlane吗?只是好奇。
  • 由于我们知道 Y 平面与图像具有相同的像素数,因此在这里应该没有区别。但是,如果我们访问像素数减少的 UV 平面,则必须使用 CVPixelBufferGetHeightOfPlane
  • 这篇文章说明了什么样的bug导致使用CVPixelBufferGetBaseAddress而不是CVPixelBufferGetBaseAddressOfPlane mkonrad.net/2014/06/24/…
  • 对于平面缓冲区,CVPixelBufferGetBaseAddress 返回一个指向 CVPlanarComponentInfo 结构的指针,如果不存在这样的结构,则返回 NULL。所以,如果你的缓冲区是平面的,你必须使用 CVPixelBufferGetBaseAddressOfPlane。
【解决方案3】:

如果您只需要亮度通道,我建议不要使用 BGRA 格式,因为它会带来转换开销。如果您正在渲染东西,Apple 建议使用 BGRA,但您不需要它来提取亮度信息。正如 Brad 已经提到的,最有效的格式是相机原生 YUV 格式。

但是,从样本缓冲区中提取正确的字节有点棘手,尤其是对于具有交错 YUV 422 格式的 iPhone 3G。这是我的代码,它适用于 iPhone 3G、3GS、iPod Touch 4 和 iPhone 4S。

#pragma mark -
#pragma mark AVCaptureVideoDataOutputSampleBufferDelegate Methods
#if !(TARGET_IPHONE_SIMULATOR)
- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection;
{
    // get image buffer reference
    CVImageBufferRef imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);

    // extract needed informations from image buffer
    CVPixelBufferLockBaseAddress(imageBuffer, 0);
    size_t bufferSize = CVPixelBufferGetDataSize(imageBuffer);
    void *baseAddress = CVPixelBufferGetBaseAddress(imageBuffer);
    CGSize resolution = CGSizeMake(CVPixelBufferGetWidth(imageBuffer), CVPixelBufferGetHeight(imageBuffer));

    // variables for grayscaleBuffer 
    void *grayscaleBuffer = 0;
    size_t grayscaleBufferSize = 0;

    // the pixelFormat differs between iPhone 3G and later models
    OSType pixelFormat = CVPixelBufferGetPixelFormatType(imageBuffer);

    if (pixelFormat == '2vuy') { // iPhone 3G
        // kCVPixelFormatType_422YpCbCr8     = '2vuy',    
        /* Component Y'CbCr 8-bit 4:2:2, ordered Cb Y'0 Cr Y'1 */

        // copy every second byte (luminance bytes form Y-channel) to new buffer
        grayscaleBufferSize = bufferSize/2;
        grayscaleBuffer = malloc(grayscaleBufferSize);
        if (grayscaleBuffer == NULL) {
            NSLog(@"ERROR in %@:%@:%d: couldn't allocate memory for grayscaleBuffer!", NSStringFromClass([self class]), NSStringFromSelector(_cmd), __LINE__);
            return nil; }
        memset(grayscaleBuffer, 0, grayscaleBufferSize);
        void *sourceMemPos = baseAddress + 1;
        void *destinationMemPos = grayscaleBuffer;
        void *destinationEnd = grayscaleBuffer + grayscaleBufferSize;
        while (destinationMemPos <= destinationEnd) {
            memcpy(destinationMemPos, sourceMemPos, 1);
            destinationMemPos += 1;
            sourceMemPos += 2;
        }       
    }

    if (pixelFormat == '420v' || pixelFormat == '420f') {
        // kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange = '420v', 
        // kCVPixelFormatType_420YpCbCr8BiPlanarFullRange  = '420f',
        // Bi-Planar Component Y'CbCr 8-bit 4:2:0, video-range (luma=[16,235] chroma=[16,240]).  
        // Bi-Planar Component Y'CbCr 8-bit 4:2:0, full-range (luma=[0,255] chroma=[1,255]).
        // baseAddress points to a big-endian CVPlanarPixelBufferInfo_YCbCrBiPlanar struct
        // i.e.: Y-channel in this format is in the first third of the buffer!
        int bytesPerRow = CVPixelBufferGetBytesPerRowOfPlane(imageBuffer, 0);
        baseAddress = CVPixelBufferGetBaseAddressOfPlane(imageBuffer,0);
        grayscaleBufferSize = resolution.height * bytesPerRow ;
        grayscaleBuffer = malloc(grayscaleBufferSize);
        if (grayscaleBuffer == NULL) {
            NSLog(@"ERROR in %@:%@:%d: couldn't allocate memory for grayscaleBuffer!", NSStringFromClass([self class]), NSStringFromSelector(_cmd), __LINE__);
            return nil; }
        memset(grayscaleBuffer, 0, grayscaleBufferSize);
        memcpy (grayscaleBuffer, baseAddress, grayscaleBufferSize); 
    }

    // do whatever you want with the grayscale buffer
    ...

    // clean-up
    free(grayscaleBuffer);
}
#endif

【讨论】:

  • 您好,谢谢您的回答,我也面临同样的问题。一件事是我也想要 Cr 和 Cb 组件,但我不知道如何获得它。我正在尝试制作皮肤检测器,并且我也需要这些值,正如我在另一篇文章中的 SO 中发现的那样。我已经使用 BGRA 格式并在转换为 YCbCr 之后做到了这一点,但我想尽可能避免该转换步骤以提高 FPS。这就是为什么我想为图像中的每个像素获取单独的 Y Cb 和 Cr 值。有什么想法吗?
  • 您是如何计算出分量信号的字节顺序的?我从 Microsoft 找到的文档将其列为 Y0CrY1Cb。
  • 我在 Apple 头文件中找到了提示。对不起,我不能再告诉你它是哪个头文件了。
【解决方案4】:

这只是其他所有人辛勤工作的成果,无论是在其他线程上还是在其他线程上,都已转换为 swift 3 以供任何认为有用的人使用。

func captureOutput(_ captureOutput: AVCaptureOutput!, didOutputSampleBuffer sampleBuffer: CMSampleBuffer!, from connection: AVCaptureConnection!) {
    if let pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) {
        CVPixelBufferLockBaseAddress(pixelBuffer, CVPixelBufferLockFlags.readOnly)

        let pixelFormatType = CVPixelBufferGetPixelFormatType(pixelBuffer)
        if pixelFormatType == kCVPixelFormatType_420YpCbCr8BiPlanarFullRange
           || pixelFormatType == kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange {

            let bufferHeight = CVPixelBufferGetHeight(pixelBuffer)
            let bufferWidth = CVPixelBufferGetWidth(pixelBuffer)

            let lumaBytesPerRow = CVPixelBufferGetBytesPerRowOfPlane(pixelBuffer, 0)
            let size = bufferHeight * lumaBytesPerRow
            let lumaBaseAddress = CVPixelBufferGetBaseAddressOfPlane(pixelBuffer, 0)
            let lumaByteBuffer = unsafeBitCast(lumaBaseAddress, to:UnsafeMutablePointer<UInt8>.self)

            let releaseDataCallback: CGDataProviderReleaseDataCallback = { (info: UnsafeMutableRawPointer?, data: UnsafeRawPointer, size: Int) -> () in
                // https://developer.apple.com/reference/coregraphics/cgdataproviderreleasedatacallback
                // N.B. 'CGDataProviderRelease' is unavailable: Core Foundation objects are automatically memory managed
                return
            }

            if let dataProvider = CGDataProvider(dataInfo: nil, data: lumaByteBuffer, size: size, releaseData: releaseDataCallback) {
                let colorSpace = CGColorSpaceCreateDeviceGray()
                let bitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.noneSkipFirst.rawValue)

                let cgImage = CGImage(width: bufferWidth, height: bufferHeight, bitsPerComponent: 8, bitsPerPixel: 8, bytesPerRow: lumaBytesPerRow, space: colorSpace, bitmapInfo: bitmapInfo, provider: dataProvider, decode: nil, shouldInterpolate: false, intent: CGColorRenderingIntent.defaultIntent)

                let greyscaleImage = UIImage(cgImage: cgImage!)
                // do what you want with the greyscale image.
            }
        }

        CVPixelBufferUnlockBaseAddress(pixelBuffer, CVPixelBufferLockFlags.readOnly)
    }
}

【讨论】:

  • 如果上述解决方案对某人不起作用,请尝试使用 let bitmapInfo = CGBitmapInfo(rawValue: CGImageByteOrderInfo.orderDefault.rawValue) 代替 让 bitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.no​​neSkipFirst.rawValue).
猜你喜欢
  • 2012-11-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-05-10
相关资源
最近更新 更多