LoginSignup
29
27

More than 5 years have passed since last update.

ios10 シャッタースピードやホワイトバランスなどiPhoneカメラに特殊機能を実装する方法

Posted at

初投稿です。

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クラスには他にも
露光やフラッシュライトなど面白いプロパティやメソッドがありました。
また、機会があれば挑戦してみようと思います。

それでは。

29
27
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
29
27