Как удалить альфа-канал из CVPixelBuffer и получить его данные в Swift - PullRequest
3 голосов
/ 04 августа 2020

Мой CVPixelBuffer имеет вид kCVPixelFormatType_32BGRA , и я пытаюсь получить данные кадра без альфа-канала в формате BGR. Вот что я пытался сделать (как extension CVPixelBuffer)

func bgrData(byteCount: Int) -> Data? {
    CVPixelBufferLockBaseAddress(self, .readOnly)
    defer { CVPixelBufferUnlockBaseAddress(self, .readOnly) }
    guard let sourceData = CVPixelBufferGetBaseAddress(self) else {
        return nil
    }
    
    let width = CVPixelBufferGetWidth(self)
    let height = CVPixelBufferGetHeight(self)
    let sourceBytesPerRow = CVPixelBufferGetBytesPerRow(self)
    let destinationBytesPerRow = 3 * width
    
    // Assign input image to `sourceBuffer` to convert it.
    var sourceBuffer = vImage_Buffer(
        data: sourceData,
        height: vImagePixelCount(height),
        width: vImagePixelCount(width),
        rowBytes: sourceBytesPerRow
    )
    
    // Make `destinationBuffer` and `destinationData` for its data to be assigned.
    guard let destinationData = malloc(height * destinationBytesPerRow) else {
        os_log("Error: out of memory", type: .error)
        return nil
    }
    defer { free(destinationData) }
    var destinationBuffer = vImage_Buffer(
        data: destinationData,
        height: vImagePixelCount(height),
        width: vImagePixelCount(width),
        rowBytes: destinationBytesPerRow)
    
    // Return `Data` with bgr image.
    return imageByteData = Data(
        bytes: sourceBuffer.data, count: destinationBuffer.rowBytes * height)
}

Но полученный буфер не кажется правильным. Как лучше всего этого добиться? Заранее спасибо

Ответы [ 2 ]

3 голосов
/ 04 августа 2020

Поскольку у вас есть доступ к вашему CVPixelBuffer, вы можете напрямую использовать фреймворк Accelerate, чтобы выполнить преобразование за вас.

Я не проверяю какие-либо ошибки, инструкции try / catch, никаких средств защиты и т. Д. c в этом коде. Вам нужно будет убедиться, что код защищен от ошибок.

Давайте сначала определим наш цветовой формат BGRA. Поскольку у нас 4 канала, нам нужно 32 бита на пиксель. Мы также определяем, что наш альфа-канал является последним битом.

var bgraSourceFormat = vImage_CGImageFormat(
  bitsPerComponent: 8,
  bitsPerPixel: 32,
  colorSpace: Unmanaged.passRetained(CGColorSpaceCreateDeviceRGB()),
  bitmapInfo: CGBitmapInfo(rawValue: CGImageAlphaInfo.last.rawValue),
  version: 0,
  decode: nil,
  renderingIntent: .defaultIntent
)

Теперь мы можем определить формат BGR. Нам нужно 3 канала, поэтому 24 бита на пиксель вполне достаточно. Мы также определяем, что этот формат не будет иметь альфа-канала.

var bgrDestinationFormat = vImage_CGImageFormat(
  bitsPerComponent: 8,
  bitsPerPixel: 24,
  colorSpace: nil,
  bitmapInfo: CGBitmapInfo(rawValue: CGImageAlphaInfo.none.rawValue),
  version: 0,
  decode: nil,
  renderingIntent: .defaultIntent
)

И создать конвертер ...

let bgraToRgbConverter = vImageConverter_CreateWithCGImageFormat(
  &bgraSourceFormat,
  &bgrDestinationFormat,
  nil,
  vImage_Flags(kvImagePrintDiagnosticsToConsole),
  nil
)

let converter = bgraToRgbConverter!.takeRetainedValue()

Теперь нам нужно создать буфер чтения из нашего существующего пиксельные данные и буфер записи для копирования того, что нам нужно. Чтобы создать буфер чтения из CVPixelBuffer, мы можем сделать что-то вроде этого:

var bgraBuffer  = vImage_Buffer()
let imageFormat = vImageCVImageFormat_CreateWithCVPixelBuffer(cvPixelBuffer).takeRetainedValue()
vImageCVImageFormat_SetColorSpace(imageFormat, CGColorSpaceCreateDeviceRGB())
vImageBuffer_InitWithCVPixelBuffer(
  &bgraBuffer,
  &bgraSourceFormat,
  cvPixelBuffer,
  imageFormat,
  nil,
  vImage_Flags(kvImageNoFlags)
)

