如何从 CVPixelBuffer 中删除 Alpha 通道并在 Swift 中获取其数据

fff*_*fff 3 alpha image-processing ios swift

我的 CVPixelBuffer 为kCVPixelFormatType_32BGRA,我试图获取没有 Alpha 通道的 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)
}
Run Code Online (Sandbox Code Playgroud)

但获得的缓冲区似乎不正确。实现这一目标的最佳方法是什么?提前致谢

emr*_*duz 6

由于您可以访问 CVPixelBuffer,因此您可以直接使用 Accelerate 框架来为您进行转换。

我不会检查此代码中的任何错误、try/catch 语句、无防护等。您需要确保代码是防错的。

让我们首先定义 BGRA 颜色格式。由于我们有 4 个通道,因此每个像素需要 32 位。我们还定义我们的 Alpha 通道是最后一位。

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

现在我们可以定义 BGR 格式。我们需要 3 个通道,因此每像素 24 位就足够了。我们还定义该格式不会有 Alpha 通道。

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

并创建转换器...

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

let converter = bgraToRgbConverter!.takeRetainedValue()
Run Code Online (Sandbox Code Playgroud)

现在我们需要从现有的像素数据创建一个读取缓冲区,以及一个用于复制我们需要的数据的写入缓冲区。要从 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)
)
Run Code Online (Sandbox Code Playgroud)

并创建空的写入缓冲区...

var bgrBuffer = vImage_Buffer()
vImageBuffer_Init(
  &bgrBuffer,
  bgraBuffer.height,
  bgraBuffer.width,
  bgrDestinationFormat.bitsPerPixel,
  vImage_Flags(kvImageNoFlags)
)
Run Code Online (Sandbox Code Playgroud)

我们准备好了...让我们告诉加速框架从一种格式转换为另一种格式

vImageConvert_AnyToAny(
  converter,
  &bgraBuffer,
  &bgrBuffer,
  nil,
  vImage_Flags(kvImagePrintDiagnosticsToConsole)
)
Run Code Online (Sandbox Code Playgroud)

就这样。您的 BGRA 现已转换为 BGR 作为vImage_Buffer. 我们可以通过直接读取像素数据来检查是否达到了我们想要的效果。首先,我们需要访问数据:

let bgraData = bgraBuffer.data!.assumingMemoryBound(to: UInt8.self)
let bgrData  = bgrBuffer.data!.assumingMemoryBound(to: UInt8.self)
Run Code Online (Sandbox Code Playgroud)

现在,我们可以打印第一个和第二个像素

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])
Run Code Online (Sandbox Code Playgroud)

这是我在游乐场中用于测试的 png 图像中看到的输出:

249 244 234 255
249 244 234

251 242 233 255
251 242 233
Run Code Online (Sandbox Code Playgroud)

正如您所看到的,像素是在没有 Alpha 通道的情况下复制的。如果您要使用 for 循环,请小心,因为我们现在有 3 个通道。

如果您正在开发游戏并在每个帧上执行此操作,请尝试保持对象处于活动状态。加速格式定义,写入缓冲区和转换器对于相同的图像大小和格式永远不会改变,因此它们可以创建一次并保存在内存中以供将来使用。

看起来您正在返回一个数据对象。您可以将UnsafeMutablePointer构造转换为您需要的任何内容。

或者您也可以vImage_Buffer使用加速方法转换回 CVPixelBuffer(如果需要)vImageBuffer_CopyToCVPixelBuffervImage_Buffer有很多转换器,绝对能满足您的需求。检查此链接以获取有关如何使用复制到像素缓冲区方法的更多信息。这个链接有一个很好的用法示例。

编辑:您的 CVPixelBuffer 可能已填充。

由于硬件要求,您的图像可能有填充,以确保缓冲区宽度和高度是 16 的倍数。这也会导致结构中出现填充vImage_Buffer。如果您需要循环,但只需要访问/更新单个像素,您可以使用 Accelerate 的函数来提高速度。检查此链接以了解可能的方法,页面末尾有很好的示例。

要完整读取数据,您可以编写如下内容:

249 244 234 255
249 244 234

251 242 233 255
251 242 233
Run Code Online (Sandbox Code Playgroud)

这将确保您读取全宽像素,但在末尾传递填充。