如何使用加速框架将iOS相机图像转换为灰度图像?

6

看起来这应该比我发现的更简单。

我有一个通过标准委托方法返回的AVFoundation帧:

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

我想使用 Accelerate.Framework 将框架转换为灰度。

该框架中有一系列的转换方法,包括 vImageConvert_RGBA8888toPlanar8(),看起来这可能是我想要的,但是我找不到任何使用它们的示例!

到目前为止,我有以下代码:

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

      @autoreleasepool {
            CVImageBufferRef imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);
            /*Lock the image buffer*/
            CVPixelBufferLockBaseAddress(imageBuffer,0);
            /*Get information about the image*/
            uint8_t *baseAddress = (uint8_t *)CVPixelBufferGetBaseAddress(imageBuffer);
            size_t width = CVPixelBufferGetWidth(imageBuffer);
            size_t height = CVPixelBufferGetHeight(imageBuffer);
            size_t stride = CVPixelBufferGetBytesPerRow(imageBuffer);

            // vImage In
            Pixel_8 *bitmap = (Pixel_8 *)malloc(width * height * sizeof(Pixel_8));
            const vImage_Buffer inImage = { bitmap, height, width, stride };

            //How can I take this inImage and convert it to greyscale?????
            //vImageConvert_RGBA8888toPlanar8()??? Is the correct starting format here??
      }    
}

所以我有两个问题: (1) 在上述代码中,RGBA8888是否是正确的起始格式? (2) 我如何实际调用Accelerate.Framework将其转换为灰度图像?


请帮我解决这个问题..https://dev59.com/R1oT5IYBdhLWcg3w2CMw - Meet Doshi
5个回答

5

这里有一个更简单的选择。如果您将摄像机获取格式更改为YUV,则已经有了一个灰度帧,您可以根据需要使用它。在设置数据输出时,请使用以下内容:

dataOutput.videoSettings = @{ (id)kCVPixelBufferPixelFormatTypeKey : @(kCVPixelFormatType_420YpCbCr8BiPlanarFullRange) };

您可以通过以下方式在捕获回调中访问Y平面:
CVPixelBufferRef pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);
CVPixelBufferLockBaseAddress(pixelBuffer, kCVPixelBufferLock_ReadOnly);
uint8_t *yPlane = CVPixelBufferGetBaseAddressOfPlane(pixelBuffer, 0);

... do stuff with your greyscale camera image ...

CVPixelBufferUnlockBaseAddress(pixelBuffer);

在某种程度上,是的。使用YUV的问题在于现在你有两个问题。原始问题和其他所有需要RGB数据的问题。虽然在有限的情况下可能足够。 - Cameron Lowell Palmer
所以,这个问题是关于将图像转换为灰度的,而你的回答只是演示了如何获取像素缓冲区地址。 - user3335999

4
vImage方法是使用vImageMatrixMultiply_Planar8和一个1x3矩阵。你需要使用vImageConvert_RGBA8888toPlanar8函数将RGBA8888缓冲区转换为4个平面缓冲区,这些缓冲区将被vImageMatrixMultiply_Planar8使用。 vImageMatrixMultiply_ARGB8888也可以一次完成,但是灰色通道将与结果中的其他三个通道交错。 vImageConvert_RGBA8888toPlanar8本身并不进行任何数学运算,它只是将您的交错图像分离为单独的图像平面。
如果您还需要调整伽马值,则可能vImageConvert_AnyToAny()是一个简单的选择。它将根据完全颜色管理从RGB格式转换为灰度颜色空间。请参见vImage_Utilities.h。
我更喜欢Tarks的答案,因为这样会使您处于手动颜色管理Luminance的位置(如果您关心)。

3

使用Accelerate vImage将BGRA图像转换为灰度图

此方法旨在说明如何使用Accelerate的vImage将BGR图像转换为灰度图。您的图像很可能是RGBA格式,因此您需要相应地调整矩阵,但是相机输出的是BGRA,因此我在这里使用它。矩阵中的值是OpenCV cvtColor中使用的相同值,您可以尝试其他值,例如luminosity。我假设您为结果分配了适当数量的内存。对于灰度级,它只有1个通道或者是BGRA使用内存的1/4。如果有人发现此代码存在问题,请留言评论。

性能注意事项

