Crear una extensión de entrada de imagen
Antes de comenzar
- Comprender conceptos básicos como cámaras, cuadro de entrada.
- Leer fuente de datos de cuadro externo para conocer las interfaces detalladas requeridas para crear una fuente de datos de cuadro externa.
- Leer datos de cuadro de entrada externo para comprender los datos de cuadro de la cámara y los datos de cuadro de renderizado.
Crear clase de fuente de datos de marco externo
Heredar de ExternalImageStreamFrameSource para crear una extensión de entrada de imagen. Es una subclase de MonoBehaviour, y el nombre del archivo debe coincidir con el nombre de la clase.
Por ejemplo:
public class MyFrameSource : ExternalImageStreamFrameSource
{
}
El ejemplo Workflow_FrameSource_ExternalImageStream es una implementación de extensión de entrada de imagen basada en un video grabado con ARCore en un teléfono móvil como entrada. El video fue capturado usando ARCore en un Pixel2 a través de una devolución de llamada de la cámara (no es una grabación de pantalla).
Definición de dispositivo
Reescribe IsCameraUnderControl y devuelve true.
Reescribe IsHMD para definir si el dispositivo es un HMD.
Por ejemplo, configúralo como false cuando se use un video como entrada.
protected override bool IsHMD => false;
Reescribe Display para definir la pantalla del dispositivo.
Por ejemplo, si solo se ejecuta en teléfonos móviles, puedes usar Display.DefaultSystemDisplay, cuyo valor de rotación cambia automáticamente según el estado actual de visualización del sistema operativo.
protected override IDisplay Display => easyar.Display.DefaultSystemDisplay;
Disponibilidad
Sobrescribe IsAvailable para definir si el dispositivo está disponible.
Por ejemplo, al usar video como entrada, siempre está disponible:
protected override Optional<bool> IsAvailable => true;
Si IsAvailable no puede determinarse durante el ensamblaje de la sesión, puedes sobrescribir la corrutina CheckAvailability() para bloquear el proceso de ensamblaje hasta que se determine la disponibilidad.
Cámara virtual
Reemplazar Camera para proporcionar una cámara virtual.
Por ejemplo, a veces se puede usar Camera.main como cámara virtual para la sesión:
protected override Camera Camera => Camera.main;
Cámara física
Utilice el tipo FrameSourceCamera para anular DeviceCameras para proporcionar información de la cámara física del dispositivo. Estos datos se utilizan al introducir los datos del fotograma de la cámara. Debe completarse cuando CameraFrameStarted es true.
Por ejemplo, usando el video del ejemplo 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;
}
Precaución
Aquí, varios parámetros de entrada deben configurarse según el video utilizado. Los parámetros en el código anterior solo son aplicables al video del ejemplo.
Anule CameraFrameStarted para proporcionar el indicador de inicio de entrada del fotograma de la cámara.
Por ejemplo:
protected override bool CameraFrameStarted => started;
Session inicio y detención
Sobrescribe OnSessionStart(ARSession) y luego realiza la inicialización específica de AR. Asegúrate de llamar primero a base.OnSessionStart.
Por ejemplo:
protected override void OnSessionStart(ARSession session)
{
base.OnSessionStart(session);
...
}
Este es el lugar adecuado para encender la cámara del dispositivo, especialmente si no está diseñada para permanecer siempre encendida. También es el lugar apropiado para obtener datos de calibración que no cambiarán durante todo el ciclo de vida. A veces puede ser necesario esperar a que el dispositivo esté listo o a que los datos se actualicen antes de que estos puedan obtenerse.
También es un lugar adecuado para iniciar un ciclo de entrada de datos. También puedes escribir este ciclo en Update() u otros métodos, especialmente si los datos necesitan obtenerse en un momento específico del orden de ejecución de Unity. No introduzcas datos antes de que la sesión esté lista (ready).
Si es necesario, puedes omitir el proceso de inicio y verificar los datos en cada actualización; esto depende completamente de los requisitos específicos.
Por ejemplo, al usar video como entrada, puedes comenzar a reproducirlo aquí e iniciar el ciclo de entrada de datos:
protected override void OnSessionStart(ARSession session)
{
base.OnSessionStart(session);
...
player.Play();
StartCoroutine(VideoDataToInputFrames());
}
Sobrescribe OnSessionStop() y libera los recursos. Asegúrate de llamar a base.OnSessionStop.
Por ejemplo, al usar video como entrada, puedes detener la reproducción y liberar los recursos relacionados aquí:
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;
}
Desde dispositivo o archivo obtener datos de fotograma de la cámara
Se pueden obtener imágenes desde cualquier fuente: cámaras del sistema, cámaras USB, archivos de video, redes, etc. Siempre que los datos puedan convertirse al formato requerido por Image. La forma de obtener datos de estos dispositivos o archivos varía y requiere consultar la documentación específica del dispositivo o formato.
Por ejemplo, al usar video como entrada, se puede emplear Texture2D.ReadPixels(Rect, int, int, bool) para obtener los datos del fotograma del RenderTexture del reproductor de video, luego copiar los datos de Texture2D.GetRawTextureData() a un 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);
}
}
[!PRECAUCIÓN] Como en el código anterior, los datos copiados del puntero de Texture2D deben invertirse verticalmente para que la disposición en memoria sea la de una imagen normal.
Al obtener las imágenes, también se deben obtener los parámetros de calibración de la cámara (o cámara equivalente) y crear una instancia de CameraParameters.
Si la fuente original de los datos proviene de la devolución de llamada de la cámara de un teléfono y los datos no se han recortado manualmente, se pueden usar directamente los parámetros de calibración de esa cámara. Al usar interfaces como ARCore o ARKit para obtener datos de la cámara, se puede consultar su documentación para obtener los parámetros intrínsecos. Si la funcionalidad AR requerida es seguimiento de imágenes u objetos, también se puede usar CameraParameters.createWithDefaultIntrinsics(Vec2I, CameraDeviceType, int) para crear los parámetros intrínsecos. Esto puede afectar ligeramente el rendimiento del algoritmo, pero generalmente el impacto es mínimo.
Si los datos provienen de otras fuentes, como cámaras USB o archivos de video no generados desde una devolución de llamada de cámara, entonces se debe calibrar la cámara o los fotogramas del video para obtener los parámetros intrínsecos correctos.
[!PRECAUCIÓN] Los datos de devolución de llamada de la cámara no se pueden recortar; después del recorte, se deben recalcular los parámetros intrínsecos. Si los datos provienen de capturas de pantalla u otros métodos que generan datos de imagen, generalmente no se pueden usar los parámetros de calibración de la cámara del teléfono. En este caso, también se necesita calibrar la cámara o los fotogramas del video para obtener parámetros intrínsecos correctos.
Parámetros intrínsecos incorrectos evitarán que la funcionalidad AR funcione normalmente, causando problemas como desalineación entre contenido virtual y objetos reales, seguimiento AR difícil de iniciar o fácil de perder.
Por ejemplo, para el video utilizado en el ejemplo Workflow_FrameSource_ExternalImageStream, sus parámetros intrínsecos de cámara correspondientes y el proceso de creación de CameraParameters son los siguientes:
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);
[!PRECAUCIÓN] Los parámetros en el código anterior son específicos para el video del ejemplo; estos parámetros intrínsecos de cámara se recopilaron al mismo tiempo que el video. Si se necesitan datos de otros videos o dispositivos, es esencial obtener simultáneamente los parámetros intrínsecos del dispositivo o realizar una calibración manual.
Entrada de datos de fotograma de cámara
Después de obtener una actualización de los datos del fotograma de la cámara, llama a HandleCameraFrameData(double, Image, CameraParameters) para introducir los datos del fotograma de la cámara.
Por ejemplo, al usar un vídeo como entrada, la implementación es la siguiente:
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);
}
}
}
[!PRECAUCIÓN] No olvides ejecutar Dispose() después de su uso o liberar Image, Buffer y otros datos relacionados mediante mecanismos como
using. De lo contrario, se producirán fugas de memoria graves y la obtención de buffer desde el buffer pool también podría fallar.