Получить значение пикселя из CVPixelBufferRef в Swift

Как я могу получить значение пикселя RGB (или любого другого формата) из CVPixelBufferRef? Я пробовал много подходов, но пока безуспешно.

func captureOutput(captureOutput: AVCaptureOutput!,
                   didOutputSampleBuffer sampleBuffer: CMSampleBuffer!,
                   fromConnection connection: AVCaptureConnection!) {
  let pixelBuffer: CVPixelBufferRef = CMSampleBufferGetImageBuffer(sampleBuffer)!
                CVPixelBufferLockBaseAddress(pixelBuffer, 0)
  let baseAddress = CVPixelBufferGetBaseAddress(pixelBuffer)

  //Get individual pixel values here

  CVPixelBufferUnlockBaseAddress(pixelBuffer, 0)
}

Ответ 1

baseAddress является небезопасным изменяемым указателем или, точнее, a UnsafeMutablePointer<Void>. Вы можете легко получить доступ к памяти, как только вы переместили указатель в сторону от Void к более конкретному типу:

// Convert the base address to a safe pointer of the appropriate type
let byteBuffer = UnsafeMutablePointer<UInt8>(baseAddress)

// read the data (returns value of type UInt8)
let firstByte = byteBuffer[0]

// write data
byteBuffer[3] = 90

Убедитесь, что вы используете правильный тип (8, 16 или 32 бит без знака int). Это зависит от формата видео. Скорее всего, это 8 бит.

Обновление форматов буфера:

Вы можете указать формат при инициализации экземпляра AVCaptureVideoDataOutput. У вас в основном есть выбор:

  • BGRA: одиночная плоскость, где синие, зеленые, красные и альфа-значения хранятся в 32-битном целочисленном значении каждый
  • 420YpCbCr8BiPlanarFullRange: две плоскости, первая содержит байт для каждого пикселя с значением Y (яркость), второй - значения Cb и Cr (цветности) для групп пикселей
  • 420YpCbCr8BiPlanarVideoRange: то же, что и 420YpCbCr8BiPlanarFullRange, но значения Y ограничены диапазоном 16 - 235 (по историческим причинам).

Если вас интересуют значения цвета и скорость (или, скорее, максимальная частота кадров), это не проблема, тогда перейдите к более простому формату BGRA. В противном случае возьмите один из более эффективных собственных видеоформатов.

Если у вас есть две плоскости, вы должны получить базовый адрес нужной плоскости (см. пример видеоформата):

Пример формата видео

let pixelBuffer: CVPixelBufferRef = CMSampleBufferGetImageBuffer(sampleBuffer)!
CVPixelBufferLockBaseAddress(pixelBuffer, 0)
let baseAddress = CVPixelBufferGetBaseAddressOfPlane(pixelBuffer, 0)
let bytesPerRow = CVPixelBufferGetBytesPerRowOfPlane(pixelBuffer, 0)
let byteBuffer = UnsafeMutablePointer<UInt8>(baseAddress)

// Get luma value for pixel (43, 17)
let luma = byteBuffer[17 * bytesPerRow + 43]

CVPixelBufferUnlockBaseAddress(pixelBuffer, 0)

Пример BGRA

let pixelBuffer: CVPixelBufferRef = CMSampleBufferGetImageBuffer(sampleBuffer)!
CVPixelBufferLockBaseAddress(pixelBuffer, 0)
let baseAddress = CVPixelBufferGetBaseAddress(pixelBuffer)
let int32PerRow = CVPixelBufferGetBytesPerRow(pixelBuffer)
let int32Buffer = UnsafeMutablePointer<UInt32>(baseAddress)

// Get BGRA value for pixel (43, 17)
let luma = int32Buffer[17 * int32PerRow + 43]

CVPixelBufferUnlockBaseAddress(pixelBuffer, 0)

Ответ 2

Обновление для Swift3:

let pixelBuffer: CVPixelBufferRef = CMSampleBufferGetImageBuffer(sampleBuffer)!
CVPixelBufferLockBaseAddress(pixelBuffer, CVPixelBufferLockFlags(rawValue: 0));
let int32Buffer = unsafeBitCast(CVPixelBufferGetBaseAddress(pixelBuffer), to: UnsafeMutablePointer<UInt32>.self)
let int32PerRow = CVPixelBufferGetBytesPerRow(pixelBuffer)
// Get BGRA value for pixel (43, 17)
let luma = int32Buffer[17 * int32PerRow + 43]

CVPixelBufferUnlockBaseAddress(pixelBuffer, 0)

Ответ 3

В дополнение к ответу Codos, вот метод для получения отдельных значений rgb из пиксельного буфера BRGA. Примечание: ваш буфер должен быть заблокирован перед вызовом этого.

func pixelFrom(x: Int, y: Int, movieFrame: CVPixelBuffer) -> (UInt8, UInt8, UInt8) {
    let baseAddress = CVPixelBufferGetBaseAddress(movieFrame)

    let width = CVPixelBufferGetWidth(movieFrame)
    let height = CVPixelBufferGetHeight(movieFrame)

    let bytesPerRow = CVPixelBufferGetBytesPerRow(movieFrame)
    let buffer = baseAddress!.assumingMemoryBound(to: UInt8.self)

    let index = x+y*bytesPerRow
    let b = buffer[index]
    let g = buffer[index+1]
    let r = buffer[index+2]

    return (r, g, b)
}

Ответ 4

Swift 5

У меня была та же проблема, и я нашел следующее решение. Мой CVPixelBuffer имел размерность 68 x 68, которую можно проверить с помощью

CVPixelBufferLockBaseAddress(pixelBuffer, CVPixelBufferLockFlags(rawValue: 0))
print(CVPixelBufferGetWidth(pixelBuffer))
print(CVPixelBufferGetHeight(pixelBuffer))

Вы также должны знать количество байтов в строке:

print(CVPixelBufferGetBytesPerRow(pixelBuffer))

который в моем случае был 320.

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

Затем я создал байтовый буфер и последовательно прочитал байты следующим образом (не забудьте заблокировать базовый адрес, как показано выше):

var byteBuffer = unsafeBitCast(CVPixelBufferGetBaseAddress(pixelBuffer), to: UnsafeMutablePointer<Float32>.self)
var pixelArray: Array<Array<Float>> = Array(repeating: Array(repeating: 0, count: 68), count: 68)
for row in 0...67{
    for col in 0...67{
        pixelArray[row][col] = byteBuffer.pointee
        byteBuffer = byteBuffer.successor()    
    }
    byteBuffer = byteBuffer.advanced(by: 12)
}
CVPixelBufferUnlockBaseAddress(pixelBuffer, CVPixelBufferLockFlags(rawValue: 0))

Вы можете спросить о роли byteBuffer = byteBuffer.advanced(by: 12). Причина, по которой мы должны это сделать, заключается в следующем.

Мы знаем, что у нас есть 320 байтов на строку. Однако наш буфер имеет ширину 68 и тип данных Float32, например 4 байта на значение. Это означает, что у нас фактически есть только 272 байтов на строку с последующим заполнением нулями. Это заполнение нулями, вероятно, имеет причины размещения памяти.

Поэтому мы должны пропустить последние 48 байтов в каждой строке, что делается byteBuffer = byteBuffer.advanced(by: 12) (12*4 = 48).

Этот подход несколько отличается от других решений, поскольку мы используем указатели на следующее byteBuffer. Тем не менее, я считаю, что это проще и интуитивно понятнее.