見出し画像

iOSアプリ開発 入門 (13) - AVCapturePhotoOutputによるカメラ撮影

「AVCapturePhotoOutput」によるカメラ撮影の手順をまとめました。

・iOS14

前回

1. iOSのカメラ撮影

iOSのカメラ撮影の実装方法には、「UIImagePickerController」と「AVCapturePhotoOutput」の2つの方法があります。

・UIImagePickerController
 ・実装が簡単
 ・画面カスタマイズ不可

・AVCapturePhotoOutput
 ・実装が手間
 ・画面カスタマイズ可

今回は、画面カスタマイズ可な「AVCapturePhotoOutput」で実装します。

2. Info.plist

「Info.plist」に以下の項目を設定します。

・Privacy - Camera Usage Description : カメラの用途の説明。
・Privacy - Photo Library Usage Description : フォトライブラリの用途の説明。

3. UI

Main.storyboardに「View」(プレビュー用)を1つ、「ImageView」(結果確認用)を1つ、「Button」を1つ配置します。「View」と「ImageView」はアスペクト比を480:640とします。ボタンのテキストには「カメラ撮影」を指定します。

画像1

4. コード

コードは、次のとおりです。

import UIKit
import AVFoundation

// ViewController
class ViewController: UIViewController, AVCapturePhotoCaptureDelegate {
    //参照
    @IBOutlet weak private var previewView: UIView!
    @IBOutlet weak private var imageView: UIImageView!

    // ビデオキャプチャー
    var session: AVCaptureSession!
    var input: AVCaptureDeviceInput!
    var output: AVCapturePhotoOutput!
    var camera: AVCaptureDevice!
   
   
//====================
// ライフサイクル
//====================
    // ロード時に呼ばれる
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // ビデオキャプチャーのセットアップ
        setupAVCapture()
    }
   
   
//====================
// ビデオキャプチャー
//====================
    // ビデオキャプチャーのセットアップ
    func setupAVCapture() {
        // セッションの生成
        self.session = AVCaptureSession()
       
        // セッションの設定開始
        self.session.beginConfiguration()
      
        // 画像解像度の設定
        self.session.sessionPreset = .vga640x480
       
        // カメラの取得
        self.camera = AVCaptureDevice.default(
            AVCaptureDevice.DeviceType.builtInWideAngleCamera,
            for: AVMediaType.video,
            position: .back)
       
        // ビデオ入力の生成
        do {
            self.input = try AVCaptureDeviceInput(device: camera!)
        } catch {
            self.session.commitConfiguration()
            print("ビデオ入力の生成に失敗しました。")
            return
        }
       
        // ビデオ入力の追加
        if self.session.canAddInput(input) {
            self.session.addInput(input)
        } else {
            self.session.commitConfiguration()
            print("ビデオ入力の追加に失敗しました。")
            return
        }
       
        // ビデオ出力の生成
        self.output = AVCapturePhotoOutput()
       
        // ビデオ出力の追加
        if session.canAddOutput(self.output) {
            session.addOutput(self.output)
        } else {
            self.session.commitConfiguration()
            print("ビデオ出力の追加に失敗しました。")
            return
        }
       
        // セッションの設定終了
        self.session.commitConfiguration()
       
        // セッションからプレビューを表示を
        let previewLayer = AVCaptureVideoPreviewLayer(session: session)
       
        // プレビューの追加
        previewLayer.frame = previewView.bounds
        previewLayer.videoGravity = .resizeAspectFill
        self.previewView.layer.addSublayer(previewLayer)
       
        // セッションの開始
        self.session.startRunning()
    }
       
       
//====================
// カメラ撮影
//====================
    // カメラ撮影ボタン押下時に呼ばれる
    @IBAction func onTakePicture(_ sender: UIButton) {
        let settings = AVCapturePhotoSettings()
        settings.flashMode = .auto
        settings.isHighResolutionPhotoEnabled = false
        self.output.capturePhoto(with: settings, delegate: self)
    }
   
    // カメラ撮影による画像取得時に呼ばれる
    func photoOutput(_ output: AVCapturePhotoOutput,
        didFinishProcessingPhoto photo: AVCapturePhoto, error: Error?) {
        let imageData = photo.fileDataRepresentation()
        let image = UIImage(data: imageData!)
       
        // イメージビューに表示
        self.imageView.image = image
    }
}

ビューとイメージビューの参照と、ボタンイベントの関連付けも行います。

◎ AVCaptureSession.Preset
AVCaptureSession.Presetの設定項目は、次のとおりです。

