Criando uma extensão de entrada de imagem
Antes de começar
- Compreenda conceitos básicos como câmeras, quadro de entrada.
- Leia Fonte de dados de quadro externo para instruções detalhadas sobre as interfaces necessárias para criar uma fonte de dados de quadro externo.
- Leia Dados de quadro de entrada externos para entender os dados de quadro da câmera e os dados de quadro de renderização.
Criar uma classe de fonte de dados de frame externa
Herde ExternalImageStreamFrameSource para criar uma extensão de entrada de imagem. É uma subclasse de MonoBehaviour, e o nome do arquivo deve ser o mesmo que o 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 telefone como entrada. Este vídeo foi capturado usando ARCore em um Pixel2 através de callbacks de câmera (não é uma gravação de tela).
Definição de dispositivo
Reescreva IsCameraUnderControl e retorne true.
Reescreva IsHMD para definir se o dispositivo é um head-mounted display.
Por exemplo, defina como false ao usar vídeo como entrada.
protected override bool IsHMD => false;
Reescreva Display para definir a exibição do dispositivo.
Por exemplo, se executado apenas em telefones, use Display.DefaultSystemDisplay, cujo valor de rotação muda automaticamente com base no 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, usando vídeo como entrada, está sempre disponível:
protected override Optional<bool> IsAvailable => true;
Se IsAvailable não puder ser determinado durante a montagem da sessão, você pode sobrescrever a corrotina CheckAvailability() para bloquear o processo de montagem até que a disponibilidade seja determinada.
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 sessão:
protected override Camera Camera => Camera.main;
Câmera física
Use o tipo FrameSourceCamera para substituir DeviceCameras para fornecer informações sobre a câmera física do dispositivo. Esses dados são usados ao inserir dados de quadros da câmera. Deve ser preenchido quando CameraFrameStarted for true.
Por exemplo, usando o vídeo do 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] Os parâmetros de entrada aqui precisam ser configurados de acordo com o vídeo real usado. Os parâmetros no código acima são apenas para o vídeo de exemplo.
Substitua CameraFrameStarted para fornecer a identificação de início da entrada do quadro da câmera.
Por exemplo:
protected override bool CameraFrameStarted => started;
Sessão inicialização e parada
Substitua OnSessionStart(ARSession) e então realize inicializações específicas para AR. Certifique-se de chamar base.OnSessionStart primeiro.
Por exemplo:
protected override void OnSessionStart(ARSession session)
{
base.OnSessionStart(session);
...
}
Este é o lugar apropriado para abrir a câmera do dispositivo, especialmente se essas câmeras não foram projetadas para ficarem sempre abertas. É também o lugar para obter dados de calibração que não mudarão durante todo o ciclo de vida. Às vezes, pode ser necessário esperar que o dispositivo esteja pronto ou que os dados sejam atualizados antes que possam ser obtidos.
Também é um lugar adequado para iniciar um loop de entrada de dados. Você também pode escrever esse loop em Update() ou outro método, especialmente se os dados precisarem ser obtidos em um ponto específico da ordem de execução do Unity. Não insira dados até que a sessão esteja pronta (ready).
Se necessário, você pode ignorar o processo de inicialização e verificar os dados em cada atualização; isso depende totalmente da necessidade específica.
Por exemplo, ao usar um vídeo como entrada, você pode começar a reproduzir o vídeo aqui e iniciar o loop de entrada de dados:
protected override void OnSessionStart(ARSession session)
{
base.OnSessionStart(session);
...
player.Play();
StartCoroutine(VideoDataToInputFrames());
}
Substitua OnSessionStop() e libere recursos. Certifique-se de chamar base.OnSessionStop.
Por exemplo, ao usar vídeo como entrada, você pode parar a reprodução aqui e liberar 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 de câmera de dispositivos ou arquivos
É possível obter imagens de qualquer fonte, como câmeras do sistema, câmeras USB, arquivos de vídeo, redes, etc. Contanto que os dados possam ser convertidos para o formato exigido por Image. A maneira de obter dados desses dispositivos ou arquivos varia, sendo necessário consultar as instruções de uso do dispositivo ou arquivo relevante.
Por exemplo, ao usar vídeo como entrada, você pode usar Texture2D.ReadPixels(Rect, int, int, bool) para obter dados de quadro de câmera do RenderTexture do player de vídeo e, em seguida, copiar os dados de Texture2D.GetRawTextureData() para um 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 do ponteiro de Texture2D precisam ser invertidos verticalmente para que o arranjo de memória dos dados se torne 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 fonte original dos dados for do retorno de chamada (callback) da câmera de um telefone e os dados não foram cortados manualmente, você pode usar diretamente os dados de calibração da câmera do telefone. Ao usar interfaces como ARCore ou ARKit para obter dados de callback da câmera, você pode consultar a documentação relevante para obter os parâmetros intrínsecos da câmera. Se a funcionalidade AR que você precisa usar for rastreamento de imagem ou rastreamento de objeto, também é possível usar CameraParameters.createWithDefaultIntrinsics(Vec2I, CameraDeviceType, int) para criar os parâmetros intrínsecos da câmera. Nesse caso, o efeito do algoritmo pode ser levemente afetado, mas geralmente o impacto é pequeno.
Se os dados vierem de outras fontes, como câmeras USB ou arquivos de vídeo gerados não por callbacks de câmera, será necessário calibrar a câmera ou o quadro de vídeo para obter os parâmetros intrínsecos corretos.
Cuidado
Os dados de callback da câmera não podem ser cortados; após o corte, os parâmetros intrínsecos precisam ser recalculados. Se os dados vierem de dados de imagem obtidos por meio de gravação de tela ou métodos similares, geralmente não é possível usar os dados de calibração da câmera do telefone. Nesse caso, também é necessário calibrar a câmera ou o quadro de vídeo para obter os parâmetros intrínsecos corretos.
Parâmetros intrínsecos incorretos impedirão o funcionamento normal da funcionalidade AR, causando problemas comuns como conteúdo virtual não se alinhar com objetos reais, além de dificultar o sucesso ou facilitar a perda do rastreamento AR.
Por exemplo, para o vídeo usado no exemplo Workflow_FrameSource_ExternalImageStream, seus parâmetros intrínsecos de 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 válidos apenas para o vídeo no exemplo; esses parâmetros intrínsecos da câmera foram coletados no mesmo momento que o vídeo. Se você precisar usar dados de outros vídeos ou dispositivos, certifique-se de obter os parâmetros intrínsecos do dispositivo ou realizar a calibração manualmente.
Entrada de dados do quadro da câmera
Após obter uma atualização dos dados do quadro da câmera, chame HandleCameraFrameData(double, Image, CameraParameters) para inserir os dados do quadro da câmera.
Por exemplo, ao usar um 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);
}
}
}
[!ATENÇÃO] Não se esqueça de executar Dispose() após o uso ou liberar Image, Buffer e outros dados relacionados por meio de mecanismos como
using. Caso contrário, ocorrerá vazamento de memória grave e a obtenção de buffer do buffer pool também pode falhar.