Ausrichtbare Kamera

Bevor Sie hier beginnen, empfehlen wir Ihnen, einen Blick auf unseren Artikel Übersicht über die locatable Kamera zu werfen, der Übersichtsinformationen und eine Tabelle mit HoloLens 1- und 2-Kameradetails enthält.

Verwenden von MediaFrameReference

Diese Anweisungen gelten, wenn Sie die MediaFrameReference-Klasse verwenden, um Bildframes von der Kamera zu lesen.

Jeder Bildrahmen (egal ob Foto oder Video) enthält ein SpatialCoordinateSystem , das zum Zeitpunkt der Aufnahme an der Kamera verwurzelt ist, auf das über die CoordinateSystem-Eigenschaft Ihres MediaFrameReference-Objekts zugegriffen werden kann. Jeder Frame enthält eine Beschreibung des Kameraobjektivmodells, die in der CameraIntrinsics-Eigenschaft zu finden ist. Zusammen definieren diese Transformationen für jedes Pixel einen Strahl im 3D-Raum, der den Pfad der Photonen darstellt, die das Pixel erzeugt haben. Diese Strahlen können mit anderen Inhalten in der App verknüpft werden, indem die Transformation aus dem Koordinatensystem des Frames in ein anderes Koordinatensystem (z. B. aus einem stationären Bezugsrahmen) abgerufen wird.

Jeder Bildrahmen bietet Folgendes:

Das HolographicFaceTracking-Beispiel zeigt die recht einfache Möglichkeit, die Transformation zwischen dem Koordinatensystem der Kamera und Ihren eigenen Anwendungskoordinatensystemen abzufragen.

Verwenden von Media Foundation

Wenn Sie Media Foundation direkt zum Lesen von Bildframes von der Kamera verwenden, können Sie das MFSampleExtension_CameraExtrinsics-Attribut und MFSampleExtension_PinholeCameraIntrinsics Attribut jedes Frames verwenden, um Kameraframes relativ zu den anderen Koordinatensystemen Ihrer Anwendung zu suchen, wie in diesem Beispielcode gezeigt:

#include <winrt/windows.perception.spatial.preview.h>
#include <mfapi.h>
#include <mfidl.h>
 
using namespace winrt::Windows::Foundation;
using namespace winrt::Windows::Foundation::Numerics;
using namespace winrt::Windows::Perception;
using namespace winrt::Windows::Perception::Spatial;
using namespace winrt::Windows::Perception::Spatial::Preview;
 
class CameraFrameLocator
{
public:
    struct CameraFrameLocation
    {
        SpatialCoordinateSystem CoordinateSystem;
        float4x4 CameraViewToCoordinateSystemTransform;
        MFPinholeCameraIntrinsics Intrinsics;
    };
 
