将图像转换为 CVPixelBuffer 用于机器学习 Swift

Posted

技术标签:

【中文标题】将图像转换为 CVPixelBuffer 用于机器学习 Swift【英文标题】:Convert Image to CVPixelBuffer for Machine Learning Swift 【发布时间】:2017-11-08 02:29:47 【问题描述】:

我正在尝试让 Apple 在 2017 年 WWDC 上演示的示例 Core ML 模型正常运行。我正在使用 GoogLeNet 尝试对图像进行分类(请参阅Apple Machine Learning Page)。该模型将 CVPixelBuffer 作为输入。我有一个名为 imageSample.jpg 的图像,用于此演示。我的代码如下:

        var sample = UIImage(named: "imageSample")?.cgImage
        let bufferThree = getCVPixelBuffer(sample!)

        let model = GoogLeNetPlaces()
        guard let output = try? model.prediction(input: GoogLeNetPlacesInput.init(sceneImage: bufferThree!)) else 
            fatalError("Unexpected runtime error.")
        

        print(output.sceneLabel)

我总是在输出而不是图像分类中收到意外的运行时错误。我转换图像的代码如下:

func getCVPixelBuffer(_ image: CGImage) -> CVPixelBuffer? 
        let imageWidth = Int(image.width)
        let imageHeight = Int(image.height)

        let attributes : [NSObject:AnyObject] = [
            kCVPixelBufferCGImageCompatibilityKey : true as AnyObject,
            kCVPixelBufferCGBitmapContextCompatibilityKey : true as AnyObject
        ]

        var pxbuffer: CVPixelBuffer? = nil
        CVPixelBufferCreate(kCFAllocatorDefault,
                            imageWidth,
                            imageHeight,
                            kCVPixelFormatType_32ARGB,
                            attributes as CFDictionary?,
                            &pxbuffer)

        if let _pxbuffer = pxbuffer 
            let flags = CVPixelBufferLockFlags(rawValue: 0)
            CVPixelBufferLockBaseAddress(_pxbuffer, flags)
            let pxdata = CVPixelBufferGetBaseAddress(_pxbuffer)

            let rgbColorSpace = CGColorSpaceCreateDeviceRGB();
            let context = CGContext(data: pxdata,
                                    width: imageWidth,
                                    height: imageHeight,
                                    bitsPerComponent: 8,
                                    bytesPerRow: CVPixelBufferGetBytesPerRow(_pxbuffer),
                                    space: rgbColorSpace,
                                    bitmapInfo: CGImageAlphaInfo.premultipliedFirst.rawValue)

            if let _context = context 
                _context.draw(image, in: CGRect.init(x: 0, y: 0, width: imageWidth, height: imageHeight))
            
            else 
                CVPixelBufferUnlockBaseAddress(_pxbuffer, flags);
                return nil
            

            CVPixelBufferUnlockBaseAddress(_pxbuffer, flags);
            return _pxbuffer;
        

        return nil
    

我从以前的 *** 帖子(最后一个答案 here)中得到了这段代码。我认识到代码可能不正确,但我自己不知道如何做到这一点。我相信这是包含错误的部分。该模型需要以下类型的输入:Image<RGB,224,224>

【问题讨论】:

我创建了一个带有完整代码的示例项目,可以在这里找到:hackernoon.com/… 【参考方案1】:

您无需自己处理大量图像即可使用带有图像的 Core ML 模型 — 新的 Vision framework 可以为您做到这一点。

import Vision
import CoreML

let model = try VNCoreMLModel(for: MyCoreMLGeneratedModelClass().model)
let request = VNCoreMLRequest(model: model, completionHandler: myResultsMethod)
let handler = VNImageRequestHandler(url: myImageURL)
handler.perform([request])

func myResultsMethod(request: VNRequest, error: Error?) 
    guard let results = request.results as? [VNClassificationObservation]
        else  fatalError("huh") 
    for classification in results 
        print(classification.identifier, // the scene label
              classification.confidence)
    


WWDC17 session on Vision 应该有更多信息 - 明天下午。

【讨论】:

像魅力一样工作(有一些修改),谢谢。我没有意识到 Vision 对从图像输入输出信息的模型有特定类型的请求。我想我应该更加关注文档... 对于原问题,VNImageRequestHandler(cgImage: CGImage)更合适。 @chengsam 不是——最初的问题是从磁盘上的资源开始的。将其作为UIImage 读入,转换为CGImage,并将其传递给Vision 会在很长一段时间内丢失元数据,但传递资源URL 会使该元数据对Vision 可用。 如果 MLModel 需要灰度图像,VNImageRequestHandler 是否将其转换为灰度图像?【参考方案2】:

您可以使用纯 CoreML,但应将图像大小调整为 (224,224)

    DispatchQueue.global(qos: .userInitiated).async 
        // Resnet50 expects an image 224 x 224, so we should resize and crop the source image
        let inputImageSize: CGFloat = 224.0
        let minLen = min(image.size.width, image.size.height)
        let resizedImage = image.resize(to: CGSize(width: inputImageSize * image.size.width / minLen, height: inputImageSize * image.size.height / minLen))
        let cropedToSquareImage = resizedImage.cropToSquare()

        guard let pixelBuffer = cropedToSquareImage?.pixelBuffer() else 
            fatalError()
        
        guard let classifierOutput = try? self.classifier.prediction(image: pixelBuffer) else 
            fatalError()
        

        DispatchQueue.main.async 
            self.title = classifierOutput.classLabel
        
    

