Table of Contents

Créer une extension d’entrée de flux d’images externe

Avant de commencer

Créer une classe de source de trame externe

Héritez de ExternalImageStreamFrameSource pour créer une extension d’entrée d’image. Elle 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. Cette vidéo a été capturée via rappel caméra avec ARCore sur un Pixel2, et non par enregistrement d’écran.

Définition de l’appareil

Redéfinissez IsCameraUnderControl et retournez true.

Redéfinissez IsHMD pour définir si l’appareil est un casque.

Par exemple, lorsque vous utilisez une vidéo comme entrée, définissez-la à false.

protected override bool IsHMD => false;

Redéfinissez Display pour définir l’affichage de l’appareil.

Par exemple, si vous ne l’exécutez que sur mobile, vous pouvez utiliser 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é

Redéfinissez IsAvailable pour définir si l’appareil est disponible.

Par exemple, lorsque vous utilisez une vidéo comme entrée, elle est 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 redéfinir la coroutine CheckAvailability() pour bloquer le processus d’assemblage jusqu’à ce que la disponibilité soit confirmée.

Caméra virtuelle

Redéfinissez Camera pour fournir une caméra virtuelle.

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

protected override Camera Camera => Camera.main;

Caméra physique

Utilisez le type FrameSourceCamera pour redéfinir DeviceCameras afin de fournir les informations de caméra physique de l’appareil. Ces données sont utilisées lors de l’entrée des trames caméra. La création doit être terminée lorsque CameraFrameStarted vaut true.

Par exemple, pour la vidéo utilisée dans 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

Les quelques paramètres d’entrée ici doivent être réglés en fonction de la vidéo réellement utilisée. Les paramètres du code ci-dessus ne conviennent qu’à la vidéo de l’exemple.

Redéfinissez CameraFrameStarted pour fournir l’indicateur de début d’entrée des trames caméra.

Par exemple :

protected override bool CameraFrameStarted => started;

Démarrage et arrêt de la session

Redéfinissez OnSessionStart(ARSession) puis effectuez l’initialisation spécifique à l’AR. Veillez à appeler base.OnSessionStart en premier.

Par exemple :

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

C’est l’endroit approprié pour ouvrir la caméra de l’appareil, surtout si elle n’est pas conçue pour rester ouverte en permanence. C’est aussi l’endroit adapté pour récupérer les données d’étalonnage qui ne changent pas pendant tout le cycle de vie. Il peut parfois être nécessaire d’attendre que l’appareil soit prêt ou que les données soient mises à jour avant de pouvoir les récupérer.

C’est également un bon endroit pour démarrer la boucle d’entrée des données. Vous pouvez aussi écrire cette boucle dans Update() ou dans une autre méthode, en particulier lorsque les données doivent être récupérées à un moment précis de l’ordre d’exécution Unity. N’entrez pas de données avant que la session soit prête.

Si nécessaire, vous pouvez aussi ignorer le processus de démarrage et vérifier les données à chaque mise à jour, selon vos besoins.

Par exemple, lorsque vous utilisez une vidéo comme entrée, vous pouvez commencer la lecture de la vidéo et la boucle d’entrée des données ici :

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

Redéfinissez OnSessionStop() et libérez les ressources, en veillant à appeler base.OnSessionStop.

Par exemple, lorsque vous utilisez une vidéo comme entrée, vous pouvez arrêter la lecture de la vidéo et libérer les ressources associées ici :

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 des données de trame caméra depuis un appareil ou un fichier

Vous pouvez obtenir des images à partir d’une caméra système, d’une caméra USB, d’un fichier vidéo, du réseau ou de toute autre source. Tant que les données peuvent être converties dans le format requis par Image, cela fonctionne. Les méthodes d’obtention de données depuis ces appareils ou fichiers diffèrent, et il faut se référer à leur documentation d’utilisation.

Par exemple, lorsque vous utilisez une vidéo comme entrée, vous pouvez utiliser Texture2D.ReadPixels(Rect, int, int, bool) pour obtenir les données de trame caméra depuis le 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 depuis le pointeur de Texture2D doivent être inversées verticalement pour que la disposition mémoire corresponde à une image normale.

En obtenant l’image, vous devez aussi récupérer les données d’étalonnage de la caméra ou de la caméra équivalente et créer une instance de CameraParameters.

Si la source originale des données provient d’un rappel caméra de téléphone et que les données n’ont pas été recadrées manuellement, vous pouvez utiliser directement les données d’étalonnage de la caméra du téléphone. Lors de l’obtention de données de rappel caméra via ARCore ou ARKit, vous pouvez consulter la documentation correspondante pour obtenir les intrinsics de la caméra. Si les fonctions AR à utiliser sont le suivi d’image ou le suivi d’objet, vous pouvez aussi utiliser CameraParameters.createWithDefaultIntrinsics(Vec2I, CameraDeviceType, int) pour créer les intrinsics de caméra ; l’effet de l’algorithme sera alors légèrement affecté, mais l’impact est généralement faible.

Si les données proviennent d’une caméra USB ou d’autres sources telles qu’un fichier vidéo généré sans rappel caméra, il faut alors calibrer la caméra ou la trame vidéo pour obtenir les bons intrinsics.

Attention

Les données de rappel caméra ne peuvent pas être recadrées. Si elles le sont, il faut recalculer les intrinsics. Si les données proviennent d’images obtenues par enregistrement d’écran, etc., les données d’étalonnage de la caméra du téléphone ne sont généralement pas utilisables. Dans ce cas, il faut aussi calibrer la caméra ou la trame vidéo pour obtenir les bons intrinsics.

Des intrinsics incorrects empêcheront les fonctions AR de fonctionner correctement, ce qui entraîne souvent un décalage entre le contenu virtuel et les objets réels, ou un suivi AR difficile à initier ou facilement perdu.

Par exemple, pour la vidéo utilisée dans l’exemple Workflow_FrameSource_ExternalImageStream, les intrinsics de caméra correspondants et le processus de création de CameraParameters sont les suivants :

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 ne conviennent qu’à la vidéo de l’exemple, et les intrinsics de la caméra ont été capturés au même moment que la vidéo. Si vous devez utiliser d’autres vidéos ou les données d’autres appareils, veillez à obtenir également les intrinsics de l’appareil ou à effectuer un étalonnage manuel.

Entrer les données de trame caméra

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

Par exemple, l’implémentation pour une vidéo en entrée 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’appeler Dispose() après utilisation, ou de libérer Image, Buffer et les autres données associées via using. Sinon, il y aura une fuite mémoire grave, et l’obtention de buffer depuis le pool peut aussi échouer.

Sujets associés