Как сделать CVPixelBuffer прямо из CIImage вместо UIImage в Swift?

Я записываю отфильтрованное видео через камеру iPhone, и при преобразовании CIImage в UIImage в режиме реального времени во время записи сильно увеличивается загрузка ЦП. Моя буферная функция для создания CVPixelBuffer использует UIImage, который до сих пор требует, чтобы я сделал это преобразование. Вместо этого я хотел бы создать буферную функцию, которая принимает CIImage, если это возможно, чтобы я мог пропустить преобразование из UIImage в CIImage. Я думаю, что это даст мне огромный прирост производительности при записи видео, поскольку не будет никакого переключения между процессором и графическим процессором.

Это то, что у меня есть прямо сейчас. В моей функции captureOutput я создаю UIImage из CIImage, который является отфильтрованным изображением. Я создаю CVPixelBuffer из буферной функции с помощью UIImage и добавляю его в pixelBufferInput assetWriter:

let imageUI = UIImage(ciImage: ciImage)

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

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

Моя буферная функция, использующая UIImage:

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
}

person Chewie The Chorkie    schedule 24.01.2019    source источник


Ответы (3)


Создайте CIContext и используйте его для рендеринга CIImage прямо на ваш CVPixelBuffer с помощью _4 _.

person rob mayoff    schedule 24.01.2019
comment
Я не знаю точно, как инициализировать CVPixelBuffer, но скопируйте и вставьте буферную функцию, которая была у меня раньше, заменив параметр UIImage на CIImage, заменив размер на экстент и удалив код UIIGraphicsContext. Я посмотрю, что еще я могу удалить там, или, возможно, вы могли бы показать это в своем ответе. Раньше я пытался использовать CIIContext.render, но без особого успеха в том, как написать вызов функции или создать CVPixelBuffer. - person Chewie The Chorkie; 24.01.2019
comment
Сузил его, удалив все до инструкции guard (status == kCVReturnSuccess), и вернул pixelBuffer. Я получаю довольно значительное улучшение процессора. Большое спасибо. - person Chewie The Chorkie; 24.01.2019
comment
Считается ли это быстрым методом? - person Roi Mulia; 23.08.2020
comment
CIContext * context = [[выделение CIContext] initWithOptions: nil]; [отрисовка контекста: изображение вCVPixelBuffer: pixelBuffer]; - person user353877; 10.01.2021

Ответ rob mayoff подводит итог, но следует помнить об одной ОЧЕНЬ-ОЧЕНЬ важной вещи:

Core Image откладывает рендеринг до тех пор, пока клиент не запросит доступ к буферу кадра, то есть CVPixelBufferLockBaseAddress.

Я узнал об этом, поговорив с инженером службы технической поддержки Apple, и не смог найти этого ни в одной документации. Я использовал это только с macOS, но представьте, что на iOS все не будет иначе.

Имейте в виду, что если вы заблокируете буфер перед рендерингом, он все равно будет работать, но будет выполняться на один кадр позже, и первый рендер будет пуст.

Наконец, в SO и даже в этом потоке неоднократно упоминалось: избегайте создания новых CVPixelBuffer для каждого рендеринга, потому что каждый буфер занимает тонну системных ресурсов. Вот почему у нас есть CVPixelBufferPool - Apple использует его в своих фреймворках, поэтому вы можете добиться еще большей производительности! ✌️

person Ian Bytchek    schedule 26.03.2020
comment
Привет, Ян! Не могли бы вы показать пример использования CVPixelBufferPool? - person Roi Mulia; 23.08.2020

Чтобы расширить ответ, который я получил от Роба Мэйоффа, я покажу, что я изменил, ниже:

В функции captureOutput я изменил свой код на:

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

filterContext?.render(_:ciImage, to:filteredBuffer!)

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

Обратите внимание, что буферная функция передает ciImage. Я отформатировал свою буферную функцию, чтобы передать CIImage, и смог избавиться от большого количества того, что было внутри:

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
}
person Chewie The Chorkie    schedule 24.01.2019