本文へスキップ

ニューラルネットワーク・モデルのプリロード( 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 を参照してください。

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

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

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

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

  1. UIオブジェクトでキャンバスを設定します:

    1. ヒエラルキーで、メインシーンを右クリックし、UIメニューを開いてキャンバスを選択します。
    2. この作業をあと4回繰り返して、Buttonオブジェクトを2つ、Textオブジェクトを2つ追加する。 好きなように見た目をカスタマイズできる。
  2. プリロードが行われるべきシーンに、空の GameObject を作成する:

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

    1. HierarchyModelPreloaderを選択し、InspectorAdd Componentをクリックして新しいスクリプトを追加します。
  4. スクリプトの一番上に、UI Objects用のシリアライズされたフィールドを追加する:

    [SerializeField]
    private Button _downloadButton;

    [SerializeField]
    private Button _clearButton;

    [SerializeField]
    private Text _progressPercentText;

    [SerializeField]
    private Text _statusText;
  5. GameObjectをInspectorでスクリプトにアタッチします: スクリプトに付属するオブジェクト

  6. プライベートなプリローダーフィールドを作成し、ダウンロードするモデル機能を設定し、状態を追跡するためにboolを追加します:

    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の初期化を待つ必要がある。
    // プリローダーのインスタンス化を後の関数に延期する。

    }
  7. ExistsInCache()を呼び出して、モデルがすでにキャッシュに存在するかどうかをチェックし、シーンの状態を設定する。

    ```cs
    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;

    }.

    ```
  8. プリローダーがモデルをダウンロードするためのDownloadメソッドを作成します:

    ```cs
    private void DownloadModel()
    {
    preloader.DownloadModel(_depthMode);
    _isDownloading = true;
    _statusText.text = "モデルをダウンロードしています...";
    _progressPercentText.text = "0%";
    _clearButton.interactable = false;
    _downloadButton.interactable = false;

    }.

    ```
  9. ダウンロードしたモデルを削除するClearCacheメソッドを追加します:

    ```cs
    private void ClearCache()
    {
    preloader.ClearFromCache(_depthMode);
    _statusText.text = $"{_depthMode} model cleared from cache";
    _progressPercentText.text = "0%";
    _clearButton.interactable = false;
    _downloadButton.interactable = true;

    }.

    ```
  10. ボタンにonClickリスナーをアタッチし、対応するメソッドを呼び出します:

    ```cs
    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の場合、モデルのプリロードは完了します。 使用例:

    ```cs
    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 = $"Current status: {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} モデルのダウンロードに成功しました";
_progressPercentText.text = "100%";
_clearButton.interactable = true;
_downloadButton.interactable = false;
_isDownloading = false;
return;
}

if (statusCode != PreloaderStatusCode.RequestInProgress)
{
// ダウンロードは進行中ではありません
_statusText.text = $"Current status: {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 = "Downloading model...";
_progressPercentText.text = "0%";
_clearButton.interactable = false;
_downloadButton.interactable = false;
}

private void ClearCache()
{
preloader.ClearFromCache(_depthMode);
_statusText.text = $"{_depthMode} model cleared from cache";
_progressPercentText.text = "0%";
_clearButton.interactable = false;
_downloadButton.interactable = true;
}.
}

ローカルモデルファイルの登録

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

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

  1. モデルファイルが、アプリケーションがアクセスできる場所にあることを確認してください。
  2. プリロードが行われるべきシーンに、空の GameObject を作成する:
    1. Hierarchyでシーンを右クリックし、 Create Emptyを選択します。 新しい GameObject ModelRegister.
  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. ModelPreloaderFactoryModelPreloaderオブジェクトをインスタンス化します:

    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 = "Depth file registration:" + (IsError(result) ? "failed" : "success");
}
else
{
result = _preloader.DownloadModel(_depthMode);
_preloadStatusText.text = IsError(result) ? "Depth download failed to start" : "Depth download starting";
}.
}
);

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

if (_localModelPath.Length > 0)
{
result = _preloader.RegisterModel(_semanticsMode, _localModelPath);
_preloadStatusText.text = "セマンティクスファイル登録:" + (IsError(result) ? "failed" : "success");
}
else
{
result = _preloader.DownloadModel(_semanticsMode);
_preloadStatusText.text = IsError(result) ? "Semantic segmentation download failed to start" : "Semantic segmentation download starting";
}.
}
);

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

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

_preloadStatusText.text = "Clear cache:" + successes + " successes";
}.
);

_depthDropdown.onValueChanged.AddListener
(
(int val) =>
{
var mode = val + (int) DepthMode.Fast; // 'unspecified'と'custom'モードをスキップする
_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 = (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()
{
// キャッシュステータス
リスト<string> modeNames = new();
DepthMode depthMode = new DepthMode();
SemanticsMode semanticsMode = new SemanticsMode();
string cacheStatusText = "Model files preloaded in cache:" + 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;
}
}