Implementierung einer benutzerdefinierten Kamera in Unity - Erweiterung für Bild- und Gerätebewegungsdaten
Durch Erweiterungen für Bild- und Gerätebewegungsdaten können Entwickler benutzerdefinierte Kameraimplementierungen für EasyAR Sense erweitern und so bestimmte Headset-Geräte oder andere Eingabegeräte unterstützen. Der folgende Inhalt beschreibt die Schritte und Hinweise zum Erstellen einer solchen Erweiterung.
Vor Beginn
- Machen Sie sich mit den grundlegenden Konzepten von camera, input frames vertraut.
- Lesen Sie Externe Frame-Datenquelle, um die Schnittstellenanforderungen für externe Frame-Datenquellen zu verstehen.
- Lesen Sie Externe Input-Frame-Daten, um Kamera-Frame-Daten und Render-Frame-Daten zu verstehen.
Erstellen externer Frame-Datenquellenklassen
- Wenn Sie eine 6DoF-Geräteeingabeerweiterung erstellen möchten, erben Sie von ExternalDeviceMotionFrameSource
- Wenn Sie eine 3DoF-Geräteeingabeerweiterung erstellen möchten, erben Sie von ExternalDeviceRotationFrameSource
Beide sind Unterklassen von MonoBehaviour, und der Dateiname sollte mit dem Klassennamen übereinstimmen.
Beispiel für eine 6DoF-Geräteeingabeerweiterung:
public class MyFrameSource : ExternalDeviceMotionFrameSource
{
}
Beim Erstellen von HMD-Erweiterungen können Sie die Vorlage com.easyar.sense.ext.hmdtemplate verwenden und darauf aufbauend anpassen. Diese Vorlage ist im Unity-Paket enthalten, das von der EasyAR-Website heruntergeladen wird.
Gerätedefinition
Überschreiben Sie IsHMD, um festzulegen, ob es sich um ein Headset handelt.
Zum Beispiel true für HMD-Geräte:
public override bool IsHMD { get => true; }
Überschreiben Sie Display, um das Anzeigegerät zu definieren.
Zum Beispiel können auf HMD-Geräten die Standardinformationen Display.DefaultHMDDisplay verwendet werden, deren Rotationswert 0 ist.
protected override IDisplay Display => easyar.Display.DefaultHMDDisplay;
Verfügbarkeit
Überschreiben Sie IsAvailable, um festzulegen, ob das Gerät verfügbar ist.
Beispiel:
protected override Optional<bool> IsAvailable => Application.platform == RuntimePlatform.Android;
Wenn IsAvailable während der Sitzungszusammenstellung nicht bestimmt werden kann, überschreiben Sie die Coroutine CheckAvailability(), um den Zusammenstellungsprozess zu blockieren, bis die Verfügbarkeit bestätigt ist.
Sitzungsursprung
Überschreiben Sie OriginType, um den vom Geräte-SDK definierten Ursprungstyp festzulegen.
Wenn OriginType Custom ist, müssen Sie auch Origin überschreiben.
Beispiel:
protected override DeviceOriginType OriginType =>
#if EASYAR_HAVE_ROKID_UXR
hasUXRComponents ? DeviceOriginType.None :
#endif
DeviceOriginType.XROrigin;
Virtuelle Kamera
Wenn OriginType Custom oder None ist, müssen Sie Camera überschreiben, um eine virtuelle Kamera bereitzustellen.
Zum Beispiel:
protected override Camera Camera => hasUXRComponents ? (cameraCandidate ? cameraCandidate : Camera.main) : base.Camera;
Physische Kamera
Verwenden Sie den Typ DeviceFrameSourceCamera, um DeviceCameras zu überschreiben und Informationen über die physische Kamera des Geräts bereitzustellen. Diese Daten werden verwendet, wenn Kamera-Frame-Daten eingegeben werden. Dies muss abgeschlossen sein, wenn CameraFrameStarted true ist.
Beispiel:
private DeviceFrameSourceCamera deviceCamera;
protected override List<FrameSourceCamera> DeviceCameras => new List<FrameSourceCamera> { deviceCamera };
{
var imageDimensions = new int[2];
RokidExtensionAPI.RokidOpenXR_API_GetImageDimensions(imageDimensions);
size = new Vector2Int(imageDimensions[0], imageDimensions[1]);
deviceCamera = new DeviceFrameSourceCamera(CameraDeviceType.Back, 0, size, new Vector2(50, 50), new DeviceFrameSourceCamera.CameraExtrinsics(Pose.identity, true), AxisSystemType.Unity);
started = true;
}
Überschreiben Sie CameraFrameStarted, um den Startpunkt für die Kamera-Frame-Eingabe anzugeben.
Beispiel:
protected override bool CameraFrameStarted => started;
Sitzungsstart und -stopp
Überschreiben Sie OnSessionStart(ARSession) und führen Sie AR-bezogene Initialisierung durch. Rufen Sie zuerst base.OnSessionStart auf.
Beispiel:
protected override void OnSessionStart(ARSession session)
{
base.OnSessionStart(session);
StartCoroutine(InitializeCamera());
}
Dies ist ein guter Ort, um Gerätekameras zu öffnen (z. B. RGB- oder VST-Kameras), insbesondere wenn sie nicht dauerhaft aktiv sein sollen. Ebenso eignet sich dieser Ort zum Abrufen von Kalibrierdaten, die während des gesamten Lebenszyklus konstant bleiben. Manchmal ist es notwendig, auf Gerätereife oder Datenaktualisierungen zu warten, bevor diese Informationen verfügbar sind.
Außerdem ist dies ein geeigneter Ort, um Daten-Eingabeschleifen zu starten. Alternativ können solche Schleifen in Update() oder anderen Methoden implementiert werden, insbesondere wenn die Datenerfassung mit bestimmten Punkten der Unity-Ausführungsreihenfolge synchronisiert werden muss. Geben Sie keine Daten ein, bevor die Sitzung bereit ist.
Wenn nötig, können Sie die Initialisierung überspringen und Datenprüfungen in jedem Update durchführen - das hängt vollständig von den jeweiligen Anforderungen ab.
Beispiel:
private IEnumerator InitializeCamera()
{
yield return new WaitUntil(() => (RokidTrackingStatus)RokidExtensionAPI.RokidOpenXR_API_GetHeadTrackingStatus() >= RokidTrackingStatus.Detecting && (RokidTrackingStatus)RokidExtensionAPI.RokidOpenXR_API_GetHeadTrackingStatus() < RokidTrackingStatus.Tracking_Paused);
var focalLength = new float[2];
RokidExtensionAPI.RokidOpenXR_API_GetFocalLength(focalLength);
var principalPoint = new float[2];
RokidExtensionAPI.RokidOpenXR_API_GetPrincipalPoint(principalPoint);
var distortion = new float[5];
RokidExtensionAPI.RokidOpenXR_API_GetDistortion(distortion);
var imageDimensions = new int[2];
RokidExtensionAPI.RokidOpenXR_API_GetImageDimensions(imageDimensions);
size = new Vector2Int(imageDimensions[0], imageDimensions[1]);
var cameraParamList = new List<float> { focalLength[0], focalLength[1], principalPoint[0], principalPoint[1] }.Concat(distortion.ToList().GetRange(1, 4)).ToList();
cameraParameters = CameraParameters.tryCreateWithCustomIntrinsics(size.ToEasyARVector(), cameraParamList, CameraModelType.OpenCV_Fisheye, CameraDeviceType.Back, 0).Value;
deviceCamera = new DeviceFrameSourceCamera(CameraDeviceType.Back, 0, size, new Vector2(50, 50), new DeviceFrameSourceCamera.CameraExtrinsics(Pose.identity, true), AxisSystemType.Unity);
RokidExtensionAPI.RokidOpenXR_API_OpenCameraPreview(OnCameraDataUpdate);
started = true;
}
Überschreiben Sie OnSessionStop() und geben Sie Ressourcen frei. Rufen Sie zuerst base.OnSessionStop auf.
Beispiel:
protected override void OnSessionStop()
{
base.OnSessionStop();
RokidExtensionAPI.RokidOpenXR_API_CloseCameraPreview();
started = false;
StopAllCoroutines();
cameraParameters?.Dispose();
cameraParameters = null;
deviceCamera?.Dispose();
deviceCamera = null;
}
Eingabe von Kamera-Frame-Daten
Nach dem Erhalt eines Updates für Kamera-Frame-Daten rufen Sie HandleCameraFrameData(DeviceFrameSourceCamera, double, Image, CameraParameters, Pose, MotionTrackingStatus) / HandleCameraFrameData(DeviceFrameSourceCamera, double, Image, CameraParameters, Quaternion) auf, um die Kamera-Frame-Daten einzugeben.
Beispiel:
private static void OnCameraDataUpdate(IntPtr ptr, int dataSize, ushort width, ushort height, long timestamp)
{
if (!instance) { return; }
if (ptr == IntPtr.Zero || dataSize == 0 || timestamp == 0) { return; }
if (timestamp == instance.curTimestamp) { return; }
instance.curTimestamp = timestamp;
RokidExtensionAPI.RokidOpenXR_API_GetHistoryCameraPhysicsPose(timestamp, positionCache, rotationCache);
var pose = new Pose
{
position = new Vector3(positionCache[0], positionCache[1], -positionCache[2]),
rotation = new Quaternion(-rotationCache[0], -rotationCache[1], rotationCache[2], rotationCache[3]),
};
// HINWEIS: Verwenden Sie möglichst den echten Tracking-Status zum Zeitpunkt der Kamera-Belichtung, wenn Sie Ihre eigene Frame-Datenquelle schreiben.
var trackingStatus = ((RokidTrackingStatus)RokidExtensionAPI.RokidOpenXR_API_GetHeadTrackingStatus()).ToEasyARStatus();
var size = instance.size;
var pixelSize = instance.size;
var pixelFormat = PixelFormat.Gray;
var yLen = pixelSize.x * pixelSize.y;
var bufferBlockSize = yLen;
var bufferO = instance.TryAcquireBuffer(bufferBlockSize);
if (bufferO.OnNone) { return; }
var buffer = bufferO.Value;
buffer.tryCopyFrom(ptr, 0, 0, bufferBlockSize);
using (buffer)
using (var image = Image.create(buffer, pixelFormat, size.x, size.y, pixelSize.x, pixelSize.y))
{
instance.HandleCameraFrameData(instance.deviceCamera, timestamp * 1e-9, image, instance.cameraParameters, pose, trackingStatus);
}
}
Vorsicht
Vergessen Sie nicht, nach der Verwendung Dispose() aufzurufen oder Image, Buffer und andere zugehörige Daten über Mechanismen wie using freizugeben. Andernfalls kann es zu schweren Speicherlecks kommen, und das Abrufen aus dem Buffer-Pool kann fehlschlagen.
Eingabe von Render-Frame-Daten
Nachdem die Gerätedaten vorbereitet wurden, rufen Sie in jedem Render-Frame HandleRenderFrameData(double, Pose, MotionTrackingStatus) / HandleRenderFrameData(double, Quaternion) auf, um Render-Frame-Daten einzugeben.
Beispiel:
protected void LateUpdate()
{
if (!started) { return; }
if ((RokidTrackingStatus)RokidExtensionAPI.RokidOpenXR_API_GetHeadTrackingStatus() < RokidTrackingStatus.Detecting) { return; }
if ((RokidTrackingStatus)RokidExtensionAPI.RokidOpenXR_API_GetHeadTrackingStatus() >= RokidTrackingStatus.Tracking_Paused) { return; }
InputRenderFrameMotionData();
}
private void InputRenderFrameMotionData()
{
var timestamp = RokidExtensionAPI.RokidOpenXR_API_GetCameraPhysicsPose(positionCache, rotationCache);
var pose = new Pose
{
position = new Vector3(positionCache[0], positionCache[1], -positionCache[2]),
rotation = new Quaternion(-rotationCache[0], -rotationCache[1], rotationCache[2], rotationCache[3]),
};
if (timestamp == 0) { return; }
HandleRenderFrameData(timestamp * 1e-9, pose, ((RokidTrackingStatus)RokidExtensionAPI.RokidOpenXR_API_GetHeadTrackingStatus()).ToEasyARStatus());
}
Nächste Schritte
- Erstellen Sie ein Headset-Erweiterungspaket