1

I'm trying to convert the CVPixelBuffer I get from the iPhone camera (using CMSampleBufferGetImageBuffer) to a grayscale CVPixelBuffer, for use in CoreML.

Code looks like this:

extension CVPixelBuffer {
    
    enum Error: Swift.Error {
        case greyscaleConversionFailure
    }
    
    func greyscalePixelBuffer() throws -> CVPixelBuffer {
        // Create color vImage_Buffer
        let baseAddress = CVPixelBufferGetBaseAddress(self)
        let width = CVPixelBufferGetWidth(self)
        let height = CVPixelBufferGetHeight(self)
        let stride = CVPixelBufferGetBytesPerRow(self)
        var colorVImageBuffer = vImage_Buffer(data: baseAddress, height: UInt(height), width: UInt(width), rowBytes: stride)
        
        // Create greyscale vImage_Buffer
        let bitmap = malloc(width * height)
        var greyscaleVImageBuffer = vImage_Buffer(data: bitmap, height: UInt(height), width: UInt(width), rowBytes: width)
        defer {
            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
        guard kvImageNoError == vImageMatrixMultiply_ARGB8888ToPlanar8(&colorVImageBuffer, &greyscaleVImageBuffer, &coefficientsMatrix, divisor, nil, 0, vImage_Flags(kvImageNoFlags)) else {
            throw Error.greyscaleConversionFailure
        }
        
        // Format of grayscale buffer needed for conversion
        let greySpace = CGColorSpaceCreateDeviceGray()
        guard var monoFormat = vImage_CGImageFormat(bitsPerComponent: 8, bitsPerPixel: 8, colorSpace: greySpace, bitmapInfo: CGBitmapInfo(rawValue: CGImageAlphaInfo.none.rawValue), renderingIntent: .defaultIntent) else {
            throw Error.greyscaleConversionFailure
        }
        
        // Create a CVPixelBuffer for result
        var destinationBuffer: CVPixelBuffer?
        let info = [kCVPixelBufferCGImageCompatibilityKey : kCFBooleanTrue, kCVPixelBufferCGBitmapContextCompatibilityKey : kCFBooleanTrue] as CFDictionary
        let status = CVPixelBufferCreate(kCFAllocatorDefault, width, height, kCVPixelFormatType_OneComponent8, info, &destinationBuffer)
        guard status == kCVReturnSuccess, let destinationBuffer = destinationBuffer else {
            throw Error.greyscaleConversionFailure
        }

        // Format of the CVPixelBuffer content
        let vformat = vImageCVImageFormat_CreateWithCVPixelBuffer(destinationBuffer).takeRetainedValue()
        vImageCVImageFormat_SetColorSpace(vformat, greySpace)

        // Copy greyscale data into CVPixelBuffer
        let error = vImageBuffer_CopyToCVPixelBuffer(&greyscaleVImageBuffer, &monoFormat, destinationBuffer, vformat, nil, vImage_Flags(kvImagePrintDiagnosticsToConsole))
        guard error == kvImageNoError else {
            throw Error.greyscaleConversionFailure
        }
        
        return destinationBuffer
    }
    
}

I see this message in the console when vImageBuffer_CopyToCVPixelBuffer is executed.

vImageBuffer_CopyToCVPixelBuffer error: CVImageFormat_GetConversionMatrix returned unhandled matrix type. Defaulting to kCVImageBufferYCbCrMatrix_ITU_R_601_4. (0)

Seems I need to set a matrix on the destination format, but not sure what I need.

Anyone know what I am missing?

Drew McCormack
  • 3,490
  • 1
  • 19
  • 23

0 Answers0