Câmera localizável

Antes de começar aqui, recomendamos que você dê uma olhada em nosso artigo visão geral da câmera locatário que contém informações de visão geral e uma tabela com detalhes da câmera 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 encontrada na propriedade CameraIntrinsics. Juntas, essas transformação definem para cada pixel um raio no espaço 3D que representa o caminho feito pelos fótons que produziram o pixel. Esses raios podem estar relacionados aoutro 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 Media Foundation

Se você estiver usando o Media Foundation diretamente para ler quadros de imagem da câmera, poderá usar o atributo MFSampleExtension_CameraExtrinsics e o atributo MFSampleExtension_PinholeCameraIntrinsics de cada quadro para localizar quadros da 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 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 uso de câmeras localizados

Mostrar uma foto ou vídeo no mundo em que ela foi capturada

Os quadros da Câmera do Dispositivo vêm com uma transformação "Câmera para o 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 nesse local (CameraToWorld.MultiplyPoint(Vector3.zero)) e até mesmo desenhar uma pequena seta na direção em que a câmera estava voltada (CameraToWorld.MultiplyVector(Vector3.forward)).

Taxa de quadros

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

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

Alguns sistemas de marcador de imagem fornecem apenas um único local de pixel (outros fornecem a transformação completa; nesse caso, essa 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 localizar o resultado final por sua interseção aproximada. Para fazer isso, você precisará:

  1. Obter um loop que vai coletar várias imagens de câmera
  2. Encontrar os pontos de recurso associados e seus raios de mundo
  3. Quando você tiver um dicionário de recursos, cada um com vários raios do mundo, poderá usar o código a seguir 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 marca rastreados, 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 marca rastreados em tempo real e as esferas azuis representam locais de marca modelados. Isso permite que o usuário mede visualmente a qualidade do alinhamento. Pressupomos a seguinte configuração em todos os nossos aplicativos:

  • Dois ou mais locais de marcação modelados
  • Um 'espaço de calibragem', que na cena é o pai das marcas
  • Identificador de recurso da câmera
  • Comportamento, que move o espaço de calibragem para alinhar as marcas modeladas com as marcas em tempo real (temos cuidado para mover o espaço pai, não os marcadores modelados em si, porque outras se conectam são posições relativas a eles).
// 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/faces do mundo real marcados ou móveis usando LEDs ou outras bibliotecas de reconhecedor

Exemplos:

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

Confira também