Câmera localizável

Antes de começar aqui, recomendamos que você dê uma olhada em nosso artigo visão geral da câmera localizável que contém informações de visão geral e uma tabela com detalhes da câmera do HoloLens 1 e 2.

Usando MediaFrameReference

Essas instruções se aplicam se você estiver usando a classe MediaFrameReference para ler quadros de imagem da câmera.

Cada quadro de imagem (seja foto ou vídeo) inclui um SpatialCoordinateSystem com raiz na câmera no momento da captura, que pode ser acessado usando a propriedade CoordinateSystem de seu MediaFrameReference. Cada quadro contém uma descrição do modelo de lente da câmera, que pode ser encontrado na propriedade CameraIntrinsics . Juntas, essas transformações definem para cada pixel um raio no espaço 3D que representa o caminho obtido pelos fótons que produziram o pixel. Esses raios podem estar relacionados a outro conteúdo no aplicativo obtendo a transformação do sistema de coordenadas do quadro para algum outro sistema de coordenadas (por exemplo, de um quadro de referência estacionário).

Cada quadro de imagem fornece o seguinte:

O exemplo HolographicFaceTracking mostra a maneira bastante simples de consultar a transformação entre o sistema de coordenadas da câmera e seus próprios sistemas de coordenadas de aplicativo.

Usando o Media Foundation

Se você estiver usando o Media Foundation diretamente para ler quadros de imagem da câmera, poderá usar o atributo MFSampleExtension_CameraExtrinsics de cada quadro e MFSampleExtension_PinholeCameraIntrinsics atributo para localizar quadros de câmera em relação aos outros sistemas de coordenadas do aplicativo, conforme mostrado neste código de exemplo:

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

Cenários de uso de câmera localizados

Mostrar uma foto ou vídeo no mundo onde ele foi capturado

Os quadros da Câmera do Dispositivo vêm com uma transformação "Câmera para Mundo", que pode ser usada para mostrar exatamente onde o dispositivo estava quando a imagem foi tirada. Por exemplo, você pode posicionar um pequeno ícone holográfico neste local (CameraToWorld.MultiplyPoint(Vector3.zero)) e até mesmo desenhar uma pequena seta na direção que a câmera estava voltada (CameraToWorld.MultiplyVector(Vector3.forward)).

Taxa de Quadros

Manter uma taxa de quadros de aplicativo interativa é essencial, especialmente ao lidar com algoritmos de reconhecimento de imagem de longa execução. Por esse motivo, normalmente usamos o seguinte padrão:

  1. Thread Principal: gerencia o objeto de câmera
  2. Thread Principal: solicita novos quadros (assíncrono)
  3. Thread Principal: passar novos quadros para o thread de rastreamento
  4. Rastreamento do Thread: processa a imagem para coletar pontos-chave
  5. Thread Principal: move o modelo virtual para corresponder aos pontos-chave encontrados
  6. Thread Principal: repetir da etapa 2

Alguns sistemas de marcadores de imagem fornecem apenas um único local de pixel (outros fornecem a transformação completa, caso em que esta seção não será necessária), o que equivale a um raio de possíveis locais. Para chegar a um único terceiro local, podemos aproveitar vários raios e encontrar o resultado final por sua interseção aproximada. Para fazer isso, você precisará:

  1. Obter um loop que está coletando várias imagens de câmera
  2. Localizar os pontos de recurso associados e seus raios mundiais
  3. Quando você tiver um dicionário de recursos, cada um com vários raios mundiais, poderá usar o seguinte código para resolver a interseçã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);
 }

Posicionando uma cena modelada

Considerando dois ou mais locais de marcas controladas, você pode posicionar uma cena modelada para se ajustar ao cenário atual do usuário. Se você não puder assumir a gravidade, precisará de três locais de marca. Em muitos casos, usamos um esquema de cores em que as esferas brancas representam locais de marcas controladas em tempo real e as esferas azuis representam locais de marca modelada. Isso permite que o usuário avalie visualmente a qualidade do alinhamento. Presumimos a seguinte configuração em todos os nossos aplicativos:

  • Dois ou mais locais de marca modelados
  • Um 'espaço de calibragem', que na cena é o pai das marcas
  • Identificador de recurso de câmera
  • Comportamento, que move o espaço de calibragem para alinhar as marcas modeladas com as marcas em tempo real (temos o cuidado de mover o espaço pai, não os marcadores modelados em si, porque outras posições de conexão são 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 do mundo real marcados ou em movimentação de objetos/rostos do mundo real usando LEDs ou outras bibliotecas de reconhecedores

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, colocando cartões de contato holográficos sobre rostos

Confira também