Aparat lokalizowalny

Przed rozpoczęciem pracy w tym miejscu zalecamy zapoznanie się z artykułem przeglądowym aparatu Locatable zawierającym informacje o przeglądzie oraz tabelę zawierającą szczegóły aparatu HoloLens 1 i 2.

Korzystanie z elementu MediaFrameReference

Te instrukcje mają zastosowanie, jeśli używasz klasy MediaFrameReference do odczytywania ramek obrazów z aparatu.

Każda ramka obrazu (niezależnie od tego, czy zdjęcie, czy wideo) zawiera element SpatialCoordinateSystem zakorzeniony w aparacie w czasie przechwytywania, do którego można uzyskać dostęp przy użyciu właściwości CoordinateSystem elementu MediaFrameReference. Każda ramka zawiera opis modelu obiektywu aparatu, który można znaleźć we właściwości CameraIntrinsics . Razem te przekształcenia definiują dla każdego piksela promienia w przestrzeni 3D reprezentującej ścieżkę wykonaną przez fotony, które wyprodukowały piksel. Te promienie mogą być powiązane z inną zawartością w aplikacji przez uzyskanie przekształcenia z układu współrzędnych ramki do innego układu współrzędnych (np. z stacjonarnej ramki odwołania).

Każda ramka obrazu udostępnia następujące elementy:

Przykład HolographicFaceTracking przedstawia dość prosty sposób wykonywania zapytań o transformację między układem współrzędnych aparatu a własnymi systemami współrzędnych aplikacji.

Korzystanie z programu Media Foundation

Jeśli używasz programu Media Foundation bezpośrednio do odczytywania ramek obrazów z aparatu, możesz użyć atrybutu MFSampleExtension_CameraExtrinsics każdej ramki i atrybutu MFSampleExtension_PinholeCameraIntrinsics w celu zlokalizowania ramek aparatu względem innych systemów współrzędnych aplikacji, jak pokazano w tym przykładowym kodzie:

#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 };
};

Scenariusze użycia aparatu lokalizowalnego

Pokaż zdjęcie lub wideo na świecie, w którym został przechwycony

Ramki kamery urządzenia są wyposażone w transformację "Camera To World", która może służyć do pokazywania dokładnie miejsca, w którym urządzenie zostało zrobione. Można na przykład umieścić małą ikonę holograficznej w tej lokalizacji (CameraToWorld.MultiplyPoint(Vector3.zero)), a nawet narysować małą strzałkę w kierunku, w którym stoi aparat (CameraToWorld.MultiplyVector(Vector3.forward)).

Klatek

Utrzymywanie interakcyjnej szybkości klatek aplikacji ma kluczowe znaczenie, zwłaszcza w przypadku długotrwałych algorytmów rozpoznawania obrazów. Z tego powodu często używamy następującego wzorca:

  1. Główny wątek: zarządza obiektem aparatu
  2. Główny wątek: żąda nowych ramek (asynchronicznych)
  3. Główny wątek: przekazywanie nowych ramek do śledzenia wątku
  4. Wątek śledzenia: przetwarza obraz do zbierania kluczowych punktów
  5. Główny wątek: przenosi model wirtualny w celu dopasowania do kluczowych punktów
  6. Główny wątek: powtórz od kroku 2

Niektóre systemy znaczników obrazów zapewniają tylko jedną lokalizację pikseli (inne zapewniają pełną transformację, w której ta sekcja nie będzie potrzebna), co odpowiada promieniu możliwych lokalizacji. Aby dostać się do jednej trzeciej lokalizacji, możemy wykorzystać wiele promieni i znaleźć końcowy wynik przez ich przybliżone skrzyżowanie. W tym celu należy wykonać następujące czynności:

  1. Uzyskiwanie pętli, która zbiera wiele obrazów aparatu
  2. Znajdź skojarzone punkty funkcji i ich promienie świata
  3. Jeśli masz słownik cech, każdy z wieloma promieniami świata, możesz użyć następującego kodu, aby rozwiązać przecięcia tych promieni:
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);
 }

Pozycjonowanie modelowanej sceny

Biorąc pod uwagę co najmniej dwie śledzone lokalizacje tagów, można umieścić modelowaną scenę w celu dopasowania do bieżącego scenariusza użytkownika. Jeśli nie możesz założyć grawitacji, będziesz potrzebować trzech lokalizacji tagów. W wielu przypadkach używamy schematu kolorów, w którym białe kule reprezentują lokalizacje tagów śledzonych w czasie rzeczywistym, a niebieskie kule reprezentują modelowane lokalizacje tagów. Dzięki temu użytkownik może wizualnie ocenić jakość wyrównania. Zakładamy następującą konfigurację we wszystkich naszych aplikacjach:

  • Co najmniej dwie modelowane lokalizacje tagów
  • Jedna "przestrzeń kalibracji", która w scenie jest elementem nadrzędnym tagów
  • Identyfikator funkcji aparatu
  • Zachowanie, które przenosi miejsce kalibracji w celu wyrównania modelowanych tagów do tagów czasu rzeczywistego (ostrożnie przenosimy przestrzeń nadrzędną, a nie same znaczniki modelowane, ponieważ inne połączenia są położeniami względem nich).
// 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;

Śledzenie lub identyfikowanie oznakowanych obiektów stacjonarnych lub ruchomych obiektów/twarzy w świecie rzeczywistym przy użyciu diod LED lub innych bibliotek rozpoznawania

Przykłady:

  • Roboty przemysłowe z diodami LED (lub kodami QR dla wolniej poruszających się obiektów)
  • Identyfikowanie i rozpoznawanie obiektów w pomieszczeniu
  • Zidentyfikuj i rozpoznaj osoby w pokoju, na przykład umieszczając holograficzne karty kontaktowe na twarzach

Zobacz też