Table of Contents

Erstellen einer bild eingabe erweiterung

Bevor Sie beginnen

Erstellen einer klasse für externe bilddatenquellen

Erbt von ExternalImageStreamFrameSource um eine Bildeingabe-Erweiterung 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 Bildeingabe-Erweiterung, die ein auf einem Smartphone mit ARCore aufgenommenes Video als Eingabe verwendet. Das Video wurde mit ARCore auf einem Pixel2 über die Kamera-Callback-Methode erfasst (nicht über Bildschirmaufnahme).

Geräte-definition

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

Überschreiben Sie IsHMD, um zu definieren, ob das Gerät ein Head-Mounted-Display ist.

Zum Beispiel: Bei Verwendung von Video als Eingabe auf false setzen.

protected override bool IsHMD => false;

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

Zum Beispiel: Wenn es nur auf Mobiltelefonen läuft, kann Display.DefaultSystemDisplay verwendet werden, dessen Rotationswert sich automatisch basierend auf dem aktuellen Anzeigezustand des Betriebssystems ändert.

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

Verfügbarkeit

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

Beispielsweise bei Verwendung von Video als Eingabe immer verfügbar:

protected override Optional<bool> IsAvailable => true;

Falls IsAvailable während des Sitzungszusammenbaus nicht bestimmt werden kann, können Sie die CheckAvailability()-Coroutine überschreiben, um den Zusammenbauprozess zu blockieren, bis die Verfügbarkeit feststeht.

Virtuelle kamera

Überschreiben Sie Camera, um eine virtuelle Kamera bereitzustellen.

Beispielsweise kann manchmal Camera.main als virtuelle Kamera für die Session verwendet werden:

protected override Camera Camera => Camera.main;

Physikalische kamera

Verwenden Sie den Typ FrameSourceCamera, um DeviceCameras zu überschreiben und Informationen zur physischen Gerätekamera bereitzustellen. Diese Daten werden bei der Eingabe von Kamerabilddaten verwendet. Muss abgeschlossen sein, wenn CameraFrameStarted true ist.

Beispielsweise mit dem im Beispiel-Workflow_FrameSource_ExternalImageStream verwendeten Video:

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

Die Eingabeparameter müssen entsprechend dem tatsächlich verwendeten Video konfiguriert werden. Die Parameter im obigen Code gelten nur für das Beispielvideo.

Überschreiben Sie CameraFrameStarted, um den Startindikator für die Kamerabildeingabe bereitzustellen.

Beispiel:

protected override bool CameraFrameStarted => started;

Session start und stop

Überschreiben Sie OnSessionStart(ARSession) und führen Sie dann AR-spezifische Initialisierungen durch. Stellen Sie sicher, dass base.OnSessionStart zuerst aufgerufen wird.

Beispiel:

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

Dies ist der geeignete Ort, um Gerätekameras zu öffnen, insbesondere wenn sie nicht dafür ausgelegt sind, ständig aktiv zu sein. Hier können auch Kalibrierungsdaten abgerufen werden, die sich während der gesamten Lebensdauer nicht ändern. Manchmal muss möglicherweise gewartet werden, bis das Gerät bereit ist oder die Daten aktualisiert wurden, bevor diese verfügbar sind.

Dies ist auch ein geeigneter Ort, um eine Dateneingabeschleife zu starten. Sie können diese Schleife auch in Update() oder einer anderen Methode schreiben, insbesondere wenn die Daten zu einem bestimmten Zeitpunkt in der Unity-Ausführungsreihenfolge abgerufen werden müssen. Geben Sie keine Daten ein, bevor die Session bereit (ready) ist.

Bei Bedarf können Sie den Startprozess auch ignorieren und bei jedem Update eine Datenprüfung durchführen. Dies hängt vollständig von den spezifischen Anforderungen ab.

Beispiel: Bei Verwendung von Video als Eingabe können Sie hier das Video abspielen und die Dateneingabeschleife starten:

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

Überschreiben Sie OnSessionStop() und geben Sie Ressourcen frei. Stellen Sie sicher, dass base.OnSessionStop aufgerufen wird.

Beispiel: Bei Verwendung von Video als Eingabe können Sie hier die Videowiedergabe stoppen und zugehörige Ressourcen freigeben:

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;
}

