swift - 如何裁剪和翻转 CVPixelBuffer 并返回 CVPixelBuffer?

标签 swift crop cgimage ciimage cvpixelbuffer

我正在制作一个快速的视频应用程序。

在我的应用程序中,我需要裁剪和水平翻转 CVPixelBuffer 并返回类型也是 CVPixelBuffer 的结果。

我尝试了几件事。

首先,我使用了“CVPixelBufferCreateWithBytes”

func resizePixelBuffer(_ pixelBuffer: CVPixelBuffer, destSize: CGSize) 
-> CVPixelBuffer? 
{

  CVPixelBufferLockAddress(pixelBuffer, CVPixelBufferLockFlags(rawValue: O))

  let baseAddress = CVPixelBufferGetBaseAddress(pixelBuffer)
  let bytesPerRow = CVPixelBufferGetBytesPerRow(pixelBuffer)
  let pixelFormat = CVPixelBufferGetPixelFormatType(pixelBuffer)
  let width = CVPixelBufferGetWidth(pixelBuffer)
  let height = CVPixelBufferGetHeight(pixelBuffer)

  var destPixelBuffer: CVPixelBuffer?

  let topMargin = (height - destsize.height) / 2
  let leftMargin = (width - destsize.width) / 2 * 4   // bytesPerPixel
  let offset = topMargin * bytesPerRow + leftMargin

  CVPixelBufferCreateWithBytes(kCFAllocatorDefault, 
                               destSize.width, 
                               destSize.height, 
                               pixelFormat, 
                               baseAddress.advanced(by: offset),
                               bytesPerRow, 
                               nil, nil, nil, 
                               &destPixelBuffer)

  CVPixelBufferUnlockBaseAddress(pixelBuffer, CVPixelBufferLockFlags(rawValue: O))

  return destPixelBuffer
)

通过这段代码,我可以直接裁剪 CVPixelBuffer 并返回 CVPixelBuffer。但是,我不知道如何水平翻转 CVPlxelBuffer。

所以我尝试了其他解决方案。

几秒钟后,我将 CVPixelBuffer 转换为 CIImage,然后返回到 CVPixelBuffer

func resizePixelBuffer(_ pixelBuffer, destSize: CGSize) 
-> CVPixelBuffer?
{
  let bufferWidth = CVPixelBufferGetWidth(pixelBuffer)
  let bufferHeight = CVPixelBufferGetHeight(pixelBuffer)

  let ciImage = CIImage(cvPixelBuffer: pixelBuffer)
  let rect = CGRect(x: (bufferWidth - destSize.width)/2, y: (bufferHeight - destSize.height)/2, width: destSize.width, height: destSize.height)
  let croppedImage = ciImage.cropped(to: rect)

  croppedImage.transformed(by: CGAffineTransform(translateX: -1, y: 0))

  var destPixelBuffer: CVPixelBuffer?
  CVPixelBufferCreate(kCFAllocatorDefault, destSize.width, destSize.height,
                      CVPixelBufferGetPixelFormatType(pixelBuffer), nil, 
                      &destPixelBuffer)

  CIContext().render(croppedImage, to: destPixelBuffer!, bounds: croppedImage.extent, croppedImage.colorSpace)

  return destPixelBuffer
}

但结果并不是我所期望的。图像的某些部分是黑色的,我认为 CGAffineTransform 不起作用。

最后,我尝试转换为 CGImage

