前書き
地味に手間取ったので備忘録として記載しようかと思います
今回はコード全部記載します
行うこと
- Androidビルド
- IL2CPP化
- APKの軽量化
できたもの
やったこと
- 配布されてるライブラリは軽量化の物を使う
- OutputStreamは非Generics化したクラスを用意する(IL2CPP使う場合のみ
- https://twitter.com/monry/status/1484832543165726721
- ↑こちらの情報に助けられた
- 実機と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以外のところに原因がありました(怒)