【问题标题】:Make an UIImage from a CMSampleBuffer从 CMSampleBuffer 制作 UIImage
【发布时间】:2013-03-21 13:01:29
【问题描述】:

这与将CMSampleBuffer 转换为UIImage 的无数问题不同。我只是想知道为什么我不能像这样转换它:

CVPixelBufferRef pixelBuffer = (CVPixelBufferRef)CMSampleBufferGetImageBuffer(sampleBuffer);
CIImage * imageFromCoreImageLibrary = [CIImage imageWithCVPixelBuffer: pixelBuffer];
UIImage * imageForUI = [UIImage imageWithCIImage: imageFromCoreImageLibrary];

它似乎更简单,因为它适用于 YCbCr 颜色空间,以及 RGBA 和其他颜色空间。这段代码有问题吗?

【问题讨论】:

  • 我知道这个问题很老,但这可以作为其他人的参考。我只想提一下您的第一行:您正在分配来自CMSampleBufferGetImageBufferCVImageBufferRef 并将其转换为CVPixelBufferRef,两个不同的东西......
  • stackoverflow.com/a/43470666/3939807 这个答案会有所帮助。

标签: ios core-image quartz-core


【解决方案1】:

使用以下代码从 PixelBuffer 转换图像 选项 1:

CIImage *ciImage = [CIImage imageWithCVPixelBuffer:pixelBuffer];

CIContext *context = [CIContext contextWithOptions:nil];
CGImageRef myImage = [context
                         createCGImage:ciImage
                         fromRect:CGRectMake(0, 0,
                                             CVPixelBufferGetWidth(pixelBuffer),
                                             CVPixelBufferGetHeight(pixelBuffer))];

UIImage *uiImage = [UIImage imageWithCGImage:myImage];

选项 2:

int w = CVPixelBufferGetWidth(pixelBuffer);
int h = CVPixelBufferGetHeight(pixelBuffer);
int r = CVPixelBufferGetBytesPerRow(pixelBuffer);
int bytesPerPixel = r/w;

unsigned char *buffer = CVPixelBufferGetBaseAddress(pixelBuffer);

UIGraphicsBeginImageContext(CGSizeMake(w, h));

CGContextRef c = UIGraphicsGetCurrentContext();

unsigned char* data = CGBitmapContextGetData(c);
if (data != NULL) {
    int maxY = h;
    for(int y = 0; y<maxY; y++) {
        for(int x = 0; x<w; x++) {
            int offset = bytesPerPixel*((w*y)+x);
            data[offset] = buffer[offset];     // R
            data[offset+1] = buffer[offset+1]; // G
            data[offset+2] = buffer[offset+2]; // B
            data[offset+3] = buffer[offset+3]; // A
        }
    }
}
UIImage *img = UIGraphicsGetImageFromCurrentImageContext();

UIGraphicsEndImageContext();

【讨论】:

  • 我正在寻找我不能在帖子中使用这三行的原因。另外,您的方法是否适用于 YCbCr 420f 和 RGBA?您不需要独立访问单独的平面吗?
  • 因为您需要上下文来捕获图像。而context支持不同类型的context,你可以根据需要改变它,它只支持RGBA,CMYK色彩空间
  • 如何将上下文的色彩空间从 YCbCr 更改为 RGB?
  • 基本上IOS里有colorSpaces可用,CGColorSpaceCreateDeviceCMYK(); CGColorSpaceCreateDeviceGray(); CGColorSpaceCreateDeviceRGB();
  • 您没有解决问题问题:如何处理 CMSampleBuffer?它与 CVPixelBuffer 不同
【解决方案2】:

对于 JPEG 图像:

斯威夫特 4:

let buff: CMSampleBuffer ...            // Have you have CMSampleBuffer 
if let imageData = AVCapturePhotoOutput.jpegPhotoDataRepresentation(forJPEGSampleBuffer: buff, previewPhotoSampleBuffer: nil) {
    let image = UIImage(data: imageData) //  Here you have UIImage
}

