5
votes

Comment pouvez-vous créer un CVPixelBuffer directement à partir d'une CIImage au lieu d'une UIImage dans Swift?

J'enregistre une vidéo filtrée via une caméra iPhone, et l'utilisation du processeur augmente considérablement lors de la conversion d'une CIImage en UIImage en temps réel pendant l'enregistrement. Ma fonction de tampon pour créer un CVPixelBuffer utilise une UIImage, qui jusqu'à présent m'oblige à effectuer cette conversion. Je voudrais plutôt créer une fonction de tampon qui prend une CIImage si possible afin que je puisse ignorer la conversion d'UIImage en CIImage. Je pense que cela me donnera un énorme coup de pouce en termes de performances lors de l'enregistrement vidéo, car il n'y aura pas de transfert entre le CPU et le GPU.

C'est ce que j'ai en ce moment. Dans ma fonction captureOutput, je crée une UIImage à partir de CIImage, qui est l'image filtrée. Je crée un CVPixelBuffer à partir de la fonction buffer à l'aide de UIImage, et je l'ajoute au pixelBufferInput de assetWriter:

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

    guard (status == kCVReturnSuccess) else {
        return nil
    }

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

    let videoRecContext = CGContext(data: pixelData,
                            width: Int(image.size.width),
                            height: Int(image.size.height),
                            bitsPerComponent: 8,
                            bytesPerRow: videoRecBytesPerRow,
                            space: (MTLCaptureView?.colorSpace)!, // It's getting the current colorspace from a MTKView
                            bitmapInfo: CGImageAlphaInfo.noneSkipFirst.rawValue)

    videoRecContext?.translateBy(x: 0, y: image.size.height)
    videoRecContext?.scaleBy(x: 1.0, y: -1.0)

    UIGraphicsPushContext(videoRecContext!)
    image.draw(in: CGRect(x: 0, y: 0, width: image.size.width, height: image.size.height))
    UIGraphicsPopContext()
    CVPixelBufferUnlockBaseAddress(pixelBuffer!, CVPixelBufferLockFlags(rawValue: 0))

    return pixelBuffer
}

Ma fonction buffer qui utilise une UIImage:

let imageUI = UIImage(ciImage: ciImage)

let filteredBuffer:CVPixelBuffer? = buffer(from: imageUI)

let success = self.assetWriterPixelBufferInput?.append(filteredBuffer!, withPresentationTime: self.currentSampleTime!)


0 commentaires

3 Réponses :


6
votes

Créez un CIContext et utilisez-le pour afficher la CIImage directement sur votre CVPixelBuffer en utilisant CIContext.render (_: CIImage, pour tamponner: CVPixelBuffer) .


3 commentaires

Je ne sais pas exactement comment initialiser le CVPixelBuffer, mais un copier-coller de la fonction de tampon que j'avais auparavant, en remplaçant le paramètre UIImage par CIImage, en échangeant la taille avec l'extension et en supprimant le code UIIGraphicsContext a fait l'affaire. Je vais voir ce que je peux supprimer d'autre là-dedans, ou peut-être pourriez-vous le montrer dans votre réponse. J'ai déjà essayé d'utiliser CIIContext.render, sans trop de chance pour écrire l'appel de fonction ou créer un CVPixelBuffer.


Il a été réduit en supprimant tout jusqu'à l'instruction guard (status == kCVReturnSuccess) et a renvoyé pixelBuffer. J'obtiens une amélioration assez significative sur le processeur. Merci beaucoup.


Est-ce considéré comme une méthode rapide?



0
votes

Notez que la fonction buffer passe une ciImage. J'ai formaté ma fonction de tampon pour passer la CIImage, et j'ai pu me débarrasser d'une grande partie de ce qui se trouvait à l'intérieur:

func buffer(from image: CIImage) -> CVPixelBuffer? {
    let attrs = [kCVPixelBufferCGImageCompatibilityKey: kCFBooleanTrue, kCVPixelBufferCGBitmapContextCompatibilityKey: kCFBooleanTrue] as CFDictionary
    var pixelBuffer : CVPixelBuffer?
    let status = CVPixelBufferCreate(kCFAllocatorDefault, Int(image.extent.width), Int(image.extent.height), kCVPixelFormatType_32ARGB, attrs, &pixelBuffer)

    guard (status == kCVReturnSuccess) else {
        return nil
    }

    return pixelBuffer
}


0 commentaires

3
votes
La réponse de

rob mayoff résume la situation, mais il y a une chose TRÈS TRÈS TRÈS importante à garder à l'esprit:

Core Image reporte le rendu jusqu'à ce que le client demande l'accès au frame buffer, c'est-à-dire CVPixelBufferLockBaseAddress .

J'ai appris cela en parlant avec l'ingénieur du support technique d'Apple et je n'ai trouvé cela dans aucun des documents. Je l'ai utilisé uniquement avec macOS, mais imaginez que ce ne serait pas différent sur iOS.

Gardez à l'esprit que si vous verrouillez la mémoire tampon avant le rendu, il fonctionnera toujours mais exécutera une image derrière et le premier rendu sera vide.

Enfin, il est mentionné plus d'une fois sur SO et même dans ce fil: évitez de créer un nouveau CVPixelBuffer pour chaque rendu car chaque tampon prend une tonne de ressources système. C'est pourquoi nous avons CVPixelBufferPool - Apple l'utilise dans leurs frameworks, alors pouvez-vous faire encore mieux performance! ✌️


1 commentaires

Hé Ian! Pouvez-vous montrer un exemple d'utilisation de CVPixelBufferPool s'il vous plaît?