И создать пустой буфер записи ...

var bgrBuffer = vImage_Buffer()
vImageBuffer_Init(
  &bgrBuffer,
  bgraBuffer.height,
  bgraBuffer.width,
  bgrDestinationFormat.bitsPerPixel,
  vImage_Flags(kvImageNoFlags)
)

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

vImageConvert_AnyToAny(
  converter,
  &bgraBuffer,
  &bgrBuffer,
  nil,
  vImage_Flags(kvImagePrintDiagnosticsToConsole)
)

И все. Ваш BGRA теперь преобразован в BGR как vImage_Buffer. Мы можем проверить, выполнили ли мы то, что хотели, напрямую считывая данные пикселей. Во-первых, нам нужно получить доступ к данным:

let bgraData = bgraBuffer.data!.assumingMemoryBound(to: UInt8.self)
let bgrData  = bgrBuffer.data!.assumingMemoryBound(to: UInt8.self)

Теперь мы можем распечатать первый и второй пиксели

print(bgraData[0], bgraData[1], bgraData[2], bgraData[3])
print(bgrData[0], bgrData[1], bgrData[2])

print(bgraData[4], bgraData[5], bgraData[6], bgraData[7])
print(bgrData[3], bgrData[4], bgrData[5])

Это результат, который я вижу из PNG image Я использовал для тестирования на игровых площадках:

249 244 234 255
249 244 234

251 242 233 255
251 242 233

Как видите, пиксели копируются без альфа-канала. Будьте осторожны с циклами for, если вы собираетесь их использовать, так как теперь у нас есть 3 канала.

Если вы разрабатываете игру + делаете это для каждого кадра, попробуйте сохранить ваши объекты живыми. Определения форматов Accelerate, буфер записи и конвертер никогда не меняются для одного и того же размера + формата изображения, поэтому их можно создать один раз и сохранить в памяти для использования в будущем.

Похоже, вы возвращаете объект данных. Вы можете преобразовать конструкцию UnsafeMutablePointer во все, что вам нужно.

Или вы также можете преобразовать vImage_Buffer обратно в CVPixelBuffer (если вам нужно) с помощью метода vImageBuffer_CopyToCVPixelBuffer ускорения. В vImage_Buffer много конвертеров, один точно вам подойдет. Проверьте эту ссылку для получения дополнительной информации о том, как использовать метод копирования в буфер пикселей. Эта ссылка содержит отличный пример использования.

Изменить: Ваш CVPixelBuffer может быть дополнен.

Из-за требований к оборудованию ваше изображение может иметь заполнение, чтобы убедиться, что ширина и высота буфера кратны 16. Это также приведет к заполнению ваших структур vImage_Buffer. Если вам нужно l oop, но нужно только получить доступ / обновить отдельные пиксели, вы можете использовать функции Accelerate для увеличения скорости. Проверьте эту ссылку , чтобы узнать о возможных методах, и в конце страницы есть отличные примеры.

Чтобы полностью прочитать данные, вы можете написать что-то вроде этого:

var bgrData = bgrBuffer.data!.assumingMemoryBound(to: UInt8.self)
print(bgrBuffer.width, bgrBuffer.height, bgrBuffer.rowBytes)

for _ in 0 ..< Int(bgrBuffer.height) {
  for x in 0 ..< Int(bgrBuffer.width) {
    let b = (bgrData + x * 3 + 0).pointee
    let g = (bgrData + x * 3 + 1).pointee
    let r = (bgrData + x * 3 + 2).pointee
    print(b, g, r)
  }
  bgrData = bgrData.advanced(by: bgrBuffer.rowBytes)
}

Это гарантирует, что вы читаете пиксели во всю ширину, но передаете заполнение в конце.

0 голосов
/ 04 августа 2020

Ваш код выполняет только распределение новой структуры данных, но, очевидно, он не «сжимает» 4 байта в 3 байта, отбрасывая последний байт Alpha.

Так что байт Alpha первого пикселя (в источнике) становится синим байтом второго пикселя (в пункте назначения) и так далее:

S: BGRABGRABGRA ...

D: BGRBGRBGRBGR ...

...