Erhalten von kamerarahmendaten von geräten oder dateien

Bilder können von beliebigen Quellen bezogen werden: Systemkamera, USB-Kamera, Videodatei, Netzwerk usw. Solange die Daten in das für Image erforderliche Format konvertiert werden können. Die Methoden zum Abrufen der Daten von diesen Geräten oder Dateien variieren und erfordern die Konsultation der entsprechenden Dokumentation für das Gerät oder die Datei.

Beispielsweise kann bei der Verwendung von Video als Eingabe Texture2D.ReadPixels(Rect, int, int, bool) verwendet werden, um Kamerarahmendaten aus dem RenderTexture des Videoplayers zu erhalten. Anschließend werden die Daten von Texture2D.GetRawTextureData() in einen Buffer kopiert:

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 im obigen Code müssen die vom Zeiger der Texture2D kopierten Daten vertikal gespiegelt werden, damit die speicherinterne Anordnung der Daten ein normales Bild darstellt.

Parallel zum Erhalt des Bildes müssen auch die Kalibrierungsdaten der Kamera oder einer äquivalenten Kamera erfasst und eine Instanz von CameraParameters erstellt werden.

Wenn die ursprüngliche Quelle der Daten aus dem Kamerarückruf eines Mobiltelefons stammt und die Daten nicht manuell beschnitten wurden, können die Kalibrierungsdaten der Handykamera direkt verwendet werden. Bei der Verwendung von Schnittstellen wie ARCore oder ARKit zum Abrufen von Kamerarückrufdaten, können die entsprechenden Dokumentationen zur Ermittlung der intrinsischen Kameraparameter konsultiert werden. Falls die benötigte AR-Funktion Bild- oder Objektverfolgung ist, kann in diesem Fall auch CameraParameters.createWithDefaultIntrinsics(Vec2I, CameraDeviceType, int) zur Erstellung der intrinsischen Parameter verwendet werden. Dies kann die Algorithmusleistung leicht beeinträchtigen, ist jedoch normalerweise nicht signifikant.

Wenn die Daten aus anderen Quellen wie USB-Kameras oder nicht aus Kamerarückrufen generierten Videodateien stammen, muss die Kamera oder der Videoframe kalibriert werden, um korrekte intrinsische Parameter zu erhalten.

Vorsicht

Kamerarückrufdaten dürfen nicht beschnitten werden; nach dem Beschneiden müssen die intrinsischen Parameter neu berechnet werden. Wenn die Daten aus Bilddaten stammen, die durch Bildschirmaufnahmen oder ähnliche Methoden erfasst wurden, können die Kalibrierungsdaten der Handykamera normalerweise nicht verwendet werden. In diesem Fall muss ebenfalls die Kamera oder der Videoframe kalibriert werden, um korrekte intrinsische Parameter zu erhalten.

Falsche intrinsische Parameter führen dazu, dass AR-Funktionen nicht ordnungsgemäß funktionieren. Häufige Probleme sind die fehlende Ausrichtung von virtuellen Inhalten mit realen Objekten sowie Schwierigkeiten beim Initiieren oder Aufrechterhalten der AR-Verfolgung.

Beispielsweise werden die intrinsischen Parameter und der Erstellungsprozess von CameraParameters für das im Beispiel Workflow_FrameSource_ExternalImageStream verwendete Video wie folgt dargestellt:

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 Parameter im obigen Code gelten nur für das Beispielvideo. Diese intrinsischen Kameraparameter wurden gleichzeitig mit dem Video erfasst. Wenn Daten von anderen Videos oder Geräten verwendet werden sollen, müssen unbedingt gleichzeitig die Geräteparameter erfasst oder manuell kalibriert werden.

Kamerarahmendaten eingeben

Nachdem aktualisierte Kamerarahmendaten erfasst wurden, rufen Sie HandleCameraFrameData(double, Image, CameraParameters) auf, um die Kamerarahmendaten einzugeben.

Beispielimplementierung 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, Image, Buffer und andere zugehörige Daten nach der Verwendung durch Dispose() oder Mechanismen wie using freizugeben. Andernfalls kann es zu schwerwiegenden Speicherlecks kommen, und das Abrufen von Buffern aus dem Buffer-Pool könnte fehlschlagen.

Verwandte themen