【讨论】:

  • 你是对的。但是如果有人在谷歌搜索“来自 CMSampleBuffer 的 UIImage”,他会在顶部看到这篇文章,就像我一样。此答案正确回答了标题中的问题。
  • 投了反对票,因为根据文档,这只有在 CMSampleBuffer 是 JPEG 图像源时才有效,答案中没有提到,也不是问题所倡导的要求。对于源为 .H264 的大量用例,这将失败。 developer.apple.com/library/ios/documentation/AVFoundation/…:
  • 正确,+[AVCaptureStillImageOutput jpegStillImageNSDataRepresentation:] 不是 jpeg 样本缓冲区。在我的情况下崩溃
  • 我收到了CRASH uncaughtExceptionHandler: *** +[AVCapturePhotoOutput JPEGPhotoDataRepresentationForJPEGSampleBuffer:previewPhotoSampleBuffer:] Not a jpeg sample buffer 2018-12-19 19:52:13.498514+0530 Stream[17414:2467572] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** +[AVCapturePhotoOutput JPEGPhotoDataRepresentationForJPEGSampleBuffer:previewPhotoSampleBuffer:] Not a jpeg sample buffer'
  • @Luke Van In,就在jpegPhotoDataRepresentation
【解决方案3】:

这将与 iOS 10 的 AVCapturePhotoOutput 类有关。假设用户想要拍摄一张照片,而您致电 capturePhoto(with:delegate:),并且您的设置包括对 预览 图像的请求。这是获取预览图像的一种非常有效的方法,但是您将如何在界面中显示它呢?预览图像在您的委托方法实现中作为 CMSampleBuffer 到达:

func capture(_ output: AVCapturePhotoOutput, 
    didFinishProcessingPhotoSampleBuffer buff: CMSampleBuffer?, 
    previewPhotoSampleBuffer: CMSampleBuffer?, 
    resolvedSettings: AVCaptureResolvedPhotoSettings, 
    bracketSettings: AVCaptureBracketedStillImageSettings?, 
    error: Error?) {

您需要将 CMSampleBuffer previewPhotoSampleBuffer 转换为 UIImage。你打算怎么做?像这样:

if let prev = previewPhotoSampleBuffer {
    if let buff = CMSampleBufferGetImageBuffer(prev) {
        let cim = CIImage(cvPixelBuffer: buff)
        let im = UIImage(ciImage: cim)
        // and now you have a UIImage! do something with it ...
    }
}

【讨论】:

  • 我试过这个,但是没有exif信息的im有一个问题(例如:方向)
  • exif信息单独到达。这只是预览,没有其他内容。
  • 感谢分享;您是否碰巧有相反的代码示例,即从 UIImage 转换为 CMSampleBuffer?
【解决方案4】:

使用 Swift 3 和 iOS 10 AVCapturePhotoOutput: 包括:

import UIKit
import CoreData
import CoreMotion
import AVFoundation

创建一个 UIView 用于预览并将其链接到主类

  @IBOutlet var preview: UIView!

创建它以设置相机会话(kCVPixelFormatType_32BGRA 很重要!!):

  lazy var cameraSession: AVCaptureSession = {
    let s = AVCaptureSession()
    s.sessionPreset = AVCaptureSessionPresetHigh
    return s
  }()

  lazy var previewLayer: AVCaptureVideoPreviewLayer = {
    let previewl:AVCaptureVideoPreviewLayer =  AVCaptureVideoPreviewLayer(session: self.cameraSession)
    previewl.frame = self.preview.bounds
    return previewl
  }()

  func setupCameraSession() {
    let captureDevice = AVCaptureDevice.defaultDevice(withMediaType: AVMediaTypeVideo) as AVCaptureDevice

    do {
      let deviceInput = try AVCaptureDeviceInput(device: captureDevice)

      cameraSession.beginConfiguration()

      if (cameraSession.canAddInput(deviceInput) == true) {
        cameraSession.addInput(deviceInput)
      }

      let dataOutput = AVCaptureVideoDataOutput()
      dataOutput.videoSettings = [(kCVPixelBufferPixelFormatTypeKey as NSString) : NSNumber(value: **kCVPixelFormatType_32BGRA** as UInt32)]
      dataOutput.alwaysDiscardsLateVideoFrames = true

      if (cameraSession.canAddOutput(dataOutput) == true) {
        cameraSession.addOutput(dataOutput)
      }

      cameraSession.commitConfiguration()

      let queue = DispatchQueue(label: "fr.popigny.videoQueue", attributes: [])
      dataOutput.setSampleBufferDelegate(self, queue: queue)

    }
    catch let error as NSError {
      NSLog("\(error), \(error.localizedDescription)")
    }
  }

在将出现:

  override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)
    setupCameraSession()
  }

