Table of Contents

Criar uma extensão de entrada de imagem

Antes de começar

Criar classe de fonte de dados de quadros externa

Herde de ExternalImageStreamFrameSource para criar uma extensão de entrada de imagem. Ela é uma subclasse de MonoBehaviour, e o nome do arquivo deve ser igual ao nome da classe.

Por exemplo:

public class MyFrameSource : ExternalImageStreamFrameSource
{
}

O exemplo Workflow_FrameSource_ExternalImageStream é uma implementação de extensão de entrada de imagem baseada em um vídeo gravado com ARCore em um celular usado como entrada. Esse vídeo foi capturado pelo ARCore em um Pixel2 por meio de callback da câmera (não é gravação de tela).

Definição do dispositivo

Sobrescreva IsCameraUnderControl e retorne true.

Sobrescreva IsHMD para definir se o dispositivo é um headset.

Por exemplo, defina como false ao usar vídeo como entrada.

protected override bool IsHMD => false;

Sobrescreva Display para definir a exibição do dispositivo.

Por exemplo, se ele for executado apenas em celulares, é possível usar Display.DefaultSystemDisplay, cujo valor de rotação muda automaticamente de acordo com o estado atual de exibição do sistema operacional.

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

Disponibilidade

Sobrescreva IsAvailable para definir se o dispositivo está disponível.

Por exemplo, ao usar vídeo como entrada, ele está sempre disponível:

protected override Optional<bool> IsAvailable => true;

Se IsAvailable não puder ser determinado durante a montagem da session, sobrescreva a corrotina CheckAvailability() para bloquear o processo de montagem até que seja possível determinar a disponibilidade.

Câmera virtual

Sobrescreva Camera para fornecer uma câmera virtual.

Por exemplo, às vezes é possível usar Camera.main como a câmera virtual da session:

protected override Camera Camera => Camera.main;

Câmera física

Use o tipo FrameSourceCamera para sobrescrever DeviceCameras e fornecer informações da câmera física do dispositivo. Esses dados serão usados ao inserir dados de quadro da câmera. A criação deve estar concluída quando CameraFrameStarted for true.

Por exemplo, usando o vídeo utilizado no exemplo 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;
}
Cuidado

Estes parâmetros de entrada precisam ser definidos de acordo com o vídeo realmente usado. Os parâmetros no código acima são aplicáveis apenas ao vídeo do exemplo.

Sobrescreva CameraFrameStarted para fornecer o identificador de início da entrada de quadros da câmera.

Por exemplo:

protected override bool CameraFrameStarted => started;

Início e parada da session

Sobrescreva OnSessionStart(ARSession) e faça a inicialização específica de AR. Certifique-se de chamar base.OnSessionStart primeiro.

Por exemplo:

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

Este é um local adequado para abrir a câmera do dispositivo, especialmente se essas câmeras não tiverem sido projetadas para permanecer sempre abertas. Também é um local adequado para obter dados de calibração que não mudam durante todo o ciclo de vida. Às vezes, pode ser necessário aguardar o dispositivo ficar pronto ou aguardar a atualização dos dados antes que esses dados possam ser obtidos.

Ao mesmo tempo, este também é um local adequado para iniciar o loop de entrada de dados. Esse loop também pode ser escrito em Update() ou em outros métodos, especialmente quando os dados precisam ser obtidos em um ponto específico da ordem de execução do Unity. Não insira dados antes que a session esteja pronta (ready).

Se necessário, você também pode ignorar o processo de inicialização e verificar os dados a cada atualização; isso depende totalmente dos requisitos específicos.

Por exemplo, ao usar vídeo como entrada, você pode iniciar aqui a reprodução do vídeo e o loop de entrada de dados:

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

Sobrescreva OnSessionStop() e libere os recursos. Certifique-se de chamar base.OnSessionStop.

Por exemplo, ao usar vídeo como entrada, você pode parar aqui a reprodução do vídeo e liberar os recursos relacionados:

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

Obter dados de quadro da câmera de um dispositivo ou arquivo

É possível obter imagens de qualquer fonte, como câmera do sistema, câmera USB, arquivo de vídeo ou rede, desde que os dados possam ser convertidos para o formato exigido por Image. As formas de obter dados desses dispositivos ou arquivos variam, e é necessário consultar as instruções de uso do dispositivo ou arquivo correspondente.

Por exemplo, ao usar vídeo como entrada, você pode usar Texture2D.ReadPixels(Rect, int, int, bool) para obter dados de quadro da câmera a partir da RenderTexture do player de vídeo e, em seguida, copiar os dados de Texture2D.GetRawTextureData() para 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);
    }
}
Cuidado

Como no código acima, os dados copiados a partir do ponteiro de Texture2D precisam ser invertidos verticalmente para que a disposição dos dados na memória seja a de uma imagem normal.

Ao obter a imagem, também é necessário obter os dados de calibração da câmera ou de uma câmera equivalente e criar uma instância de CameraParameters.

Se a origem original dos dados vier de um callback de câmera de celular e os dados não tiverem sido recortados manualmente, é possível usar diretamente os dados de calibração da câmera do celular. Ao usar interfaces como ARCore ou ARKit para obter dados de callback da câmera, consulte a documentação correspondente para obter os parâmetros intrínsecos da câmera. Se a função de AR a ser usada for rastreamento de imagem ou rastreamento de objeto, nessa situação também é possível usar CameraParameters.createWithDefaultIntrinsics(Vec2I, CameraDeviceType, int) para criar os intrínsecos da câmera. Nesse caso, o efeito do algoritmo será levemente afetado, mas em geral o impacto não é grande.

Se os dados vierem de uma câmera USB, de um arquivo de vídeo que não foi gerado por callback da câmera ou de outras fontes, será necessário calibrar a câmera ou os quadros do vídeo para obter intrínsecos corretos.

Cuidado

Dados de callback da câmera não podem ser recortados; após o recorte, é necessário recalcular os intrínsecos. Se os dados vierem de imagens obtidas por gravação de tela ou outros métodos semelhantes, geralmente não é possível usar os dados de calibração da câmera do celular. Nesse caso, também é necessário calibrar a câmera ou os quadros do vídeo para obter intrínsecos corretos.

Intrínsecos incorretos farão com que as funções de AR não funcionem normalmente. Casos comuns incluem conteúdo virtual que não se alinha a objetos reais, rastreamento AR difícil de iniciar ou facilmente perdido.

Por exemplo, para o vídeo usado no exemplo Workflow_FrameSource_ExternalImageStream, os intrínsecos da câmera correspondentes e o processo de criação de CameraParameters são os seguintes:

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

Os parâmetros no código acima são aplicáveis apenas ao vídeo do exemplo; os intrínsecos da câmera e o vídeo foram coletados ao mesmo tempo. Se precisar usar dados de outro vídeo ou dispositivo, obtenha obrigatoriamente os intrínsecos do dispositivo ao mesmo tempo ou faça calibração manual.

Inserir dados de quadro da câmera

Depois de obter a atualização dos dados de quadro da câmera, chame HandleCameraFrameData(double, Image, CameraParameters) para inserir os dados de quadro da câmera.

Por exemplo, ao usar vídeo como entrada, a implementação é a seguinte:

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

Não se esqueça de executar Dispose() depois do uso ou liberar Image, Buffer e outros dados relacionados por mecanismos como using. Caso contrário, ocorrerão vazamentos graves de memória, e a obtenção de buffers no buffer pool também poderá falhar.

Tópicos relacionados