以这种方式转换为灰度可能不是最快的方法。您应该检查在您的环境中任何方法的性能。Brad Larson的GPUImage可能更快,甚至是OpenCV的cvtColor。无论如何,您都希望删除中间缓冲区的malloc和free调用,并为应用程序生命周期管理它们。否则,函数调用将被malloc和free所主导。苹果的文档建议尽可能重用整个vImage_Buffer。

您还可以阅读有关使用NEON intrinsics解决相同问题的内容。

最后,最快的方法是根本不进行转换。如果您从设备相机获取图像数据,则设备相机本地处于kCVPixelFormatType_420YpCbCr8BiPlanarFullRange格式。意味着获取第一个平面的数据(Y通道,亮度)是获得灰度的最快方法。

BGRA到灰度

- (void)convertBGRAFrame:(const CLPBasicVideoFrame &)bgraFrame toGrayscale:(CLPBasicVideoFrame &)grayscaleFrame
{
    vImage_Buffer bgraImageBuffer = {
        .width = bgraFrame.width,
        .height = bgraFrame.height,
        .rowBytes = bgraFrame.bytesPerRow,
        .data = bgraFrame.rawPixelData
    };

    void *intermediateBuffer = malloc(bgraFrame.totalBytes);
    vImage_Buffer intermediateImageBuffer = {
        .width = bgraFrame.width,
        .height = bgraFrame.height,
        .rowBytes = bgraFrame.bytesPerRow,
        .data = intermediateBuffer
    };

    int32_t divisor = 256;
//    int16_t a = (int16_t)roundf(1.0f * divisor);
    int16_t r = (int16_t)roundf(0.299f * divisor);
    int16_t g = (int16_t)roundf(0.587f * divisor);
    int16_t b = (int16_t)roundf(0.114f * divisor);
    const int16_t bgrToGray[4 * 4] = { b, 0, 0, 0,
                                       g, 0, 0, 0,
                                       r, 0, 0, 0,
                                       0, 0, 0, 0 };

    vImage_Error error;
    error = vImageMatrixMultiply_ARGB8888(&bgraImageBuffer, &intermediateImageBuffer, bgrToGray, divisor, NULL, NULL, kvImageNoFlags);
    if (error != kvImageNoError) {
        NSLog(@"%s, vImage error %zd", __PRETTY_FUNCTION__, error);
    }

    vImage_Buffer grayscaleImageBuffer = {
        .width = grayscaleFrame.width,
        .height = grayscaleFrame.height,
        .rowBytes = grayscaleFrame.bytesPerRow,
        .data = grayscaleFrame.rawPixelData
    };

    void *scratchBuffer = malloc(grayscaleFrame.totalBytes);
    vImage_Buffer scratchImageBuffer = {
        .width = grayscaleFrame.width,
        .height = grayscaleFrame.height,
        .rowBytes = grayscaleFrame.bytesPerRow,
        .data = scratchBuffer
    };

    error = vImageConvert_ARGB8888toPlanar8(&intermediateImageBuffer, &grayscaleImageBuffer, &scratchImageBuffer, &scratchImageBuffer, &scratchImageBuffer, kvImageNoFlags);
    if (error != kvImageNoError) {
        NSLog(@"%s, vImage error %zd", __PRETTY_FUNCTION__, error);
    }
    free(intermediateBuffer);
    free(scratchBuffer);
}

CLPBasicVideoFrame.h - 参考资料

typedef struct
{
    size_t width;
    size_t height;
    size_t bytesPerRow;
    size_t totalBytes;
    unsigned long pixelFormat;
    void *rawPixelData;
} CLPBasicVideoFrame;

我已完成灰度转换,但在发现网上这本名为Instant OpenCV for iOS的书籍后,我在质量方面遇到了一些问题。我个人买了一本,里面有很多宝藏,尽管代码有点乱。好的一面是,这是一本价格非常合理的电子书。
我对那个矩阵非常好奇。我花了几个小时来摆弄它,试图找出应该怎样排列。我原以为值应该在对角线上,但Instant OpenCV的人将其放在了上面。

