LoginSignup
2
0

【MediaPipe,Unity】vrm使ってVtuberになれる仕組みを作ってみた(Androidビルド編)

Posted at

前書き

地味に手間取ったので備忘録として記載しようかと思います
今回はコード全部記載します

行うこと

  • Androidビルド
  • IL2CPP化
  • APKの軽量化

できたもの

Test.gif

やったこと

  • 配布されてるライブラリは軽量化の物を使う
    • スクリーンショット 2024-05-04 145807.png
    • AOSビルドが180MB→50MBまで減少(リリースビルド
  • OutputStreamは非Generics化したクラスを用意する(IL2CPP使う場合のみ
  • 実機とEditorのカメラの差に気を付ける
    • Editorと実機ではカメラの角度や解像度が違うため、差を埋める作業が必要
    • 前回作成したVrmFace側で吸収しているのでコードなし

実際使用したコード

前回までのコードは配布されたサンプルのラッパークラスから独自のSolutionとGraphを作成して作っていた
今回はこちらの公式WikiのHelloWorldから作成を行った


using System;
using System.Diagnostics;
using Cysharp.Threading.Tasks;
using Mediapipe;
using Mediapipe.Unity;
using UnityEngine;
using UnityEngine.UI;

public class MediaPipeController : MonoBehaviour
{
    [SerializeField] private TextAsset gpuSetting;
    [SerializeField] private TextAsset cpuSetting;
    [SerializeField] private RawImage screen;
    [SerializeField] private int width = 640;
    [SerializeField] private int height = 480;
    [SerializeField] private int fps = 60;
    // VRMの表情を変更するためのコンポーネント
    [SerializeField] private VrmFace vrmFace;
    // 顔のランドマークを描画するためのコンポーネント
    [SerializeField] private FaceLandmarkListAnnotationController faceLandmarkListAnnotationController;
    
    private WebCamTexture webCamTexture;
    private OutputStreamFace outputStream;
    private Texture2D inputTexture;
    private Color32[] inputPixelData;
    
    private CalculatorGraph graph;
    private ResourceManager resourceManager;

    private async void Start()
    {
        // Androidかの判定
        bool isAOS = Application.platform == RuntimePlatform.Android && !Application.isEditor;

        // Androidの場合はカメラのデバイスを切り替える
        var index = !isAOS ? 0 : 1;
        var webCamDevice = WebCamTexture.devices[index];
        
        // widthとheightは設定値よりカメラの上限のwidthとheightが低いと書き換えられるので注意
        // ここで書き換えられた場合、↓のInputTextureの設定で壊れる
        webCamTexture = new WebCamTexture(webCamDevice.name, width, height, fps);
        webCamTexture.Play();

        await UniTask.WaitUntil(() => webCamTexture.width > 16);
        // 初期化が早すぎるとMediapipeの初期化が間に合わないので少し待つ
        await UniTask.WaitForSeconds(0.5f);

        // Androidの場合はカメラの向きを変更する 縦向きなため
        if (isAOS)
        {
            screen.rectTransform.sizeDelta = new Vector2(width / 3, height / 3);
            Vector3 angles = screen.rectTransform.eulerAngles;
            angles.z = 90;
            screen.rectTransform.eulerAngles = angles;
        }

        // カメラの映像をテクスチャに変換する
        inputTexture = new Texture2D(width, height, TextureFormat.RGBA32, false);
        inputPixelData = new Color32[width * height];
        screen.texture = webCamTexture;

        // モデルの読み込み
        resourceManager = new StreamingAssetsResourceManager();
        await resourceManager.PrepareAssetAsync("face_detection_short_range.bytes");
        await resourceManager.PrepareAssetAsync("face_landmark.bytes");
        await resourceManager.PrepareAssetAsync("iris_landmark.bytes");

        var stopwatch = new Stopwatch();
        // 設定を読み込む(エディターの場合はCPUしか対応してない)
        var textConfig = Application.isEditor ? cpuSetting : gpuSetting;
        var baseConfig = textConfig == null ? null : CalculatorGraphConfig.Parser.ParseFromTextFormat(textConfig.text);

        // サイドパケットの設定
        var sidePacket = new PacketMap();
        sidePacket.Emplace("input_rotation", Packet.CreateInt(0));
        sidePacket.Emplace("input_horizontally_flipped", Packet.CreateBool(false));
        sidePacket.Emplace("input_vertically_flipped", Packet.CreateBool(true));

        // Mediapipeの初期化
        graph = new CalculatorGraph();
        graph.Initialize(baseConfig, sidePacket);
        
        // 出力の設定,OutputStreamFaceはOutputStream<NormalizedLandmarkList>を非Generics化したクラス
        // Genericsクラスのメソッドにたいして[AOT.MonoPInvokeCallback]を付与してもIL2CPPでは動作しないため、別で定義する必要がある
        outputStream = new OutputStreamFace(graph, "face_landmarks_with_iris");
        
        // メインスレッドでの処理
        outputStream.StartPolling();
        graph.StartRun();
        stopwatch.Start();

        while (true)
        {
            // メインスレッドに切り替える
            await UniTask.SwitchToMainThread();
            if(webCamTexture == null || inputTexture == null || inputPixelData == null) break;
            inputTexture.SetPixels32(webCamTexture.GetPixels32(inputPixelData));
            var imageFrame = new ImageFrame(ImageFormat.Types.Format.Srgba, width, height, width * 4, inputTexture.GetRawTextureData<byte>());
            var currentTimestamp = stopwatch.ElapsedTicks / (TimeSpan.TicksPerMillisecond / 1000);
            graph.AddPacketToInputStream("input_video", Packet.CreateImageFrameAt(imageFrame, currentTimestamp));
            
            var result = await outputStream.WaitNextAsync();
            if (!result.ok) throw new Exception("Something went wrong");
            var outputPacket = result.packet;
            if (outputPacket != null)
            {
                var landmarks = outputPacket.Get(NormalizedLandmarkList.Parser);
                await vrmFace.SyncFace(landmarks);
                faceLandmarkListAnnotationController.DrawNow(landmarks);
            }

            await UniTask.Yield(PlayerLoopTiming.LastUpdate);
        }
    }
    
    private void OnDestroy()
    {
        if (webCamTexture != null)
        {
            webCamTexture.Stop();
        }

        outputStream?.Dispose();
        outputStream = null;

        if (graph != null)
        {
            try
            {
                graph.CloseInputStream("input_video");
                graph.WaitUntilDone();
            }
            finally
            {
                graph.Dispose();
                graph = null;
            }
        }
    }
}

あとがき

実は一番手間取ったのは↑のコードの「widthとheightは設定値よりカメラの上限のwidthとheightが低いと書き換えられるので注意」の部分でした
実機上だと正常動作するのにEditorだと動かない状態でした
Mediapipe側のコード読みまくっても原因がわからずめっちゃ迷いました
そしたらMediapipe以外のところに原因がありました(怒)

2
0
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
2
0