質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.35%
iPad

iPadは、Appleがデザインしたタブレット型コンピュータです。iPadアプリケーションは通常Xcode IDEのObjective-Cで書かれますが、iPadアプリケーションを組むためのほかのツールを使うことも可能です。

iOS

iOSとは、Apple製のスマートフォンであるiPhoneやタブレット端末のiPadに搭載しているオペレーションシステム(OS)です。その他にもiPod touch・Apple TVにも搭載されています。

Xcode

Xcodeはソフトウェア開発のための、Appleの統合開発環境です。Mac OSXに付随するかたちで配布されています。

Swift

Swiftは、アップルのiOSおよびOS Xのためのプログラミング言語で、Objective-CやObjective-C++と共存することが意図されています

iPhone

iPhoneとは、アップル社が開発・販売しているスマートフォンです。 同社のデジタルオーディオプレーヤーiPodの機能、電話機能、インターネットやメールなどのWeb通信機能の3つをドッキングした機器です。

Q&A

解決済

1回答

10417閲覧

【Swift/iOS】AVFoundationによるカメラの露出(明るさ)調整時に、被写体がブレる

tsuki01

総合スコア1751

iPad

iPadは、Appleがデザインしたタブレット型コンピュータです。iPadアプリケーションは通常Xcode IDEのObjective-Cで書かれますが、iPadアプリケーションを組むためのほかのツールを使うことも可能です。

iOS

iOSとは、Apple製のスマートフォンであるiPhoneやタブレット端末のiPadに搭載しているオペレーションシステム(OS)です。その他にもiPod touch・Apple TVにも搭載されています。

Xcode

Xcodeはソフトウェア開発のための、Appleの統合開発環境です。Mac OSXに付随するかたちで配布されています。

Swift

Swiftは、アップルのiOSおよびOS Xのためのプログラミング言語で、Objective-CやObjective-C++と共存することが意図されています

iPhone

iPhoneとは、アップル社が開発・販売しているスマートフォンです。 同社のデジタルオーディオプレーヤーiPodの機能、電話機能、インターネットやメールなどのWeb通信機能の3つをドッキングした機器です。

0グッド

0クリップ

投稿2020/06/19 11:12

編集2020/06/22 01:55

実現したいこと・発生している問題

現在、AVFoundationを利用したカメラ機能を開発しております。
iPhone標準のカメラアプリにある、露出調整(明るさ調整)と同等の機能を実装したいと考えているのですが、
実装したものだと露出を上げるほどに被写体がブレる(かくつくような動き)ようになります。

「露出を変更する = シャッタースピードが変わる」という認識でいます。
その影響で被写体がブレる現象が発生していると考えているのですが、iPhone標準のカメラではブレる現象が発生しません。

※以下、iPhoneのカメラアプリの露出調整機能
iPhoneカメラ

質問内容

どような実装を行えば、被写体がブレる現象を抑えられますでしょうか?
「露出を上げすぎないことで対応するしかない」、「ISO感度と組み合わせて実装する」など、何かアドバイスなどあればご回答をお願いしたいです。

補足情報(FW/ツールのバージョンなど)

  • 開発環境:Xcode11.2.1
  • 言語  :Swift5
  • 端末  :iPhoneX、iPadPro11インチ

参考にしたサイトなど

Swiftでカメラアプリを作成する(1) 
AVCamManual: Extending AVCam to Use Manual Capture API 
z-hao-wang / ios-pro-camera 

該当のソースコード

【露出調整箇所抜粋】
以下はAppleのサンプルソースを元に実装。
「case 1」配下で露出調整を行なっているのですが、ここの処理に問題があると想定しております。

Swift

