Cámara localizable

Antes de empezar aquí, te recomendamos que eches un vistazo a nuestro artículo de información general sobre la cámara locatable 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 foto o vídeo) incluye un SpatialCoordinateSystem rooteado en la cámara en el momento de la captura, al que se puede acceder mediante la propiedad CoordinateSystem de su MediaFrameReference. Cada fotograma contiene una descripción del modelo de lente de 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 produjeron 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 otro sistema de coordenadas (por ejemplo, desde un marco fijo de referencia).

Cada fotograma 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 la 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 de cada fotograma y MFSampleExtension_PinholeCameraIntrinsics atributo para buscar fotogramas de cámara en relación con los demás 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 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 };
};

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 de 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 larga duración. Por este motivo, normalmente usamos el siguiente patrón:

  1. Subproceso principal: administra el objeto de cámara.
  2. Subproceso principal: solicita nuevos fotogramas (asincrónico)
  3. Subproceso principal: pase 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: repetir desde el paso 2

Algunos sistemas de marcadores de imagen solo proporcionan una ubicación de píxel único (otros proporcionan la transformación completa en cuyo caso no se necesitará esta sección), lo que equivale a un rayo de posibles ubicaciones. 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. Obtención de 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 uno con varios rayos mundiales, 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 de seguimiento, puede colocar una escena modelada para ajustarse 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 en la que las esferas blancas representan ubicaciones de etiquetas de 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 realiza en todas las 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ísticas de la 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 posiciones de conexión son relativas a ellas).
// 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;

Realizar un seguimiento o identificar objetos o caras etiquetados fijos o móviles en el mundo real mediante LED u otras bibliotecas de reconocedores

Ejemplos:

  • Robots industriales con LED (o códigos QR para objetos móviles 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