Сделайте UIImage из CMSampleBuffer
Это не то же самое, что бесчисленные вопросы о преобразовании CMSampleBuffer
в UIImage
. Я просто удивляюсь, почему я не могу преобразовать его так:
CVPixelBufferRef pixelBuffer = (CVPixelBufferRef)CMSampleBufferGetImageBuffer(sampleBuffer);
CIImage * imageFromCoreImageLibrary = [CIImage imageWithCVPixelBuffer: pixelBuffer];
UIImage * imageForUI = [UIImage imageWithCIImage: imageFromCoreImageLibrary];
Это кажется намного проще, потому что он работает для цветовых пространств YCbCr, а также RGBA и других. Что-то не так с этим кодом?
7 ответов:
С 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)") } }
В WillAppear:
override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) setupCameraSession() }
В Didappear:
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 ключевые вещи-это bitmapInfo, который должен соответствовать 32BGRA 32 little с premultfirst и alpha info:
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); }
Свифт 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 }
Используйте следующий код для преобразования изображения из 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();
Я написал простое расширение для использования с Swift 4.х / 3.x для получения a
UIImage
из ACMSampleBuffer
.Это также обрабатывает масштабирование и ориентацию, хотя вы можете просто принять значения по умолчанию, если они работают для вас.
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 } }
- Если он может получить буферные данные из изображения, он будет продолжать, в противном случае возвращается nil
- используя буфер, он инициализирует
CIImage
- он возвращает
UIImage
, инициализированный значениемciImage
, а такжеscale
&orientation
ценности. Если они не указаны, используются значения по умолчаниюup
и1.0
соответственно
Это будет очень много в связи с классом 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 ... } }
Для всех : Не используйте такие методы, как:
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) }
Они съедают гораздо больше процессора, больше времени на преобразование
Использовать решение из 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); }
Версия Swift 4 / iOS 11 ответа Попиньи:
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