    std::optional<CameraFrameLocation> TryLocateCameraFrame(IMFSample* pSample)
    {
        MFCameraExtrinsics cameraExtrinsics;
        MFPinholeCameraIntrinsics cameraIntrinsics;
        UINT32 sizeCameraExtrinsics = 0;
        UINT32 sizeCameraIntrinsics = 0;
        UINT64 sampleTimeHns = 0;
 
        // query sample for calibration and validate
        if (FAILED(pSample->GetUINT64(MFSampleExtension_DeviceTimestamp, &sampleTimeHns)) ||
            FAILED(pSample->GetBlob(MFSampleExtension_CameraExtrinsics, (UINT8*)& cameraExtrinsics, sizeof(cameraExtrinsics), &sizeCameraExtrinsics)) ||
            FAILED(pSample->GetBlob(MFSampleExtension_PinholeCameraIntrinsics, (UINT8*)& cameraIntrinsics, sizeof(cameraIntrinsics), &sizeCameraIntrinsics)) ||
            (sizeCameraExtrinsics != sizeof(cameraExtrinsics)) ||
            (sizeCameraIntrinsics != sizeof(cameraIntrinsics)) ||
            (cameraExtrinsics.TransformCount == 0))
        {
            return std::nullopt;
        }
 
        // compute extrinsic transform
        const auto& calibratedTransform = cameraExtrinsics.CalibratedTransforms[0];
        const GUID& dynamicNodeId = calibratedTransform.CalibrationId;
        const float4x4 cameraToDynamicNode =
            make_float4x4_from_quaternion(quaternion{ calibratedTransform.Orientation.x, calibratedTransform.Orientation.y, calibratedTransform.Orientation.z, calibratedTransform.Orientation.w }) *
            make_float4x4_translation(calibratedTransform.Position.x, calibratedTransform.Position.y, calibratedTransform.Position.z);
 
        // update locator cache for dynamic node
        if (dynamicNodeId != m_currentDynamicNodeId || !m_locator)
        {
            m_locator = SpatialGraphInteropPreview::CreateLocatorForNode(dynamicNodeId);
            if (!m_locator)
            {
                return std::nullopt;
            }
 
            m_frameOfReference = m_locator.CreateAttachedFrameOfReferenceAtCurrentHeading();
            m_currentDynamicNodeId = dynamicNodeId;
        }
 
        // locate dynamic node
        auto timestamp = PerceptionTimestampHelper::FromSystemRelativeTargetTime(TimeSpan{ sampleTimeHns });
        auto coordinateSystem = m_frameOfReference.GetStationaryCoordinateSystemAtTimestamp(timestamp);
        auto location = m_locator.TryLocateAtTimestamp(timestamp, coordinateSystem);
        if (!location)
        {
            return std::nullopt;
        }
 
        const float4x4 dynamicNodeToCoordinateSystem = make_float4x4_from_quaternion(location.Orientation()) * make_float4x4_translation(location.Position());
 
        return CameraFrameLocation{ coordinateSystem, cameraToDynamicNode * dynamicNodeToCoordinateSystem, cameraIntrinsics };
    }

private:
    GUID m_currentDynamicNodeId{ GUID_NULL };
    SpatialLocator m_locator{ nullptr };
    SpatialLocatorAttachedFrameOfReference m_frameOfReference{ nullptr };
};

Verwendungsszenarien für locatable Kameras

Anzeigen eines Fotos oder Videos in der Welt, in der es aufgenommen wurde

Die Gerätekameraframes verfügen über eine Transformation von "Camera to World", die verwendet werden kann, um genau anzuzeigen, wo sich das Gerät befand, als das Bild aufgenommen wurde. Beispielsweise könnten Sie an dieser Stelle ein kleines holografisches Symbol (CameraToWorld.MultiplyPoint(Vector3.zero)) positionieren und sogar einen kleinen Pfeil in die Richtung zeichnen, in die die Kamera gerichtet war (CameraToWorld.MultiplyVector(Vector3.forward)).

Frame Rate

Die Beibehaltung einer interaktiven Anwendungsframerate ist wichtig, insbesondere wenn es um Algorithmen für die Bilderkennung mit langer Ausführungszeit geht. Aus diesem Grund verwenden wir häufig das folgende Muster:

  1. Hauptthread: Verwaltet das Kameraobjekt
  2. Hauptthread: Fordert neue Frames (asynchron) an
  3. Hauptthread: Übergeben neuer Frames an den Nachverfolgungsthread
  4. Nachverfolgungsthread: Verarbeitet das Image zum Sammeln von Schlüsselpunkten
  5. Hauptthread: Verschiebt das virtuelle Modell, um gefundene Schlüsselpunkte abzugleichen
  6. Hauptthread: Wiederholung aus Schritt 2

