Table of Contents

Bild-Eingabeerweiterung in Unity implementieren

Vor Beginn

Erstellen einer externen Frame-Datenquellenklasse

Erben Sie von ExternalImageStreamFrameSource, um eine Bild-Eingabeerweiterung zu erstellen. Es ist eine Unterklasse von MonoBehaviour, und der Dateiname sollte mit dem Klassennamen übereinstimmen.

Beispiel:

public class MyFrameSource : ExternalImageStreamFrameSource
{
}

Das Beispiel Workflow_FrameSource_ExternalImageStream ist eine Implementierung einer Bild-Eingabeerweiterung, die ein mit ARCore auf einem Mobiltelefon aufgenommenes Video als Eingabe verwendet. Dieses Video wurde mit ARCore auf einem Pixel 2 über einen Kamera-Callback aufgenommen (nicht per Bildschirmaufnahme).

Gerätedefinition

Überschreiben Sie IsCameraUnderControl und geben Sie true zurück.

Überschreiben Sie IsHMD, um festzulegen, ob das Gerät ein Headset ist.

Zum Beispiel auf false setzen, wenn ein Video als Eingabe verwendet wird:

protected override bool IsHMD => false;

Überschreiben Sie Display, um das Display des Geräts zu definieren.

Zum Beispiel kann bei Ausführung nur auf Mobiltelefonen Display.DefaultSystemDisplay verwendet werden, dessen Rotationswert sich automatisch an den aktuellen Anzeigezustand des Betriebssystems anpasst.

protected override IDisplay Display => easyar.Display.DefaultSystemDisplay;

Verfügbarkeit

Überschreiben Sie IsAvailable, um festzulegen, ob das Gerät verfügbar ist.

Zum Beispiel ist es bei Verwendung eines Videos als Eingabe immer verfügbar:

protected override Optional<bool> IsAvailable => true;

Wenn IsAvailable während der Sitzungszusammenstellung nicht bestimmt werden kann, überschreiben Sie die Coroutine CheckAvailability(), um den Zusammenstellungsprozess zu blockieren, bis die Verfügbarkeit bestätigt ist.

Virtuelle Kamera

Überschreiben Sie Camera, um eine virtuelle Kamera bereitzustellen.

Beispiel:

protected override Camera Camera => Camera.main;

Physische Kamera

Überschreiben Sie DeviceCameras mit dem Typ FrameSourceCamera, um Informationen über die physische Kamera des Geräts bereitzustellen. Diese Daten werden verwendet, wenn Kamera-Frame-Daten eingegeben werden. Dies muss abgeschlossen sein, wenn CameraFrameStarted true ist.

Beispiel mit dem Video aus dem Sample Workflow_FrameSource_ExternalImageStream:

private FrameSourceCamera deviceCamera;
protected override List<FrameSourceCamera> DeviceCameras => new List<FrameSourceCamera> { deviceCamera };

{
    var size = new Vector2Int(640, 360);
    var cameraType = CameraDeviceType.Back;
    var cameraOrientation = 90;
    deviceCamera = new FrameSourceCamera(cameraType, cameraOrientation, size, new Vector2(30, 30));
    started = true;
}
Vorsicht

Einige Eingabeparameter hier müssen entsprechend dem tatsächlich verwendeten Video gesetzt werden. Die obigen Werte gelten nur für das Beispielvideo.

Überschreiben Sie CameraFrameStarted, um den Start der Kamera-Frame-Eingabe anzugeben.

Beispiel:

protected override bool CameraFrameStarted => started;

Sitzungsstart und -stopp

Überschreiben Sie OnSessionStart(ARSession) und führen Sie AR-spezifische Initialisierungsarbeiten aus. Rufen Sie zuerst base.OnSessionStart auf.

Beispiel:

protected override void OnSessionStart(ARSession session)
{
    base.OnSessionStart(session);
    ...
}

Dies ist der geeignete Ort, um die Gerätekamera zu öffnen, besonders wenn diese Kameras nicht dauerhaft aktiv bleiben sollen. Er eignet sich auch zum Abrufen von Kalibrierdaten, die sich während des gesamten Lebenszyklus nicht ändern. Manchmal ist es notwendig, auf die Bereitschaft des Geräts oder auf Datenaktualisierungen zu warten, bevor diese Daten abgerufen werden können.

Außerdem ist dies ein geeigneter Ort, um die Daten-Eingabeschleife zu starten. Sie können diese Schleife auch in Update() oder anderen Methoden schreiben, insbesondere wenn Daten zu bestimmten Punkten der Unity-Ausführungsreihenfolge erfasst werden müssen. Geben Sie keine Daten ein, bevor die Sitzung bereit ist.

Wenn nötig, können Sie den Startvorgang überspringen und Datenprüfungen in jedem Update durchführen - das hängt vollständig von den konkreten Anforderungen ab.

Beispiel:

protected override void OnSessionStart(ARSession session)
{
    base.OnSessionStart(session);
    ...
    player.Play();
    StartCoroutine(VideoDataToInputFrames());
}

Überschreiben Sie OnSessionStop() und geben Sie Ressourcen frei. Rufen Sie zuerst base.OnSessionStop auf.

Beispiel:

protected override void OnSessionStop()
{
    base.OnSessionStop();

    StopAllCoroutines();
    player.Stop();
    if (renderTexture) { Destroy(renderTexture); }
    cameraParameters?.Dispose();
    cameraParameters = null;
    frameIndex = -1;
    started = false;
    deviceCamera?.Dispose();
    deviceCamera = null;
}

Kamera-Frame-Daten aus Gerät oder Datei abrufen

