今回はiOSのカメラ機能についてまとめようと思います
カメラ権限設定
最初にカメラと写真のライブラリーを使用するため、アクセスの許可をする必要があります。
プロジェクトのinfo.plist
を開いて、アプリのアクセス許可を表示させるように設定を追加します
info.plistのInformationPropertyListの右側の+を押して
Privacy - Camera Usage Description
(カメラを使用することの許可を求める)を設定します。
右側のvalueに許可を求めるダイアログに表示する文言を設定します。
設定した文言が権限チェックのダイアログに表示されます。
権限を設定していない場合には下記のような権限確認のダイアログを表示させる処理を実装しましょう
AVCaptureDevice.requestAccess(for: .video, completionHandler: { granted in })
AVCaptureSessionの設定
次にAVCaptureSession
の設定を行います。
AVCaptureSessionはキャプチャ動作を管理し、入力デバイスからキャプチャ出力へのデータの流れを調整するオブジェクトです。
AVCaptureDeviceInput
とAVCaptureOutput
の仲介役を行なっているのがAVCaptureSessionクラスになります。
AVCaptureDeviceInputは、AVFoundation
フレームワークで使用されるクラスの1つで、
ビデオやオーディオなどのキャプチャデバイスからの入力をキャプチャセッションに提供するために使用されます
AVCaptureOutputも同様に、AVFoundationフレームワークのクラスの1つで、キャプチャーセッションからの出力データを処理するために使用されます。 キャプチャーセッションからのビデオフレームやオーディオサンプルなど、セッションからのデータを取得、加工、保存するために使用されます。
AVCaptureSessionの実装の流れは次の手順で行います
- AVCaptureDeviceインスタンスを生成
- AVCaptureDeviceインスタンスからAVCaptureDeviceInputを構築
- AVCaptureSessionにAVCaptureDeviceInputを登録
- AVCaptureSessionにAVCaptureOutputを登録
- AVCaptureVideoPreviewLayerで画面を構築
AVCaptureDeviceInputを構築
AVCaptureDeviceInputインスタンスを構築するためにはAVCaptureDevice
クラスを用いて、まず使用するデバイスを設定する必要があります
使用するAVCaptureDeviceインスタンスを生成するには以下の2つのどちらかを使用します
- AVCaptureDevice.defaultメソッド
- AVCaptureDevice.DiscoverySessionクラス
defaultメソッドは引数に指定されたタイプのデフォルトデバイスを返します。引数にはAVMediaType
の任意の値を渡します
// .audioなど指定できる guard let videoDevice = AVCaptureDevice.default(for: .video) else { return }
DiscoverySessionは特定の条件にマッチするAVCaptureDeviceを検索するクラスです。 引数にはデバイスタイプ(カメラの種類)とメディアタイプ(videoやaudioなど)、ポジション(カメラの位置)を渡します。 こちらの方がカスタマイズできます
let cameraDevice = AVCaptureDevice.default( AVCaptureDevice.DeviceType.builtInWideAngleCamera, for: AVMediaType.video, position: .back )
AVCaptureDeviceインスタンスからVCaptureDeviceInputを構築
設定したデバイスを元にAVCaptureDeviceInputインスタンスを生成します。 またAVCaptureSessionインスタンスもここで生成しておきます。そして、AVCaptureSessionインスタンスに対してInputとOutputを登録します
let cameraDevice = AVCaptureDevice.default( AVCaptureDevice.DeviceType.builtInWideAngleCamera, for: AVMediaType.video, position: .back ) let captureSession = AVCaptureSession() let videoInput: AVCaptureDeviceInput do { videoInput = try AVCaptureDeviceInput(device: cameraDevice) } catch { captureSession.commitConfiguration() return }
AVCaptureSessionにAVCaptureDeviceInputを登録
AVCaptureDeviceInputは、デバイスがカメラ、マイク、またはその他の種類であるかどうかに応じて、AVCaptureDeviceInputを使用してビデオデータまたはオーディオデータをキャプチャできます。 カメラの映像を処理するアプリケーションでは、AVCaptureDeviceInputを使用して、カメラからの映像を取得し、処理することができます
AVCaptureDeviceInputを登録する際はcanAddInput
メソッドを使用して追加が可能かを識別し、問題なければ登録します
if captureSession.canAddInput(videoInput) { captureSession.addInput(videoInput) } else { captureSession.commitConfiguration() return }
AVCaptureSessionにAVCaptureOutputを登録
AVCaptureOutputは、AVCaptureSessionに接続され、キャプチャされたデータを受け取り、アプリケーションが処理できるフォーマットに変換することができます。 例えば、AVCaptureVideoDataOutputを使用して、キャプチャーセッションからのビデオデータをアプリケーションに提供することができます。 同様に、AVCaptureAudioDataOutputを使用して、キャプチャーセッションからのオーディオデータをアプリケーションに提供することができます
AVCaptureOutputもcanAddOutput
メソッドを使用して追加が可能かを識別し、問題なければ登録します
let photoOutput = AVCapturePhotoOutput() if captureSession.canAddOutput(photoOutput!) { captureSession.addOutput(photoOutput!) }
AVCaptureVideoPreviewLayerで画面を構築
カメラデバイスからビューを表示するためのレイヤーを構築します。 この手順を行わないとカメラアプリを起動したときのように背景が画面が映らないので注意してください
let previewLayer = AVCaptureVideoPreviewLayer(session: captureSession) // photoViewはカメラを表示させるview(適時置き換えてください) previewLayer.frame = photoView.bounds // カメラから得られた映像の表示形式を設定 previewLayer.videoGravity = .resizeAspectFill captureSession.sessionPreset = AVCaptureSession.Preset.photo self.view.layer.addSublayer(previewLayer) // 最後にstartRunningメソッドを実行して、セッションの入力から出力へのデータフローを開始します captureSession.startRunning()
startRunning()
を行うことでカメラアプリを起動したときのように端末を移動させると背景も移動するようになります。
逆にセッションを終了させる場合にはstopRunning()
を行いましょう。
ここまでがカメラ撮影機能を実装する基本の流れです
撮影する
先ほどまでの状態実行するとでiPhone標準のカメラアプリを起動したときと同じになりはずです。 ここからはシャッターボタンを押したときの処理を追加する必要があります。 なお今回はシャッターボタンの作成は省きます。 シャッターボタンをタップして撮影するところからの実装です
AVCapturePhotoSettings
クラスで、撮影する際のフラッシュや手ぶれ補正などをおこなうかなどの設定をします。
そして、AVCapturePhotoOutput
クラスのcapturePhotoメソッドで指定した設定で写真の撮影を開始します
let settings = AVCapturePhotoSettings() // フラッシュの設定 settings.flashMode = .auto // 撮影された画像をdelegateメソッドで処理 photoOutput?.capturePhoto(with: settings, delegate: self)
しかし、この処理だけだと不完全です。 撮影された写真データををdelegateメソッドで受け取る必要があります
撮影後の処理
写真を撮影した後、画像データを保存したりする場合にどうすればいいでしょうか?
AVCapturePhotoOutputクラスのcapturePhoto
メソッドで撮影した画像データを受け取るために、AVCapturePhotoCaptureDelegate
のphotoOutput
メソッドで撮影した画像データを取得します。
撮影された画像データは、パラメータのphotoにピクセルデータとメタデータなどの関連データと共に格納されています。
fileDataRepresentationメソッドでデータを生成し、UIImageオブジェクトに変換することが多い気がします
extension ViewController: AVCapturePhotoCaptureDelegate { // 撮影した画像データが生成されたときに呼び出されるデリゲートメソッド func photoOutput(_ output: AVCapturePhotoOutput, didFinishProcessingPhoto photo: AVCapturePhoto, error: Error?) { if let imageData = photo.fileDataRepresentation() { // Data型をUIImageオブジェクトに変換 let uiImage = UIImage(data: imageData) } } }
補足
ここからは今までの説明の補足をいくつか追加しようと思います
向きを変える
iPhoneだとあまりないと思いますが、iPadだと横向きに変更して撮影を行う場合があるかもしれません。 ここまでの実装だけだとカメラの向きが変わらないので、少し実装を追加する必要があります
// UIInterfaceOrientationをAVCaptureVideoOrientationにConvert(全方位カメラの向きを許容する) func convertOrientation(orientation: () -> UIInterfaceOrientation) -> AVCaptureVideoOrientation? { let orientation = orientation() switch orientation { case UIInterfaceOrientation.unknown: return nil default: return ([ .portrait: .portrait, .portraitUpsideDown: .portraitUpsideDown, .landscapeLeft: .landscapeLeft, .landscapeRight: .landscapeRight ])[orientation] } } // UIInterfaceOrientationをAVCaptureVideoOrientationにConvert func appOrientation() -> UIInterfaceOrientation { let scenes = UIApplication.shared.connectedScenes let windowScenes = scenes.first as? UIWindowScene guard let orientation = windowScenes?.interfaceOrientation else { retrun .landscape } return orientation }
プレビューに関しては AVCaptureVideoPreviewLayer を利用しており、AVCaptureVideoPreviewLayer.connection.videoOrientation
、
撮影後の写真に関しては AVCaptureConnection.videoOrientation
をそれぞれ画面の回転に合わせて更新する必要があります
画面の向きについてはUIApplication.shared.connectedScenesから取得しています
上記の処理をプレビューのフレームを設定するときや写真を撮る前、画面が回転したときに行うと正しい向きで処理が行われます。 次のように使います
// 画面を回転したとき、プレビューの向き設定 override func viewWillTransition( to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator ) { super.viewWillTransition(to: size, with: coordinator) coordinator.animate( alongsideTransition: nil, completion: {(UIViewControllerTransitionCoordinatorContext) in if let orientation = self.convertOrientation(orientation: {return self.appOrientation()}) { previewLayer.connection?.videoOrientation = orientation } }) } // 撮影後の写真の向き設定 if let connection = photOutPut?.connection(with: .video), if let orientation = self.convertOrientation(orientation: {return self.appOrientation()}) { connection.videoOrientation = orientation }
lockForConfiguration()について
focusMode
やexposureMode
などデバイスのプロパティを設定・更新を行う場合には更新前にlockForConfiguration()
で端末をロックして設定を更新します。
更新が終わったらunlockForConfiguration()
をロックを解除します
イメージとしてはDBを更新する際に他からの更新がないようにロックするようなイメージです
下記はライトを点灯させる処理です。 ちなみにライトが搭載されていない端末で下記の処理を行おうとするとクラッシュするので、try~catchで囲みましょう
let avCaptureDevice = AVCaptureDevice.default(for: AVMediaType.video) if avCaptureDevice!.hasTorch, avCaptureDevice!.isTorchAvailable { do { try avCaptureDevice!.lockForConfiguration() try avCaptureDevice!.setTorchModeOn(level: 1.0) } catch let error { print(error) } avCaptureDevice!.unlockForConfiguration() }