・low : 3Gの共有に適した設定。
・medium : WiFiの共有に適した設定。
・high : 高品質の出力に適した設定。
・photo : 高解像度の写真品質出力に適した設定。
・inputPriority : 制御なし。
・qHD960x540 : QHD品質(960 x 540ピクセル)のビデオ出力に適した設定。
・hd1280x720 : 720p品質(1280 x 720ピクセル)のビデオ出力に適した設定。
・hd1920x1080 : 1080p品質(1920 x 1080ピクセル)のビデオ出力に適した設定。
・hd4K3840x2160 : 2160p品質(3840 x 2160ピクセル)のビデオ出力に適した設定。
・iFrame960x540 : AACオーディオで約30Mbits/secで960x540品質のiFrameH.264ビデオを実現するための設定。
・iFrame1280x720 : AACオーディオで約40Mbits/secで1280x720品質のiFrameH.264ビデオを実現するための設定。
・qvga320x240 : 320 x240ピクセルのビデオ出力に適した設定。
・vga640x480 : VGA品質(640 x 480ピクセル)のビデオ出力に適した設定。
・cif352x288 : CIF品質(352 x 288ピクセル)のビデオ出力に適した設定。

◎ AVCapturePhotoCaptureDelegate
AVCapturePhotoCaptureDelegateの主なコールバックは、次のとおりです。

func photoOutput(AVCapturePhotoOutput, didFinishProcessingPhoto: AVCapturePhoto, error: Error?)
カメラ撮影終了時に呼ばれる。画像とメタデータが渡される。

func photoOutput(AVCapturePhotoOutput, didFinishRecordingLivePhotoMovieForEventualFileAt: URL, resolvedSettings: AVCaptureResolvedPhotoSettings)
ライブフォトの動画コンテンツの録画終了時に呼ばれる。

func photoOutput(AVCapturePhotoOutput, didFinishProcessingLivePhotoToMovieFileAt: URL, duration: CMTime, photoDisplayTime: CMTime, resolvedSettings: AVCaptureResolvedPhotoSettings, error: Error?)
ライブフォト撮影結果の動画ファイルのURLが渡される。

◎ AVCapturePhotoSettings
設定項目は、次のとおりです。

・flashMode : 写真撮影時にフラッシュを発光させるかどうか。
・photoQualityPrioritization : 写真の速度より品質を優先するかどうか。
・isAutoRedEyeReductionEnabled : フラッシュキャプチャで自動red-eye軽減を使用するかどうか。
・isHighResolutionPhotoEnabled : 最高の解像度で静止画像をキャプチャするかどうか。
・isCameraCalibrationDataDeliveryEnabled : カメラキャリブレーションデータも配信するかどうか。
・isAutoContentAwareDistortionCorrectionEnabled : 歪み補正を使用するかどうか。
・isAutoVirtualDeviceFusionEnabled : 自動仮想デバイス画像融合を使用するかどうか。
・virtualDeviceConstituentPhotoDeliveryEnabledDevices : 仮想デバイスが写真を配信する必要がある構成デバイス。

【おまけ】 UIImageのimageOrientationの修正

MLKitなどに画像を渡す場合は、UIImageimageOrientation.upに揃える必要がある。
このコードを参考に、iOS14に対応したコードは、次のとおり。

extension UIImage {
    func fixedOrientation() -> UIImage? {
        guard imageOrientation != UIImage.Orientation.up else {
            return self.copy() as? UIImage
        }
       
        guard let cgImage = self.cgImage else {
            return nil
        }

        guard let colorSpace = cgImage.colorSpace, let ctx = CGContext(data: nil, width: Int(size.width), height: Int(size.height),  bitsPerComponent: cgImage.bitsPerComponent, bytesPerRow: 0, space: colorSpace, bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue) else {
            return nil
        }

        var transform: CGAffineTransform = CGAffineTransform.identity

        if imageOrientation == .down || imageOrientation == .downMirrored {
            transform = transform.translatedBy(x: size.width, y: size.height)
            transform = transform.rotated(by: CGFloat.pi)
        } else if imageOrientation == .left || imageOrientation == .leftMirrored {
            transform = transform.translatedBy(x: size.width, y: 0)
            transform = transform.rotated(by: CGFloat.pi / 2.0)
        } else if imageOrientation == .right || imageOrientation == .rightMirrored {
            transform = transform.translatedBy(x: 0, y: size.height)
            transform = transform.rotated(by: CGFloat.pi / -2.0)
        }

        if imageOrientation == .upMirrored || imageOrientation == .downMirrored {
            transform.translatedBy(x: size.width, y: 0)
            transform.scaledBy(x: -1, y: 1)
        } else if imageOrientation == .leftMirrored || imageOrientation == .rightMirrored {
            transform.translatedBy(x: size.height, y: 0)
            transform.scaledBy(x: -1, y: 1)
        }

        ctx.concatenate(transform)

        if imageOrientation == .left || imageOrientation == .leftMirrored ||
            imageOrientation == .right || imageOrientation == .rightMirrored {
            ctx.draw(self.cgImage!, in: CGRect(x: 0, y: 0, width: size.height, height: size.width))
        } else {
            ctx.draw(self.cgImage!, in: CGRect(x: 0, y: 0, width: size.width, height: size.height))
        }
        guard let newCGImage = ctx.makeImage() else { return nil }
        return UIImage.init(cgImage: newCGImage, scale: 1, orientation: .up)
    }
}

次回


この記事が気に入ったらサポートをしてみませんか?