Câmera localização

Antes de começar aqui, recomendamos que dê uma olhada no nosso artigo de visão geral da câmara local que contém informações gerais e uma tabela com HoloLens detalhes da câmara 1 e 2.

Usando a MediaFrameReference

Estas instruções aplicam-se se estiver a utilizar a aula MediaFrameReference para ler as molduras da câmara.

Cada quadro de imagem (seja foto ou vídeo) inclui um Sistema EspacialCoordinate enraizado na câmara no momento da captura, que pode ser acedido usando a propriedade Do Sistema de Coordenadas da sua MediaFrameReference. Cada quadro contém uma descrição do modelo de lente da câmara, que pode ser encontrado na propriedade CameraIntrinsics. Juntas, estas transformações definem para cada pixel um raio em espaço 3D representando o caminho percorrido pelos fotões que produziram o pixel. Estes raios podem estar relacionados com outros conteúdos da app, obtendo a transformação do sistema de coordenadas do quadro para outro sistema de coordenadas (por exemplo, a partir de um quadro estacionário de referência).

Cada quadro de imagem fornece o seguinte:

A amostra HolographicFaceTracking mostra a forma bastante simples de consultar a transformação entre o sistema de coordenadas da câmara e os seus próprios sistemas de coordenadas de aplicação.

Utilização da Media Foundation

Se estiver a utilizar a Media Foundation diretamente para ler quadros de imagem a partir da câmara, pode utilizar o atributo MFSampleExtension_CameraExtrinsics de cada frame e MFSampleExtension_PinholeCameraIntrinsics atributo para localizar quadros de câmaras relativos aos outros sistemas de coordenadas da sua aplicação, como mostra este código de amostra:

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

Cenários de utilização da câmara localização

Mostre uma foto ou vídeo no mundo onde foi capturado

As molduras da Câmara do Dispositivo vêm com uma transformação "Camera To World", que pode ser usada para mostrar exatamente onde o dispositivo estava quando a imagem foi tirada. Por exemplo, pode posicionar um pequeno ícone holográfico neste local (CameraToWorld.MultiplyPoint (Vetor3.zero)) e até desenhar uma pequena seta na direção que a câmara estava virada (CameraToWorld.MultiplyVector (Vetor3.forward)).

Taxa de Fotogramas

Manter uma taxa de fotogramas de aplicação interativa é fundamental, especialmente quando se lida com algoritmos de reconhecimento de imagem de longa duração. Por esta razão, usamos comumente o seguinte padrão:

  1. Fio Principal: gere o objeto da câmara
  2. Fio Principal: solicita novos quadros (async)
  3. Fio Principal: passe novos quadros para o fio de rastreio
  4. Tracking Thread: processa a imagem para recolher pontos-chave
  5. Fio Principal: move modelo virtual para combinar pontos-chave encontrados
  6. Fio Principal: repetir do passo 2

Alguns sistemas de marcador de imagem apenas fornecem uma única localização de pixel (outros fornecem a transformação completa, caso em que esta secção não será necessária), o que equivale a um raio de possíveis localizações. Para chegar a um único terceiro local, podemos então aproveitar vários raios e encontrar o resultado final pela sua intersecção aproximada. Para fazer isto, terá de:

  1. Obtenha um loop indo recolhendo várias imagens de câmara
  2. Encontre os pontos de características associados e os seus raios mundiais
  3. Quando tiver um dicionário de funcionalidades, cada uma com vários raios mundiais, pode usar o seguinte código para resolver para a intersecção desses raios:
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);
 }

Posicionamento de uma cena modelada

Tendo em conta dois ou mais locais de identificação, pode posicionar uma cena modelada para se adaptar ao cenário atual do utilizador. Se não consegues assumir a gravidade, vais precisar de três localizações. Em muitos casos, usamos um esquema de cores onde esferas brancas representam locais de identificação rastreados em tempo real, e esferas azuis representam locais de marcação modelados. Isto permite ao utilizador medir visualmente a qualidade do alinhamento. Assumimos a seguinte configuração em todas as nossas aplicações:

  • Dois ou mais locais de marcação modelados
  • Um "espaço de calibração", que na cena é o pai das etiquetas
  • Identificador de recurso de câmara
  • Comportamento, que move o espaço de calibração para alinhar as tags modeladas com as etiquetas em tempo real (temos o cuidado de mover o espaço dos pais, não os próprios marcadores modelados, porque outras ligações são posições relativas a elas).
// 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;

Rastrear ou identificar objetos/rostos estacionários ou móveis do mundo real utilizando LEDs ou outras bibliotecas reconhecíveis

Exemplos:

  • Robôs industriais com LEDs (ou códigos QR para objetos móveis mais lentos)
  • Identificar e reconhecer objetos na sala
  • Identificar e reconhecer pessoas na sala, por exemplo, colocar cartões de contacto holográficos sobre rostos

Ver também