func resizePixelBuffer(_ pixelBuffer: CVPixelBuffer, destSize: CGSize)
-> CVPixelBuffer? 
{
  let ciImage = CIImage(cvPixelBuffer: pixelBuffer)
  let cgImage = CIContext().createCGImage(ciImage, from: ciImage.extent)
  let rect = CGRect(x: (bufferWidth - destSize.width)/2, y: (bufferHeight - destSize.height)/2, width: destSize.width, height: destSize.height)

  let croppedImage = cgImage.cropping(to: rect)

  let width = croppedImage.width
  let height = croppedImage.height
  let pixelFormat = CVPixelBufferGetPixelFormatType(pixelBuffer)

  var destPixelBuffer: CVPixelBuffer?
  CVPixelBufferCreate(kCFAllocatorDefault, width, height, pixelFormat, &destPixelBuffer)

  CVPixelBufferLockBaseAddress(destPixelBuffer, CVPixelBufferLockFlags(rawValue: 0))

  let destBaseAddress = CVPixelBufferGetBaseAddress(destPixelBuffer)
  let destBytesPerRow = CVPixelBufferGetBytesPerRow(destPixelBuffer)

  let context = CGContext(data: destBaseAddress, 
                          width: width, 
                          height: height, 
                          bitsPerComponent: 8, 
                          bytesPerRow: destBytesPerRow, 
                          space: croppedImage.colorSpace, 
                          bitmapInfo: CGImageAlphaInfo.premultipliedFirst.rawValue | CGBitmapInfo.byteOrder32Little.rawValue)

  context?.concatenate(__CGAffineTransformMake( 1, 0, 0, -1, 0, CGFloat(height)))

  context?.draw(croppedCgImage, in: CGRect(x: 0, y: 0, width: CGFloat(width), height: CGFloat(height)))

  CVPixelBufferUnlockBaseAddress(srcPixelBuffer, CVPixelBufferLockFlags(rawValue: 0))

  return destPixelBuffer
}


此时,输出像素缓冲区是完全黑色的。

我不知道如何裁剪和翻转 CVPixelBuffer 并返回 CVPixelBuffer。

我认为转换 CIImage 或 CGImage 是更好的方法,因为我可以用这些格式做很多事情。

但我不知道如何将这些格式转换回 CVPixelBuffer。

请让我知道如何做到这一点。

最佳答案

