[iOS]リアルタイムで画像処理をする時の カメラの内部パラメーターの取得方法


先日 [SceneKitで内部パラメーターを取得する記事を書いた](https://qiita.com/shirahama_x/items/e489f12d60e2e3e90498)のですが、今回は<font color="Blue">AVFoundation</font>で内部パラメーターを取得する話。
iOS 11から内部パラメーターの取得ができるようになったのですが、これまた情報がないので、ソースコードだけそっと置いておきます。

#ターゲット
* Swift 4でAVCaptureSessionをつかってリアルタイムの顔認識や画像処理をしたい人
* また、iOS 11から使えるようになったカメラの内部パラメーター(intrinsicMatrix)の取得をしたい人
    * 他のパラメーター取得にも使えそう

#ポイント
## VideoDataOutputへの設定
まずセッションにVideoDataOutput情報を追加しないと内部パラメーター含むコネクション情報が取得できず、nilエラーが起きます。
`captureSession.addOutput(videoDataOutput)`をしてからコネクションに対してenable設定を行います。

```swift
let videoDataOutput = AVCaptureVideoDataOutput()

// ~~~ もろもろの設定 

captureSession.addOutput(videoDataOutput)
videoDataOutput.connections.first?.isCameraIntrinsicMatrixDeliveryEnabled = true
```

このenable設定を行っておくことで、CMSampleBufferから内部パラメーターの取得をすることができます。

```swift
if let camData = CMGetAttachment(sampleBuffer, kCMSampleBufferAttachmentKey_CameraIntrinsicMatrix, nil) as? Data {
    let matrix: matrix_float3x3 = camData.withUnsafeBytes { $0.pointee }
    print(matrix)
}
```


### (些事) Swift 3から Swift 4に移行した場合の注意点
キャプチャした際に呼び出される`captureOutput`ですが、Swift 3では`didOutputSampleBuffer`がSwift 4では`didOutput`になっているので注意が必要です。
（アプリは起動するのに写真が表示されない不具合が起きます）

```swift
    func captureOutput(_ captureOutput: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) 
```

#以下ソースコード
## ViewController

```swift
class ViewController: UIViewController, AVCaptureDelegate {

    @IBOutlet weak var imageView: UIImageView!
    
    let avCapture = AVCapture()

    override func viewDidLoad() {
        
        super.viewDidLoad()
        avCapture.delegate = self
    }

    func capture(image: UIImage) {

        // 何かしらの画像処理
        imageView.image = image
        
    }
}

```

## avCapture.swift
（本来ならもっとエラーハンドリングするべきなんですが。）

``` swift

import UIKit
import AVFoundation

protocol AVCaptureDelegate {
    func capture(image: UIImage)
}

class AVCapture:NSObject, AVCaptureVideoDataOutputSampleBufferDelegate {
    
    var captureSession: AVCaptureSession!
    var delegate: AVCaptureDelegate?

    // DISMISS_COUNT に1回画像処理 （captureの実行)
    var counter = 0
    let DISMISS_COUNT = 5
    
    override init(){
        super.init()
        
        captureSession = AVCaptureSession()
        captureSession.sessionPreset = AVCaptureSession.Preset.hd1920x1080
        
        
        let videoDevice = AVCaptureDevice.default(for: AVMediaType.video)
        videoDevice?.activeVideoMinFrameDuration = CMTimeMake(1, 30)// 1/30秒 (１秒間に30フレーム)
        
        
        let videoInput = try! AVCaptureDeviceInput.init(device: videoDevice!)
        captureSession.addInput(videoInput)
        
        
        let videoDataOutput = AVCaptureVideoDataOutput()
        videoDataOutput.setSampleBufferDelegate(self, queue: DispatchQueue.main)
        videoDataOutput.videoSettings = [kCVPixelBufferPixelFormatTypeKey as AnyHashable as! String : Int(kCVPixelFormatType_32BGRA)]
        videoDataOutput.alwaysDiscardsLateVideoFrames = true
        // 先にセッションにOutput情報を追加
        captureSession.addOutput(videoDataOutput)

        // 内部パラメーターの配信を有効にする
        // isCameraIntrinsicMatrixDeliverySupported がtrueの場合のみ
        videoDataOutput.connections.first?.isCameraIntrinsicMatrixDeliveryEnabled = true

        DispatchQueue.global(qos: .userInitiated).async {
            self.captureSession.startRunning()
        }
        
    }
    
    // 新しいキャプチャの追加で呼ばれる関数
    func captureOutput(_ captureOutput: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) {

        if (counter % DISMISS_COUNT) == 0 {

            let image = imageFromSampleBuffer(sampleBuffer: sampleBuffer)
            delegate?.capture(image: image)
        
            // カメラの内部パラメーターの入手
            if let camData = CMGetAttachment(sampleBuffer, kCMSampleBufferAttachmentKey_CameraIntrinsicMatrix, nil) as? Data {
                
                let matrix: matrix_float3x3 = camData.withUnsafeBytes { $0.pointee }
                print(matrix)
            }
        }
        counter += 1
    }
    
    // SampleBufferからUIImageの作成
    func imageFromSampleBuffer(sampleBuffer :CMSampleBuffer) -> UIImage {

        let imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer)!
        
        // イメージバッファのロック
        CVPixelBufferLockBaseAddress(imageBuffer, CVPixelBufferLockFlags(rawValue: 0))
        
        // 画像情報を取得
        let base = CVPixelBufferGetBaseAddressOfPlane(imageBuffer, 0)!
        let bytesPerRow = UInt(CVPixelBufferGetBytesPerRow(imageBuffer))
        let width = UInt(CVPixelBufferGetWidth(imageBuffer))
        let height = UInt(CVPixelBufferGetHeight(imageBuffer))
        
        // ビットマップコンテキスト作成
        let colorSpace = CGColorSpaceCreateDeviceRGB()
        let bitsPerCompornent = 8
        let bitmapInfo = CGBitmapInfo(rawValue: (CGBitmapInfo.byteOrder32Little.rawValue | CGImageAlphaInfo.premultipliedFirst.rawValue) as UInt32)
        let newContext = CGContext(data: base, width: Int(width), height: Int(height), bitsPerComponent: Int(bitsPerCompornent), bytesPerRow: Int(bytesPerRow), space: colorSpace, bitmapInfo: bitmapInfo.rawValue)! as CGContext

        // イメージバッファのアンロック
        CVPixelBufferUnlockBaseAddress(imageBuffer, CVPixelBufferLockFlags(rawValue: 0))
        
        // 画像作成
        let imageRef = newContext.makeImage()!
        let image = UIImage(cgImage: imageRef, scale: 1.0, orientation: UIImageOrientation.right)
        
        return image
    }
}


```

#参考記事
該当 Apple ドキュメント：[isCameraIntrinsicMatrixDeliveryEnabled](https://developer.apple.com/documentation/avfoundation/avcaptureconnection/2875903-iscameraintrinsicmatrixdeliverye)
ソースコード元： [[iOS] AVFoundation+OpenCVで劇画調カメラを作ってみた](https://dev.classmethod.jp/smartphone/ios-avfoundationavcapturevideodataoutput-opencv/)
[[コピペで使える]swift3/swift4でリアルタイム顔認識をする方法](https://qiita.com/TakahiroYamamoto/items/e970658a98a4e659cf9e)