本文へスキップ
バージョン: 3.0

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

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

前提条件

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

Neural Network Performance Modes

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

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

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

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

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

Lightship Neural Network Modelのプリロード

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

  1. プリロードが行われるべきシーンに、空の GameObject を作成する:
    1. Hierarchyでシーンを右クリックし、 Create Emptyを選択します。
  2. 新しいスクリプトコンポーネントを空に追加する:
    1. 空の GameObjectHierarchyで選択し、 Inspectorで、 Add Componentをクリックし、新しいスクリプトを追加します。
  3. アプリケーションのパフォーマンスと品質のニーズに基づいて、プリロードするモデルを決定します。
  4. プリロードのタイミングを決めます。 プリロードは、AR Foundation が Lightship ローダーを初期化した後( XRGeneralSettings.Instance.Manager.isInitializationComplete が true の場合)、アウェアネス機能が開始する前であればいつでも実行できます。 例えば、ARスクリプトが MonoBehaviourの場合、プリロードは Start()の間に行うことができます。
重要!

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

  1. ModelPreloader オブジェクトを ModelPreloaderFactoryでインスタンス化します:
private void Start()
{
IModelPreloader preloader = ModelPreloaderFactory.Create();

if (null == preloader)
{
// プリロードする前にXRが初期化されるのを待つ必要がある。
// プリローダーのインスタンス化を後の関数に延期する。
return;
}.
}
  1. カスタムニューラルネットワークファイルを使用している場合は、 ExistsInCache() を呼び出して、モデルがすでにキャッシュに存在するかどうかをチェックします。
if (preloader.ExistsInCache(DepthMode.Medium))
{
// モデルはすでにキャッシュにある。 ダウンロードは不要。
}
  1. DownloadModel(Feature, FeatureMode) を呼び出し、ダウンロードを開始する:
preloader.DownloadModel(DepthMode.Medium)
  1. ユーザーにダウンロードの進捗状況を表示したい場合は、定期的に CurrentProgress()を呼び出す。 progress1.0の場合、モデルのプリロードは完了します。 使用例:
private void Update()
{
var statusCode = preloader.CurrentProgress(DepthMode.Medium, out float progress)
if (Mathf.Approximately(progress, 1.0f))
{
// The download has completed successfully, or the model was already in the cache
}
}

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

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

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

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

機能モードにモデルを登録すると、ARセッションの残りの時間はそのモデルが上書きされます。 これを避けたい場合は、 Custom

  1. ModelPreloader オブジェクトを ModelPreloaderFactoryでインスタンス化する:
private void Start()
{
IModelPreloader preloader = ModelPreloaderFactory.Create();

if (null == preloader)
{
// プリロードする前にXRが初期化されるのを待つ必要がある。
// プリローダーのインスタンス化を後の関数に延期する。
return;
}.
}
  1. 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")
modeNames.Add(modeName);
}
dropdown.AddOptions(modeNames);
}

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

UpdateDownloadProgress();
UpdateCacheStatusText();
}

private bool TryInitializePreloader()
{
if (!XRGeneralSettings.Instance.Manager.isInitializationComplete)
{
// Need to wait for XR to initialize before we can preload
return false;
}

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

_preloader = ModelPreloaderFactory.Create();

if (null == _preloader)
{
// Need to wait for XR to initialize before we can preload
return false;
}

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

if (_depthMode == DepthMode.Custom)
{
if (_localModelPath.Length == 0)
{
_preloadStatusText.text = "Depth file registration: Please specify a local path to a model file in PreloaderTestManager";
return;
}

result = _preloader.RegisterModel(_depthMode, _localModelPath);
_preloadStatusText.text = "Depth file registration: " + (IsError(result) ? "failed" : "success");
return;
}

result = _preloader.DownloadModel(_depthMode);
_preloadStatusText.text = "Depth download started: " + (IsError(result) ? "failed" : "success");
}
);

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

if (_semanticsMode == SemanticsMode.Custom)
{
if (_localModelPath.Length == 0)
{
_preloadStatusText.text = "Semantics file registration: Please specify a local path to a model file in PreloaderTestManager";
return;
}

result = _preloader.RegisterModel(_semanticsMode, _localModelPath);
_preloadStatusText.text = "Semantics file registration: " + (IsError(result) ? "failed" : "success");
return;
}

result = _preloader.DownloadModel(_semanticsMode);
_preloadStatusText.text = "Semantics download started: " + (IsError(result) ? "failed" : "success");
}
);

_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 + 1; // We skip the 'unspecified' mode
_depthMode = (DepthMode) mode;
}
);

_semanticsDropdown.onValueChanged.AddListener
(
(int val) =>
{
var mode = val + 1; // We skip the 'unspecified' mode
_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()
{
// Display the progress of the feature modes that are selected with the dropdown menus
var depthStatus = _preloader.CurrentProgress(_depthMode, out var selectedDepthProgress);
if (IsError(depthStatus))
{
_depthStatusText.text = depthStatus == PreloaderStatusCode.RequestNotFound ?
"0%" : "失敗しました:" + depthStatus.ToString();
}
else
{
_depthStatusText.text = (selectedDepthProgress * 100).ToString("0") + "%";
}

var semanticsStatus = _preloader.CurrentProgress(_semanticsMode, out var selectedSemanticsProgress);
if (IsError(semanticsStatus))
{
_semanticsStatusText.text = semanticsStatus == PreloaderStatusCode.RequestNotFound ?
"0%" : "Failure: " + semanticsStatus.ToString();
}
else
{
_semanticsStatusText.text = (selectedSemanticsProgress * 100).ToString("0") + "%";
}

// Summarize their download progress
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()
{
// Cache status
List<string> modeNames = new();
DepthMode depthMode = new DepthMode();
SemanticsMode semanticsMode = new SemanticsMode();
string cacheStatusText = "Model files preloaded in cache: " + System.Environment.NewLine;

// Update the cache status for all depth modes
foreach (DepthMode i in Enum.GetValues(typeof(DepthMode)))
{
if (i == DepthMode.Unspecified)
continue;

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

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

// Update the cache status for all semantics modes
foreach (SemanticsMode i in Enum.GetValues(typeof(SemanticsMode)))
{
if (i == SemanticsMode.Unspecified)
continue;

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

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


// Summarize cache status
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;
}
}

詳細情報