How-to convert an iOS camera image to greyscale using the Accelerate Framework?

空扰寡人 提交于 2019-12-05 06:10:52

There is an easier option here. If you change the camera acquire format to YUV, then you already have a greyscale frame that you can use as you like. When setting up your data output, use something like:

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

You can then access the Y plane in your capture callback using:

CVPixelBufferRef pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);
CVPixelBufferLockBaseAddress(pixelBuffer, kCVPixelBufferLock_ReadOnly);
uint8_t *yPlane = CVPixelBufferGetBaseAddressOfPlane(pixelBuffer, 0);

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

CVPixelBufferUnlockBaseAddress(pixelBuffer);

Convert BGRA Image to Grayscale with Accelerate vImage

This method is meant to illustrate getting Accelerate's vImage use in converting BGR images to grayscale. Your image may very well be in RGBA format and you'll need to adjust the matrix accordingly, but the camera outputs BGRA so I'm using it here. The values in the matrix are the same values used in OpenCV for cvtColor, there are other values you might play with like luminosity. I assume you malloc the appropriate amount of memory for the result. In the case of grayscale it is only 1-channel or 1/4 the memory used for BGRA. If anyone finds issues with this code please leave a comment.

Performance note

Converting to grayscale in this way may NOT be the fastest. You should check the performance of any method in your environment. Brad Larson's GPUImage might be faster, or even OpenCV's cvtColor. In any case you will want to remove the calls to malloc and free for the intermediate buffers and manage them for the app lifecycle. Otherwise, the function call will be dominated by the malloc and free. Apple's docs recommend reusing the whole vImage_Buffer when possible.

You can also read about solving the same problem with NEON intrinsics.

Finally, the fastest method is not converting at all. If you're getting image data from the device camera the device camera is natively in the kCVPixelFormatType_420YpCbCr8BiPlanarFullRange format. Meaning, grabbing the first plane's data (Y-Channel, luma) is the fastest way to get grayscale.

BGRA to Grayscale

- (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 - For reference

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

I got through the grayscale conversion, but was having trouble with the quality when I found this book on the web called Instant OpenCV for iOS. I personally picked up a copy and it has a number of gems, although the code is bit of a mess. On the bright-side it is a very reasonably priced eBook.

I'm very curious about that matrix. I toyed around with it for hours trying to figure out what the arrangement should be. I would have thought the values should be on the diagonal, but the Instant OpenCV guys put it as above.

Ian Ollmann

The vImage method is to use vImageMatrixMultiply_Planar8 and a 1x3 matrix. vImageConvert_RGBA8888toPlanar8 is the function you use to convert a RGBA8888 buffer into 4 planar buffers. These are used by vImageMatrixMultiply_Planar8. vImageMatrixMultiply_ARGB8888 will do it too in one pass, but your gray channel will be interleaved with three other channels in the result. vImageConvert_RGBA8888toPlanar8 itself doesn't do any math. All it does is separate your interleaved image into separate image planes.

If you need to adjust the gamma as well, then probably vImageConvert_AnyToAny() is the easy choice. It will do the fully color managed conversion from your RGB format to a grayscale colorspace. See vImage_Utilities.h.

I like Tarks answer better though. It just leaves you in a position of having to color manage the Luminance manually (if you care).

if you need to use BGRA vide streams - you can use this excellent conversion here

This is the function you'll need to take:

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;
          }
      }

more optimisations (using assembly) are in the link

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!