Table of Contents

Créer une extension d'entrée d'image

Avant de commencer

Créer une classe source de données de trame externe

Héritez de ExternalImageStreamFrameSource pour créer une extension d'entrée d'image. C'est une sous-classe de MonoBehaviour, et le nom du fichier doit correspondre au nom de la classe.

Par exemple :

public class MyFrameSource : ExternalImageStreamFrameSource
{
}

L'exemple Workflow_FrameSource_ExternalImageStream est une implémentation d'extension d'entrée d'image basée sur une vidéo enregistrée avec ARCore sur un téléphone comme entrée. Cette vidéo a été capturée via les rappels de caméra d'ARCore sur un Pixel2 (pas un enregistrement d'écran).

Définition de l'appareil

Remplacez IsCameraUnderControl et renvoyez true.

Remplacez IsHMD pour définir si l'appareil est un casque de réalité virtuelle.

Par exemple, utilisez la vidéo comme entrée, définissez sur false.

protected override bool IsHMD => false;

Remplacez Display pour définir l'affichage de l'appareil.

Par exemple, si vous exécutez uniquement sur mobile, utilisez Display.DefaultSystemDisplay, dont la rotation change automatiquement selon l'état d'affichage actuel du système d'exploitation.

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

Disponibilité

Remplacez IsAvailable pour définir si l'appareil est disponible.

Par exemple, lors de l'utilisation de la vidéo comme entrée, toujours disponible :

protected override Optional<bool> IsAvailable => true;

Si IsAvailable ne peut pas être déterminé lors de l'assemblage de la session, vous pouvez remplacer la co-routine CheckAvailability() pour bloquer le processus d'assemblage jusqu'à ce que la disponibilité soit confirmée.

Caméra virtuelle

Réécrire Camera pour fournir une caméra virtuelle.

Par exemple, il est parfois possible d'utiliser Camera.main comme caméra virtuelle pour la session :

protected override Camera Camera => Camera.main;

Caméra physique

Utilisez le type FrameSourceCamera pour remplacer DeviceCameras afin de fournir des informations sur les caméras physiques de l'appareil. Ces données sont utilisées lors de la saisie des trames de la caméra. Doit être complété lorsque CameraFrameStarted est true.

Par exemple, en utilisant la vidéo de l'exemple 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;
}
Attention

Plusieurs paramètres d'entrée ici doivent être configurés en fonction de la vidéo réellement utilisée. Les paramètres dans le code ci-dessus s'appliquent uniquement à la vidéo de l'exemple.

Remplacez CameraFrameStarted pour fournir l'indicateur de début de saisie de la trame de la caméra.

Par exemple :

protected override bool CameraFrameStarted => started;

Session démarrage et arrêt

Remplacez OnSessionStart(ARSession) puis effectuez l'initialisation spécifique à la RA. Assurez-vous d'appeler d'abord base.OnSessionStart.

Par exemple :

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

C'est l'endroit approprié pour activer la caméra de l'appareil, surtout si elle n'est pas conçue pour rester toujours allumée. C'est aussi ici que vous pouvez récupérer des données d'étalonnage qui ne changeront pas pendant le cycle de vie. Parfois, il peut être nécessaire d'attendre que l'appareil soit prêt ou que les données soient mises à jour avant de pouvoir les obtenir.

C'est également un endroit approprié pour démarrer une boucle d'entrée de données. Vous pouvez aussi écrire cette boucle dans Update() ou d'autres méthodes, surtout si les données doivent être acquises à un moment précis dans l'ordre d'exécution d'Unity. N'envoyez pas de données avant que la session soit prête (ready).

Si nécessaire, vous pouvez ignorer la procédure de démarrage et vérifier les données à chaque mise à jour, cela dépend entièrement des besoins spécifiques.

Par exemple, lors de l'utilisation d'une vidéo en entrée, vous pouvez démarrer la lecture ici et lancer la boucle d'entrée de données :

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

Remplacez OnSessionStop() et libérez les ressources. Assurez-vous d'appeler base.OnSessionStop.

Par exemple, lors de l'utilisation d'une vidéo en entrée, vous pouvez arrêter la lecture ici et libérer les ressources associées :

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

Obtenir les données de trame de caméra à partir d'appareils ou de fichiers

Les images peuvent être obtenues à partir de n'importe quelle source : caméra système, caméra USB, fichier vidéo, réseau, etc. Tant que les données peuvent être converties dans le format requis par Image. Les méthodes pour obtenir des données à partir de ces appareils ou fichiers varient et nécessitent de se référer à leur documentation.

Par exemple, lors de l'utilisation d'une vidéo comme entrée, Texture2D.ReadPixels(Rect, int, int, bool) peut être utilisé pour obtenir les données de trame de caméra à partir du RenderTexture du lecteur vidéo, puis copier les données de Texture2D.GetRawTextureData() dans 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);
    }
}
Attention

Comme dans le code ci-dessus, les données copiées à partir du pointeur de Texture2D doivent être inversées verticalement pour que l'arrangement mémoire corresponde à une image normale.

Lors de l'acquisition des images, les paramètres d'étalonnage de la caméra (ou équivalent) doivent également être obtenus pour créer une instance de CameraParameters.

Si la source provient directement du rappel de caméra d'un téléphone sans recadrage artificiel, les paramètres d'étalonnage natifs peuvent être utilisés. Lors de l'utilisation d'interfaces comme ARCore ou ARKit, référez-vous à leur documentation pour obtenir les paramètres intrinsèques. Pour les fonctionnalités AR comme le suivi d'image ou d'objet, CameraParameters.createWithDefaultIntrinsics(Vec2I, CameraDeviceType, int) peut être utilisé, bien que cela puisse légèrement réduire les performances.

Pour les sources comme caméras USB ou fichiers vidéo non générés par rappel de caméra, un étalonnage manuel est nécessaire pour obtenir des paramètres intrinsèques corrects.

Attention

Les données de rappel de caméra ne doivent pas être recadrées - le recadrage nécessite un recalcul des paramètres intrinsèques. Les images obtenues via des enregistrements d'écran ne peuvent généralement pas utiliser les paramètres natifs de la caméra du téléphone et nécessitent un étalonnage.

Des paramètres intrinsèques incorrects empêchent le bon fonctionnement des fonctionnalités AR, causant des désalignements entre contenu virtuel et réel, ainsi qu'un suivi instable.

Par exemple, pour la vidéo utilisée dans l'exemple Workflow_FrameSource_ExternalImageStream, les paramètres de caméra et la création de CameraParameters se font comme suit :

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);
Attention

Les paramètres ci-dessus sont spécifiques à la vidéo d'exemple, où les paramètres intrinsèques ont été capturés simultanément avec la vidéo. Pour d'autres vidéos ou appareils, obtenez toujours les paramètres intrinsèques de l'appareil ou effectuez un étalonnage manuel.

Entrée des données de trame de caméra

Après avoir obtenu la mise à jour des données de trame de caméra, appelez HandleCameraFrameData(double, Image, CameraParameters) pour entrer les données de trame de caméra.

Par exemple, lors de l'utilisation d'une vidéo comme entrée, l'implémentation est la suivante :

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

N'oubliez pas d'exécuter Dispose() après utilisation ou de libérer Image, Buffer et autres données associées via des mécanismes tels que using. Sinon, des fuites mémoire graves se produiront et l'obtention de buffer via le pool de buffers pourrait échouer.

Sujets connexes