Membuat ekstensi input gambar
Sebelum memulai
- Pahami konsep dasar seperti kamera, frame input.
- Baca Sumber data frame eksternal untuk penjelasan detail antarmuka yang diperlukan dalam membuat sumber data frame eksternal.
- Baca Data frame input eksternal untuk memahami data frame kamera dan data frame rendering.
Membuat kelas sumber data frame eksternal
Warisi ExternalImageStreamFrameSource untuk membuat ekstensi input gambar. Ini adalah subkelas dari MonoBehaviour, dan nama file harus sama dengan nama kelas.
Contoh:
public class MyFrameSource : ExternalImageStreamFrameSource
{
}
Contoh Workflow_FrameSource_ExternalImageStream adalah implementasi ekstensi input gambar yang menggunakan video rekaman ARCore di ponsel sebagai input. Video ini diambil menggunakan ARCore pada Pixel2 melalui metode callback kamera (bukan rekaman layar).
Definisi perangkat
Override IsCameraUnderControl dan kembalikan true.
Override IsHMD untuk mendefinisikan apakah perangkat adalah head-mounted display (HMD).
Contoh, saat menggunakan video sebagai input, atur menjadi false.
protected override bool IsHMD => false;
Override Display untuk mendefinisikan tampilan perangkat.
Contoh, jika hanya berjalan di ponsel, gunakan Display.DefaultSystemDisplay yang nilai rotasinya berubah otomatis berdasarkan status tampilan sistem operasi saat ini.
protected override IDisplay Display => easyar.Display.DefaultSystemDisplay;
Ketersediaan
Override IsAvailable untuk mendefinisikan apakah perangkat tersedia atau tidak.
Sebagai contoh, saat menggunakan video sebagai input, selalu tersedia:
protected override Optional<bool> IsAvailable => true;
Jika ketersediaan IsAvailable tidak dapat ditentukan saat perakitan sesi, override korutin CheckAvailability() untuk memblokir proses perakitan hingga ketersediaan dapat dipastikan.
Kamera virtual
Timpa Camera untuk menyediakan kamera virtual.
Contohnya, terkadang bisa menggunakan Camera.main sebagai kamera virtual sesi:
protected override Camera Camera => Camera.main;
Kamera fisik
Gunakan tipe FrameSourceCamera untuk menimpa DeviceCameras guna menyediakan informasi kamera fisik perangkat. Data ini akan digunakan saat memasukkan data bingkai kamera. Harus diselesaikan saat CameraFrameStarted bernilai true.
Contohnya, menggunakan video dari sampel 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;
}
[!PERINGATAN] Beberapa parameter input di sini perlu diatur sesuai dengan video yang sebenarnya digunakan. Parameter dalam kode di atas hanya berlaku untuk video dalam sampel.
Timpa CameraFrameStarted untuk menyediakan penanda dimulainya input bingkai kamera.
Contoh:
protected override bool CameraFrameStarted => started;
Session start dan stop
Override OnSessionStart(ARSession) kemudian lakukan inisialisasi khusus AR. Pastikan untuk memanggil base.OnSessionStart terlebih dahulu.
Contoh:
protected override void OnSessionStart(ARSession session)
{
base.OnSessionStart(session);
...
}
Ini adalah tempat yang tepat untuk menyalakan kamera perangkat, terutama jika kamera tersebut tidak dirancang untuk selalu menyala. Ini juga tempat yang tepat untuk mendapatkan data kalibrasi yang tidak akan berubah selama masa pakai. Terkadang, mungkin perlu menunggu perangkat siap atau menunggu pembaruan data sebelum data ini dapat diperoleh.
Ini juga merupakan tempat yang baik untuk memulai loop input data. Anda juga dapat menulis loop ini di Update() atau metode lain, terutama jika data perlu diperoleh pada titik waktu tertentu dalam urutan eksekusi Unity. Jangan input data sebelum session siap (ready).
Jika perlu, Anda juga dapat mengabaikan proses start dan memeriksa data pada setiap pembaruan, ini sepenuhnya tergantung pada kebutuhan spesifik.
Contoh, saat menggunakan video sebagai input, Anda dapat mulai memutar video dan memulai loop input data di sini:
protected override void OnSessionStart(ARSession session)
{
base.OnSessionStart(session);
...
player.Play();
StartCoroutine(VideoDataToInputFrames());
}
Override OnSessionStop() dan lepaskan sumber daya. Pastikan untuk memanggil base.OnSessionStop.
Contoh, saat menggunakan video sebagai input, Anda dapat menghentikan pemutaran video dan melepaskan sumber daya terkait di sini:
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;
}
Mendapatkan data frame kamera dari perangkat atau berkas
Gambar dapat diperoleh dari berbagai sumber seperti kamera sistem, kamera USB, berkas video, jaringan, atau lainnya. Selama data dapat dikonversi ke format yang diperlukan oleh Image. Cara memperoleh data dari perangkat atau berkas ini berbeda-beda, perlu merujuk ke petunjuk penggunaan perangkat atau berkas terkait.
Misalnya, saat menggunakan video sebagai input, Anda dapat menggunakan Texture2D.ReadPixels(Rect, int, int, bool) untuk mendapatkan data frame kamera dari RenderTexture pemutar video, lalu menyalin data dari Texture2D.GetRawTextureData() ke dalam 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);
}
}
Hati-Hati
Seperti pada kode di atas, data yang disalin dari pointer Texture2D perlu dibalik secara vertikal (flipped vertically) agar susunan memori data menjadi gambar yang normal.
Saat mendapatkan gambar, Anda juga perlu mendapatkan data kalibrasi kamera atau kamera setara dan membuat instance CameraParameters.
Jika sumber asli data berasal dari callback kamera ponsel dan data tidak dipotong secara manual, maka Anda dapat langsung menggunakan data kalibrasi kamera ponsel. Saat menggunakan antarmuka seperti ARCore atau ARKit untuk mendapatkan data callback kamera, Anda dapat merujuk ke dokumentasi terkait untuk mendapatkan parameter intrinsik kamera. Jika fitur AR yang perlu digunakan adalah pelacakan gambar atau pelacakan objek, dalam kasus ini Anda juga dapat menggunakan CameraParameters.createWithDefaultIntrinsics(Vec2I, CameraDeviceType, int) untuk membuat parameter intrinsik kamera. Efek algoritma mungkin akan sedikit terpengaruh, tetapi umumnya tidak signifikan.
Jika data berasal dari sumber lain seperti kamera USB atau berkas video yang tidak dihasilkan dari callback kamera, maka kamera atau frame video perlu dikalibrasi untuk mendapatkan parameter intrinsik yang benar.
Hati-Hati
Data callback kamera tidak dapat dipotong. Setelah dipotong, parameter intrinsik perlu dihitung ulang. Jika data berasal dari rekaman layar atau metode lain untuk mendapatkan data gambar, umumnya tidak dapat menggunakan data kalibrasi kamera ponsel. Dalam kasus ini, kamera atau frame video juga perlu dikalibrasi untuk mendapatkan parameter intrinsik yang benar.
Parameter intrinsik yang tidak benar akan menyebabkan fitur AR tidak dapat berfungsi normal, seperti konten virtual tidak sejajar dengan objek nyata, serta pelacakan AR sulit berhasil atau mudah hilang.
Misalnya, menggunakan video yang digunakan dalam contoh Workflow_FrameSource_ExternalImageStream, parameter intrinsik kamera yang sesuai dan proses pembuatan CameraParameters adalah sebagai berikut:
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);
Hati-Hati
Parameter dalam kode di atas hanya berlaku untuk video dalam contoh tersebut. Parameter intrinsik kamera ini dan video direkam pada waktu yang sama. Jika Anda perlu menggunakan data dari video atau perangkat lain, pastikan untuk mendapatkan parameter intrinsik perangkat atau melakukan kalibrasi manual secara bersamaan.
Memasukkan data bingkai kamera
Setelah mendapatkan pembaruan data bingkai kamera, panggil HandleCameraFrameData(double, Image, CameraParameters) untuk memasukkan data bingkai kamera.
Contoh implementasi saat menggunakan video sebagai input:
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);
}
}
}
Hati-Hati
Jangan lupa untuk melakukan Dispose() atau melepaskan Image, Buffer dan data terkait lainnya melalui mekanisme seperti using setelah digunakan. Jika tidak, akan terjadi kebocoran memori serius dan akuisisi buffer dari buffer pool mungkin gagal.