Einige Bildmarkersysteme bieten nur eine einzelne Pixelposition (andere bieten die vollständige Transformation, in diesem Fall wird dieser Abschnitt nicht benötigt), was einem Strahl möglicher Positionen entspricht. Um an einen einzelnen dritten Ort zu gelangen, können wir dann mehrere Strahlen nutzen und das Endergebnis anhand ihrer ungefähren Schnittmenge ermitteln. Gehen Sie hierzu wie folgt vor:

  1. Abrufen einer Schleife zum Sammeln mehrerer Kamerabilder
  2. Suchen der zugeordneten Featurepunkte und ihrer Weltstrahlen
  3. Wenn Sie über ein Wörterbuch mit Features verfügen, die jeweils mehrere Weltstrahlen enthalten, können Sie den folgenden Code verwenden, um die Schnittmenge dieser Strahlen zu lösen:
public static Vector3 ClosestPointBetweenRays(
   Vector3 point1, Vector3 normalizedDirection1,
   Vector3 point2, Vector3 normalizedDirection2) {
   float directionProjection = Vector3.Dot(normalizedDirection1, normalizedDirection2);
   if (directionProjection == 1) {
     return point1; // parallel lines
   }
   float projection1 = Vector3.Dot(point2 - point1, normalizedDirection1);
   float projection2 = Vector3.Dot(point2 - point1, normalizedDirection2);
   float distanceAlongLine1 = (projection1 - directionProjection * projection2) / (1 - directionProjection * directionProjection);
   float distanceAlongLine2 = (projection2 - directionProjection * projection1) / (directionProjection * directionProjection - 1);
   Vector3 pointOnLine1 = point1 + distanceAlongLine1 * normalizedDirection1;
   Vector3 pointOnLine2 = point2 + distanceAlongLine2 * normalizedDirection2;
   return Vector3.Lerp(pointOnLine2, pointOnLine1, 0.5f);
 }

Positionieren einer modellierten Szene

Bei zwei oder mehr nachverfolgten Tagspeicherorten können Sie eine modellierte Szene so positionieren, dass sie dem aktuellen Szenario des Benutzers entspricht. Wenn Sie die Schwerkraft nicht annehmen können, benötigen Sie drei Tagpositionen. In vielen Fällen verwenden wir ein Farbschema, bei dem weiße Kugeln in Echtzeit nachverfolgte Tagpositionen und blaue Kugeln modellierte Tagpositionen darstellen. Dadurch kann der Benutzer die Ausrichtungsqualität visuell messen. Wir gehen von folgendem Setup in allen unseren Anwendungen aus:

  • Zwei oder mehr modellierte Tagspeicherorte
  • Ein "Kalibrierungsbereich", der in der Szene das übergeordnete Element der Tags ist
  • Bezeichner des Kamerafeatures
  • Verhalten, das den Kalibrierungsbereich verschiebt, um die modellierten Tags an den Echtzeittags auszurichten (wir achten darauf, den übergeordneten Bereich zu verschieben, nicht die modellierten Marker selbst, da andere Verbindungspositionen relativ zu ihnen sind).
// In the two tags case:
 Vector3 idealDelta = (realTags[1].EstimatedWorldPos - realTags[0].EstimatedWorldPos);
 Vector3 curDelta = (modelledTags[1].transform.position - modelledTags[0].transform.position);
 if (IsAssumeGravity) {
   idealDelta.y = 0;
   curDelta.y = 0;
 }
 Quaternion deltaRot = Quaternion.FromToRotation(curDelta, idealDelta);
 trans.rotation = Quaternion.LookRotation(deltaRot * trans.forward, trans.up);
 trans.position += realTags[0].EstimatedWorldPos - modelledTags[0].transform.position;

Nachverfolgen oder Identifizieren von markierten stationären oder bewegten objekten/gesichtern mithilfe von LEDs oder anderen Erkennungsbibliotheken

Beispiele:

  • Industrieroboter mit LEDs (oder QR-Codes für langsamer bewegte Objekte)
  • Identifizieren und Erkennen von Objekten im Raum
  • Identifizieren und Erkennen von Personen im Raum, z. B. Platzieren holografischer Visitenkarten über Gesichtern

Weitere Informationen