在出现时:

  override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)
    preview.layer.addSublayer(previewLayer)
    cameraSession.startRunning()
  }

创建一个函数来捕获输出:

  func captureOutput(_ captureOutput: AVCaptureOutput!, didOutputSampleBuffer sampleBuffer: CMSampleBuffer!, from connection: AVCaptureConnection!) {

    // Here you collect each frame and process it
    let ts:CMTime = CMSampleBufferGetPresentationTimeStamp(sampleBuffer)
    self.mycapturedimage = imageFromSampleBuffer(sampleBuffer: sampleBuffer)
}

这里是将 kCVPixelFormatType_32BGRA CMSampleBuffer 转换为 UIImage 的代码,关键是必须对应于 32BGRA 的 bitmapInfo 32 little with premultfirst 和 alpha 信息:

  func imageFromSampleBuffer(sampleBuffer : CMSampleBuffer) -> UIImage
  {
    // Get a CMSampleBuffer's Core Video image buffer for the media data
    let  imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);
    // Lock the base address of the pixel buffer
    CVPixelBufferLockBaseAddress(imageBuffer!, CVPixelBufferLockFlags.readOnly);


    // Get the number of bytes per row for the pixel buffer
    let baseAddress = CVPixelBufferGetBaseAddress(imageBuffer!);

    // Get the number of bytes per row for the pixel buffer
    let bytesPerRow = CVPixelBufferGetBytesPerRow(imageBuffer!);
    // Get the pixel buffer width and height
    let width = CVPixelBufferGetWidth(imageBuffer!);
    let height = CVPixelBufferGetHeight(imageBuffer!);

    // Create a device-dependent RGB color space
    let colorSpace = CGColorSpaceCreateDeviceRGB();

    // Create a bitmap graphics context with the sample buffer data
    var bitmapInfo: UInt32 = CGBitmapInfo.byteOrder32Little.rawValue
    bitmapInfo |= CGImageAlphaInfo.premultipliedFirst.rawValue & CGBitmapInfo.alphaInfoMask.rawValue
    //let bitmapInfo: UInt32 = CGBitmapInfo.alphaInfoMask.rawValue
    let context = CGContext.init(data: baseAddress, width: width, height: height, bitsPerComponent: 8, bytesPerRow: bytesPerRow, space: colorSpace, bitmapInfo: bitmapInfo)
    // Create a Quartz image from the pixel data in the bitmap graphics context
    let quartzImage = context?.makeImage();
    // Unlock the pixel buffer
    CVPixelBufferUnlockBaseAddress(imageBuffer!, CVPixelBufferLockFlags.readOnly);

    // Create an image object from the Quartz image
    let image = UIImage.init(cgImage: quartzImage!);

    return (image);
  }

【讨论】:

  • 如何将RGBA转换为CMYK?不只是调整每个像素,而是快速使用一些 API。
  • 方向呢?
  • let image = UIImage.init(cgImage: quartzImage!); 不起作用,我收到下一个错误Thread 14: Fatal error: Unexpectedly found nil while unwrapping an Optional value
  • 我设置了videoOutput.videoSettings = [(kCVPixelBufferPixelFormatTypeKey as String) : NSNumber(value: kCVPixelFormatType_32BGRA as UInt32)],现在它可以工作了,当我改变格式类型时它会更多地加载设备CPU吗?为什么最初默认不是 kCVPixelFormatType_32BGRA?
  • @user924 默认情况下,它是像kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange 这样的 BiPlanar 格式,因为这样更高效,因为表示图像所需的字节数更少
【解决方案5】:

我为 Swift 4.x/3.x 编写了一个简单的扩展来从 CMSampleBuffer 生成 UIImage

这也处理缩放和方向,但如果它们适合你,你可以接受默认值。

import UIKit
import AVFoundation

extension CMSampleBuffer {
    func image(orientation: UIImageOrientation = .up, 
               scale: CGFloat = 1.0) -> UIImage? {
        if let buffer = CMSampleBufferGetImageBuffer(self) {
            let ciImage = CIImage(cvPixelBuffer: buffer)

            return UIImage(ciImage: ciImage, 
                           scale: scale,
                           orientation: orientation)
        }

        return nil
    }
}
  1. 如果可以从图片中获取缓冲区数据,则继续,否则返回nil
  2. 使用缓冲区,它初始化一个CIImage
  3. 它返回一个用ciImage 值初始化的UIImage,以及scaleorientation 值。如果没有提供,则分别使用up1.0 的默认值

