Fotocamera individuabile

Prima di iniziare, è consigliabile esaminare l'articolo panoramica della fotocamera locatable che contiene informazioni generali e una tabella con i dettagli della fotocamera HoloLens 1 e 2.

Uso di MediaFrameReference

Queste istruzioni si applicano se si usa la classe MediaFrameReference per leggere fotogrammi di immagine dalla fotocamera.

Ogni fotogramma dell'immagine (foto o video) include un spatialCoordinateSystem con radice nella fotocamera al momento dell'acquisizione, accessibile tramite la proprietà CoordinateSystem di MediaFrameReference. Ogni fotogramma contiene una descrizione del modello di obiettivo della fotocamera, disponibile nella proprietà CameraIntrinsics. Insieme, queste trasformazioni definiscono per ogni pixel un raggio nello spazio 3D che rappresenta il percorso tracciato dai fotoni che hanno prodotto il pixel. Questi raggi possono essere correlati ad altri contenuti nell'app ottenendo la trasformazione dal sistema di coordinate del fotogramma a un altro sistema di coordinate (ad esempio da un fotogramma zionario di riferimento).

Ogni cornice di immagine offre quanto segue:

L'esempio HolographicFaceTracking illustra il modo piuttosto semplice per eseguire una query per la trasformazione tra il sistema di coordinate della fotocamera e i sistemi di coordinate dell'applicazione.

Uso di Media Foundation

Se si usa Media Foundation direttamente per leggere fotogrammi di immagine dalla fotocamera, è possibile usare l'attributo MFSampleExtension_CameraExtrinsics e l'attributo MFSampleExtension_PinholeCameraIntrinsics di ogni fotogramma per individuare i fotogrammi della fotocamera relativi agli altri sistemi di coordinate dell'applicazione, come illustrato in questo codice di esempio:

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

Scenari di utilizzo delle fotocamere localizzate

Visualizzare una foto o un video nel mondo in cui è stato acquisito

I fotogrammi della fotocamera del dispositivo sono associati a una trasformazione "Camera to World", che può essere usata per mostrare esattamente dove si trova il dispositivo quando è stata scattata l'immagine. Ad esempio, è possibile posizionare una piccola icona olografica in questa posizione (CameraToWorld.MultiplyPoint(Vector3.zero)) e persino disegnare una piccola freccia nella direzione in cui la fotocamera era rivolta (CameraToWorld.MultiplyVector(Vector3.forward)).

Frame Rate

Mantenere una frequenza fotogrammi interattiva dell'applicazione è fondamentale, soprattutto quando si gestiscono algoritmi di riconoscimento delle immagini a esecuzione elevata. Per questo motivo, si usa in genere il modello seguente:

  1. Thread principale: gestisce l'oggetto fotocamera
  2. Thread principale: richiede nuovi frame (asincroni)
  3. Thread principale: passare nuovi frame al thread di rilevamento
  4. Thread di rilevamento: elabora l'immagine per raccogliere i punti chiave
  5. Thread principale: sposta il modello virtuale in modo che corrisponda ai punti chiave trovati
  6. Thread principale: ripetere dal passaggio 2

Alcuni sistemi di marcatori di immagine forniscono solo una posizione di pixel singolo (altri forniscono la trasformazione completa, nel qual caso questa sezione non sarà necessaria), che equivale a un raggio di possibili posizioni. Per raggiungere una singola terza posizione, è quindi possibile sfruttare più raggi e trovare il risultato finale in base all'intersezione approssimativa. A tale scopo è necessario:

  1. Ottenere un ciclo per raccogliere più immagini della fotocamera
  2. Trovare i punti caratteristiche associati e i relativi raggi del mondo
  3. Quando si dispone di un dizionario di funzionalità, ognuna con più raggi del mondo, è possibile usare il codice seguente per risolvere l'intersezione di tali raggi:
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);
 }

Posizionamento di una scena modellata

Dati due o più percorsi di tag monitorati, è possibile posizionare una scena modellata in base allo scenario corrente dell'utente. Se non si può presupporre la gravità, sono necessarie tre posizioni di tag. In molti casi, si usa una combinazione di colori in cui le sfera bianca rappresentano le posizioni dei tag tracciati in tempo reale e le aree blu rappresentano le posizioni dei tag modellate. Ciò consente all'utente di misurare visivamente la qualità dell'allineamento. Si presuppone la configurazione seguente in tutte le applicazioni:

  • Due o più posizioni di tag modellate
  • Uno "spazio di calibrazione", che nella scena è l'elemento padre dei tag
  • Identificatore della funzionalità fotocamera
  • Comportamento, che sposta lo spazio di calibrazione per allineare i tag modellati con i tag in tempo reale (è importante spostare lo spazio padre, non i marcatori modellati stessi, perché altri elementi di connessione sono posizioni relative a essi).
// 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;

Rilevare o identificare oggetti/visi reali con tag stazionari o in movimento usando LED o altre librerie di riconoscimento

Esempi:

  • Robot industriali con LED (o codici a qr per oggetti in movimento più lento)
  • Identificare e riconoscere gli oggetti nella stanza
  • Identificare e riconoscere le persone nella stanza, ad esempio posizionando schede di contatto olografiche sui visi

Vedi anche