【问题标题】:How to remove alpha channel from CVPixelBuffer and get its Data in Swift如何从 CVPixelBuffer 中删除 alpha 通道并在 Swift 中获取其数据
【发布时间】:2020-11-24 09:05:11
【问题描述】:

我的 CVPixelBuffer 以 kCVPixelFormatType_32BGRA 的形式出现,我正在尝试以 BGR 格式获取不带 Alpha 通道的帧数据。这是我尝试做的(作为 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)
}

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

【问题讨论】:

    标签: ios swift image-processing alpha


    【解决方案1】:

    由于您可以访问您的 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
    )
    

    现在我们可以定义 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
    )
    

    然后创建转换器...

    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 现在以 vImage_Buffer 的形式转换为 BGR。我们可以通过直接读取像素数据来检查我们是否完成了我们想要的事情。首先,我们需要访问数据:

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

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

    249 244 234 255
    249 244 234
    
    251 242 233 255
    251 242 233
    

    如您所见,像素是在没有 Alpha 通道的情况下复制的。如果您要使用任何循环,请注意您的 for 循环,因为我们现在有 3 个通道。

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

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

    或者您也可以使用加速的vImageBuffer_CopyToCVPixelBuffer 方法将vImage_Buffer 转换回CVPixelBuffer(如果需要)。 vImage_Buffer 有很多转换器,一个肯定会满足您的需求。检查this link 以获取有关如何使用复制到像素缓冲区方法的更多信息。 This link 有一个很好的用法示例。

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

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

    要完全读取数据,你可以这样写:

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

    这将确保您正在读取全宽像素,但在最后传递填充。

    【讨论】:

    • 哇,这来自似乎知道自己在做什么的人。谢谢!
    • 嗨,这似乎确实是正确的方法,但我遇到了以下问题:我使用的是 192x192 大小的图像,我使用 4bytes 来存储每个值,在 BGRA 中表示 192*192*4 个通道*4 个字节=589824。对于 3 个通道 (BGR),总字节数应为 =442368。但是在上面答案的 bgrBuffer 中,我得到了 491520 字节(高度=192,宽度=192,rowBytes=640(不应该是 640)),我不明白为什么。知道出了什么问题吗?
    • 您在哪里看到每行多余的字节数?转换回 CVPixelBuffer 后?
    • 我在转换后立即看到每行多余的字节数。 (height=192, width=192, rowBytes=640) 包含在bgrBuffer中,是一个vImage_Buffer
    • 当我测试时,我得到192 192 576 的输出,表示每个像素的宽度、高度和字节数。刚刚创建了一个gist,所以你可以复制粘贴gist.github.com/emrahgunduz/5eb1f3c9058305b779e0195ebfe3fd07
    【解决方案2】:

    您的代码只分配了新的数据结构,但显然它没有通过丢弃最后一个 Alpha 字节将 4 个字节“挤压”成 3 个字节。

    这样第一个像素(在 Source 中)的 Alpha 字节变成了第二个像素(在 Destination 中)的 Blue 字节,依此类推:

    S: BGRABGRABGRA...

    D: BGRBGRBGRBGR...

    【讨论】:

    • 嗯,好吧,我认为 alpha 通道字节位于缓冲区的末尾,所以我“剪切”了它。以优化方式执行此操作的任何方式,还是我应该循环 sourceBuffer 并每隔四个字节丢弃一次?
    【解决方案3】:

    如上所述,vImage 很棒。但是下面的解决方案要短一些。如果 inData 在 BGRA 中,outData 将包含 BGR 中的缓冲区。 vImageConvert_ARGB8888toRGB888 不关心通道的顺序,只要 alpha 在前即可。

       guard let baseAddress = CVPixelBufferGetBaseAddress(imageBuffer)  else { return }  
       var inBuff = vImage_Buffer(data: baseAddress, height: inHeight, width: inWidth, rowBytes: inRowbytes)     
       
       let outData = UnsafeMutableRawPointer.allocate(byteCount: outSize, alignment: MemoryLayout<UInt>.alignment)
       var outBuff = vImage_Buffer(data: outData, height: outHeight, width: outWidth, rowBytes: outRowbytes)
    
       vImagePermuteChannels_ARGB8888(&inBuff, &inBuff, [3, 0, 1, 2], vImage_Flags(kvImageNoFlags))
       vImageConvert_ARGB8888toRGB888(&inBuff, &outBuff, vImage_Flags(kvImageNoFlags))
    
       outData.deallocate()
    

    【讨论】:

      猜你喜欢
      • 2019-12-11
      • 2014-02-20
      • 1970-01-01
      • 2017-10-11
      • 2010-09-24
      • 1970-01-01
      • 2021-08-30
      • 1970-01-01
      • 2014-11-28
      相关资源
      最近更新 更多