本文へスキップ

ニューラルネットワーク・モデルのプリロード( Neural Network Model Preloading)の使い方

メッシングやデプスなどの認識機能は、ARセッションで各ピクセルをどのように描画するかを知るためにニューラルネットワークモデルを使用します。 実行時のパフォーマンスを向上させるために、モデルのプリロードにより、前もってモデルファイルをダウンロードすることができます。これにより、インストールプロセスをフロントローディングし、ユーザーエクスペリエンスに悪影響を与えるジャストインタイムのロード遅延を回避することができます。

サンプルグラフィックのプリロード

前提条件

  • Lightship ARを有効にしたUnityのプロジェクトが必要です。 詳細については、 ARDK 3.0 のインストール をご覧ください。

Neural Network Performance Modes

Lightshipのアウェアネス機能は、さまざまなパフォーマンスモードをサポートしており、開発者はパフォーマンス、品質、またはバランスの取れたミックスを選択することができます。 これらのモードは、 XROcclusionSubsystemで使用される Unity EnvironmentDepthMode 値 に類似しています。

例えば、 Fast は、品質を犠牲にして処理時間を短縮するように最適化し、 Smooth は、最高のグラフィックを提供するために時間をかけます。 各モードは、AR機能を使用する前にプリロードできる独自のニューラルネットワークモデルファイルを持っています。 独自のニューラルネットワークモデルファイルを指定する場合は、 RegisterModel を使用して、Lightshipの定義済みのモデルをオーバーライドできます。

機能モードを要求するには、アウェアネス機能を開始する前に機能とモードを指定します:

DownloadModel(DepthMode.Medium);
DownloadModel(SemanticsMode.Smooth)

Runtime/Utilities/Preloading/Feature.cs を参照してください。

Lightshipニューラルネットワークモデルのプリロード

アウェアネス機能を開始する前に、ニューラルネットワークモデルファイルをプリロードします:

  1. アプリケーションのパフォーマンスと品質のニーズに基づいて、プリロードするモデルを決定します。
  2. プリロードのタイミングを決めます。 プリロードは、AR Foundation が Lightship ローダーを初期化した後( XRGeneralSettings.Instance.Manager.isInitializationComplete が true の場合)、アウェアネス機能が開始する前であればいつでも実行できます。 例えば、ARスクリプトが MonoBehaviourの場合、プリロードは Start()の間に行うことができます。 :::caution 注意!
    プリロードAPIを使用するシーンに、アクティブなARマネージャーが存在しないことを確認してください。 ARマネージャーが存在する場合、関連するモデルは自動的にダウンロードされます。 :::
重要!

