Cámara localizable

Antes de empezar aquí, se recomienda echar un vistazo al artículo Información general de la cámara localizable, que contiene información general y una tabla con los detalles de la cámara HoloLens 1 y 2.

Uso de MediaFrameReference

Estas instrucciones se aplican si usa la clase MediaFrameReference para leer fotogramas de imagen de la cámara.

Cada fotograma de imagen (ya sea una foto o un vídeo) incluye un elemento SpatialCoordinateSystem basado en la cámara en el momento de la captura, al que se puede acceder mediante la propiedad CoordinateSystem de MediaFrameReference. Cada fotograma contiene una descripción del modelo de lente de la cámara, que se puede encontrar en la propiedad CameraIntrinsics. Juntas, estas transformaciones definen para cada píxel un rayo en el espacio 3D que representa la ruta de acceso tomada por los fotones que generaron el píxel. Estos rayos pueden estar relacionados con otro contenido de la aplicación mediante la obtención de la transformación del sistema de coordenadas del marco a algún otro sistema de coordenadas (por ejemplo, deun marco de referencia estacionado).

Cada marco de imagen proporciona lo siguiente:

El ejemplo HolographicFaceTracking muestra la manera bastante sencilla de consultar la transformación entre el sistema de coordenadas de la cámara y sus propios sistemas de coordenadas de aplicación.

Uso de Media Foundation

Si usa Media Foundation directamente para leer fotogramas de imagen de la cámara, puede usar el atributo MFSampleExtension_CameraExtrinsics y el atributo MFSampleExtension_PinholeCameraIntrinsics de cada fotograma para buscar fotogramas de cámara en relación con otros sistemas de coordenadas de la aplicación, como se muestra en este código de ejemplo:

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

Escenarios de uso de cámara localizables

Mostrar una foto o un vídeo en el mundo donde se capturó

Los fotogramas de la cámara del dispositivo vienen con una transformación "Cámara a mundo", que se puede usar para mostrar exactamente dónde estaba el dispositivo cuando se tomó la imagen. Por ejemplo, podría colocar un pequeño icono holográfico en esta ubicación (CameraToWorld.MultiplyPoint(Vector3.zero)) e incluso dibujar una pequeña flecha en la dirección a la que estaba orientada la cámara (CameraToWorld.MultiplyVector(Vector3.forward)).

Velocidad de fotogramas

Mantener una velocidad de fotogramas de aplicación interactiva es fundamental, especialmente cuando se trabaja con algoritmos de reconocimiento de imágenes de ejecución larga. Por esta razón, normalmente usamos el siguiente patrón:

  1. Subproceso principal: administra el objeto de cámara
  2. Subproceso principal: solicita nuevos fotogramas (asincrónicos)
  3. Subproceso principal: pasar nuevos fotogramas al subproceso de seguimiento
  4. Subproceso de seguimiento: procesa la imagen para recopilar puntos clave
  5. Subproceso principal: mueve el modelo virtual para que coincida con los puntos clave encontrados
  6. Subproceso principal: repita el paso 2

Algunos sistemas de marcadores de imagen solo proporcionan una ubicación de un solo píxel (otros proporcionan la transformación completa en cuyo caso esta sección no será necesaria), lo que equivale a un rayo de ubicaciones posibles. Para llegar a una sola tercera ubicación, podemos aprovechar varios rayos y encontrar el resultado final por su intersección aproximada. Para ello, necesitará lo siguiente:

  1. Obtener un bucle que va a recopilar varias imágenes de cámara
  2. Buscar los puntos de características asociados y sus rayos del mundo
  3. Cuando tenga un diccionario de características, cada una con varios rayos del mundo, puede usar el código siguiente para resolver la intersección de esos rayos:
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);
 }

Colocación de una escena modelada

Dadas dos o más ubicaciones de etiquetas con seguimiento, puede colocar una escena modelada para adaptarla al escenario actual del usuario. Si no puede asumir la gravedad, necesitará tres ubicaciones de etiquetas. En muchos casos, usamos una combinación de colores donde las esferas blancas representan ubicaciones de etiquetas con seguimiento en tiempo real y las esferas azules representan ubicaciones de etiquetas modeladas. Esto permite al usuario medir visualmente la calidad de alineación. Se supone que la siguiente configuración se encuentra en todas nuestras aplicaciones:

  • Dos o más ubicaciones de etiquetas modeladas
  • Un "espacio de calibración", que en la escena es el elemento primario de las etiquetas
  • Identificador de característica de cámara
  • Comportamiento, que mueve el espacio de calibración para alinear las etiquetas modeladas con las etiquetas en tiempo real (tenemos cuidado de mover el espacio primario, no los propios marcadores modelados, porque otras conectaciones son posiciones relativas a ellos).
// 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;

Seguimiento o identificación de caras o objetos del mundo real etiquetados o estacionados mediante LED u otras bibliotecas de reconocedores

Ejemplos:

  • Robots industriales con LED (o códigos QR para objetos de movimiento más lentos)
  • Identificación y reconocimiento de objetos en la sala
  • Identificar y reconocer personas en la sala, por ejemplo, colocar tarjetas de contacto holográficas sobre caras

Consulte también