这是CVPixelBuffer中的一个流程使用 vImage .

  • 查询 iOS - Scale and crop CMSampleBufferRef/CVImageBufferRef裁剪如何为缓冲区工作
  • 查询 https://developer.apple.com/documentation/accelerate/reading_from_and_writing_to_core_video_pixel_buffers用于 vImage 的基本用法。基本上,您需要创建源和目标缓冲区并对其应用转换。
  • vImageBuffer 到 CVPixelBuffer 部分引用自 https://github.com/tensorflow/examples/blob/master/lite/examples/object_detection/ios/ObjectDetection/Extensions/CVPixelBufferExtension.swift

  • import Cocoa
    import Accelerate
    
    extension CVPixelBuffer {
        func crop(to rect: CGRect) -> CVPixelBuffer? {
            CVPixelBufferLockBaseAddress(self, .readOnly)
            defer { CVPixelBufferUnlockBaseAddress(self, .readOnly) }
    
            guard let baseAddress = CVPixelBufferGetBaseAddress(self) else {
                return nil
            }
    
            let inputImageRowBytes = CVPixelBufferGetBytesPerRow(self)
    
            let imageChannels = 4
            let startPos = Int(rect.origin.y) * inputImageRowBytes + imageChannels * Int(rect.origin.x)
            let outWidth = UInt(rect.width)
            let outHeight = UInt(rect.height)
            let croppedImageRowBytes = Int(outWidth) * imageChannels
    
            var inBuffer = vImage_Buffer()
            inBuffer.height = outHeight
            inBuffer.width = outWidth
            inBuffer.rowBytes = inputImageRowBytes
    
            inBuffer.data = baseAddress + UnsafeMutableRawPointer.Stride(startPos)
    
            guard let croppedImageBytes = malloc(Int(outHeight) * croppedImageRowBytes) else {
                return nil
            }
    
            var outBuffer = vImage_Buffer(data: croppedImageBytes, height: outHeight, width: outWidth, rowBytes: croppedImageRowBytes)
    
            let scaleError = vImageScale_ARGB8888(&inBuffer, &outBuffer, nil, vImage_Flags(0))
    
            guard scaleError == kvImageNoError else {
                free(croppedImageBytes)
                return nil
            }
    
            return croppedImageBytes.toCVPixelBuffer(pixelBuffer: self, targetWith: Int(outWidth), targetHeight: Int(outHeight), targetImageRowBytes: croppedImageRowBytes)
        }
        
        func flip() -> CVPixelBuffer? {
            CVPixelBufferLockBaseAddress(self, .readOnly)
            defer { CVPixelBufferUnlockBaseAddress(self, .readOnly) }
    
            guard let baseAddress = CVPixelBufferGetBaseAddress(self) else {
                return nil
            }
            
            let width = UInt(CVPixelBufferGetWidth(self))
            let height = UInt(CVPixelBufferGetHeight(self))
            let inputImageRowBytes = CVPixelBufferGetBytesPerRow(self)
            let outputImageRowBytes = inputImageRowBytes
            
            var inBuffer = vImage_Buffer(
                data: baseAddress,
                height: height,
                width: width,
                rowBytes: inputImageRowBytes)
            
            guard let targetImageBytes = malloc(Int(height) * outputImageRowBytes) else {
                return nil
            }
            var outBuffer = vImage_Buffer(data: targetImageBytes, height: height, width: width, rowBytes: outputImageRowBytes)
            
            // See https://developer.apple.com/documentation/accelerate/vimage/vimage_operations/image_reflection for other transformations
            let reflectError = vImageHorizontalReflect_ARGB8888(&inBuffer, &outBuffer, vImage_Flags(0))
            // let reflectError = vImageVerticalReflect_ARGB8888(&inBuffer, &outBuffer, vImage_Flags(0))
            
            guard reflectError == kvImageNoError else {
                free(targetImageBytes)
                return nil
            }
    
            return targetImageBytes.toCVPixelBuffer(pixelBuffer: self, targetWith: Int(width), targetHeight: Int(height), targetImageRowBytes: outputImageRowBytes)
        }
    }
    
    extension UnsafeMutableRawPointer {
        // Converts the vImage buffer to CVPixelBuffer
        func toCVPixelBuffer(pixelBuffer: CVPixelBuffer, targetWith: Int, targetHeight: Int, targetImageRowBytes: Int) -> CVPixelBuffer? {
            let pixelBufferType = CVPixelBufferGetPixelFormatType(pixelBuffer)
            let releaseCallBack: CVPixelBufferReleaseBytesCallback = {mutablePointer, pointer in
                if let pointer = pointer {
                    free(UnsafeMutableRawPointer(mutating: pointer))
                }
            }
    
            var targetPixelBuffer: CVPixelBuffer?
            let conversionStatus = CVPixelBufferCreateWithBytes(nil, targetWith, targetHeight, pixelBufferType, self, targetImageRowBytes, releaseCallBack, nil, nil, &targetPixelBuffer)
    
            guard conversionStatus == kCVReturnSuccess else {
                free(self)
                return nil
            }
    
            return targetPixelBuffer
        }
    }
    
    // Change this to your input pixelBuffer
    var pixelBuffer: CVPixelBuffer?
    
    // The result would be stored in resultPixelBuffer
    let resultPixelBuffer = pixelBuffer?.crop(to: CGRect(x: 50, y: 50, width: 100, height: 100))?.flip()
    

    关于swift - 如何裁剪和翻转 CVPixelBuffer 并返回 CVPixelBuffer?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/55287140/

    相关文章:

    ios - 处理长时间运行的任务和 Parse.com API

    imagemagick - 用 Imagemagick 裁剪的图像会以某种方式记住它们的旧尺寸吗?如果是这样,如何避免呢?

    ios - iOS 7 中的图像解压缩

    ios - 从 GraphicContext 绘制图像时图像变得模糊?

    ios - 应用程序在 AdHoc 期间崩溃,但在调试期间不会崩溃

    Swift:想要对现有功能使用扩展

    ios - 如何绘制UIImage图像如fit out风格

    php - 存储图像裁剪的方法

    ios - 泄漏 CGImageMergeXMPPropsWhithLegacyProps

    ios - 在 iOS 上为 BLE 配对设置 IO 功能