モデルのプリロードAPIは、Lightship ARセッションが開始した後のランタイム中にのみ利用可能です。 モデルがプリロードされていない場合、アウェアネス機能が起動時に自動的に選択します。

  1. 以下の手順で、UIオブジェクトが配置された Canvas を設定します。

    1. Hierarchy でメインシーンを右クリックし、 UI メニューを開いて、 Canvas を選択します。
    2. 同じ手順を4回繰り返し、 Button オブジェクトと Text オブジェクトをそれぞれ2つずつ追加します。 見た目は自由にカスタマイズしてください。
  2. プリロードが行われるべきシーンに、空の GameObject を作成する:

    1. Hierarchyでシーンを右クリックし、 Create Emptyを選択します。 新しい GameObjectModelPreloader と名前を付けます。
  3. 新しいスクリプトコンポーネントを空に追加する:

    1. HierarchyModelPreloader を選択し、 InspectorAdd Component をクリックし、新しいスクリプトを追加します。
  4. スクリプトの先頭に、UIオブジェクト用のシリアライズされたフィールドを追加します。

    [SerializeField]
    private Button _downloadButton;

    [SerializeField]
    private Button _clearButton;

    [SerializeField]
    private Text _progressPercentText;

    [SerializeField]
    private Text _statusText;
  5. Inspector のスクリプトにGameObjectsをアタッチします。 スクリプトにアタッチするオブジェクト

  6. プリローダーの非公開フィールドを作成し、ダウンロードするモデルの機能を設定し、状態を追跡するためのブール値を追加します。

    1. Start メソッドで ModelPreloaderFactory を使用してオブジェクトをインスタンス化します。
    private IModelPreloader preloader;

    private DepthMode _depthMode = DepthMode.Medium;
    private bool _isDownloading = false;

    private void Start()
    {
    preloader = ModelPreloaderFactory.Create();

    if (null == preloader)
    {
    // プリロードはXRの初期化を待ってから行う必要がある
    // プリローダーのインスタンス化を後の関数に延期する。
    return;
    }
  7. ExistsInCache() を呼び出して、モデルが既にキャッシュに存在するかを確認し、シーンのステートを設定します。

        if (!preloader.ExistsInCache(_depthMode))
    {
    _statusText.text = $"{_depthMode} モデルがキャッシュに見つかりません";
    _progressPercentText.text = "0%";
    _clearButton.interactable = false;
    _downloadButton.interactable = true;
    }
    else
    {
    // モデルはすでにキャッシュ内に存在します。 ダウンロードは不要です。
    _statusText.text = $"{_depthMode} モデルがキャッシュに見つかりました";
    _progressPercentText.text = "100%";
    _clearButton.interactable = true;
    _downloadButton.interactable = false;
    }
  8. プリローダーでモデルをダウンロードするための Download メソッドを作成します。

    private void DownloadModel()
    {
    preloader.DownloadModel(_depthMode);
    _isDownloading = true;
    _statusText.text = "モデルをダウンロード中...";
    _progressPercentText.text = "0%";
    _clearButton.interactable = false;
    _downloadButton.interactable = false;
    }
  9. ダウンロードしたモデルを削除する ClearCache メソッドを追加します。

    private void ClearCache()
    {
    preloader.ClearFromCache(_depthMode);
    _statusText.text = $"{_depthMode} モデルがキャッシュから削除されました";
    _progressPercentText.text = "0%";
    _clearButton.interactable = false;
    _downloadButton.interactable = true;
    }
  10. onClick リスナーをボタンにアタッチし、対応するメソッドを呼び出します。

    private void OnEnable()
    {
    _downloadButton.onClick.AddListener(DownloadModel);
    _clearButton.onClick.AddListener(ClearCache);
    }

    private void OnDisable()
    {
    _downloadButton.onClick.RemoveListener(DownloadModel);
    _clearButton.onClick.RemoveListener(ClearCache);
    }
  11. ユーザーにダウンロードの進捗状況を表示したい場合は、定期的に CurrentProgress()を呼び出す。 progress1.0の場合、モデルのプリロードは完了します。 使用例:

    private void Update()
    {
    if (!_isDownloading)
    {
    return;
    }

    var statusCode = preloader.CurrentProgress(_depthMode, out float progress);
    if (Mathf.Approximately(progress, 1.0f))
    {
    // ダウンロードが正常に完了したか、またはモデルがすでにキャッシュ内に存在していた
    _statusText.text = $"{_depthMode} モデルが正常にダウンロードされました";
    _progressPercentText.text = "100%";
    _clearButton.interactable = true;
    _downloadButton.interactable = false;
    _isDownloading = false;
    return;
    }

    if (statusCode != PreloaderStatusCode.RequestInProgress)
    {
    // ダウンロードは進行中ではない
    _statusText.text = $"現在のステータス: {statusCode}";
    _isDownloading = false;
    return;
    }

    _progressPercentText.text = $"{progress * 100.0f}%";
    }
  12. Unityエディターでシーンをテストしたものの、うまくいかない場合は、上記の手順を再度確認してください。

モデルプリロードのフルサンプルスクリプト

クリックしてサンプルスクリプトを開く
using Niantic.Lightship.AR.Utilities.Preloading;
using UnityEngine;
using UnityEngine.UI;

public class ModelPreloader : MonoBehaviour
{
[SerializeField]
private Button _downloadButton;

[SerializeField]
private Button _clearButton;

[SerializeField]
private Text _progressPercentText;

[SerializeField]
private Text _statusText;

private IModelPreloader preloader;

private DepthMode _depthMode = DepthMode.Medium;
private bool _isDownloading = false;

private void Start()
{
preloader = ModelPreloaderFactory.Create();
if (null == preloader)
{
// プリロードはXRの初期化を待ってから行う必要がある
// プリローダーのインスタンス化を後の関数に延期する。
return;
}

if (!preloader.ExistsInCache(_depthMode))
{
_statusText.text = $"{_depthMode} model not found in cache";
_progressPercentText.text = "0%";
_clearButton.interactable = false;
_downloadButton.interactable = true;
}
else
{
// モデルはすでにキャッシュ内に存在します。 ダウンロードは不要です。
_statusText.text = $"{_depthMode} モデルがキャッシュに見つかりました";
_progressPercentText.text = "100%";
_clearButton.interactable = true;
_downloadButton.interactable = false;
}
}

private void Update()
{
if (!_isDownloading)
{
return;
}

var statusCode = preloader.CurrentProgress(_depthMode, out float progress);
if (Mathf.Approximately(progress, 1.0f))
{
// ダウンロードが正常に完了したか、またはモデルがすでにキャッシュ内に存在していた
_statusText.text = $"{_depthMode} model downloaded successfully";
_progressPercentText.text = "100%";
_clearButton.interactable = true;
_downloadButton.interactable = false;
_isDownloading = false;
return;
}

if (statusCode != PreloaderStatusCode.RequestInProgress)
{
// ダウンロードは進行中ではない
_statusText.text = $"現在のステータス: {statusCode}";
_isDownloading = false;
return;
}

_progressPercentText.text = $"{progress * 100.0f}%";
}

private void OnEnable()
{
_downloadButton.onClick.AddListener(DownloadModel);
_clearButton.onClick.AddListener(ClearCache);
}

private void OnDisable()
{
_downloadButton.onClick.RemoveListener(DownloadModel);
_clearButton.onClick.RemoveListener(ClearCache);
}

private void DownloadModel()
{
preloader.DownloadModel(_depthMode);
_isDownloading = true;
_statusText.text = "モデルをダウンロード中...";
_progressPercentText.text = "0%";
_clearButton.interactable = false;
_downloadButton.interactable = false;
}

private void ClearCache()
{
preloader.ClearFromCache(_depthMode);
_statusText.text = $"{_depthMode} モデルがキャッシュから削除されました";
_progressPercentText.text = "0%";
_clearButton.interactable = false;
_downloadButton.interactable = true;
}
}

ローカルモデルファイルを登録する

ARセッションを開始するときに、高価なダウンロードプロセスを実行するのを避けるために、モデルファイルを事前にダウンロードしておくとよいでしょう。 これをサポートするために、ModelPreloaderは、任意のフィーチャーモードに対してローカルモデルファイルを登録することができます。

ローカルモデルファイルをARDKに登録する:

  1. モデルファイルが、アプリケーションがアクセスできる場所にあることを確認してください。
  2. プリロードが行われるべきシーンに、空の GameObject を作成する:
    1. Hierarchyでシーンを右クリックし、 Create Emptyを選択します。 新しい GameObjectModelRegister という名前を付けます。
  3. 新しいスクリプトコンポーネントを空に追加する:
    1. 空の GameObjectHierarchyで選択し、 Inspectorで、 Add Componentをクリックし、新しいスクリプトを追加します。
  4. アプリケーションのパフォーマンスと品質のニーズに基づいて、プリロードするモデルを決定します。
  5. プリロードのタイミングを決めます。 プリロードは、AR Foundation が Lightship ローダーを初期化した後( XRGeneralSettings.Instance.Manager.isInitializationComplete が true の場合)、アウェアネス機能が開始する前であればいつでも実行できます。 例えば、ARスクリプトが MonoBehaviourの場合、プリロードは Start()の間に行うことができます。
  6. ARManagerが存在する場合、 OnEnable が呼び出されると、必要なモデルのダウンロードが自動的に開始されます。
備考

機能モードにモデルを登録すると、ARセッションの残りの時間はそのモデルが上書きされます。 ARセッションが終了すると、機能モードはデフォルトにリセットされます。

  1. ModelPreloaderFactory を使用して、 ModelPreloader オブジェクトを次のようにインスタンス化します。

    private bool TryInitializePreloader()
    {
    IModelPreloader preloader = ModelPreloaderFactory.Create();
    if (null == preloader)
    {
    // プリロードはXRの初期化を待ってから行う必要がある
    return false;
    }
    }
  2. RegisterModel(Feature, FeatureMode, filepath) を呼び出し、ARDKにモデルを登録する。 このファイルはアプリケーションからアクセス可能でなければならず、ARセッションの間、そのままでなければならない。

var statusCode = preloader.RegisterModel(DepthMode.Medium, "/example/path/to/model_file");
if (statusCode is PreloaderStatusCode.Success or
PreloaderStatusCode.RequestInProgress or
PreloaderStatusCode.FileExistsInCache)
{
// モデルがキャッシュに登録されたか、モデルが既にキャッシュに登録されていた。
// これは、今後LightshipのEnvironmentDepthMode.Mediumに使用されるモデルになります。
}

詳細情報

クリックして、複数のモデルダウンロードと進行状況バーを含むプリロードサンプルを表示
using System;
using System.Collections.Generic;
using System.Linq;
using Niantic.Lightship.AR.Utilities.Preloading;

using UnityEngine;
using UnityEngine.Serialization;
using UnityEngine.UI;
using UnityEngine.XR.Management;

public class PreloaderTestManager :MonoBehaviour
{
[SerializeField]
private Button _depthButton;

[SerializeField]
private Dropdown _depthDropdown;

[SerializeField]
private Button _semanticsButton;

[SerializeField]
private Dropdown _semanticsDropdown;

[SerializeField]
private Text _depthStatusText;

[SerializeField]
private Text _semanticsStatusText;

[SerializeField]
private Button _clearCacheButton;

[SerializeField]
private Text _preloadStatusText;

[SerializeField]
private Slider _percentageSlider;

[SerializeField]
private Text _percentageText;

[SerializeField]
private Text _cacheStatusText;

[SerializeField]
private string _localModelPath;

private IModelPreloader _preloader;

private DepthMode _depthMode;
private SemanticsMode _semanticsMode;

private void Start()
{
TryInitializePreloader()
InitializeDropdownNames<DepthMode>(_depthDropdown);
InitializeDropdownNames<SemanticsMode>(_semanticsDropdown);
}

private void OnDisable()
{
_preloader?.Dispose();
_preloader = null;
}

private void InitializeDropdownNames<T>(Dropdown dropdown)
{
// ドロップダウンメニューの機能モード名を初期化します。
SetFeatureModeNames<T>(dropdown);
dropdown.value = dropdown.options.Select(option => option.text).ToList().IndexOf("Medium");
}

private void SetFeatureModeNames<T>(Dropdown dropdown)
{
List<string> modeNames = new();
foreach (var i in Enum.GetValues(typeof(T)))
{
var modeName = Enum.GetName(typeof(T), i);
if (modeName != "Unspecified" && modeName != "Custom")
modeNames.Add(modeName);
}
dropdown.AddOptions(modeNames);
}

private void Update()
{
if (_preloader == null && !TryInitializePreloader())
return;

UpdateDownloadProgress();
UpdateCacheStatusText();
}

private bool TryInitializePreloader()
{
if (!XRGeneralSettings.Instance.Manager.isInitializationComplete)
{
// プリロードはXRの初期化を待ってから行う必要がある
return false;
}

_depthMode = DepthMode.Medium;
_semanticsMode = SemanticsMode.Medium;

_preloader = ModelPreloaderFactory.Create();

if (null == _preloader)
{
// プリロードはXRの初期化を待ってから行う必要がある
return false;
}

_depthButton.onClick.AddListener
(
() =>
{
_depthMode = ParseFeatureMode<DepthMode>(_depthDropdown);
PreloaderStatusCode result;

if (_localModelPath.Length > 0)
{
result = _preloader.RegisterModel(_depthMode, _localModelPath);
_preloadStatusText.text = "深度ファイルの登録: " + (IsError(result) ? "失敗" : "成功");
}
else
{
result = _preloader.DownloadModel(_depthMode);
_preloadStatusText.text = IsError(result) ? "深度ダウンロード開始に失敗" : "深度ダウンロード開始";
}
}
);

_semanticsButton.onClick.AddListener
(
() =>
{
_semanticsMode = ParseFeatureMode<SemanticsMode>(_semanticsDropdown);
PreloaderStatusCode result;

if (_localModelPath.Length > 0)
{
result = _preloader.RegisterModel(_semanticsMode, _localModelPath);
_preloadStatusText.text = "セマンティックファイルの登録: " + (IsError(result) ? "失敗" : "成功");
}
else
{
result = _preloader.DownloadModel(_semanticsMode);
_preloadStatusText.text = IsError(result) ? "セマンティック セグメンテーションのダウンロード開始に失敗" : "セマンティック セグメンテーションのダウンロード開始";
}
}
);

_clearCacheButton.onClick.AddListener
(
() =>
{
int successes = 0;
if (_preloader.ClearFromCache(_depthMode))
successes++;

if (_preloader.ClearFromCache(_semanticsMode))
successes++;

_preloadStatusText.text = "キャッシュクリア: " + successes + " 回成功";
}
);

_depthDropdown.onValueChanged.AddListener
(
(int val) =>
{
var mode = val + (int) DepthMode.Fast; // 「未指定」モードと「カスタム」モードをスキップ
_depthMode = (DepthMode) mode;
}
);

_semanticsDropdown.onValueChanged.AddListener
(
(int val) =>
{
var mode = val + (int) SemanticsMode.Fast; // 「未指定」モードと「カスタム」モードをスキップ
_semanticsMode = (SemanticsMode) mode;
}
);

return true;
}

private static T ParseFeatureMode<T>(Dropdown dropdown)
{
var modeName = dropdown.options[dropdown.value].text;

T mode = (T) Enum.Parse(typeof(T), modeName);
return mode;
}

private void UpdateDownloadProgress()
{
// ドロップダウンメニューで選択された機能モードの進行状況を表示
var depthStatus = _preloader.CurrentProgress(_depthMode, out var selectedDepthProgress);
if (IsError(depthStatus))
{
if (depthStatus == PreloaderStatusCode.RequestNotFound)
{
_depthStatusText.text = "0%";
}
else
{
_depthStatusText.text = "失敗: " + depthStatus;
_preloadStatusText.text = "ダウンロード失敗";
}
}
else
{
_depthStatusText.text = (selectedDepthProgress * 100).ToString("0") + "%";
}

var semanticsStatus = _preloader.CurrentProgress(_semanticsMode, out var selectedSemanticsProgress);
if (IsError(semanticsStatus))
{
if (semanticsStatus == PreloaderStatusCode.RequestNotFound)
{
_semanticsStatusText.text = "0%";
}
else
{
_semanticsStatusText.text = "失敗: " + semanticsStatus;
_preloadStatusText.text = "ダウンロード失敗";
}
}
else
{
_semanticsStatusText.text = (selectedSemanticsProgress * 100).ToString("0") + "%";
}

// ダウンロード進行状況の概要を表示
float combinedProgress = 0, activeDownloads = 0;
if (selectedDepthProgress > 0 && selectedDepthProgress < 1)
{
combinedProgress += selectedDepthProgress;
activeDownloads++;
}

if (selectedSemanticsProgress > 0 && selectedSemanticsProgress < 1)
{
combinedProgress += selectedSemanticsProgress;
activeDownloads++;
}

float totalProgress = activeDownloads > 0 ? combinedProgress / activeDownloads : 0;
_percentageText.text = (totalProgress * 100).ToString("0") + "%";
_percentageSlider.value = totalProgress;
}

private void UpdateCacheStatusText()
{
// キャッシュのステータス
List<string> modeNames = new();
DepthMode depthMode = new DepthMode();
SemanticsMode semanticsMode = new SemanticsMode();
string cacheStatusText = "キャッシュ内にプリロードされたモデルファイル: " + System.Environment.NewLine;

// すべての深度モードのキャッシュステータスを更新
foreach (DepthMode i in Enum.GetValues(typeof(DepthMode)))
{
if (i == DepthMode.Unspecified || i == DepthMode.Custom)
continue;

depthMode = i;
var present = _preloader.ExistsInCache(depthMode);

if (present)
modeNames.Add(Enum.GetName(typeof(DepthMode), i) + " Depth");
}

// すべてのセマンティックモードのキャッシュステータスを更新
foreach (SemanticsMode i in Enum.GetValues(typeof(SemanticsMode)))
{
if (i == SemanticsMode.Unspecified || i == SemanticsMode.Custom)
continue;

semanticsMode = i;
var present = _preloader.ExistsInCache(semanticsMode);

if (present)
modeNames.Add(Enum.GetName(typeof(SemanticsMode), i) + " Semantics");
}


// キャッシュステータスの概要を表示
for (int i = 0; i < modeNames.Count; i++)
{
cacheStatusText += modeNames[i] + (i < modeNames.Count - 1 ? ", " : "");
}

_cacheStatusText.text = cacheStatusText;
}

private static bool IsError(PreloaderStatusCode statusCode)
{
if (statusCode is PreloaderStatusCode.Success or
PreloaderStatusCode.RequestInProgress or
PreloaderStatusCode.FileExistsInCache)
return false;

return true;
}
}