😎

Apple Vision Proでアプリからペルソナを表示する

2024/04/25に公開

image

Apple Vision Pro では SharePlay でペルソナを使用することができます。SharePlayで会話相手にそっくりなアバターが表示され、そこから声が聞こえたり、身振り手振りや表情が反映されていると、実際に会って話しているのと同じような感覚を得ることができます。

https://twitter.com/AR_Ojisan/status/1776242198872531241

今回はそんなペルソナをアプリケーション側から使用する方法について調べたので解説していきます。

環境設定

現在は映像としてしか自分のペルソナを表示することができないため、仮想カメラから自分のペルソナの映像を取得する必要があります。

まずはカメラにアクセスするためプライバシーの設定を行います。
CameraUsageDescriptionに使用理由を追加します。
image

ペルソナ用カメラ映像の取得

まずは setupCameraという関数を用意しカメラへのアクセス権をリクエストします。

func setupCamera() async {
    await AVCaptureDevice.requestAccess(for: .video)
    ...
}

次にカメラの取得をします。visionOSではsystemPreferredCameraを取得することでペルソナ用のカメラを取得することができるようです。

guard let camera = AVCaptureDevice.systemPreferredCamera else { return }

取得したカメラを使用してセッションを作成します。captureSession.addInputAVCaptureDeviceInputを追加することでセッションの入力ソースを追加しています。

guard let videoInput = try? AVCaptureDeviceInput(device: camera) else { return }
captureSession = AVCaptureSession()
if captureSession.canAddInput(videoInput) {
    captureSession.addInput(videoInput)
} else {
    print("Could not add video input")
    return
}

次に出力の設定をします。
videoDataOutput.setSampleBufferDelegateではバッファを取得するためのデリゲートを設定しています。今回作っているクラスはNSObjectAVCaptureVideoDataOutputSampleBufferDelegateを継承し、captureOutputを実装することで、captureOutputでバッファを取得できるようになります。

videoDataOutput = AVCaptureVideoDataOutput()
// ピクセルバッファのフォーマットを32bitRGBAにしている
videoDataOutput.videoSettings = [(kCVPixelBufferPixelFormatTypeKey as String): Int(kCVPixelFormatType_32BGRA)]
// ビデオに遅延が起きた時にフレームを削除するか
videoDataOutput.alwaysDiscardsLateVideoFrames = true
// 出力用のキューとデリゲートの設定
// AVCaptureVideoDataOutputSampleBufferDelegate を継承しているのでselfを渡す
videoDataOutputQueue = DispatchQueue(label: "VideoDataOutputQueue")
videoDataOutput.setSampleBufferDelegate(self, queue: videoDataOutputQueue)

if captureSession.canAddOutput(videoDataOutput) {
    captureSession.addOutput(videoDataOutput)
} else {
    print("Could not add video output")
    return
}

//セッションを開始する
captureSession.startRunning()

最後にcaptureOutputの実装を見ていきます。
CMSampleBufferGetImageBufferを使用してpixelBufferを取得します、そこからCIMageを作成し createCGImageCGImageに変換した後、UIImageに変換しています。

func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) {
    if let pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) {
        let ciImage = CIImage(cvPixelBuffer: pixelBuffer)
        let imageRect = CGRect(x: 0, y: 0, width: CVPixelBufferGetWidth(pixelBuffer), height: CVPixelBufferGetHeight(pixelBuffer))
        let context = CIContext()
        if let image = context.createCGImage(ciImage, from: imageRect) {
            DispatchQueue.main.async {
                self.callback(UIImage(cgImage: image))
            }
        }
    }
}

ペルソナ用カメラ映像の表示

これで UIImageを取得することができるようになりました。
あとは取得したUIImageを表示することでカメラの映像を表示することができます。

@State var personaCamera: PersonaCamera?
@State var image: UIImage?

var body: some View {
    ZStack {
        if let image = self.image {
            Image(uiImage: image)
                .resizable()
                .scaledToFill()
                .frame(width: 800, height: 800)
        }
    }
    
    .glassBackgroundEffect()
    .onAppear() {
        personaCamera = .init(callback: { image in
            self.image = image
        })
        
        Task {
            await personaCamera?.setupCamera()
        }
    }
}

image

まとめ

これで自分のペルソナを使用することができました!
まだ映像でしか使用できないですが今後のアップデートでモデルにもアプリケーション側で触れるようになると良いですね。

書いた人

ひー

佐藤 寿樹

株式会社コナミデジタルエンタテインメントに入社し5年間ウイニングイレブンのオンライン実装に携わる。
その後、株式会社コロプラで9年間エンジニアとしてアプリ開発・運用を行い、位置情報やARを使用したARゲーム開発、OculusRiftやPSVRなどのVRゲーム開発を経験しMESONへ入社。

Twitter

MESON Works

MESONの制作実績一覧もあります。ご興味ある方はぜひ見てみてください。

MESON Works

Discussion