Creare un'estensione di input per immagini
Prima di iniziare
- Conoscere concetti di base come fotocamere e frame di input.
- Leggere Sorgente frame esterna per i dettagli delle interfacce richieste per creare una sorgente frame esterna.
- Leggere Frame di input esterni per conoscere i dati dei frame della fotocamera e dei frame renderizzati.
Creare una classe di sorgente frame esterna
Ereditare da ExternalImageStreamFrameSource per creare un'estensione di input per immagini. È una sottoclasse di MonoBehaviour e il nome del file deve corrispondere al nome della classe.
Per esempio:
public class MyFrameSource : ExternalImageStreamFrameSource
{
}
L'esempio Workflow_FrameSource_ExternalImageStream è un'implementazione di estensione di input per immagini basata su un video registrato con ARCore su smartphone. Il video è stato acquisito tramite callback della fotocamera su Pixel 2 con ARCore (non tramite registrazione dello schermo).
Definizione del dispositivo
Ridefinire IsCameraUnderControl e restituire true.
Ridefinire IsHMD per definire se il dispositivo è un visore.
Per esempio, quando si usa un video come input, impostarlo a false.
protected override bool IsHMD => false;
Ridefinire Display per definire il display del dispositivo.
Per esempio, se l'app gira solo su telefono, si può usare Display.DefaultSystemDisplay, il cui valore di rotazione cambia automaticamente in base allo stato di visualizzazione corrente del sistema operativo.
protected override IDisplay Display => easyar.Display.DefaultSystemDisplay;
Disponibilità
Ridefinire IsAvailable per definire se il dispositivo è disponibile.
Per esempio, quando si usa un video come input, è sempre disponibile:
protected override Optional<bool> IsAvailable => true;
Se IsAvailable non può essere determinato durante l'assemblaggio della sessione, è possibile ridefinire la coroutine CheckAvailability() per bloccare il processo di assemblaggio fino a quando la disponibilità non è confermata.
Fotocamera virtuale
Ridefinire Camera per fornire una fotocamera virtuale.
Per esempio, a volte è possibile usare Camera.main come fotocamera virtuale della sessione:
protected override Camera Camera => Camera.main;
Fotocamera fisica
Usare il tipo FrameSourceCamera per ridefinire DeviceCameras e fornire le informazioni della fotocamera fisica del dispositivo. Questi dati vengono usati quando si inviano i frame della fotocamera. La creazione deve essere completata quando CameraFrameStarted è true.
Per esempio, usando il video dell'esempio 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;
}
Attenzione
I parametri di input qui sopra devono essere impostati in base al video realmente usato. I parametri del codice sopra sono validi solo per il video dell'esempio.
Ridefinire CameraFrameStarted per fornire l'indicatore che segnala l'inizio dell'invio dei frame della fotocamera.
Per esempio:
protected override bool CameraFrameStarted => started;
Avvio e arresto della sessione
Ridefinire OnSessionStart(ARSession) ed eseguire l'inizializzazione specifica per l'AR. Assicurarsi di chiamare prima base.OnSessionStart.
Per esempio:
protected override void OnSessionStart(ARSession session)
{
base.OnSessionStart(session);
...
}
Questo è il punto adatto per aprire la fotocamera del dispositivo, soprattutto se non è pensata per restare sempre aperta. È anche il punto adatto per ottenere dati di calibrazione che non cambiano per tutto il ciclo di vita. A volte, prima di poterli ottenere, può essere necessario attendere che il dispositivo sia pronto o che i dati si aggiornino.
È anche un punto adatto per avviare il ciclo di input dei dati. Questo ciclo può essere scritto anche in Update() o in altri metodi, soprattutto quando i dati devono essere letti in un momento specifico dell'ordine di esecuzione di Unity. Non inviare dati prima che la sessione sia pronta.
Se necessario, si può anche ignorare il processo di avvio ed eseguire controlli dei dati a ogni aggiornamento; dipende interamente dalle esigenze specifiche.
Per esempio, quando si usa un video come input, qui si può avviare la riproduzione e il ciclo di input dei dati:
protected override void OnSessionStart(ARSession session)
{
base.OnSessionStart(session);
...
player.Play();
StartCoroutine(VideoDataToInputFrames());
}
Ridefinire OnSessionStop() e rilasciare le risorse, assicurandosi di chiamare base.OnSessionStop.
Per esempio, quando si usa un video come input, qui si può interrompere la riproduzione e rilasciare le risorse correlate:
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;
}
Ottenere i frame della fotocamera da dispositivo o file
È possibile ottenere immagini da fotocamere di sistema, fotocamere USB, file video, rete e qualsiasi altra sorgente. Basta poter convertire i dati nel formato richiesto da Image. Il modo di ottenere i dati da questi dispositivi o file varia, quindi bisogna fare riferimento alle rispettive istruzioni d'uso.
Per esempio, quando si usa un video come input, si può usare Texture2D.ReadPixels(Rect, int, int, bool) per ottenere i dati dei frame della fotocamera dal RenderTexture del player video, quindi copiare i dati di Texture2D.GetRawTextureData() in 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);
}
}
Attenzione
Come nel codice sopra, i dati copiati dal puntatore di Texture2D devono essere invertiti verticalmente affinché la disposizione in memoria corrisponda a un'immagine normale.
Quando si ottengono le immagini, è necessario anche ottenere i dati di calibrazione della fotocamera o di una fotocamera equivalente e creare un'istanza di CameraParameters.
Se la sorgente originale dei dati è una callback della fotocamera del telefono e i dati non sono stati ritagliati manualmente, si possono usare direttamente i dati di calibrazione della fotocamera del telefono. Quando si ottengono dati di callback della fotocamera tramite API come ARCore o ARKit, si può fare riferimento alla documentazione pertinente per ottenere i parametri intrinseci della fotocamera. Se la funzione AR da usare è il tracking immagine o il tracking oggetti, si può anche usare CameraParameters.createWithDefaultIntrinsics(Vec2I, CameraDeviceType, int) per creare gli intrinseci della fotocamera: in questo caso le prestazioni dell'algoritmo saranno leggermente influenzate, ma in genere l'impatto non è grande.
Se i dati provengono da una fotocamera USB o da altre sorgenti, come file video generati senza callback della fotocamera, è necessario calibrare la fotocamera o il frame video per ottenere gli intrinseci corretti.
Attenzione
I dati delle callback della fotocamera non possono essere ritagliati; se vengono ritagliati, gli intrinseci devono essere ricalcolati. Se i dati immagine provengono da registrazioni dello schermo o simili, in genere non si possono usare i dati di calibrazione della fotocamera del telefono e anche in questo caso è necessario calibrare la fotocamera o il frame video per ottenere gli intrinseci corretti.
Intrinseci errati impediscono il corretto funzionamento delle funzioni AR; i problemi più comuni sono contenuti virtuali che non si allineano agli oggetti reali e tracking AR che fatica a riuscire o si perde facilmente.
Per esempio, per il video usato nell'esempio Workflow_FrameSource_ExternalImageStream, i relativi intrinseci della fotocamera e il processo di creazione di CameraParameters sono i seguenti:
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);
Attenzione
I parametri del codice sopra sono validi solo per il video dell'esempio; quegli intrinseci e quel video sono stati acquisiti nello stesso momento. Se si vogliono usare altri video o dati di altri dispositivi, bisogna ottenere insieme gli intrinseci del dispositivo oppure eseguire una calibrazione manuale.
Inviare i frame della fotocamera
Dopo aver ricevuto l'aggiornamento dei dati del frame della fotocamera, chiamare HandleCameraFrameData(double, Image, CameraParameters) per inviare i dati del frame della fotocamera.
Per esempio, quando si usa un video come input, l'implementazione è la seguente:
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);
}
}
}
Attenzione
Non dimenticare di eseguire Dispose() oppure di rilasciare Image, Buffer e gli altri dati correlati tramite meccanismi come using dopo l'uso. In caso contrario si verificheranno gravi perdite di memoria e il buffer pool potrebbe non riuscire più ad acquisire buffer.