Locatable kamera

Innan du börjar här rekommenderar vi att du tar en titt på vår locatable kameraöversiktsartikel som innehåller översiktsinformation och en tabell med HoloLens 1- och 2-kamerainformation.

Använda MediaFrameReference

De här anvisningarna gäller om du använder klassen MediaFrameReference för att läsa bildramar från kameran.

Varje bildram (oavsett om det är foto eller video) innehåller ett SpatialCoordinateSystem som är rotat i kameran vid tidpunkten för avbildningen, som kan nås med egenskapen CoordinateSystem för din MediaFrameReference. Varje ram innehåller en beskrivning av kameralinsmodellen, som finns i egenskapen CameraIntrinsics . Tillsammans definierar dessa transformeringar för varje bildpunkt en stråle i 3D-utrymme som representerar sökvägen som tas av fotonerna som producerade pixeln. Dessa strålar kan vara relaterade till annat innehåll i appen genom att hämta transformeringen från ramens koordinatsystem till något annat koordinatsystem (t.ex. från en stationär referensram).

Varje bildram innehåller följande:

HolographicFaceTracking-exemplet visar det ganska enkla sättet att fråga efter transformeringen mellan kamerans koordinatsystem och dina egna programkoordinatsystem.

Använda Media Foundation

Om du använder Media Foundation direkt för att läsa bildramar från kameran kan du använda varje rams MFSampleExtension_CameraExtrinsics-attribut och MFSampleExtension_PinholeCameraIntrinsics attribut för att hitta kameraramar i förhållande till programmets andra koordinatsystem, som du ser i den här exempelkoden:

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

Scenarier för locatable Camera Usage

Visa ett foto eller en video i världen där det fångades

Enhetskameraramarna levereras med en "Camera To World"-transformering som kan användas för att visa exakt var enheten var när bilden togs. Du kan till exempel placera en liten holografisk ikon på den här platsen (CameraToWorld.MultiplyPoint(Vector3.zero)) och till och med rita en liten pil i den riktning som kameran var riktad mot (CameraToWorld.MultiplyVector(Vector3.forward)).

Bildfrekvens

Det är viktigt att ha en interaktiv programramfrekvens, särskilt när du hanterar långvariga bildigenkänningsalgoritmer. Därför använder vi ofta följande mönster:

  1. Huvudtråd: hanterar kameraobjektet
  2. Huvudtråd: begär nya ramar (async)
  3. Huvudtråd: Skicka nya ramar till spårningstråden
  4. Spårningstråd: bearbetar avbildning för att samla in nyckelpunkter
  5. Huvudtråd: flyttar virtuell modell för att matcha hittade nyckelpunkter
  6. Huvudtråd: upprepa från steg 2

Vissa bildmarkörsystem tillhandahåller bara en plats med en bildpunkt (andra tillhandahåller den fullständiga transformeringen i vilket fall det här avsnittet inte behövs), vilket motsvarar en rad möjliga platser. För att komma till en tredje plats kan vi sedan utnyttja flera strålar och hitta slutresultatet genom deras ungefärliga skärningspunkt. För att göra detta måste du:

  1. Hämta en loop som samlar in flera kamerabilder
  2. Hitta de associerade funktionspunkterna och deras världsstrålar
  3. När du har en ordlista med funktioner, var och en med flera världsstrålar, kan du använda följande kod för att lösa skärningspunkten för dessa strålar:
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);
 }

Placera en modellerad scen

Med tanke på två eller flera spårade taggplatser kan du placera en modellerad scen så att den passar användarens aktuella scenario. Om du inte kan anta allvaret behöver du tre taggplatser. I många fall använder vi ett färgschema där vita sfärer representerar spårade taggplatser i realtid och blå sfärer representerar modellerade taggplatser. På så sätt kan användaren visuellt mäta justeringskvaliteten. Vi förutsätter följande konfiguration i alla våra program:

  • Två eller flera modellerade taggplatser
  • Ett "kalibreringsutrymme", som i scenen är överordnat till taggarna
  • Kamerafunktionsidentifierare
  • Beteende, som flyttar kalibreringsutrymmet för att justera de modellerade taggarna med realtidstaggar (vi är noga med att flytta det överordnade utrymmet, inte själva de modellerade markörer, eftersom andra anslutningar är positioner i förhållande till dem).
// 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;

Spåra eller identifiera taggade stationära eller flytta verkliga objekt/ansikten med hjälp av lysdioder eller andra identifierarbibliotek

Exempel:

  • Industrirobotar med lysdioder (eller QR-koder för långsammare rörliga objekt)
  • Identifiera och identifiera objekt i rummet
  • Identifiera och identifiera personer i rummet, till exempel placera holografiska kontaktkort över ansikten

Se även