初投稿です。
iPhoneのカメラアプリ開発の案件頂いたのですが
カメラの特殊機能を解説しているサイトを中々、見かけなかったので解説してみようと思います。
まず、今回実装する機能ですが
- シャッタースピード
- ホワイトバランス
になります。
シャッタースピードは
カメラのISOと撮影時のレンズの閉じる速さを調節して
流動的な写真を撮ったり、早く動くものを的確にレンズで捉えて
繊細に撮影したりできる機能です。
ホワイトバランスは、
そのシーンにおいて
光の色を暖色や白色に変えたり出来る機能です。
では、早速説明していきたいと思います。
カメラの立ち上げや撮影後のデリゲート
フォトアルバムに保存するなどの情報はたくさんありますので
各自、ググって下さい。
import UIKit
import Foundation
import AVFoundation
class ViewController: UIViewController, AVCaptureVideoDataOutputSampleBufferDelegate,UIGestureRecognizerDelegate, AVCapturePhotoCaptureDelegate {
var captureSesssion: AVCaptureSession!
var stillImageOutput: AVCapturePhotoOutput?
var previewLayer: AVCaptureVideoPreviewLayer?
var ssSlider: UISlider!
var isoSlider: UISlider!
var timerMapping: Float!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
override func viewWillAppear(_ animated: Bool){
captureSesssion = AVCaptureSession()
stillImageOutput = AVCapturePhotoOutput()
captureSesssion.sessionPreset = AVCaptureSessionPreset1920x1080
let device = AVCaptureDevice.defaultDevice(withMediaType: AVMediaTypeVideo)
do {
let input = try AVCaptureDeviceInput(device: device)
if (captureSesssion.canAddInput(input)) {
captureSesssion.addInput(input)
if (captureSesssion.canAddOutput(stillImageOutput)) {
captureSesssion.addOutput(stillImageOutput)
captureSesssion.startRunning()
previewLayer = AVCaptureVideoPreviewLayer(session: captureSesssion)
previewLayer?.videoGravity = AVLayerVideoGravityResizeAspect//アスペクト
previewLayer?.connection.videoOrientation = AVCaptureVideoOrientation.portrait//カメラの向き
previewLayer?.frame = self.view.bounds
self.view.layer.addSublayer(previewLayer!)
//撮影ボタン
let shotButton = UIButton()
shotButton.layer.borderColor = UIColor.orange.cgColor
shotButton.translatesAutoresizingMaskIntoConstraints = false
shotButton.backgroundColor = UIColor.clear
shotButton.layer.borderWidth = 1
shotButton.layer.masksToBounds = true
shotButton.setTitleColor(UIColor.orange, for: .normal)
shotButton.setTitle("撮影", for: .normal)
shotButton.layer.cornerRadius = 35
shotButton.addTarget(self, action: #selector(shot), for: .touchUpInside)
self.view.addSubview(shotButton)
//撮影ボタンの制約
shotButton.centerXAnchor.constraint(equalTo: self.view.centerXAnchor).isActive = true
shotButton.bottomAnchor.constraint(equalTo: self.view.bottomAnchor, constant: -10.0).isActive = true
shotButton.widthAnchor.constraint(equalToConstant: 70.0).isActive = true
shotButton.heightAnchor.constraint(equalToConstant: 70.0).isActive = true
//シャッタースピード調節スライダー
ssSlider = UISlider()
ssSlider.minimumValue = 100.0
ssSlider.maximumValue = 500.0
ssSlider.value = 100.0
ssSlider.translatesAutoresizingMaskIntoConstraints = false
ssSlider.maximumTrackTintColor = UIColor.orange
ssSlider.minimumTrackTintColor = UIColor.red
ssSlider.addTarget(self, action: #selector(ViewController.ChangeValue(sender:)), for: UIControlEvents.valueChanged)
self.view.addSubview(ssSlider)
//シャッタースピード調節スライダーの制約
ssSlider.leadingAnchor.constraint(equalTo: self.view.leadingAnchor, constant: 30.0).isActive = true
ssSlider.bottomAnchor.constraint(equalTo: self.view.bottomAnchor, constant: -210.0).isActive = true
ssSlider.widthAnchor.constraint(equalToConstant: 250.0).isActive = true
ssSlider.heightAnchor.constraint(equalToConstant: 30.0).isActive = true
//シャッタースピードのラベル
let ssLabel = UILabel()
ssLabel.textColor = UIColor.white
ssLabel.layer.borderColor = UIColor.clear.cgColor
ssLabel.translatesAutoresizingMaskIntoConstraints = false
ssLabel.backgroundColor = UIColor.clear
ssLabel.text = "SS"
self.view.addSubview(ssLabel)
//シャッタースピード調節スライダーのラベルの制約
ssLabel.leadingAnchor.constraint(equalTo: self.view.leadingAnchor, constant: 30.0).isActive = true
ssLabel.bottomAnchor.constraint(equalTo: self.view.bottomAnchor, constant: -180).isActive = true
ssLabel.widthAnchor.constraint(equalToConstant: 30.0).isActive = true
ssLabel.heightAnchor.constraint(equalToConstant: 35.0).isActive = true
//ISO調節スライダー
isoSlider = UISlider()
isoSlider.minimumValue = 100.0
isoSlider.maximumValue = 520.0
isoSlider.value = 100.0
isoSlider.translatesAutoresizingMaskIntoConstraints = false
isoSlider.maximumTrackTintColor = UIColor.orange
isoSlider.minimumTrackTintColor = UIColor.red
isoSlider.addTarget(self, action: #selector(ViewController.ChangeValue(sender:)), for: UIControlEvents.valueChanged)
self.view.addSubview(isoSlider)
//ISO調節スライダーの制約
isoSlider.leadingAnchor.constraint(equalTo: self.view.leadingAnchor, constant: 30.0).isActive = true
isoSlider.bottomAnchor.constraint(equalTo: self.view.bottomAnchor, constant: -150.0).isActive = true
isoSlider.widthAnchor.constraint(equalToConstant:250.0).isActive = true
isoSlider.heightAnchor.constraint(equalToConstant:30.0)
//ISO調節スライダーのラベル
let isoLabel = UILabel()
isoLabel.textColor = UIColor.white
isoLabel.layer.borderColor = UIColor.clear.cgColor
isoLabel.backgroundColor = UIColor.clear
isoLabel.translatesAutoresizingMaskIntoConstraints = false
isoLabel.text = "ISO"
self.view.addSubview(isoLabel)
//ISO調節スライダーのラベルの制約
isoLabel.leadingAnchor.constraint(equalTo: self.view.leadingAnchor, constant: 30.0).isActive = true
isoLabel.bottomAnchor.constraint(equalTo: self.view.bottomAnchor, constant: -120.0).isActive = true
isoLabel.widthAnchor.constraint(equalToConstant: 30.0).isActive = true
isoLabel.heightAnchor.constraint(equalToConstant: 35.0).isActive = true
//ホワイトバランスONボタン
let wbButton = UIButton()
wbButton.layer.borderColor = UIColor.orange.cgColor
wbButton.backgroundColor = UIColor.clear
wbButton.layer.borderWidth = 1
wbButton.layer.masksToBounds = true
wbButton.setTitleColor(UIColor.orange, for: .normal)
wbButton.setTitle("WB", for: .normal)
wbButton.translatesAutoresizingMaskIntoConstraints = false
wbButton.layer.cornerRadius = 35
wbButton.addTarget(self, action: #selector(WB), for: .touchUpInside)
self.view.addSubview(wbButton)
//ホワイトバランスONボタンの制約
wbButton.trailingAnchor.constraint(equalTo: self.view.trailingAnchor, constant: -10.0).isActive = true
wbButton.bottomAnchor.constraint(equalTo: self.view.bottomAnchor, constant: -10.0).isActive = true
wbButton.widthAnchor.constraint(equalToConstant: 70.0).isActive = true
wbButton.heightAnchor.constraint(equalToConstant: 70.0).isActive = true
//ライトON
let Light = UIButton()
Light.layer.borderColor = UIColor.orange.cgColor
Light.backgroundColor = UIColor.clear
Light.layer.borderWidth = 1
Light.layer.masksToBounds = true
Light.setTitleColor(UIColor.orange, for: .normal)
Light.setTitle("ライト", for: .normal)
Light.layer.cornerRadius = 35
Light.translatesAutoresizingMaskIntoConstraints = false
Light.addTarget(self, action: #selector(LightOn), for: .touchUpInside)
self.view.addSubview(Light)
//ライトONボタンの制約
Light.leadingAnchor.constraint(equalTo: self.view.leadingAnchor, constant: 10.0).isActive = true
Light.bottomAnchor.constraint(equalTo: self.view.bottomAnchor, constant:-10.0).isActive = true
Light.widthAnchor.constraint(equalToConstant: 70.0).isActive = true
Light.heightAnchor.constraint(equalToConstant: 70.0).isActive = true
//ホワイトバランスOFFボタン
let risetButton = UIButton()
risetButton.layer.borderColor = UIColor.orange.cgColor
risetButton.backgroundColor = UIColor.clear
risetButton.layer.borderWidth = 1
risetButton.layer.masksToBounds = true
risetButton.setTitleColor(UIColor.orange, for: .normal)
risetButton.translatesAutoresizingMaskIntoConstraints = false
risetButton.setTitle("WB OFF", for: .normal)
risetButton.addTarget(self, action: #selector(risetWB), for: .touchUpInside)
self.view.addSubview(risetButton)
//ホワイトバランスOFFのボタンの制約
risetButton.centerXAnchor.constraint(equalTo: self.view.centerXAnchor).isActive = true
risetButton.topAnchor.constraint(equalTo: self.view.topAnchor, constant: 10.0).isActive = true
risetButton.widthAnchor.constraint(equalToConstant: 130.0).isActive = true
risetButton.heightAnchor.constraint(equalToConstant: 50.0).isActive = true
}
}
}
catch {
print(error)
}
}
//撮影ボタンの処理
func shot () {
let setting = AVCapturePhotoSettings()
setting.flashMode = .off
setting.isAutoStillImageStabilizationEnabled = true
setting.isHighResolutionPhotoEnabled = false
stillImageOutput?.capturePhoto(with: setting, delegate: self as AVCapturePhotoCaptureDelegate)
}
//ISOとシャッタースピードの処理
func ChangeValue(sender: UISlider){
let Setting = AVCaptureDevice.defaultDevice(withMediaType: AVMediaTypeVideo)
do {
try Setting?.lockForConfiguration()
let isoSetting: Float = isoSlider.value
timerMapping = ssSlider.value
let StockTime: Int32 = Int32(timerMapping)
let SetTime: CMTime = CMTimeMake(1, StockTime)
Setting?.setExposureModeCustomWithDuration(SetTime, iso: isoSetting, completionHandler: nil)
Setting?.unlockForConfiguration()
} catch {
let alertController = UIAlertController(title: "Cheak", message: "False !!", preferredStyle: .alert)
let defaultAction = UIAlertAction(title: "OK", style: .default, handler: nil)
alertController.addAction(defaultAction)
present(alertController, animated: true, completion: nil)
}
}
//ホワイトバランスの処理
func WB(sender: UIButton){
let wbSetting = AVCaptureDevice.defaultDevice(withMediaType: AVMediaTypeVideo)
do{
try wbSetting?.lockForConfiguration()
var g:AVCaptureWhiteBalanceGains = AVCaptureWhiteBalanceGains(redGain: 0.0, greenGain: 0.0, blueGain: 0.0)
g.blueGain = 1.5
g.greenGain = 1.0
g.redGain = 4.0
wbSetting?.setWhiteBalanceModeLockedWithDeviceWhiteBalanceGains(g, completionHandler: nil )
} catch {
let alertController = UIAlertController(title: "Cheak", message: "False !!", preferredStyle: .alert)
let defaultAction = UIAlertAction(title: "OK", style: .default, handler: nil)
alertController.addAction(defaultAction)
present(alertController, animated: true, completion: nil)
}
}
// トーチライトの処理
func LightOn(sender: UIButton){
let lightOn = AVCaptureDevice.defaultDevice(withMediaType: AVMediaTypeVideo)
if (lightOn?.hasTorch)!{
if (lightOn?.isTorchAvailable)!{
do{
try lightOn?.lockForConfiguration()
if (lightOn?.isTorchActive)!{
lightOn?.torchMode = AVCaptureTorchMode.off
} else {
lightOn?.torchMode = AVCaptureTorchMode.on
}
lightOn?.unlockForConfiguration()
} catch{
let alertController = UIAlertController(title: "Cheak", message: "ERROR !!", preferredStyle: .alert)
let defaultAction = UIAlertAction(title: "OK", style: .default, handler: nil)
alertController.addAction(defaultAction)
present(alertController, animated: true, completion: nil)
}
} else {
let alertController = UIAlertController(title: "Cheak", message: "This iPhone is not use Torch !!", preferredStyle: .alert)
let defaultAction = UIAlertAction(title: "OK", style: .default, handler: nil)
alertController.addAction(defaultAction)
present(alertController, animated: true, completion: nil)
}
} else {
let alertController = UIAlertController(title: "Cheak", message: "This iPhone is not Torch !!", preferredStyle: .alert)
let defaultAction = UIAlertAction(title: "OK", style: .default, handler: nil)
alertController.addAction(defaultAction)
present(alertController, animated: true, completion: nil)
}
}
//ホワイトバランスリセットの処理
func risetWB(sender: UIButton){
let risetWB = AVCaptureDevice.defaultDevice(withMediaType: AVMediaTypeVideo)
do{
try risetWB?.lockForConfiguration()
var kaito:AVCaptureWhiteBalanceGains = AVCaptureWhiteBalanceGains(redGain: 0.0, greenGain: 0.0, blueGain: 0.0)
kaito.blueGain = 2.5
kaito.greenGain = 1.3
kaito.redGain = 2.5
risetWB?.setWhiteBalanceModeLockedWithDeviceWhiteBalanceGains(kaito, completionHandler: nil )
} catch {
let alertController = UIAlertController(title: "Cheak", message: "False !!", preferredStyle: .alert)
let defaultAction = UIAlertAction(title: "OK", style: .default, handler: nil)
alertController.addAction(defaultAction)
present(alertController, animated: true, completion: nil)
}
}
//デリゲート。カメラで撮影が完了した後呼ばれる。JPEG形式でフォトライブラリに保存。
func capture(_ captureOutput: AVCapturePhotoOutput, didFinishProcessingPhotoSampleBuffer photoSampleBuffer: CMSampleBuffer?, previewPhotoSampleBuffer: CMSampleBuffer?, resolvedSettings: AVCaptureResolvedPhotoSettings, bracketSettings: AVCaptureBracketedStillImageSettings?, error: Error?) {
if let photoSampleBuffer = photoSampleBuffer {
// JPEG形式で画像データを取得
let photoData = AVCapturePhotoOutput.jpegPhotoDataRepresentation(forJPEGSampleBuffer: photoSampleBuffer, previewPhotoSampleBuffer: previewPhotoSampleBuffer)
let image = UIImage(data: photoData!)
// フォトライブラリに保存
UIImageWriteToSavedPhotosAlbum(image!, nil, nil, nil)
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
#手順1、プレビュー時のボタンを作成する。
ここでは、プレビュー時の、ボタンを作成していきます。
カメラのプレビュー画面を作成する時にaddSubViewで追加しています。
そのためAutoLayoutでは、オーバーレイ表示が出来ないため手っ取り早くコードから直接AutoLayoutを制御しました。
コード内では、
//シャッタースピード調節スライダー
ssSlider = UISlider()
ssSlider.minimumValue = 100.0
ssSlider.maximumValue = 500.0
ssSlider.value = 100.0
ssSlider.translatesAutoresizingMaskIntoConstraints = false
ssSlider.maximumTrackTintColor = UIColor.orange
ssSlider.minimumTrackTintColor = UIColor.red
ssSlider.addTarget(self, action: #selector(ViewController.ChangeValue(sender:)), for: UIControlEvents.valueChanged)
self.view.addSubview(ssSlider)
//シャッタースピード調節スライダーの制約
ssSlider.leadingAnchor.constraint(equalTo: self.view.leadingAnchor, constant: 30.0).isActive = true
ssSlider.bottomAnchor.constraint(equalTo: self.view.bottomAnchor, constant: -210.0).isActive = true
ssSlider.widthAnchor.constraint(equalToConstant: 250.0).isActive = true
ssSlider.heightAnchor.constraint(equalToConstant: 30.0).isActive = true
こちらがそのコードなどです。
これと同じようなコードを他の
撮影ボタンやホワイトバランスボタンなどにも同様の処理を書いていきます。
これで、カメラが立ち上がりプレビューが表示された時に
それぞれのUIパーツがオーバーレイ表示されてなおかつ、AutoLayoutも効いているはずです。
#手順2、それぞれの特殊機能の実装をしていこう
まずは、シャッタスピードから
説明していきます。
func ChangeValue(sender: UISlider){
let Setting = AVCaptureDevice.defaultDevice(withMediaType: AVMediaTypeVideo)
do {
try Setting?.lockForConfiguration()
let isoSetting: Float = isoSlider.value
timerMapping = ssSlider.value
let StockTime: Int32 = Int32(timerMapping)
let SetTime: CMTime = CMTimeMake(1, StockTime)
Setting?.setExposureModeCustomWithDuration(SetTime, iso: isoSetting, completionHandler: nil)
Setting?.unlockForConfiguration()
} catch {
let alertController = UIAlertController(title: "Cheak", message: "False !!", preferredStyle: .alert)
let defaultAction = UIAlertAction(title: "OK", style: .default, handler: nil)
alertController.addAction(defaultAction)
present(alertController, animated: true, completion: nil)
}
}
こちらがシャッタースピードの処理ですが
まず、シャッタースピードはISOと一緒に設定するのが通常です。
関数内を見てもらえれば分かると思いますが
- isoSetting
- timerMapping
この2つの変数でそれぞれisoとシャッタースピードのスライダーの値を取得しているのが
分かるかと思います。
そしてそれを最終的にCMTimeMake関数でCMTime型にコンバージョンしているのが分かるかと思います。
ここで、注意しなければならないのは
CMTimeMakeの第二引数はint32型なので、Float型である、timerMapping変数をあらかじめ
int32にコンバージョンしておく必要があります。
- setExposureModeCustomWithDuration(SetTime, iso: isoSetting, completionHandler: nil)
それが終われば上記のメソッドの第一引数に、先ほどのCMTimer型の、SetTime変数を指定し、第二引数は、先ほどの変数isoSettingを指定すればおkです。
こちらはFloatで何ら問題ありません。そして
cimpletionHandlerもnilで問題ありません。
一つ注意しなければならないのは
AVCaptureDeviceクラスを指定する時に
引数として()内にwithMediaType: AVMediaTypeVideoを必ず指定して下さい。
これないと落ちます。
あと、do try catch 構文の
tryは必ず Setting?.lockForConfiguration()こちらのメソッドを指定して下さい。
そして、終わりは必ず、unlockForConfiguration()こちらのメソッドを指定して下さい。
これないと落ちます。
#手順3、ホワイトバランスを実装する。
func WB(sender: UIButton){
let wbSetting = AVCaptureDevice.defaultDevice(withMediaType: AVMediaTypeVideo)
do{
try wbSetting?.lockForConfiguration()
var g:AVCaptureWhiteBalanceGains = AVCaptureWhiteBalanceGains(redGain: 0.0, greenGain: 0.0, blueGain: 0.0)
g.blueGain = 1.5
g.greenGain = 1.0
g.redGain = 4.0
wbSetting?.setWhiteBalanceModeLockedWithDeviceWhiteBalanceGains(g, completionHandler: nil )
} catch {
let alertController = UIAlertController(title: "Cheak", message: "False !!", preferredStyle: .alert)
let defaultAction = UIAlertAction(title: "OK", style: .default, handler: nil)
alertController.addAction(defaultAction)
present(alertController, animated: true, completion: nil)
}
}
こちらがホワイトバランスのコードになります。
まず、
上のシャッタースピード同様
do try cathch構文の
try文にwbSetting?.lockForConfiguration()を指定します。
そして、変数gにAVCaptureWhiteBalanceGainsクラスを宣言します。
そして、下記の
- g.blueGain = 1.5
- g.greenGain = 1.0
- g.redGain = 4.0
とあるように
それぞれのプロパティをFloatで指定します。
勘のいい方なら既に築かれたかと思われますが
このRGB値に似た数値がWBの正体です。
これを変更する事でカメラに様々なフィルダーをかけて
暖色で撮影したり白色で撮影したりといった事ができます。
そして、最後は、
-setWhiteBalanceModeLockedWithDeviceWhiteBalanceGains()
上記のメソッドの第一引数にblueGain、greenGain、redGain、の値が入った変数gを指定して
第二引数をcompletion: nilに指定して実行を行いボタンを押すと、指定した色のフィルターがかけれます。
#まとめ。
どうでしたでしょうか。
AVCaptureDeveiceクラスには他にも
露光やフラッシュライトなど面白いプロパティやメソッドがありました。
また、機会があれば挑戦してみようと思います。
それでは。