2017年3月1日水曜日

AVCapturePhotoOutputで少し難しくカメラアプリを作る

カメラで撮影するアプリを作るには UIImagePickerController を使うのが一番楽なんだけど、これだと細かいことができないので、 AVCapturePhotoOutput を使った書き方をしてみる。
iOS9までは別のクラス使ってたんだけど、iOS10からは非推奨になったので、これを使わにゃいかんのだ。

必要なFrameworkはAVFoundation。
以下に全コードを載せる。
撮影ボタンをUIButtonじゃなくて上部のツールバーにしてるけど、まあいいっしょ?
コードをシンプルにして構造をわかりやすくするため、極力エラー処理は省いてある。


カメラの使用とフォトライブラリへのアクセスをするので、あらかじめInfo.plistにそれに関するプライバシーの警告テキストを入力しておくこと。んでないと落ちる。
AVCaptureSessionクラス に入出力の情報をいろいろ設定し、startRunning() でカメラのライブビュー(プレビュー)表示を開始。ライブビュー映像はLayerを指定しないといけないようなので、imageViewにLayerを追加して表示している。
撮影ボタンを押すと takePhoto() が呼ばれ撮影。
撮影された画像はdelegateメソッドである capture(〜)メソッド の引数として受け取れるので、そこでDataからUIImageに変換。
それを普通に UIImageWriteToSavedPhotosAlbum()メソッド で写真ライブラリに保存している。

シミュレータじゃカメラ機能が使えないからiPadの実機で試したんだけど、撮影時のflashModeの行をコメントアウトしないと落ちてしまう。
iPadにはストロボないけど、.autoにしとけば自動で判断してくれそうなものだが。

コード

import UIKit
import AVFoundation

//キャプチャの入出力管理クラス
var captureSession:AVCaptureSession?
//静止画データの出力用
var stillImageOutput:AVCapturePhotoOutput?
//キャプチャ画像のプレビュー用
var videoPreviewLayer:AVCaptureVideoPreviewLayer?
//プレビュー表示用
var imageView = UIImageView()

class ViewController: UIViewController, AVCapturePhotoCaptureDelegate {