1// MARK: - Slider Action 2@IBAction func changeSlider(_ sender: UISlider) { 3 4 let tag: Int = sender.tag 5 do { 6 try currentDevice!.lockForConfiguration() 7 defer { currentDevice!.unlockForConfiguration() } 8 9 // 露出、または ISO感度を設定 10 switch tag { 11 case 1: 12 // 露出調整 13 let p = Float64(pow(sender.value, kExposureDurationPower)) // Apply power function to expand slider's low-end range 14 let minDurationSeconds = Float64(max(CMTimeGetSeconds(currentDevice!.activeFormat.minExposureDuration), kExposureMinimumDuration)) 15 let maxDurationSeconds = Float64(CMTimeGetSeconds(currentDevice!.activeFormat.maxExposureDuration)) 16 let newDurationSeconds = Float64(p * (maxDurationSeconds - minDurationSeconds)) + minDurationSeconds // Scale from 0-1 slider range to actual duration 17 18 currentDevice!.setExposureModeCustom(duration: CMTimeMakeWithSeconds( newDurationSeconds, preferredTimescale: 1000*1000*1000 ), 19 iso: AVCaptureDevice.currentISO, 20 completionHandler: nil) 21 22 case 2: 23 // ISO感度調整 24 currentDevice!.setExposureModeCustom(duration: AVCaptureDevice.currentExposureDuration, 25 iso: sender.value, 26 completionHandler: nil) 27 default: 28 break 29 } 30 } catch { 31 print("(error.localizedDescription)") 32 } 33}

【処理全体】

Swift