【讨论】:

  • 很棒的扩展,不知道为什么它没有任何赞成票。对我来说完美无缺。
  • @Flupp 谢谢,我更喜欢尽可能多地使用扩展,因为它非常适合 Swift 范式。
  • 感谢您的回答。您是否碰巧有相反的代码,即从 UIImage 转换为 CMSampleBuffer?
【解决方案6】:

一个 Swift 4 / iOS 11 版本的 Popigny 的回答:

import Foundation
import AVFoundation
import UIKit

class ViewController : UIViewController {
    let captureSession = AVCaptureSession()
    let photoOutput = AVCapturePhotoOutput()
    let cameraPreview = UIView(frame: .zero)
    let progressIndicator = ProgressIndicator()

    override func viewDidLoad() {
        super.viewDidLoad()

        setupVideoPreview()

        do {
            try setupCaptureSession()
        } catch {
            let errorMessage = String(describing:error)
            print("[--ERROR--]: \(#file):\(#function):\(#line): " + errorMessage)
            alert(title: "Error", message: errorMessage)
        }
    }

    private func setupCaptureSession() throws {
        let deviceDiscovery = AVCaptureDevice.DiscoverySession(deviceTypes: [AVCaptureDevice.DeviceType.builtInWideAngleCamera], mediaType: AVMediaType.video, position: AVCaptureDevice.Position.back)
        let devices = deviceDiscovery.devices

        guard let captureDevice = devices.first else {
            let errorMessage = "No camera available"
            print("[--ERROR--]: \(#file):\(#function):\(#line): " + errorMessage)
            alert(title: "Error", message: errorMessage)
            return
        }

        let captureDeviceInput = try AVCaptureDeviceInput(device: captureDevice)
        captureSession.addInput(captureDeviceInput)
        captureSession.sessionPreset = AVCaptureSession.Preset.photo
        captureSession.startRunning()

        if captureSession.canAddOutput(photoOutput) {
            captureSession.addOutput(photoOutput)
        }
    }