    override func viewDidLoad() {
        super.viewDidLoad()
        
        //ツールバーに撮影ボタン追加
        let toolBar = UIToolbar(frame: CGRect(x: 0, y: 10, width: view.bounds.size.width, height: 44))
        let takePhotoButton = UIBarButtonItem(title: "撮影", style: .plain, target: self, action: #selector(ViewController.takePhoto))
        toolBar.items = [takePhotoButton]
        view.addSubview(toolBar)

        //プレビューに使うimageViewを作成
        imageView = UIImageView(frame: CGRect(x: 0, y: toolBar.frame.size.height, width: view.bounds.size.width, height: view.bounds.size.height))
        self.view.addSubview(imageView)

        
        //カメラの画像をキャプチャするクラスを作る
        captureSession = AVCaptureSession()
        //撮影解像度設定
        captureSession?.sessionPreset = AVCaptureSessionPresetPhoto
        
        //キャプチャーデバイスをビデオカメラに設定
        let device = AVCaptureDevice.defaultDevice(withMediaType: AVMediaTypeVideo)
        //ビデオを入力する。try!なのでエラー処理をせず、入力エラーの場合クラッシュ
        let input = try! AVCaptureDeviceInput(device: device)
        //キャプチャー管理のクラスに入力情報追加
        captureSession?.addInput(input)
        //キャプチャー管理のクラスに出力情報追加
        stillImageOutput = AVCapturePhotoOutput()
        captureSession?.addOutput(stillImageOutput)
        
        
        //プレビューレイヤー作成
        videoPreviewLayer = AVCaptureVideoPreviewLayer(session: captureSession)
        //プレビューレイヤーのframe設定
        videoPreviewLayer?.frame = imageView.bounds
        //UIImageViewContentModeに相当
        videoPreviewLayer?.videoGravity = AVLayerVideoGravityResizeAspectFill
        //Layerbounds部分以外をマスクする
        videoPreviewLayer?.masksToBounds = true
        //imageViewlayerとしてプレビューレイヤーを乗せる
        imageView.layer.addSublayer(videoPreviewLayer!)

        
        //セッション開始
        captureSession?.startRunning()
        
    }
    
    func takePhoto() {
        let settingsForMonitoring = AVCapturePhotoSettings()
        //カメラのストロボモード
//        settingsForMonitoring.flashMode = .auto //コメントにしないと落ちる
        //カメラの手ぶれ補正
        settingsForMonitoring.isAutoStillImageStabilizationEnabled = true
        //最高解像度で撮影するか否か
        settingsForMonitoring.isHighResolutionPhotoEnabled = false
        //画面キャプチャ処理
        //撮影された画像はdelegateメソッドで処理する
        stillImageOutput?.capturePhoto(with: settingsForMonitoring, delegate: self)
    }
    
    //AVCapturePhotoCaptureDelegateメソッド
    func capture(_ captureOutput: AVCapturePhotoOutput, didFinishProcessingPhotoSampleBuffer photoSampleBuffer: CMSampleBuffer?, previewPhotoSampleBuffer: CMSampleBuffer?, resolvedSettings: AVCaptureResolvedPhotoSettings, bracketSettings: AVCaptureBracketedStillImageSettings?, error: Error?) {
        //結果がnilじゃなければ
        if let photoSampleBuffer = photoSampleBuffer {
            //キャプチャされたphotoSampleBufferの画像をJPEGとしてDataに直す
            //previewPhotoはサムネイル画像らしい
            let photoData = AVCapturePhotoOutput.jpegPhotoDataRepresentation(forJPEGSampleBuffer: photoSampleBuffer, previewPhotoSampleBuffer: previewPhotoSampleBuffer)
            //DataUIImageに変換
            let image = UIImage(data: photoData!)
            //写真ライブラリに保存
            UIImageWriteToSavedPhotosAlbum(image!, nil, nil, nil)
        }
    }

}


エラーチェックをする場合

     で挟まれたところを置き換える。

//キャプチャするデバイスをビデオに設定
        guard let device = AVCaptureDevice.defaultDevice(withMediaType: AVMediaTypeVideo) else {
            print("デバイスエラー")
            return
        }
        //ビデオを入力する。try!なのでエラー処理をせず、入力エラーの場合クラッシュ
        let input = try! AVCaptureDeviceInput(device: device)
        //追加可能な入力かどうか調べる
        if captureSession!.canAddInput(input) {
            //キャプチャー管理のクラスに入力情報追加
            captureSession?.addInput(input)
            //静止画の出力クラスを設定
            stillImageOutput = AVCapturePhotoOutput()
            //追加可能な出力かどうか調べる
            if captureSession!.canAddOutput(stillImageOutput) {
                //キャプチャー管理のクラスに出力情報追加
                captureSession?.addOutput(stillImageOutput)
                
                //プレビューレイヤー作成
                videoPreviewLayer = AVCaptureVideoPreviewLayer(session: captureSession)
                //guardは、nilでなければunwrapして返し、nilならそこで終る
                guard let _videoPreviewLayer = videoPreviewLayer else {
                    return
                }
                //プレビューレイヤーのframe設定
                _videoPreviewLayer.frame = imageView.bounds
                //UIImageViewで言うところのContentModeのこと
                _videoPreviewLayer.videoGravity = AVLayerVideoGravityResizeAspectFill
                //Layerbounds部分以外をマスクする
                _videoPreviewLayer.masksToBounds = true
                //imageViewlayerとしてプレビューレイヤーを乗せる
                imageView.layer.addSublayer(_videoPreviewLayer)
                
                //セッション開始
                captureSession?.startRunning()
            } else {
                print("出力先エラー")
            }
        } else {
            print("入力元エラー")
        }
参考

0 件のコメント:

コメントを投稿