1import UIKit 2import AVFoundation 3 4class ViewController: UIViewController { 5 6@IBOutlet weak var cameraView: UIView! // プレビュー配置先のView 7var captureSession = AVCaptureSession() // セッション管理用 8var mainCamera: AVCaptureDevice? // メインカメラ用 9var innerCamera: AVCaptureDevice? // インカメラ用意 10var currentDevice: AVCaptureDevice? // 現在利用中のカメラ用 11var photoOutput : AVCapturePhotoOutput? // キャプチャーの出力データを受け付けるオブジェクト 12var cameraPreviewLayer : AVCaptureVideoPreviewLayer?// プレビュー表示用のレイヤ 13 14@IBOutlet weak var brightnessSlider: UISlider! // 露出用スライダー 15@IBOutlet weak var isoSlider: UISlider! // ISO感度用スライダー 16 17// 露出制御時用の定数 18let kExposureDurationPower : Float = 4 // Higher numbers will give the slider more sensitivity at shorter durations 19let kExposureMinimumDuration: Float64 = 1.0/2000 // Limit exposure duration to a useful range 20 21 22 23// MARK: - Life Cycle 24override func viewDidLoad() { 25 super.viewDidLoad() 26 27 // カメラ周り設定 28 self.setupCaptureSession() 29 self.setupDevice() 30 self.setupInputOutput() 31 self.setupPreviewLayer() 32 self.captureSession.startRunning() 33 34 // スライダー初期設定 35 self.setupSlider() 36} 37 38// スライダー初期設定 39func setupSlider() { 40 41 // 露出用スライダー設定 42 brightnessSlider.minimumValue = 0.0 43 brightnessSlider.maximumValue = 1.0 44 self.brightnessSlider.value = 0.5 45 46 // ISO感度用スライダー設定 47 isoSlider.minimumValue = Float(currentDevice!.activeFormat.minISO) 48 isoSlider.maximumValue = Float(currentDevice!.activeFormat.maxISO) 49 self.isoSlider.value = (isoSlider.minimumValue + isoSlider.maximumValue) / 2 50} 51 52 53// MARK: - Camera Setting 54// カメラの画質の設定 55func setupCaptureSession() { 56 captureSession.sessionPreset = AVCaptureSession.Preset.photo 57} 58 59// デバイスの設定 60func setupDevice() { 61 // カメラデバイスのプロパティ設定 62 let deviceDiscoverySession = AVCaptureDevice.DiscoverySession(deviceTypes: [AVCaptureDevice.DeviceType.builtInWideAngleCamera], 63 mediaType: AVMediaType.video, 64 position: AVCaptureDevice.Position.unspecified) 65 // カメラデバイスの取得 66 let devices = deviceDiscoverySession.devices 67 for device in devices { 68 if device.position == AVCaptureDevice.Position.back { 69 mainCamera = device 70 } else if device.position == AVCaptureDevice.Position.front { 71 innerCamera = device 72 } 73 } 74 75 // 起動時のカメラを設定 76 currentDevice = mainCamera 77 78 // 露出とISO感度の初期値設定 79 do { 80 try mainCamera!.lockForConfiguration() 81 defer { mainCamera!.unlockForConfiguration() } 82 mainCamera!.exposureMode = .custom 83 mainCamera!.setExposureModeCustom(duration: AVCaptureDevice.currentExposureDuration, iso: AVCaptureDevice.currentISO, completionHandler: nil) 84 } catch { 85 print("(error.localizedDescription)") 86 } 87} 88 89// 入出力データの設定 90func setupInputOutput() { 91 do { 92 // 入力を初期化 93 let captureDeviceInput = try AVCaptureDeviceInput(device: currentDevice!) 94 captureSession.addInput(captureDeviceInput) 95 96 // 出力を初期化 97 photoOutput = AVCapturePhotoOutput() 98 photoOutput!.setPreparedPhotoSettingsArray([AVCapturePhotoSettings(format: [AVVideoCodecKey : AVVideoCodecType.jpeg])], 99 completionHandler: nil) 100 captureSession.addOutput(photoOutput!) 101 } catch { 102 print(error) 103 } 104} 105 106// カメラプレビュー用のレイヤを設定 107func setupPreviewLayer() { 108 // プレビューレイヤを初期化 109 self.cameraPreviewLayer = AVCaptureVideoPreviewLayer(session: captureSession) 110 self.cameraPreviewLayer?.videoGravity = AVLayerVideoGravity.resizeAspectFill 111 self.cameraPreviewLayer?.connection?.videoOrientation = AVCaptureVideoOrientation.portrait 112 self.cameraPreviewLayer?.frame = view.frame 113 114 self.cameraPreviewLayer?.frame = self.cameraView.bounds 115 self.cameraView.layer.insertSublayer(self.cameraPreviewLayer!, at: 0) 116} 117 118 119// MARK: - Slider Action 120@IBAction func changeSlider(_ sender: UISlider) { 121 122 let tag: Int = sender.tag 123 do { 124 try currentDevice!.lockForConfiguration() 125 defer { currentDevice!.unlockForConfiguration() } 126 127 // 露出、または ISO感度を設定 128 switch tag { 129 case 1: 130 let p = Float64(pow(sender.value, kExposureDurationPower)) // Apply power function to expand slider's low-end range 131 let minDurationSeconds = Float64(max(CMTimeGetSeconds(currentDevice!.activeFormat.minExposureDuration), kExposureMinimumDuration)) 132 let maxDurationSeconds = Float64(CMTimeGetSeconds(currentDevice!.activeFormat.maxExposureDuration)) 133 let newDurationSeconds = Float64(p * (maxDurationSeconds - minDurationSeconds)) + minDurationSeconds // Scale from 0-1 slider range to actual duration 134 135 currentDevice!.setExposureModeCustom(duration: CMTimeMakeWithSeconds( newDurationSeconds, preferredTimescale: 1000*1000*1000 ), 136 iso: AVCaptureDevice.currentISO, 137 completionHandler: nil) 138 139 case 2: 140 currentDevice!.setExposureModeCustom(duration: AVCaptureDevice.currentExposureDuration, 141 iso: sender.value, 142 completionHandler: nil) 143 default: 144 break 145 } 146 } catch { 147 print("(error.localizedDescription)") 148 } 149} 150} 151

2020.06.22追記

発生している現象をGIFにしてみました。
露出を上げる前は問題なくカメラプレビューが表示されますが、
露出を上げると、カメラプレビュー自体がブレるような動きになってしまいます。
※iPhoneの標準カメラアプリではこの様な現象は発生しないため、何か特殊な処理が必要なのでしょうか。

GIF

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答1

0

自己解決

以下の対応で一旦解決としました。
調査を行って頂いた方がいらっしゃいましたら、ありがとうございました。

対応方法

そもそも、露出調整(明るさ調整)方法が誤っていた様です。
以下プロジェクトを再度確認していたら、**「露出時間」と「露出バイアス」**によって明るさを変更している処理がありました。
AVCamManual: Extending AVCam to Use Manual Capture API

私は**「露出時間」を変更することで明るさ調整を行おうとしていたので、被写体がブレる問題が発生していた様です。「露出バイアス」**を利用した調整に変更したところ、ブレる問題が改善しました。

参考URL

露出バイアス設定:setExposureTargetBias
露出時間設定 :setExposureModeCustom(duration:iso:completionHandler:)


変更後ソース

【主な変更箇所】

Diff

1// MARK: - Slider Action 2@IBAction func changeSlider(_ sender: UISlider) { 3 4 let tag: Int = sender.tag 5 do { 6 try currentDevice!.lockForConfiguration() 7 defer { currentDevice!.unlockForConfiguration() } 8 9 // 露出、または ISO感度を設定 10 switch tag { 11 case 1: 12 // 露出調整 13- let p = Float64(pow(sender.value, kExposureDurationPower)) // Apply power function to expand slider's low-end range 14- let minDurationSeconds = Float64(max(CMTimeGetSeconds(currentDevice!.activeFormat.minExposureDuration), kExposureMinimumDuration)) 15- let maxDurationSeconds = Float64(CMTimeGetSeconds(currentDevice!.activeFormat.maxExposureDuration)) 16- let newDurationSeconds = Float64(p * (maxDurationSeconds - minDurationSeconds)) + minDurationSeconds // Scale from 0-1 slider range to actual duration 17 18- currentDevice!.setExposureModeCustom(duration: CMTimeMakeWithSeconds( newDurationSeconds, preferredTimescale: 1000*1000*1000 ), 19- iso: AVCaptureDevice.currentISO, 20- completionHandler: nil) 21 22+ currentDevice!.exposureMode = .autoExpose 23+ currentDevice!.setExposureTargetBias(sender.value, completionHandler: nil) 24 25 case 2: 26+ currentDevice!.exposureMode = .custom 27 currentDevice!.setExposureModeCustom(duration: AVCaptureDevice.currentExposureDuration, 28 iso: sender.value, 29 completionHandler: nil) 30 default: 31 break 32 } 33 } catch { 34 print("(error.localizedDescription)") 35 } 36}

【処理全体】

Swift

1// 2// ViewController.swift 3// 4 5import UIKit 6import AVFoundation 7 8class ViewController: UIViewController { 9 10 @IBOutlet weak var cameraView: UIView! // プレビュー配置先のView 11 var captureSession = AVCaptureSession() // セッション管理用 12 var mainCamera: AVCaptureDevice? // メインカメラ用 13 var innerCamera: AVCaptureDevice? // インカメラ用意 14 var currentDevice: AVCaptureDevice? // 現在利用中のカメラ用 15 var photoOutput : AVCapturePhotoOutput? // キャプチャーの出力データを受け付けるオブジェクト 16 var cameraPreviewLayer : AVCaptureVideoPreviewLayer?// プレビュー表示用のレイヤ 17 18 @IBOutlet weak var brightnessSlider: UISlider! // 露出用スライダー 19 @IBOutlet weak var isoSlider: UISlider! // ISO感度用スライダー 20 21 22 // MARK: - Life Cycle 23 override func viewDidLoad() { 24 super.viewDidLoad() 25 26 // カメラ周り設定 27 self.setupCaptureSession() 28 self.setupDevice() 29 self.setupInputOutput() 30 self.setupPreviewLayer() 31 self.captureSession.startRunning() 32 33 // スライダー初期設定 34 self.setupSlider() 35 } 36 37 // スライダー初期設定 38 func setupSlider() { 39 40 // 露出用スライダー設定 41 brightnessSlider.minimumValue = Float(currentDevice!.minExposureTargetBias) 42 brightnessSlider.maximumValue = Float(currentDevice!.maxExposureTargetBias) 43 self.brightnessSlider.value = (brightnessSlider.minimumValue + brightnessSlider.maximumValue) / 2 44 45 // ISO感度用スライダー設定 46 isoSlider.minimumValue = Float(currentDevice!.activeFormat.minISO) 47 isoSlider.maximumValue = Float(currentDevice!.activeFormat.maxISO) 48 self.isoSlider.value = (isoSlider.minimumValue + isoSlider.maximumValue) / 2 49 } 50 51 52 // MARK: - Camera Setting 53 // カメラの画質の設定 54 func setupCaptureSession() { 55 captureSession.sessionPreset = AVCaptureSession.Preset.photo 56 } 57 58 // デバイスの設定 59 func setupDevice() { 60 // カメラデバイスのプロパティ設定 61 let deviceDiscoverySession = AVCaptureDevice.DiscoverySession(deviceTypes: [AVCaptureDevice.DeviceType.builtInWideAngleCamera], 62 mediaType: AVMediaType.video, 63 position: AVCaptureDevice.Position.unspecified) 64 // カメラデバイスの取得 65 let devices = deviceDiscoverySession.devices 66 for device in devices { 67 if device.position == AVCaptureDevice.Position.back { 68 mainCamera = device 69 } else if device.position == AVCaptureDevice.Position.front { 70 innerCamera = device 71 } 72 } 73 74 // 起動時のカメラを設定 75 currentDevice = mainCamera 76 77 // 露出とISO感度の初期値設定 78 do { 79 try mainCamera!.lockForConfiguration() 80 defer { mainCamera!.unlockForConfiguration() } 81 mainCamera!.exposureMode = .autoExpose 82 mainCamera!.setExposureModeCustom(duration: AVCaptureDevice.currentExposureDuration, iso: AVCaptureDevice.currentISO, completionHandler: nil) 83 } catch { 84 print("(error.localizedDescription)") 85 } 86 } 87 88 // 入出力データの設定 89 func setupInputOutput() { 90 do { 91 // 入力を初期化 92 let captureDeviceInput = try AVCaptureDeviceInput(device: currentDevice!) 93 captureSession.addInput(captureDeviceInput) 94 95 // 出力を初期化 96 photoOutput = AVCapturePhotoOutput() 97 photoOutput!.setPreparedPhotoSettingsArray([AVCapturePhotoSettings(format: [AVVideoCodecKey : AVVideoCodecType.jpeg])], 98 completionHandler: nil) 99 captureSession.addOutput(photoOutput!) 100 } catch { 101 print(error) 102 } 103 } 104 105 // カメラプレビュー用のレイヤを設定 106 func setupPreviewLayer() { 107 // プレビューレイヤを初期化 108 self.cameraPreviewLayer = AVCaptureVideoPreviewLayer(session: captureSession) 109 self.cameraPreviewLayer?.videoGravity = AVLayerVideoGravity.resizeAspectFill 110 self.cameraPreviewLayer?.connection?.videoOrientation = AVCaptureVideoOrientation.portrait 111 self.cameraPreviewLayer?.frame = view.frame 112 113 self.cameraPreviewLayer?.frame = self.cameraView.bounds 114 self.cameraView.layer.insertSublayer(self.cameraPreviewLayer!, at: 0) 115 } 116 117 118 // MARK: - Slider Action 119 @IBAction func changeSlider(_ sender: UISlider) { 120 121 let tag: Int = sender.tag 122 do { 123 try currentDevice!.lockForConfiguration() 124 defer { currentDevice!.unlockForConfiguration() } 125 126 // 露出、または ISO感度を設定 127 switch tag { 128 case 1: 129 currentDevice!.exposureMode = .autoExpose 130 currentDevice!.setExposureTargetBias(sender.value, completionHandler: nil) 131 132 case 2: 133 currentDevice!.exposureMode = .custom 134 currentDevice!.setExposureModeCustom(duration: AVCaptureDevice.currentExposureDuration, 135 iso: sender.value, 136 completionHandler: nil) 137 default: 138 break 139 } 140 } catch { 141 print("(error.localizedDescription)") 142 } 143 } 144} 145

投稿2020/06/22 09:21

tsuki01

総合スコア1751

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.35%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問