Камера с определяемым местоположением
Прежде чем приступить к работе, рекомендуем ознакомиться со статьей Обзор камеры Locatable , которая содержит обзорную информацию и таблицу с подробными сведениями о камере HoloLens 1 и 2.
Использование MediaFrameReference
Эти инструкции применяются, если вы используете класс MediaFrameReference для чтения кадров изображений с камеры.
Каждый кадр изображения (как фото, так и видео) включает spatialCoordinateSystem , укорененная на камере во время захвата, доступ к которой можно получить с помощью свойства CoordinateSystemобъекта MediaFrameReference. Каждый кадр содержит описание модели объектива камеры, которое можно найти в свойстве CameraIntrinsics . Вместе эти преобразования определяют для каждого пикселя луч в трехмерном пространстве, представляющий путь, сделанный фотонами, создающими пиксель. Эти лучи могут быть связаны с другим содержимым в приложении путем получения преобразования из системы координат кадра в какую-то другую систему координат (например, из стационарной системы отсчета).
Каждый кадр изображения предоставляет следующие возможности:
- Пиксельные данные (в формате RGB/NV12/JPEG/т. д.)
- Объект SpatialCoordinateSystem из расположения записи
- Класс CameraIntrinsics, содержащий режим объектива камеры
В примере 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 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 };
};
Сценарии использования камеры с поддержкой locatable
Показать фотографию или видео в мире, где они были сняты
Кадры камеры устройства поставляются с преобразованием "Камера в мир", которое можно использовать для отображения точного расположения устройства на момент получения изображения. Например, можно разместить небольшой голографический значок в этом расположении (CameraToWorld.MultiplyPoint(Vector3.zero)) и даже нарисовать маленькую стрелку в направлении, к которому была обращена камера (CameraToWorld.MultiplyVector(Vector3.forward)).
Частота кадров
Сохранение частоты кадров в интерактивном приложении имеет решающее значение, особенно при работе с длительными алгоритмами распознавания изображений. По этой причине мы обычно используем следующий шаблон:
- Основной поток: управляет объектом камеры.
- Основной поток: запрашивает новые кадры (асинхронные)
- Основной поток: передача новых кадров в поток отслеживания
- Поток отслеживания: обрабатывает изображение для сбора ключевых точек.
- Основной поток: перемещает виртуальную модель в соответствие с найденными ключевыми точками
- Основной поток: повтор из шага 2
Некоторые системы маркеров изображений предоставляют только одно расположение пикселей (другие предоставляют полное преобразование, в этом случае этот раздел не потребуется), что приравнивается к лучу возможных расположений. Чтобы добраться до одного третьего расположения, мы можем использовать несколько лучей и найти конечный результат по их приблизительному пересечению. Вот как это сделать.
- Получение цикла собирает несколько изображений камеры
- Поиск связанных точек признаков и их мировых лучей
- При наличии словаря признаков с несколькими лучами мира можно использовать следующий код для решения проблемы пересечения этих лучей:
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;
Отслеживание или идентификация помеченных стационарных или движущихся реальных объектов или лиц с помощью светодиодов или других библиотек распознавателя
Примеры:
- Промышленные роботы со светодиодами (или QR-кодами для медленно движущихся объектов)
- Идентификация и распознавание объектов в комнате
- Идентификация и распознавание людей в комнате, например размещение голографических карточек контакта на лицах