Fotocamera individuabile

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

Uso di MediaFrameReference

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

Ogni fotogramma dell'immagine (foto o video) include un spatialCoordinateSystem rooted 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 di riferimento zionario.

Ogni frame 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 i fotogrammi dell'immagine dalla fotocamera, è possibile usare l'attributo MFSampleExtension_CameraExtrinsics di ogni fotogramma e l'attributo MFSampleExtension_PinholeCameraIntrinsics 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 hanno una trasformazione "Camera To World", che può essere usata per mostrare esattamente dove si trova il dispositivo quando è stata eseguita 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 verso cui era rivolta la fotocamera (CameraToWorld.MultiplyVector(Vector3.forward)).

Frame Rate

Mantenere una frequenza dei fotogrammi dell'applicazione interattiva è fondamentale, soprattutto quando si gestiscono algoritmi di riconoscimento delle immagini a esecuzione elevata. Per questo motivo, in genere viene usato 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 un singolo pixel (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 raccogliendo più immagini della fotocamera
  2. Trovare i punti caratteristiche associati e i raggi del mondo
  3. Quando si dispone di un dizionario di caratteristiche, 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

Date due o più posizioni di tag rilevate, è possibile posizionare una scena modellata in base allo scenario corrente dell'utente. Se non si può presupporre la gravità, saranno necessari tre percorsi 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 sphere blu rappresentano le posizioni dei tag modellate. In questo modo l'utente può 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 di funzionalità della fotocamera
  • Comportamento, che sposta lo spazio di calibrazione per allineare i tag modellati con i tag in tempo reale (è necessario prestare attenzione a spostare lo spazio padre, non i marcatori modellati stessi, perché altre posizioni di connessione sono 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;

Tenere traccia 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 codici a barre per oggetti in movimento più lenti)
  • Identificare e riconoscere gli oggetti nella stanza
  • Identificare e riconoscere le persone nella stanza, ad esempio inserendo schede di contatto olografiche sui visi

Vedi anche