위치를 찾을 수 있는 카메라

여기서 시작하기 전에 개요 정보와 HoloLens 1 및 2 카메라 세부 정보가 포함된 테이블이 포함된 로케이블 카메라 개요 문서를 살펴보는 것이 좋습니다.

MediaFrameReference 사용

이러한 지침은 MediaFrameReference 클래스를 사용하여 카메라에서 이미지 프레임을 읽는 경우에 적용됩니다.

각 이미지 프레임(사진 또는 비디오)에는 캡처 시 카메라에 루팅된 SpatialCoordinateSystem이 포함되어 있으며 MediaFrameReferenceCoordinateSystem 속성을 사용하여 액세스할 수 있습니다. 각 프레임에는 CameraIntrinsics 속성에서 찾을 수 있는 카메라 렌즈 모델에 대한 설명이 포함되어 있습니다. 이러한 변환은 픽셀을 생성한 광자가 촬영한 경로를 나타내는 3D 공간의 각 픽셀에 대해 광선을 정의합니다. 이러한 광선은 프레임의 좌표계에서 다른 좌표계(예: 고정된 참조 프레임에서)로 변환을 가져와 앱의 다른 콘텐츠와 관련될 수 있습니다.

각 이미지 프레임은 다음을 제공합니다.

HolographicFaceTracking 샘플은 카메라의 좌표계와 사용자 고유의 애플리케이션 좌표계 간의 변환을 쿼리하는 매우 간단한 방법을 보여줍니다.

Media Foundation 사용

Media Foundation을 직접 사용하여 카메라에서 이미지 프레임을 읽는 경우 다음 샘플 코드와 같이 각 프레임의 MFSampleExtension_CameraExtrinsics 특성MFSampleExtension_PinholeCameraIntrinsics 특성을 사용하여 애플리케이션의 다른 좌표계를 기준으로 카메라 프레임을 찾을 수 있습니다.

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

찾기 가능한 카메라 사용 시나리오

캡처된 세계 사진 또는 비디오 표시

디바이스 카메라 프레임에는 "Camera To World" 변환이 함께 제공되며, 이 변환은 이미지를 촬영할 때 디바이스가 어디에 있었는지 정확하게 표시하는 데 사용할 수 있습니다. 예를 들어 이 위치(CameraToWorld.MultiplyPoint(Vector3.zero))에 작은 홀로그램 아이콘을 배치하고 카메라가 향하고 있는 방향으로 작은 화살표를 그릴 수도 있습니다(CameraToWorld.MultiplyVector(Vector3.forward)).

프레임 속도

특히 장기 실행 이미지 인식 알고리즘을 처리할 때 대화형 애플리케이션 프레임 속도를 유지하는 것이 중요합니다. 이러한 이유로 일반적으로 다음 패턴을 사용합니다.

  1. 주 스레드: 카메라 개체 관리
  2. 주 스레드: 새 프레임 요청(비동기)
  3. 주 스레드: 추적 스레드에 새 프레임 전달
  4. 추적 스레드: 이미지를 처리하여 키 포인트 수집
  5. 주 스레드: 찾은 키 포인트와 일치하도록 가상 모델을 이동합니다.
  6. 주 스레드: 2단계에서 반복

일부 이미지 표식 시스템은 단일 픽셀 위치만 제공합니다(이 섹션이 필요하지 않은 경우 전체 변환을 제공하는 경우도 있음). 이는 가능한 위치의 광선과 동일합니다. 단일 세 번째 위치에 도착하기 위해 여러 광선을 활용하고 대략적인 교집합을 통해 최종 결과를 찾을 수 있습니다. 이 작업을 수행하려면 다음 작업이 필요합니다.

  1. 여러 카메라 이미지를 수집하는 루프 가져오기
  2. 연결된 기능 지점 및 해당 세계 광선 찾기
  3. 여러 세계 광선이 있는 기능 사전이 있는 경우 다음 코드를 사용하여 해당 광선의 교차점에 대해 해결할 수 있습니다.
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);
 }

모델링된 장면 위치 지정

추적된 태그 위치가 두 개 이상 지정된 경우 사용자의 현재 시나리오에 맞게 모델링된 장면을 배치할 수 있습니다. 중력을 가정할 수 없는 경우 세 개의 태그 위치가 필요합니다. 대부분의 경우 흰색 구가 실시간으로 추적된 태그 위치를 나타내고 파란색 구가 모델링된 태그 위치를 나타내는 색 구성표를 사용합니다. 이렇게 하면 사용자가 맞춤 품질을 시각적으로 측정할 수 있습니다. 모든 애플리케이션에서 다음 설정을 가정합니다.

  • 둘 이상의 모델링된 태그 위치
  • 장면에서 태그의 부모인 하나의 '보정 공간'입니다.
  • 카메라 기능 식별자
  • 모델링된 태그를 실시간 태그에 맞추기 위해 보정 공간을 이동하는 동작(다른 연결은 해당 태그에 상대적인 위치이므로 모델링된 마커 자체가 아닌 부모 공간을 이동하도록 주의해야 합니다).
// 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;

LED 또는 기타 인식기 라이브러리를 사용하여 태그가 지정된 고정 또는 이동 실제 개체/얼굴 추적 또는 식별

예:

  • LED가 있는 산업용 로봇(또는 느리게 움직이는 물체를 위한 QR 코드)
  • 회의실에서 개체 식별 및 인식
  • 방에 있는 사람 식별 및 인식(예: 얼굴 위에 홀로그램 연락처 카드 배치)

참고 항목