// ...

extension UIImage 

    func resize(to newSize: CGSize) -> UIImage 
        UIGraphicsBeginImageContextWithOptions(CGSize(width: newSize.width, height: newSize.height), true, 1.0)
        self.draw(in: CGRect(x: 0, y: 0, width: newSize.width, height: newSize.height))
        let resizedImage = UIGraphicsGetImageFromCurrentImageContext()!
        UIGraphicsEndImageContext()

        return resizedImage
    

    func cropToSquare() -> UIImage? 
        guard let cgImage = self.cgImage else 
            return nil
        
        var imageHeight = self.size.height
        var imageWidth = self.size.width

        if imageHeight > imageWidth 
            imageHeight = imageWidth
        
        else 
            imageWidth = imageHeight
        

        let size = CGSize(width: imageWidth, height: imageHeight)

        let x = ((CGFloat(cgImage.width) - size.width) / 2).rounded()
        let y = ((CGFloat(cgImage.height) - size.height) / 2).rounded()

        let cropRect = CGRect(x: x, y: y, width: size.height, height: size.width)
        if let croppedCgImage = cgImage.cropping(to: cropRect) 
            return UIImage(cgImage: croppedCgImage, scale: 0, orientation: self.imageOrientation)
        

        return nil
    

    func pixelBuffer() -> CVPixelBuffer? 
        let width = self.size.width
        let height = self.size.height
        let attrs = [kCVPixelBufferCGImageCompatibilityKey: kCFBooleanTrue,
                     kCVPixelBufferCGBitmapContextCompatibilityKey: kCFBooleanTrue] as CFDictionary
        var pixelBuffer: CVPixelBuffer?
        let status = CVPixelBufferCreate(kCFAllocatorDefault,
                                         Int(width),
                                         Int(height),
                                         kCVPixelFormatType_32ARGB,
                                         attrs,
                                         &pixelBuffer)

        guard let resultPixelBuffer = pixelBuffer, status == kCVReturnSuccess else 
            return nil
        

        CVPixelBufferLockBaseAddress(resultPixelBuffer, CVPixelBufferLockFlags(rawValue: 0))
        let pixelData = CVPixelBufferGetBaseAddress(resultPixelBuffer)

        let rgbColorSpace = CGColorSpaceCreateDeviceRGB()
        guard let context = CGContext(data: pixelData,
                                      width: Int(width),
                                      height: Int(height),
                                      bitsPerComponent: 8,
                                      bytesPerRow: CVPixelBufferGetBytesPerRow(resultPixelBuffer),
                                      space: rgbColorSpace,
                                      bitmapInfo: CGImageAlphaInfo.noneSkipFirst.rawValue) else 
                                        return nil
        

        context.translateBy(x: 0, y: height)
        context.scaleBy(x: 1.0, y: -1.0)

        UIGraphicsPushContext(context)
        self.draw(in: CGRect(x: 0, y: 0, width: width, height: height))
        UIGraphicsPopContext()
        CVPixelBufferUnlockBaseAddress(resultPixelBuffer, CVPixelBufferLockFlags(rawValue: 0))

        return resultPixelBuffer
    

您可以在mimodel 文件中找到输入的预期图像大小:

一个使用纯 CoreML 和 Vision 变体的演示项目,您可以在此处找到:https://github.com/handsomecode/ios11-Demos/tree/coreml_vision/CoreML/CoreMLDemo

【讨论】:

我以为我在 Vision 会话(或者可能是其他 ML 会话之一)中听说您不必调整图像大小......但也许我错了。 @pinkeerach :如果您使用 Vision API(VNCoreMLRequest,如我的回答),您不必调整图像大小,因为 Vision 会为您处理图像处理部分。如果您直接使用 Core ML(不使用 Vision),则必须调整图像大小并重新格式化(根据您使用的特定模型),然后自己将其转换为 CVPixelBuffer @mauryat 您的示例项目什么都不做。真的没有代码。 @zumzum 你可以在这里查看我的示例github.com/handsomecode/iOS11-Demos/tree/coreml_vision,我已经实现了这两种方法 @zumzum 抱歉,我想我没有提交就推动了。在修复之前,我会从 cmets 中删除我的链接。【参考方案3】:

如果输入的是UIImage,而不是URL,并且你想使用VNImageRequestHandler,你可以使用CIImage

func updateClassifications(for image: UIImage) 

    let orientation = CGImagePropertyOrientation(image.imageOrientation)

    guard let ciImage = CIImage(image: image) else  return 

    let handler = VNImageRequestHandler(ciImage: ciImage, orientation: orientation)


来自Classifying Images with Vision and Core ML

【讨论】:

以上是关于将图像转换为 CVPixelBuffer 用于机器学习 Swift的主要内容,如果未能解决你的问题,请参考以下文章

如何将 YUV 帧(来自 OTVideoFrame)转换为 CVPixelBuffer

关于 UIImage -> CVPixelBuffer -> UIImage 转换的问题

PHP 将 GD 图像转换为用于验证码的 CSS 代码

调整CVPixelBuffer的大小

是否可以更改 CVPixelBuffer 中捕获的 AR 图像的分辨率?

CVPixelBuffer 到 FBO