bgrToGray矩阵而言,每个输入通道映射到bgrToGray的一行,而bgrToGray的每一列映射到一个输出通道。在数学术语中,bgraImageBuffer中的每个像素都是一个1x4矩阵,bgrToGray是一个4x4矩阵,并且vImageMatrixMultiply_ARGB8888()执行点积,将结果(也是一个1x4矩阵)放入intermediateImageBuffer中。只有bgrToGray的第一列很重要,因为vImageConvert_ARGB8888toPlanar8将其他3个通道丢弃到scratchBuffer中。我的答案通过使用vImageMatrixMultiply_ARGB8888ToPlanar8()结合了这些步骤。 - Matt

0

如果您需要使用 BGRA 视频流 - 您可以使用这个优秀的转换 在这里

这是您需要使用的函数:

void neon_convert (uint8_t * __restrict dest, uint8_t * __restrict src, int numPixels)
      {
          int i;
          uint8x8_t rfac = vdup_n_u8 (77);
          uint8x8_t gfac = vdup_n_u8 (151);
          uint8x8_t bfac = vdup_n_u8 (28);
          int n = numPixels / 8;

          // Convert per eight pixels
          for (i=0; i < n; ++i)
          {
              uint16x8_t  temp;
              uint8x8x4_t rgb  = vld4_u8 (src);
              uint8x8_t result;

              temp = vmull_u8 (rgb.val[0],      bfac);
              temp = vmlal_u8 (temp,rgb.val[1], gfac);
              temp = vmlal_u8 (temp,rgb.val[2], rfac);

              result = vshrn_n_u16 (temp, 8);
              vst1_u8 (dest, result);
              src  += 8*4;
              dest += 8;
          }
      }

更多的优化(使用汇编)在链接中


0

(1) 我在 iOS 相机框架中的经验是使用 kCMPixelFormat_32BGRA 格式的图像,该格式与 ARGB8888 函数族兼容。(也可能可以使用其他格式。)

(2) 在 iOS 上将 BGR 转换为灰度的最简单方法是使用 vImageMatrixMultiply_ARGB8888ToPlanar8()https://developer.apple.com/documentation/accelerate/1546979-vimagematrixmultiply_argb8888top

这里有一个用 Swift 编写的相当完整的例子。我假设 Objective-C 代码会类似。

        guard let imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else {
            // TODO: report error
            return
        }
        
        // Lock the image buffer
        if (kCVReturnSuccess != CVPixelBufferLockBaseAddress(imageBuffer, CVPixelBufferLockFlags.readOnly)) {
            // TODO: report error
            return
        }
        defer {
            CVPixelBufferUnlockBaseAddress(imageBuffer, CVPixelBufferLockFlags.readOnly)
        }
        
        // Create input vImage_Buffer
        let baseAddress = CVPixelBufferGetBaseAddress(imageBuffer)
        let width = CVPixelBufferGetWidth(imageBuffer)
        let height = CVPixelBufferGetHeight(imageBuffer)
        let stride = CVPixelBufferGetBytesPerRow(imageBuffer)
        var inImage = vImage_Buffer(data: baseAddress, height: UInt(height), width: UInt(width), rowBytes: stride)
        
        // Create output vImage_Buffer
        let bitmap = malloc(width * height)
        var outImage = vImage_Buffer(data: bitmap, height: UInt(height), width: UInt(width), rowBytes: width)
        defer {
            // Make sure to free unless the caller is responsible for this
            free(bitmap)
        }

        // Arbitrary divisor to scale coefficients to integer values
        let divisor: Int32 = 0x1000
        let fDivisor = Float(divisor)
        
        // Rec.709 coefficients
        var coefficientsMatrix = [
            Int16(0.0722 * fDivisor),  // blue
            Int16(0.7152 * fDivisor),  // green
            Int16(0.2126 * fDivisor),  // red
            0  // alpha
        ]

        // Convert to greyscale
        if (kvImageNoError != vImageMatrixMultiply_ARGB8888ToPlanar8(
            &inImage, &outImage, &coefficientsMatrix, divisor, nil, 0, vImage_Flags(kvImageNoFlags))) {
            // TODO: report error
            return
        }

上面的代码是受到了苹果公司有关灰度转换的教程的启发,该教程可以在以下链接中找到。它还包括将图像转换为CGImage的方法(如果需要)。请注意,他们假设RGB顺序而不是BGR,并且只提供3个系数而不是4个(错误?) https://developer.apple.com/documentation/accelerate/vimage/converting_color_images_to_grayscale

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接