Appareil photo localisable

Avant de commencer ici, nous vous recommandons de jeter un coup d’œil à notre article vue d’ensemble de l’appareil photo Locatable qui contient des informations de vue d’ensemble et un tableau avec les détails de l’appareil photo HoloLens 1 et 2.

Utilisation de MediaFrameReference

Ces instructions s’appliquent si vous utilisez la classe MediaFrameReference pour lire des images à partir de l’appareil photo.

Chaque image image (qu’il s’agisse d’une photo ou d’une vidéo) inclut un SpatialCoordinateSystem rooté au niveau de l’appareil photo au moment de la capture, qui est accessible à l’aide de la propriété CoordinateSystem de votre MediaFrameReference. Chaque image contient une description du modèle d’objectif de l’appareil photo, qui se trouve dans la propriété CameraIntrinsics . Ensemble, ces transformations définissent pour chaque pixel un rayon dans l’espace 3D représentant le chemin emprunté par les photons qui ont produit le pixel. Ces rayons peuvent être liés à d’autres contenus de l’application en obtenant la transformation du système de coordonnées de l’image vers un autre système de coordonnées (par exemple, à partir d’un cadre fixe de référence).

Chaque image image fournit les éléments suivants :

L’exemple HolographicFaceTracking montre la façon assez simple de rechercher la transformation entre le système de coordonnées de la caméra et vos propres systèmes de coordonnées d’application.

Utilisation de Media Foundation

Si vous utilisez Media Foundation directement pour lire des images à partir de l’appareil photo, vous pouvez utiliser l’attribut MFSampleExtension_CameraExtrinsics de chaque image et MFSampleExtension_PinholeCameraIntrinsics attribut pour localiser les images de caméra par rapport aux autres systèmes de coordonnées de votre application, comme indiqué dans cet exemple de code :

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

Scénarios d’utilisation des caméras locatables

Afficher une photo ou une vidéo dans le monde où elle a été capturée

Les images appareil photo sont fournies avec une transformation « Camera To World », qui peut être utilisée pour afficher exactement où se trouvait l’appareil lorsque l’image a été prise. Par exemple, vous pouvez positionner une petite icône holographique à cet emplacement (CameraToWorld.MultiplyPoint(Vector3.zero)) et même dessiner une petite flèche dans la direction où la caméra était orientée (CameraToWorld.MultiplyVector(Vector3.forward)).

Fréquence d'images

Il est essentiel de conserver une fréquence d’images d’application interactive, en particulier lorsqu’il s’agit d’algorithmes de reconnaissance d’images à long terme. Pour cette raison, nous utilisons généralement le modèle suivant :

  1. Thread principal : gère l’objet caméra
  2. Thread principal : demande de nouvelles images (asynchrone)
  3. Thread principal : passer de nouvelles images au thread de suivi
  4. Thread de suivi : traite l’image pour collecter des points clés
  5. Thread principal : déplace le modèle virtuel pour qu’il corresponde aux points clés trouvés
  6. Thread principal : répéter à partir de l’étape 2

Certains systèmes de marqueurs d’image ne fournissent qu’un seul emplacement de pixel (d’autres fournissent la transformation complète, auquel cas cette section n’est pas nécessaire), ce qui équivaut à un rayon d’emplacements possibles. Pour atteindre un seul troisième emplacement, nous pouvons ensuite tirer parti de plusieurs rayons et trouver le résultat final par leur intersection approximative. Pour ce faire, procédez comme suit :

  1. Obtenir une boucle en cours de collecte de plusieurs images de caméra
  2. Rechercher les points de caractéristique associés et leurs rayons mondiaux
  3. Lorsque vous disposez d’un dictionnaire de fonctionnalités, chacune avec plusieurs rayons mondiaux, vous pouvez utiliser le code suivant pour résoudre l’intersection de ces rayons :
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);
 }

Positionnement d’une scène modélisée

Avec au moins deux emplacements de balise suivis, vous pouvez positionner une scène modélisée en fonction du scénario actuel de l’utilisateur. Si vous ne pouvez pas supposer la gravité, vous aurez besoin de trois emplacements de balise. Dans de nombreux cas, nous utilisons un jeu de couleurs où les sphères blanches représentent les emplacements des étiquettes suivies en temps réel et les sphères bleues représentent les emplacements des étiquettes modélisées. Cela permet à l’utilisateur d’évaluer visuellement la qualité de l’alignement. Nous supposons la configuration suivante dans toutes nos applications :

  • Au moins deux emplacements de balise modélisés
  • Un « espace d’étalonnage », qui dans la scène est le parent des balises
  • Identificateur de fonctionnalité de l’appareil photo
  • Comportement, qui déplace l’espace d’étalonnage pour aligner les balises modélisées sur les balises en temps réel (nous prenons soin de déplacer l’espace parent, et non les marqueurs modélisés eux-mêmes, car les autres points de connexion sont des positions par rapport à eux).
// 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;

Suivre ou identifier des objets/visages fixes marqués ou en déplacement du monde réel à l’aide de LED ou d’autres bibliothèques de reconnaissance

Exemples :

  • Robots industriels à LED (ou codes QR pour les objets à déplacement plus lent)
  • Identifier et reconnaître les objets dans la salle
  • Identifier et reconnaître les personnes dans la salle, par exemple placer des cartes de visite holographiques sur des visages

Voir aussi