Bilder können aus jeder Quelle stammen - Systemkameras, USB-Kameras, Videodateien, Netzwerke usw. - solange die Daten in das von Image benötigte Format konvertiert werden können. Die Methoden zum Abrufen der Daten aus diesen Geräten oder Dateien unterscheiden sich; konsultieren Sie die jeweilige Dokumentation.

Beispiel: Wenn ein Video als Eingabe verwendet wird, verwenden Sie Texture2D.ReadPixels(Rect, int, int, bool), um Frame-Daten aus der RenderTexture eines Videoplayers zu erfassen. Kopieren Sie dann die Daten aus Texture2D.GetRawTextureData() nach Buffer:

void VideoDataToInputFrames()
{
    ...
    RenderTexture.active = renderTexture;
    var pixelSize = new Vector2Int((int)player.width, (int)player.height);
    var texture = new Texture2D(pixelSize.x, pixelSize.y, TextureFormat.RGB24, false);
    texture.ReadPixels(new Rect(0, 0, pixelSize.x, pixelSize.y), 0, 0);
    texture.Apply();
    RenderTexture.active = null;
    ...
    CopyRawTextureData(buffer, texture.GetRawTextureData<byte>(), pixelSize);
} 

static unsafe void CopyRawTextureData(Buffer buffer, Unity.Collections.NativeArray<byte> data, Vector2Int size)
{
    int oneLineLength = size.x * 3;
    int totalLength = oneLineLength * size.y;
    var ptr = new IntPtr(data.GetUnsafeReadOnlyPtr());
    for (int i = 0; i < size.y; i++)
    {
        buffer.tryCopyFrom(ptr, oneLineLength * i, totalLength - oneLineLength * (i + 1), oneLineLength);
    }
}
Vorsicht

Wie oben gezeigt, müssen Daten, die aus dem Zeiger von Texture2D kopiert werden, vertikal gespiegelt werden, um die normale Bildspeicheranordnung wiederherzustellen.

Beim Abrufen von Bildern müssen Sie auch die Kalibrierungsdaten der Kamera (oder einer entsprechenden Quelle) erfassen und eine Instanz von CameraParameters erstellen.

Wenn die Quelle ein mobiler Kamera-Callback mit unveränderten Daten ist, verwenden Sie die nativen Kalibrierungsdaten des Geräts direkt. Bei Verwendung von ARCore/ARKit-APIs konsultieren Sie deren Dokumentation für die Kameraintrinsics. Für Bild-/Objektverfolgung kann CameraParameters.createWithDefaultIntrinsics(Vec2I, CameraDeviceType, int) Intrinsics erzeugen, was jedoch die Wirksamkeit des Algorithmus leicht verringern kann.

Für USB-Kameras, Videodateien oder andere nicht-native Quellen müssen Kamera-/Videoframes kalibriert werden, um korrekte Intrinsics zu erhalten.

Vorsicht

Beschnittene Kamera-Callback-Daten erfordern eine Neuberechnung der Intrinsics. Bildschirmausnahmen können in der Regel keine nativen Kalibrierungsdaten verwenden - kalibrieren Sie explizit.

Falsche Intrinsics beeinträchtigen die AR-Funktion, verursachen Fehlanpassungen von virtuellen Inhalten, instabiles Tracking oder häufige Fehler.

Beispiel für das Video im Sample Workflow_FrameSource_ExternalImageStream:

var size = new Vector2Int(640, 360);
var cameraType = CameraDeviceType.Back;
var cameraOrientation = 90;
cameraParameters = new CameraParameters(size.ToEasyARVector(), new Vec2F(506.085f, 505.3105f), new Vec2F(318.1032f, 177.6514f), cameraType, cameraOrientation);
Vorsicht

Die obigen Parameter gelten nur für das Beispielvideo, da die Intrinsics zusammen mit diesem Video erfasst wurden. Für andere Videos/Geräte müssen die Geräte-Intrinsics immer erfasst oder manuell kalibriert werden.

Kamera-Frame-Daten eingeben

Nach Erhalt von Aktualisierungen der Kamera-Frame-Daten rufen Sie HandleCameraFrameData(double, Image, CameraParameters) auf, um die Kamera-Frame-Daten einzugeben.

Beispiel bei Verwendung eines Videos als Eingabe:

IEnumerator VideoDataToInputFrames()
{
    yield return new WaitUntil(() => player.isPrepared);
    var pixelSize = new Vector2Int((int)player.width, (int)player.height);
    ...
    yield return new WaitUntil(() => player.isPlaying && player.frame >= 0);
    while (true)
    {
        yield return null;
        if (frameIndex == player.frame) { continue; }
        frameIndex = player.frame;
        ...
        var pixelFormat = PixelFormat.RGB888;
        var bufferO = TryAcquireBuffer(pixelSize.x * pixelSize.y * 3);
        if (bufferO.OnNone) { continue; }

        var buffer = bufferO.Value;
        CopyRawTextureData(buffer, texture.GetRawTextureData<byte>(), pixelSize);

        using (buffer)
        using (var image = Image.create(buffer, pixelFormat, pixelSize.x, pixelSize.y, pixelSize.x, pixelSize.y))
        {
            HandleCameraFrameData(player.time, image, cameraParameters);
        }
    }
}
Vorsicht

Vergessen Sie nicht, nach der Verwendung Dispose() auszuführen oder Ressourcen über Mechanismen wie using für Image, Buffer und andere zugehörige Daten freizugeben. Andernfalls kann es zu schweren Speicherlecks kommen und das Abrufen aus dem Buffer-Pool kann fehlschlagen.

Verwandte Themen