見出し画像

[Swift]GoogleのAR CoreのFaceTrackingのサンプルのコード読んでみた(前編)


AR Core(Faceサンプル)のファイル構成

・FacesViewController
・FaceMeshGeometryConverter

FacesViewController

viewDidLoad

if !setupScene() {
      return
    }
    if !setupCamera() {
      return
    }
    if !setupMotion() {
      return
    }

シーン、カメラ、モーションが起動しなければアプリはここで落ちます

do {
      faceSession = try GARAugmentedFaceSession(fieldOfView: videoFieldOfView)
    } catch {
      alertWindowTitle = "A fatal error occurred."
      alertMessage = "Failed to create session. Error description: \(error)"
      popupAlertWindowOnError(alertWindowTitle: alertWindowTitle, alertMessage: alertMessage)
    }

Google謹製のARsessionをfetchしようと試みますが失敗するとエラーを吐きます。

videoFieldOfViewはFloat型の変数です。現時点では無視でおk

 viewDidAppear

・判定用BoolであるviewDidAppearReachedがtrueになります
・エラーが発生したらpopupします

 ◆setupScene()

 guard let faceImage = UIImage(named: "Face.scnassets/face_texture.png"),
      let scene = SCNScene(named: "Face.scnassets/fox_face.scn"),
      let modelRoot = scene.rootNode.childNode(withName: "asset", recursively: false)
    else {
      alertWindowTitle = "A fatal error occurred."
      alertMessage = "Failed to load face scene!"
      popupAlertWindowOnError(alertWindowTitle: alertWindowTitle, alertMessage: alertMessage)
      return false
    }

・顔面に貼り付けるためのNodeをここで呼び出しており、見つからないとエラーを吐きます

      foreheadLeftNode = modelRoot.childNode(withName: "FOREHEAD_LEFT", recursively: true)
    foreheadRightNode = modelRoot.childNode(withName: "FOREHEAD_RIGHT", recursively: true)
    noseTipNode = modelRoot.childNode(withName: "NOSE_TIP", recursively: true)

    faceNode.addChildNode(faceTextureNode)
    faceNode.addChildNode(faceOccluderNode)
    scene.rootNode.addChildNode(faceNode)

・Nodeの定義と描画

    let cameraNode = SCNNode()
    cameraNode.camera = sceneCamera
    scene.rootNode.addChildNode(cameraNode)

    sceneView.scene = scene
    sceneView.frame = view.bounds
    sceneView.delegate = self
    sceneView.rendersContinuously = true
    sceneView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
    sceneView.backgroundColor = .clear
    // Flip 'x' to mirror content to mimic 'selfie' mode
    sceneView.layer.transform = CATransform3DMakeScale(-1, 1, 1)
    view.addSubview(sceneView)

・CameraNodeを追加し、sceneViewのプロパティを行います。ポイントになりそうなのはsceneViewのバックグラウンドが透明なところとか。発想的にはセル画のアニメみたいに現実世界の映像とCGの映像のレイヤーを切り離していそうです。

        faceTextureMaterial.diffuse.contents = faceImage
    // SCNMaterial does not premultiply alpha even with blendMode set to alpha, so do it manually.
    faceTextureMaterial.shaderModifiers =
      [SCNShaderModifierEntryPoint.fragment: "_output.color.rgb *= _output.color.a;"]
    faceOccluderMaterial.colorBufferWriteMask = []

    return true

・マテリアルとシェーダーの設定です。モデリング勢なら馴染み深い概念。
・問題なければtrueを返します。falseだと起動できません


◆setUpCamera()

 private func setupCamera() -> Bool {
    guard
      let device =
        AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .front)
    else {
      alertWindowTitle = "A fatal error occurred."
      alertMessage = "Failed to get device from AVCaptureDevice."
      popupAlertWindowOnError(alertWindowTitle: alertWindowTitle, alertMessage: alertMessage)
      return false
    }

カメラを呼び出す準備をします。出来なければエラーを返します。

    guard
      let input = try? AVCaptureDeviceInput(device: device)
    else {
      alertWindowTitle = "A fatal error occurred."
      alertMessage = "Failed to get device input from AVCaptureDeviceInput."
      popupAlertWindowOnError(alertWindowTitle: alertWindowTitle, alertMessage: alertMessage)
      return false
    }

・カメラのインプットを呼び出す準備をします。出来なければエラーを返します。

       let output = AVCaptureVideoDataOutput()
    output.videoSettings = [kCVPixelBufferPixelFormatTypeKey as String: kCVPixelFormatType_32BGRA]
    output.setSampleBufferDelegate(self, queue: DispatchQueue.global(qos: .userInteractive))

    let session = AVCaptureSession()
    session.sessionPreset = .high
    session.addInput(input)
    session.addOutput(output)
    captureSession = session
    captureDevice = device

    videoFieldOfView = captureDevice?.activeFormat.videoFieldOfView ?? 0

    cameraImageLayer.contentsGravity = .center
    cameraImageLayer.frame = self.view.bounds
    view.layer.insertSublayer(cameraImageLayer, at: 0)

・アウトプットやセッションのコンフィグをすましていきます。 videoFieldOfViewにデバイスのview数を代入しておきます。

   getVideoPermission(permissionHandler: { granted in
      guard granted else {
        NSLog("Permission not granted to use camera.")
        self.alertWindowTitle = "Alert"
        self.alertMessage = "Permission not granted to use camera."
        self.popupAlertWindowOnError(
          alertWindowTitle: self.alertWindowTitle, alertMessage: self.alertMessage)
        return
      }
      self.captureSession?.startRunning()
    })

    return true
  }

・ビデオを使う設定をユーザーに許可されていなければここでエラーを返します。そうでなければデバイスを起動します。

◆setUpMotion()

  private func setupMotion() -> Bool {
    guard motionManager.isDeviceMotionAvailable else {
      alertWindowTitle = "Alert"
      alertMessage = "Device does not have motion sensors."
      popupAlertWindowOnError(alertWindowTitle: alertWindowTitle, alertMessage: alertMessage)
      return false
    }
    motionManager.deviceMotionUpdateInterval = 0.01
    motionManager.startDeviceMotionUpdates()

    return true
  }

・アプリのダウンロードされた端末にモバイルセンサーが付いていなければエラーを投げます。クッソ面倒な事前準備してこのエラー吐き出されたらキレそうですね
・なお、対応端末は公式サイトから確認できます。ちゃんと見てませんがARKitを難なく使いこなせていれば基本ARCoreを動かせるはずです。確かSE2くらいからAR Core使えると思います
・deviceMotionUpdateIntervalではフレーム数を指定できます

getVideoPermission()

  private func getVideoPermission(permissionHandler: @escaping (Bool) -> Void) {
    switch AVCaptureDevice.authorizationStatus(for: .video) {
    case .authorized:
      permissionHandler(true)
    case .notDetermined:
      AVCaptureDevice.requestAccess(for: .video, completionHandler: permissionHandler)
    default:
      permissionHandler(false)
    }
  }

・カメラの使用許可の状況によって対応を分岐させます


今回はここまで

この記事が気に入ったらサポートをしてみませんか?