14

我正在尝试CVPixelBuffer从 Apple 的 ARKit 中获得 RGB 色彩空间。在func session(_ session: ARSession, didUpdate frame: ARFrame)方法中,ARSessionDelegate我得到了一个ARFrame. 在Displaying an AR Experience with Metal页面上,我发现这个像素缓冲区位于 YCbCr (YUV) 颜色空间中。

我需要将其转换为 RGB 颜色空间(我实际上需要CVPixelBuffer而不是UIImage)。我在 iOS 上找到了一些关于颜色转换的东西,但我无法在 Swift 3 中使用它。

4

5 回答 5

9

有几种方法可以做到这一点,这取决于你所追求的。实时执行此操作(也就是说,将缓冲区渲染到视图)的最佳方法是使用自定义着色器将 YCbCr CVPixelBuffer 转换为 RGB。

使用 Metal: 如果您创建一个新项目,选择“Augmented Reality App”并选择“Metal”作为内容技术,生成的项目将包含进行此转换所需的代码和着色器。

使用 OpenGL: Apple 的GLCameraRipple 示例使用 AVCaptureSession 来捕获相机,并展示了如何将生成的 CVPixelBuffer 映射到 GL 纹理,然后在着色器中将其转换为 RGB(同样,在示例中提供)。

非实时:这个 stackoverflow 问题 的答案解决了将缓冲区转换为 UIImage 的问题,并提供了一种非常简单的方法。

于 2017-06-11T19:16:01.587 回答
3

我也坚持这个问题好几天了。我在 Internet 上找到的所有代码片段都是用 Objective-C 而不是 Swift 编写的,关于转换CVPixelBufferUIImage.

最后,以下代码片段非常适合我,将 YUV 图像转换为 JPG 或 PNG 文件格式,然后您可以将其写入应用程序的本地文件。

func pixelBufferToUIImage(pixelBuffer: CVPixelBuffer) -> UIImage {
    let ciImage = CIImage(cvPixelBuffer: pixelBuffer)
    let context = CIContext(options: nil)
    let cgImage = context.createCGImage(ciImage, from: ciImage.extent)
    let uiImage = UIImage(cgImage: cgImage!)
    return uiImage
}
于 2017-06-27T02:59:07.127 回答
2

您可能需要 Accelerate 框架的图像转换功能。也许是vImageConvert_420Yp8_Cb8_Cr8ToARGB8888和的组合vImageConvert_ARGB8888toRGB888(如果您不想要 alpha 通道)。根据我的经验,这些工作是实时的。

于 2019-08-12T14:40:11.810 回答
2

文档明确表示您需要访问亮度和色度平面:

ARKit 以平面 YCbCr 格式(也称为 YUV)格式捕获像素缓冲区。要在设备显示器上渲染这些图像,您需要访问像素缓冲区的亮度和色度平面并将像素值转换为 RGB 格式。

因此,没有办法直接获取 RGB 平面,您必须在着色器中处理这个问题,无论是在 Metal 还是 openGL 中,如@joshue 所述

于 2017-06-16T20:25:06.580 回答
0

也为此苦苦挣扎了很长时间,我最终编写了以下代码,这对我有用:

// Helper macro to ensure pixel values are bounded between 0 and 255
#define clamp(a) (a > 255 ? 255 : (a < 0 ? 0 : a));

- (void)processImageBuffer:(CVImageBufferRef)imageBuffer
{
    OSType type  = CVPixelBufferGetPixelFormatType(imageBuffer);
    if (type == kCVPixelFormatType_420YpCbCr8BiPlanarFullRange)
    {
        CVPixelBufferLockBaseAddress(imageBuffer, 0);
        // We know the return format of the base address based on the YpCbCr8BiPlanarFullRange format (as per doc)
        StandardBuffer baseAddress = (StandardBuffer)CVPixelBufferGetBaseAddress(imageBuffer);

        // Get the number of bytes per row for the pixel buffer, width and height
        size_t bytesPerRow = CVPixelBufferGetBytesPerRow(imageBuffer);
        size_t width = CVPixelBufferGetWidth(imageBuffer);
        size_t height = CVPixelBufferGetHeight(imageBuffer);

        // Get buffer info and planar pixel data
        CVPlanarPixelBufferInfo_YCbCrBiPlanar *bufferInfo = (CVPlanarPixelBufferInfo_YCbCrBiPlanar *)baseAddress;
        uint8_t* cbrBuff = (uint8_t *)CVPixelBufferGetBaseAddressOfPlane(imageBuffer, 1);
        // This just moved the pointer past the offset
        baseAddress = (uint8_t *)CVPixelBufferGetBaseAddressOfPlane(imageBuffer, 0);
        int bytesPerPixel = 4;
        uint8_t *rgbData =  rgbFromYCrCbBiPlanarFullRangeBuffer(baseAddress,
                                                                cbrBuff,
                                                                bufferInfo,
                                                                width,
                                                                height,
                                                                bytesPerRow);

        [self doStuffOnRGBBuffer:rgbData width:width height:height bitsPerComponent:8 bytesPerPixel:bytesPerPixel bytesPerRow:bytesPerRow];

        free(rgbData);
        CVPixelBufferUnlockBaseAddress(imageBuffer, 0);
    }
    else
    {
        NSLog(@"Unsupported image buffer type");
    }
}

uint8_t * rgbFromYCrCbBiPlanarFullRangeBuffer(uint8_t *inBaseAddress,
                                              uint8_t *cbCrBuffer,
                                              CVPlanarPixelBufferInfo_YCbCrBiPlanar * inBufferInfo,
                                              size_t inputBufferWidth,
                                              size_t inputBufferHeight,
                                              size_t inputBufferBytesPerRow)
{
    int bytesPerPixel = 4;
    NSUInteger yPitch = EndianU32_BtoN(inBufferInfo->componentInfoY.rowBytes);
    uint8_t *rgbBuffer = (uint8_t *)malloc(inputBufferWidth * inputBufferHeight * bytesPerPixel);
    NSUInteger cbCrPitch = EndianU32_BtoN(inBufferInfo->componentInfoCbCr.rowBytes);
    uint8_t *yBuffer = (uint8_t *)inBaseAddress;

    for(int y = 0; y < inputBufferHeight; y++)
    {
        uint8_t *rgbBufferLine = &rgbBuffer[y * inputBufferWidth * bytesPerPixel];
        uint8_t *yBufferLine = &yBuffer[y * yPitch];
        uint8_t *cbCrBufferLine = &cbCrBuffer[(y >> 1) * cbCrPitch];
        for(int x = 0; x < inputBufferWidth; x++)
        {
            int16_t y = yBufferLine[x];
            int16_t cb = cbCrBufferLine[x & ~1] - 128;
            int16_t cr = cbCrBufferLine[x | 1] - 128;

            uint8_t *rgbOutput = &rgbBufferLine[x*bytesPerPixel];

            int16_t r = (int16_t)roundf( y + cr *  1.4 );
            int16_t g = (int16_t)roundf( y + cb * -0.343 + cr * -0.711 );
            int16_t b = (int16_t)roundf( y + cb *  1.765);

            // ABGR image representation
            rgbOutput[0] = 0Xff;
            rgbOutput[1] = clamp(b);
            rgbOutput[2] = clamp(g);
            rgbOutput[3] = clamp(r);
        }
    }

    return rgbBuffer;
}
于 2018-05-21T07:56:58.973 回答