    private func setupVideoPreview() {

        let previewLayer = AVCaptureVideoPreviewLayer(session: captureSession)
        previewLayer.bounds = view.bounds
        previewLayer.position = CGPoint(x:view.bounds.midX, y:view.bounds.midY)
        previewLayer.videoGravity = AVLayerVideoGravity.resizeAspectFill

        cameraPreview.layer.addSublayer(previewLayer)
        cameraPreview.addGestureRecognizer(UITapGestureRecognizer(target: self, action:#selector(capturePhoto)))

        cameraPreview.translatesAutoresizingMaskIntoConstraints = false

        view.addSubview(cameraPreview)

        let viewsDict = ["cameraPreview":cameraPreview]
        view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|-0-[cameraPreview]-0-|", options: [], metrics: nil, views: viewsDict))
        view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|-0-[cameraPreview]-0-|", options: [], metrics: nil, views: viewsDict))

    }

    @objc func capturePhoto(_ sender: UITapGestureRecognizer) {
        progressIndicator.add(toView: view)
        let photoOutputSettings = AVCapturePhotoSettings(format: [AVVideoCodecKey:AVVideoCodecType.jpeg])
        photoOutput.capturePhoto(with: photoOutputSettings, delegate: self)
    }

    func saveToPhotosAlbum(_ image: UIImage) {
        UIImageWriteToSavedPhotosAlbum(image, self, #selector(photoWasSavedToAlbum), nil)
    }

    @objc func photoWasSavedToAlbum(_ image: UIImage, _ error: Error?, _ context: Any?) {
        alert(message: "Photo saved to device photo album")
    }

    func alert(title: String?=nil, message:String?=nil) {
        let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)
        alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
        present(alert, animated:true)
    }

}

extension ViewController : AVCapturePhotoCaptureDelegate {
    func photoOutput(_ output: AVCapturePhotoOutput, didFinishProcessingPhoto photo: AVCapturePhoto, error: Error?) {

        guard  let photoData = photo.fileDataRepresentation() else {
            let errorMessage = "Photo capture did not provide output data"
            print("[--ERROR--]: \(#file):\(#function):\(#line): " + errorMessage)
            alert(title: "Error", message: errorMessage)
            return
        }

        guard let image = UIImage(data: photoData) else {
            let errorMessage = "could not create image to save"
            print("[--ERROR--]: \(#file):\(#function):\(#line): " + errorMessage)
            alert(title: "Error", message: errorMessage)
            return
        }

        saveToPhotosAlbum(image)

        progressIndicator.hide()
    }
}

在上下文中查看此内容的完整示例项目:https://github.com/cruinh/CameraCapture

【讨论】:

  • CMSampleBuffer在哪里?
  • 你没看懂问题
【解决方案7】:

TO ALL:不要使用以下方法:

    private let context = CIContext()

    private func imageFromSampleBuffer2(_ sampleBuffer: CMSampleBuffer) -> UIImage? {
        guard let imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else { return nil }
        let ciImage = CIImage(cvPixelBuffer: imageBuffer)
        guard let cgImage = context.createCGImage(ciImage, from: ciImage.extent) else { return nil }
        return UIImage(cgImage: cgImage)
    }

他们吃更多的 CPU,更多的时间来转换

使用https://stackoverflow.com/a/40193359/7767664的解决方案

不要忘记为 AVCaptureVideoDataOutput 设置下一个设置

    videoOutput = AVCaptureVideoDataOutput()

    videoOutput.videoSettings = [(kCVPixelBufferPixelFormatTypeKey as String) : NSNumber(value: kCVPixelFormatType_32BGRA as UInt32)]
    //videoOutput.alwaysDiscardsLateVideoFrames = true

    videoOutput.setSampleBufferDelegate(self, queue: DispatchQueue(label: "MyQueue"))

转换方法

    func imageFromSampleBuffer(_ sampleBuffer : CMSampleBuffer) -> UIImage {
        // Get a CMSampleBuffer's Core Video image buffer for the media data
        let  imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);
        // Lock the base address of the pixel buffer
        CVPixelBufferLockBaseAddress(imageBuffer!, CVPixelBufferLockFlags.readOnly);


    // Get the number of bytes per row for the pixel buffer
    let baseAddress = CVPixelBufferGetBaseAddress(imageBuffer!);

    // Get the number of bytes per row for the pixel buffer
    let bytesPerRow = CVPixelBufferGetBytesPerRow(imageBuffer!);
    // Get the pixel buffer width and height
    let width = CVPixelBufferGetWidth(imageBuffer!);
    let height = CVPixelBufferGetHeight(imageBuffer!);

    // Create a device-dependent RGB color space
    let colorSpace = CGColorSpaceCreateDeviceRGB();

    // Create a bitmap graphics context with the sample buffer data
    var bitmapInfo: UInt32 = CGBitmapInfo.byteOrder32Little.rawValue
    bitmapInfo |= CGImageAlphaInfo.premultipliedFirst.rawValue & CGBitmapInfo.alphaInfoMask.rawValue
    //let bitmapInfo: UInt32 = CGBitmapInfo.alphaInfoMask.rawValue
    let context = CGContext.init(data: baseAddress, width: width, height: height, bitsPerComponent: 8, bytesPerRow: bytesPerRow, space: colorSpace, bitmapInfo: bitmapInfo)
    // Create a Quartz image from the pixel data in the bitmap graphics context
    let quartzImage = context?.makeImage();
    // Unlock the pixel buffer
    CVPixelBufferUnlockBaseAddress(imageBuffer!, CVPixelBufferLockFlags.readOnly);

    // Create an image object from the Quartz image
    let image = UIImage.init(cgImage: quartzImage!);

    return (image);
}

【讨论】:

    【解决方案8】:

    斯威夫特 5.0

    if let cvImageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) {
       let ciimage = CIImage(cvImageBuffer: cvImageBuffer)
       let context = CIContext()
    
       if let cgImage = context.createCGImage(ciimage, from: ciimage.extent) {
          let uiImage = UIImage(cgImage: cgImage)
       }
    }
    

    【讨论】:

    • 虽然看起来多余,但在我们的例子中,通过context.createCGImage(而不是简单地调用UIImage(ciImage:))正确地保留了缓冲区的尺寸。谢谢!
    • 为什么每次获取 samplebuffer 时都要创建 CIContect()?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-11-15
    • 1970-01-01
    • 2011-05-28
    • 1970-01-01
    • 2012-02-08
    • 1970-01-01
    相关资源
    最近更新 更多