【问题标题】:Get pixel value from CVPixelBufferRef in Swift从 Swift 中的 CVPixelBufferRef 获取像素值
【发布时间】:2016-04-06 19:15:33
【问题描述】:

如何从 CVPixelBufferRef 获取 RGB(或任何其他格式)像素值?我尝试了很多方法,但都没有成功。

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)
}

【问题讨论】:

  • 核心视频像素缓冲区不保存有关单个像素的信息,而是保存从场景中捕获的所有像素的像素信息(像素的位图矩阵)。您的意思是要获取缓冲区内每个像素的 RGB 值吗?

标签: ios swift image-processing cvpixelbuffer


【解决方案1】:

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 位无符号整数)。这取决于视频格式。很可能是 8 位。

缓冲区格式更新:

您可以在初始化AVCaptureVideoDataOutput 实例时指定格式。您基本上可以选择:

  • BGRA:一个平面,其中蓝色、绿色、红色和 alpha 值分别存储在一个 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)

【讨论】:

  • 让 luma = int32Buffer[17 * int32Buffer + 43] 无法编译。 “二元运算符“*”不能应用于“Int”和“UnsafeMutablePointer”类型的操作数。我遇到过类似的问题。如果我找到正确转换它的方法,将会更新。
  • 对不起。错字。修好了。
  • 如何从整个 CMSampleBuffer 中获取 UInt8 数组? CMSampleBuffer 到 [UInt8]
  • @codo let int32Buffer = UnsafeMutablePointer&lt;UInt32&gt;(baseAddress) 的 swift4 转换似乎是 let int32Buffer = baseAddress.assumingMemoryBound(to: UInt32.self) 但它似乎不起作用! 相反,baseAddress.assumingMemoryBound(to: UInt8.self) 可以。我不明白为什么,因为我的像素缓冲区格式是kCVPixelFormatType_32BGRA。有什么线索吗?
  • /!\ BGRA 示例中有一个错字:let luma = int32Buffer[17 * int32PerRow + 43] 应该是 let bgra = int32Buffer[17 * int32PerRow + 43*4] 因为每个像素有 4 个值(B、G、R、A) ,水平位移应为 x4
【解决方案2】:

这是一种从 BGRA 像素缓冲区获取单个 rgb 值的方法。注意:在调用此之前,您的缓冲区必须被锁定。

func pixelFrom(x: Int, y: Int, movieFrame: CVPixelBuffer) -> (UInt8, UInt8, UInt8) {
    let baseAddress = CVPixelBufferGetBaseAddress(movieFrame)
    
    let bytesPerRow = CVPixelBufferGetBytesPerRow(movieFrame)
    let buffer = baseAddress!.assumingMemoryBound(to: UInt8.self)
    
    let index = x*4 + y*bytesPerRow
    let b = buffer[index]
    let g = buffer[index+1]
    let r = buffer[index+2]
    
    return (r, g, b)
}

【讨论】:

  • 宽度以像素数给出,BGRA-pixelbuffer 的每个像素由 4 个字节表示。因此index 应该是4*x + y*bytesPerRow
【解决方案3】:

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)

【讨论】:

  • 如何从中获取包含整个图像的 UInt8 数组? CMSampleBuffer 到 [UInt8]
  • 如果我使用的是 kCVPixelFormatType_14Bayer_RGGB RAW 格式,如何获取像素值?
  • 警告:如果您不打开对CVPixelBufferGetBaseAddress 的调用,您可能会错过导致未定义行为的重要警告。请参阅this answer 了解更多信息。
【解决方案4】:

斯威夫特 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 的指针。但是,我发现这更容易、更直观。

【讨论】:

  • let advance = (bytesPerRow - bufferWidth) / MemoryLayout&lt;Float32&gt;.size
  • 警告:如果您不打开对CVPixelBufferGetBaseAddress 的调用,您可能会错过导致未定义行为的重要警告。请参阅this answer 了解更多信息。
猜你喜欢
  • 2017-10-26
  • 2023-03-24
  • 1970-01-01
  • 2016-11-30
  • 2018-11-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多