Fotocamera individuabile

Prima di iniziare, ti consigliamo di esaminare il nostro 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 i fotogrammi immagine dalla fotocamera.

Ogni fotogramma dell'immagine (che si tratti di foto o video) include un oggetto 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 eseguito 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 frame ad un altro sistema di coordinate ,ad esempio da un frame fisso di riferimento.

Ogni cornice di immagine fornisce quanto segue:

L'esempio HolographicFaceTracking mostra il modo abbastanza semplice per eseguire query sulla trasformazione tra il sistema di coordinate della fotocamera e i propri sistemi di coordinate dell'applicazione.

Uso di Media Foundation

Se si usa Media Foundation direttamente per leggere i fotogrammi immagine dalla fotocamera, è possibile usare l'attributo MFSampleExtension_CameraExtrinsics di ogni fotogramma e MFSampleExtension_PinholeCameraIntrinsics attributo per individuare i fotogrammi della fotocamera rispetto 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 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 };
};

Scenari di utilizzo della fotocamera locatable

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

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

Frame Rate

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

  1. Thread principale: gestisce l'oggetto fotocamera
  2. Thread principale: richiede nuovi frame (asincrono)
  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 in pixel (altri forniscono la trasformazione completa nel qual caso questa sezione non sarà necessaria), che equivale a un raggio di possibili posizioni. Per arrivare a un'unica 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 che raccoglie più immagini della fotocamera
  2. Trovare i punti di funzionalità associati e i relativi raggi del mondo
  3. Quando si dispone di un dizionario di funzionalità, ognuno con più raggi mondiali, è 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

Dato due o più percorsi di tag rilevati, è possibile posizionare una scena modellata per adattarsi allo scenario corrente dell'utente. Se non si può presupporre la gravità, saranno necessarie tre posizioni tag. In molti casi viene usata una combinazione di colori in cui le sfere bianche rappresentano posizioni dei tag tracciate in tempo reale e le sfere blu rappresentano 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ù percorsi di tag modellati
  • 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 (è consigliabile spostare lo spazio padre, non i marcatori modellati stessi, perché altre connessioni sono posizioni relative a tali tag).
// 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 o visi reali contrassegnati con tag o spostamento di oggetti/visi usando LED o altre librerie di riconoscimento

Esempi:

  • Robot industriali con LED (o codici a matrice per oggetti in movimento più lenti)
  • Identificare e riconoscere gli oggetti nella stanza
  • Identificare e riconoscere le persone nella stanza, ad esempio inserendo schede contatto olografiche su visi

Vedi anche