Получить значение пикселя из 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)
}
3 ответа
baseAddress
небезопасный изменяемый указатель или точнее 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-битный unsigned 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)
В дополнение к ответу 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)
}
Обновление для 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)
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
. Однако мне это кажется более простым и интуитивно понятным.