Sponsor Link
目次
AVCapturePhotoOutputと取り出す手順
AVCaptureSession を使って表示している画像を取り出すためには、AVCapturePhotoOutput を使用します。
取り出すためには以下の手順が必要となります。
- AVCaptureSession と Input を設定し、プレビューできるようにする
- 前回の記事で説明しました。
- AVCapturePhotoOutput を準備して、AVCaptureSession の Output に設定する
- AVCapturePhotoOutput をインスタンス化して、AVCaptureSession の Output に設定します。
- AVCapturePhotoOutput#capturePhoto で画像取得リクエストを発行
- 画像データは、関数返り値ではなく、Delegate(AVCapturePhotoCaptureDelegate) に渡されます
- AVCapturePhotoCaptureDelegate#photoOutput で取得し、処理する
- 引数の photo に AVCapturePhoto タイプのデータとして渡されますので、処理します
それでは、それぞれのステップをみていきます。
AVCaptureSession と Input を設定し、プレビューできるようにする
以前の記事で説明した通りです。
AVCapturePhotoOutput を準備して、AVCaptureSession の Output に設定する
以下のように、AVCapturePhotoOutput をインスタンス化し、AVCaptureSession の Output に設定します。
この段階で詳細な設定は不要です。
1 2 3 4 5 6 |
let photoOutput = AVCapturePhotoOutput() guard captureSession.canAddOutput(photoOutput) else { return } captureSession.sessionPreset = .photo captureSession.addOutput(photoOutput) |
AVCapturePhotoOutput#capturePhoto で画像取得リクエストを発行
AVCaputurePhotoOutput#capturePhoto 関数によって、画像取得のリクエストを発行できます。
リクエスト時に、Delegate を渡して、処理されたデータはその Delegate に渡されてきます。
また、リクエスト時には撮影設定を、AVCapturePhotoSettings として渡す必要があります。
以下のようなコードとなります。
1 2 3 4 5 6 |
let photoSetting = AVCapturePhotoSettings() photoSetting.flashMode = .auto photoSetting.isHighResolutionPhotoEnabled = false photoOutput.capturePhoto(with: photoSetting, delegate: self) |
AVCapturePhotoCaptureDelegate#photoOutput で取得し、処理する
処理が終われば、Delegate の photoOutput がコールされますので、その中で処理します。
以下では、UIImage を作成しています。
1 2 3 4 5 6 |
public func photoOutput(_ output: AVCapturePhotoOutput, didFinishProcessingPhoto photo: AVCapturePhoto, error: Error?) { let imageData = photo.fileDataRepresentation() self.image = UIImage(data: imageData!) } |
サンプルコード
capturePhoto がAVsession の Output に設定されたオブジェクトの関数なので、キャプチャーする機能性をもつ外部オブジェクトとして AVCaptureModel というものを作成し、保持するようにしました。
アプリの動作としては、ボタンを押されると取得したUIImageをプレビューとは別に表示するようにしました。
画像の向きがおかしいのは、UIImageを表示する時に、デバイスの方向を考慮していないからです。
アプリケーションのコードは以下です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 |
// // AVCaputureModel.swift // CameraWithAVFoundation // // Created by Tomoaki Yagishita on 2020/08/11. // import Foundation import AVFoundation import UIKit public class AVCaptureModel : NSObject, AVCapturePhotoCaptureDelegate, ObservableObject { public var captureSession: AVCaptureSession public var videoInput: AVCaptureDeviceInput! public var photoOutput: AVCapturePhotoOutput @Published var image: UIImage? public override init() { self.captureSession = AVCaptureSession() self.photoOutput = AVCapturePhotoOutput() } public func setupSession() { captureSession.beginConfiguration() guard let videoCaputureDevice = AVCaptureDevice.default(for: .video) else { return } guard let videoInput = try? AVCaptureDeviceInput(device: videoCaputureDevice) else { return } self.videoInput = videoInput guard captureSession.canAddInput(videoInput) else { return } captureSession.addInput(videoInput) guard captureSession.canAddOutput(photoOutput) else { return } captureSession.sessionPreset = .photo captureSession.addOutput(photoOutput) captureSession.commitConfiguration() } public func updateInputOrientation(orientation: UIDeviceOrientation) { for conn in captureSession.connections { conn.videoOrientation = ConvertUIDeviceOrientationToAVCaptureVideoOrientation(deviceOrientation: orientation) } } public func takePhoto() { let photoSetting = AVCapturePhotoSettings() photoSetting.flashMode = .auto photoSetting.isHighResolutionPhotoEnabled = false photoOutput.capturePhoto(with: photoSetting, delegate: self) return } public func photoOutput(_ output: AVCapturePhotoOutput, didFinishProcessingPhoto photo: AVCapturePhoto, error: Error?) { let imageData = photo.fileDataRepresentation() self.image = UIImage(data: imageData!) } func getImageFromSampleBuffer(sampleBuffer: CMSampleBuffer) ->UIImage? { guard let pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else { return nil } CVPixelBufferLockBaseAddress(pixelBuffer, .readOnly) let baseAddress = CVPixelBufferGetBaseAddress(pixelBuffer) let width = CVPixelBufferGetWidth(pixelBuffer) let height = CVPixelBufferGetHeight(pixelBuffer) let bytesPerRow = CVPixelBufferGetBytesPerRow(pixelBuffer) let colorSpace = CGColorSpaceCreateDeviceRGB() let bitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.premultipliedFirst.rawValue | CGBitmapInfo.byteOrder32Little.rawValue) guard let context = CGContext(data: baseAddress, width: width, height: height, bitsPerComponent: 8, bytesPerRow: bytesPerRow, space: colorSpace, bitmapInfo: bitmapInfo.rawValue) else { return nil } guard let cgImage = context.makeImage() else { return nil } let image = UIImage(cgImage: cgImage, scale: 1, orientation:.right) CVPixelBufferUnlockBaseAddress(pixelBuffer, .readOnly) return image } } public func ConvertUIDeviceOrientationToAVCaptureVideoOrientation(deviceOrientation: UIDeviceOrientation) -> AVCaptureVideoOrientation { switch deviceOrientation { case .portrait: return .portrait case .portraitUpsideDown: return .portraitUpsideDown case .landscapeLeft: return .landscapeRight case .landscapeRight: return .landscapeLeft default: return .portrait } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 |
// // SwiftUIAVCaptureVideoPreviewView.swift // CameraWithAVFoundation // // Created by Tomoaki Yagishita on 2020/08/05. // import Foundation import AVFoundation import SwiftUI public class UIAVCaptureVideoPreviewView: UIView { var captureSession: AVCaptureSession! var previewLayer: AVCaptureVideoPreviewLayer! public init(frame: CGRect, session: AVCaptureSession) { self.captureSession = session super.init(frame: frame) } public required init?(coder: NSCoder) { super.init(coder: coder) // no implementation } func setupPreview(previewSize: CGRect) { self.frame = previewSize self.previewLayer = AVCaptureVideoPreviewLayer(session: self.captureSession) self.previewLayer.frame = self.bounds self.updatePreviewOrientation() self.layer.addSublayer(previewLayer) self.captureSession.startRunning() } func updateFrame(frame: CGRect) { self.frame = frame self.previewLayer.frame = frame } func updatePreviewOrientation() { switch UIDevice.current.orientation { case .portrait: self.previewLayer.connection?.videoOrientation = .portrait case .portraitUpsideDown: self.previewLayer.connection?.videoOrientation = .portraitUpsideDown case .landscapeLeft: self.previewLayer.connection?.videoOrientation = .landscapeRight case .landscapeRight: self.previewLayer.connection?.videoOrientation = .landscapeLeft default: self.previewLayer.connection?.videoOrientation = .portrait } return } } public struct SwiftUIAVCaptureVideoPreviewView: UIViewRepresentable { let previewFrame: CGRect let captureModel: AVCaptureModel public func makeUIView(context: Context) -> UIAVCaptureVideoPreviewView { let view = UIAVCaptureVideoPreviewView(frame: previewFrame, session: self.captureModel.captureSession) view.setupPreview(previewSize: previewFrame) return view } public func updateUIView(_ uiView: UIAVCaptureVideoPreviewView, context: Context) { print("in updateUIView") self.captureModel.updateInputOrientation(orientation: UIDevice.current.orientation) uiView.updateFrame(frame: previewFrame) } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
// // ContentView.swift // Shared // // Created by Tomoaki Yagishita on 2020/08/05. // import SwiftUI struct ContentView: View { @ObservedObject var captureModel: AVCaptureModel @GestureState var scale: CGFloat = 1.0 var body: some View { captureModel.setupSession() return VStack { Image(systemName: "camera") .resizable() .scaledToFit() .frame(height: 100) GeometryReader { geom in SwiftUIAVCaptureVideoPreviewView(previewFrame: CGRect(x: 0, y: 0, width: geom.size.width, height: geom.size.height), captureModel: captureModel) } .border(Color.red) HStack { if captureModel.image != nil { Image(uiImage: captureModel.image!) .resizable() .scaledToFit() .frame(height:100) } Button("Button", action: { captureModel.takePhoto() }) } } } } struct ContentView_Previews: PreviewProvider { static var previews: some View { ContentView(captureModel: AVCaptureModel()) } } |
まとめ:AVSession を使ったプレビューから画像を取得するのは、非常に簡単です
画像を取得するのは非常に簡単なのですが、詳細なセッティングは、沼になっていそうです。
AVCapturePhotoSettings の詳細は、非常に多そうです。
説明は以上です。
Sponsor Link