659 lines
16 KiB
C++
659 lines
16 KiB
C++
|
// Fill out your copyright notice in the Description page of Project Settings.
|
||
|
#include "AzureKinectDevice.h"
|
||
|
#include "Runtime/RHI/Public/RHI.h"
|
||
|
|
||
|
DEFINE_LOG_CATEGORY(AzureKinectDeviceLog);
|
||
|
|
||
|
UAzureKinectDevice::UAzureKinectDevice() :
|
||
|
NativeDevice(nullptr),
|
||
|
Thread(nullptr),
|
||
|
DeviceIndex(-1),
|
||
|
bOpen(false),
|
||
|
NumTrackedSkeletons(0),
|
||
|
DepthMode(EKinectDepthMode::NFOV_2X2BINNED),
|
||
|
ColorMode(EKinectColorResolution::RESOLUTION_720P),
|
||
|
Fps(EKinectFps::PER_SECOND_30),
|
||
|
SensorOrientation(EKinectSensorOrientation::DEFAULT),
|
||
|
bSkeletonTracking(false)
|
||
|
{
|
||
|
LoadDevices();
|
||
|
}
|
||
|
|
||
|
UAzureKinectDevice::UAzureKinectDevice(const FObjectInitializer& ObjectInitializer) :
|
||
|
Super(ObjectInitializer)
|
||
|
{
|
||
|
LoadDevices();
|
||
|
}
|
||
|
|
||
|
void UAzureKinectDevice::LoadDevices()
|
||
|
{
|
||
|
|
||
|
int32 NumKinect = GetNumConnectedDevices();
|
||
|
|
||
|
DeviceList.Empty(NumKinect + 1);
|
||
|
DeviceList.Add(MakeShared<FString>("No Device"));
|
||
|
|
||
|
if (NumKinect > 0)
|
||
|
{
|
||
|
for (int32 i = 0; i < NumKinect; i++)
|
||
|
{
|
||
|
try
|
||
|
{
|
||
|
// Open connection to the device.
|
||
|
k4a::device Device = k4a::device::open(i);
|
||
|
// Get and store the device serial number
|
||
|
DeviceList.Add(MakeShared<FString>(Device.get_serialnum().c_str()));
|
||
|
Device.close();
|
||
|
}
|
||
|
catch (const k4a::error& Err)
|
||
|
{
|
||
|
UE_LOG(AzureKinectDeviceLog, Error, TEXT("Can't load: %s"), TCHAR_TO_UTF8(ANSI_TO_TCHAR(Err.what())));
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
bool UAzureKinectDevice::StartDevice()
|
||
|
{
|
||
|
if (bOpen)
|
||
|
{
|
||
|
UE_LOG(AzureKinectDeviceLog, Warning, TEXT("This Device has been open."));
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
|
||
|
if (DeviceIndex == -1)
|
||
|
{
|
||
|
UE_LOG(AzureKinectDeviceLog, Warning, TEXT("No Device is selected."));
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
CalcFrameCount();
|
||
|
|
||
|
try
|
||
|
{
|
||
|
// Open connection to the device.
|
||
|
NativeDevice = k4a::device::open(DeviceIndex);
|
||
|
|
||
|
// Start the Camera and make sure the Depth Camera is Enabled
|
||
|
k4a_device_configuration_t DeviceConfig = K4A_DEVICE_CONFIG_INIT_DISABLE_ALL;
|
||
|
DeviceConfig.depth_mode = static_cast<k4a_depth_mode_t>(DepthMode);
|
||
|
DeviceConfig.color_resolution = static_cast<k4a_color_resolution_t>(ColorMode);
|
||
|
DeviceConfig.camera_fps = static_cast<k4a_fps_t>(Fps);
|
||
|
DeviceConfig.color_format = k4a_image_format_t::K4A_IMAGE_FORMAT_COLOR_BGRA32;
|
||
|
DeviceConfig.synchronized_images_only = true;
|
||
|
DeviceConfig.wired_sync_mode = K4A_WIRED_SYNC_MODE_STANDALONE;
|
||
|
|
||
|
NativeDevice.start_cameras(&DeviceConfig);
|
||
|
|
||
|
KinectCalibration = NativeDevice.get_calibration(DeviceConfig.depth_mode, DeviceConfig.color_resolution);
|
||
|
KinectTransformation = k4a::transformation(KinectCalibration);
|
||
|
|
||
|
if (bSkeletonTracking)
|
||
|
{
|
||
|
k4abt_tracker_configuration_t TrackerConfig = K4ABT_TRACKER_CONFIG_DEFAULT;
|
||
|
TrackerConfig.sensor_orientation = static_cast<k4abt_sensor_orientation_t>(SensorOrientation);
|
||
|
|
||
|
// Retain body tracker
|
||
|
BodyTracker = k4abt::tracker::create(KinectCalibration, TrackerConfig);
|
||
|
}
|
||
|
|
||
|
}
|
||
|
catch (const k4a::error& Err)
|
||
|
{
|
||
|
if (NativeDevice)
|
||
|
{
|
||
|
NativeDevice.close();
|
||
|
}
|
||
|
|
||
|
FString Msg(ANSI_TO_TCHAR(Err.what()));
|
||
|
UE_LOG(AzureKinectDeviceLog, Error, TEXT("Cant't open: %s"), *Msg);
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
Thread = new FAzureKinectDeviceThread(this);
|
||
|
|
||
|
bOpen = true;
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
bool UAzureKinectDevice::StopDevice()
|
||
|
{
|
||
|
|
||
|
if (!bOpen)
|
||
|
{
|
||
|
UE_LOG(AzureKinectDeviceLog, Warning, TEXT("KinectDevice is not running."));
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if (Thread)
|
||
|
{
|
||
|
Thread->EnsureCompletion();
|
||
|
Thread = nullptr;
|
||
|
}
|
||
|
|
||
|
if (BodyTracker)
|
||
|
{
|
||
|
BodyTracker.shutdown();
|
||
|
BodyTracker.destroy();
|
||
|
BodyTracker = nullptr;
|
||
|
}
|
||
|
|
||
|
if (RemapImage)
|
||
|
{
|
||
|
RemapImage.reset();
|
||
|
}
|
||
|
|
||
|
if (NativeDevice)
|
||
|
{
|
||
|
NativeDevice.stop_cameras();
|
||
|
NativeDevice.close();
|
||
|
NativeDevice = nullptr;
|
||
|
UE_LOG(AzureKinectDeviceLog, Verbose, TEXT("KinectDevice Camera is Stopped and Closed."));
|
||
|
}
|
||
|
|
||
|
bOpen = false;
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
int32 UAzureKinectDevice::GetNumConnectedDevices()
|
||
|
{
|
||
|
return k4a_device_get_installed_count();
|
||
|
}
|
||
|
|
||
|
int32 UAzureKinectDevice::GetNumTrackedSkeletons() const
|
||
|
{
|
||
|
if (!bOpen)
|
||
|
{
|
||
|
return 0;
|
||
|
}
|
||
|
if (!bSkeletonTracking)
|
||
|
{
|
||
|
UE_LOG(AzureKinectDeviceLog, Error, TEXT("GetNumTrackedBodies: Skeleton Tracking is disabled!"));
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
FScopeLock Lock(Thread->GetCriticalSection());
|
||
|
return NumTrackedSkeletons;
|
||
|
}
|
||
|
|
||
|
FAzureKinectSkeleton UAzureKinectDevice::GetSkeleton(int32 Index) const
|
||
|
{
|
||
|
if (bOpen)
|
||
|
{
|
||
|
if (!bSkeletonTracking)
|
||
|
{
|
||
|
UE_LOG(AzureKinectDeviceLog, Error, TEXT("GetSkeleton: Skeleton Tracking is disabled!"));
|
||
|
return FAzureKinectSkeleton();
|
||
|
}
|
||
|
|
||
|
FScopeLock Lock(Thread->GetCriticalSection());
|
||
|
if (Skeletons.IsValidIndex(Index))
|
||
|
{
|
||
|
return Skeletons[Index];
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
UE_LOG(AzureKinectDeviceLog, Error, TEXT("GetSkeleton: Index is out of range!"));
|
||
|
return FAzureKinectSkeleton();
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
return FAzureKinectSkeleton();
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
const TArray<FAzureKinectSkeleton>& UAzureKinectDevice::GetSkeletons() const {
|
||
|
if (bOpen)
|
||
|
{
|
||
|
FScopeLock Lock(Thread->GetCriticalSection());
|
||
|
return Skeletons;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
return Skeletons;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void UAzureKinectDevice::UpdateAsync()
|
||
|
{
|
||
|
// Threaded function
|
||
|
try
|
||
|
{
|
||
|
if (!NativeDevice.get_capture(&Capture, FrameTime))
|
||
|
{
|
||
|
UE_LOG(AzureKinectDeviceLog, Verbose, TEXT("Timed out waiting for capture."));
|
||
|
}
|
||
|
}
|
||
|
catch (const k4a::error& Err)
|
||
|
{
|
||
|
FString Msg(ANSI_TO_TCHAR(Err.what()));
|
||
|
UE_LOG(AzureKinectDeviceLog, Error, TEXT("Can't capture frame: %s"), *Msg);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (ColorMode != EKinectColorResolution::RESOLUTION_OFF && ColorTexture)
|
||
|
{
|
||
|
CaptureColorImage();
|
||
|
}
|
||
|
|
||
|
if (DepthMode != EKinectDepthMode::OFF && DepthTexture)
|
||
|
{
|
||
|
CaptureDepthImage();
|
||
|
}
|
||
|
|
||
|
if (DepthMode != EKinectDepthMode::OFF && InflaredTexture)
|
||
|
{
|
||
|
CaptureInflaredImage();
|
||
|
}
|
||
|
|
||
|
if (bSkeletonTracking && BodyTracker)
|
||
|
{
|
||
|
UpdateSkeletons();
|
||
|
}
|
||
|
|
||
|
Capture.reset();
|
||
|
|
||
|
}
|
||
|
|
||
|
void UAzureKinectDevice::CaptureColorImage()
|
||
|
{
|
||
|
int32 Width = 0, Height = 0;
|
||
|
uint8* SourceBuffer;
|
||
|
|
||
|
if (RemapMode == EKinectRemap::COLOR_TO_DEPTH)
|
||
|
{
|
||
|
k4a::image DepthCapture = Capture.get_depth_image();
|
||
|
k4a::image ColorCapture = Capture.get_color_image();
|
||
|
|
||
|
if (!DepthCapture.is_valid() || !ColorCapture.is_valid()) return;
|
||
|
|
||
|
Width = DepthCapture.get_width_pixels();
|
||
|
Height = DepthCapture.get_height_pixels();
|
||
|
|
||
|
if (Width == 0 || Height == 0) return;
|
||
|
|
||
|
//
|
||
|
if (!RemapImage || !RemapImage.is_valid())
|
||
|
{
|
||
|
RemapImage = k4a::image::create(K4A_IMAGE_FORMAT_COLOR_BGRA32, Width, Height, Width * static_cast<int>(sizeof(uint8) * 4));
|
||
|
}
|
||
|
|
||
|
try
|
||
|
{
|
||
|
KinectTransformation.color_image_to_depth_camera(DepthCapture, ColorCapture, &RemapImage);
|
||
|
}
|
||
|
catch (const k4a::error& Err)
|
||
|
{
|
||
|
FString Msg(ANSI_TO_TCHAR(Err.what()));
|
||
|
UE_LOG(AzureKinectDeviceLog, Error, TEXT("Cant't transform Color to Depth: %s"), *Msg);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
SourceBuffer = RemapImage.get_buffer();
|
||
|
|
||
|
DepthCapture.reset();
|
||
|
ColorCapture.reset();
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
k4a::image ColorCapture = Capture.get_color_image();
|
||
|
|
||
|
if (!ColorCapture.is_valid()) return;
|
||
|
|
||
|
Width = ColorCapture.get_width_pixels();
|
||
|
Height = ColorCapture.get_height_pixels();
|
||
|
if (Width == 0 || Height == 0) return;
|
||
|
|
||
|
SourceBuffer = ColorCapture.get_buffer();
|
||
|
|
||
|
ColorCapture.reset();
|
||
|
}
|
||
|
|
||
|
if (ColorTexture->GetSurfaceWidth() != Width || ColorTexture->GetSurfaceHeight() != Height)
|
||
|
{
|
||
|
ColorTexture->InitCustomFormat(Width, Height, EPixelFormat::PF_B8G8R8A8, false);
|
||
|
ColorTexture->RenderTargetFormat = ETextureRenderTargetFormat::RTF_RGBA8;
|
||
|
ColorTexture->UpdateResource();
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
|
||
|
FTextureResource* TextureResource = ColorTexture->Resource;
|
||
|
auto Region = FUpdateTextureRegion2D(0, 0, 0, 0, Width, Height);
|
||
|
|
||
|
ENQUEUE_RENDER_COMMAND(UpdateTextureData)(
|
||
|
[TextureResource, Region, SourceBuffer](FRHICommandListImmediate& RHICmdList) {
|
||
|
FTexture2DRHIRef Texture2D = TextureResource->TextureRHI ? TextureResource->TextureRHI->GetTexture2D() : nullptr;
|
||
|
if (!Texture2D)
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
RHIUpdateTexture2D(Texture2D, 0, Region, 4 * Region.Width, SourceBuffer);
|
||
|
});
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
void UAzureKinectDevice::CaptureDepthImage()
|
||
|
{
|
||
|
int32 Width = 0, Height = 0;
|
||
|
uint8* SourceBuffer;
|
||
|
if (RemapMode == EKinectRemap::DEPTH_TO_COLOR)
|
||
|
{
|
||
|
k4a::image DepthCapture = Capture.get_depth_image();
|
||
|
k4a::image ColorCapture = Capture.get_color_image();
|
||
|
|
||
|
if (!DepthCapture.is_valid() || !ColorCapture.is_valid()) return;
|
||
|
|
||
|
Width = ColorCapture.get_width_pixels();
|
||
|
Height = ColorCapture.get_height_pixels();
|
||
|
|
||
|
if (Width == 0 || Height == 0) return;
|
||
|
|
||
|
//
|
||
|
if (!RemapImage || !RemapImage.is_valid())
|
||
|
{
|
||
|
RemapImage = k4a::image::create(K4A_IMAGE_FORMAT_DEPTH16, Width, Height, Width * static_cast<int>(sizeof(uint16)));
|
||
|
}
|
||
|
|
||
|
try
|
||
|
{
|
||
|
KinectTransformation.depth_image_to_color_camera(DepthCapture, &RemapImage);
|
||
|
}
|
||
|
catch (const k4a::error& Err)
|
||
|
{
|
||
|
FString Msg(ANSI_TO_TCHAR(Err.what()));
|
||
|
UE_LOG(AzureKinectDeviceLog, Error, TEXT("Cant't transform Depth to Color: %s"), *Msg);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
SourceBuffer = RemapImage.get_buffer();
|
||
|
|
||
|
DepthCapture.reset();
|
||
|
ColorCapture.reset();
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
k4a::image DepthCapture = Capture.get_depth_image();
|
||
|
if (!DepthCapture.is_valid()) return;
|
||
|
|
||
|
Width = DepthCapture.get_width_pixels();
|
||
|
Height = DepthCapture.get_height_pixels();
|
||
|
|
||
|
if (Width == 0 || Height == 0) return;
|
||
|
|
||
|
SourceBuffer = DepthCapture.get_buffer();
|
||
|
|
||
|
DepthCapture.reset();
|
||
|
}
|
||
|
|
||
|
if (DepthTexture->GetSurfaceWidth() != Width || DepthTexture->GetSurfaceHeight() != Height)
|
||
|
{
|
||
|
DepthTexture->InitCustomFormat(Width, Height, EPixelFormat::PF_R8G8B8A8, true);
|
||
|
DepthTexture->RenderTargetFormat = ETextureRenderTargetFormat::RTF_RGBA8;
|
||
|
DepthTexture->UpdateResource();
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
|
||
|
TArray<uint8> SrcData;
|
||
|
SrcData.Reset(Width * Height * 4);
|
||
|
for (int hi = 0; hi < Height; hi++)
|
||
|
{
|
||
|
for (int wi = 0; wi < Width; wi++)
|
||
|
{
|
||
|
int index = hi * Width + wi;
|
||
|
uint16 R = SourceBuffer[index * 2];
|
||
|
uint16 G = SourceBuffer[index * 2 + 1];
|
||
|
|
||
|
uint16 Sample = G << 8 | R;
|
||
|
|
||
|
SrcData.Push(SourceBuffer[index * 2]);
|
||
|
SrcData.Push(SourceBuffer[index * 2 + 1]);
|
||
|
SrcData.Push(Sample > 0 ? 0x00 : 0xFF);
|
||
|
SrcData.Push(0xFF);
|
||
|
|
||
|
}
|
||
|
}
|
||
|
|
||
|
FTextureResource* TextureResource = DepthTexture->Resource;
|
||
|
auto Region = FUpdateTextureRegion2D(0, 0, 0, 0, Width, Height);
|
||
|
|
||
|
ENQUEUE_RENDER_COMMAND(UpdateTextureData)(
|
||
|
[TextureResource, Region, SrcData](FRHICommandListImmediate& RHICmdList) {
|
||
|
FTexture2DRHIRef Texture2D = TextureResource->TextureRHI ? TextureResource->TextureRHI->GetTexture2D() : nullptr;
|
||
|
if (!Texture2D)
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
RHIUpdateTexture2D(Texture2D, 0, Region, 4 * Region.Width, SrcData.GetData());
|
||
|
|
||
|
});
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
void UAzureKinectDevice::CaptureInflaredImage()
|
||
|
{
|
||
|
const k4a::image& InflaredCapture = Capture.get_ir_image();
|
||
|
if (!InflaredCapture.is_valid()) return;
|
||
|
|
||
|
int32 Width = InflaredCapture.get_width_pixels(), Height = InflaredCapture.get_height_pixels();
|
||
|
if (Width == 0 || Height == 0) return;
|
||
|
|
||
|
if (InflaredTexture->GetSurfaceWidth() != Width || InflaredTexture->GetSurfaceWidth() != Height)
|
||
|
{
|
||
|
InflaredTexture->InitCustomFormat(Width, Height, EPixelFormat::PF_R8G8B8A8, true);
|
||
|
InflaredTexture->RenderTargetFormat = ETextureRenderTargetFormat::RTF_RGBA8;
|
||
|
InflaredTexture->UpdateResource();
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
const uint8* S = InflaredCapture.get_buffer();
|
||
|
TArray<uint8> SrcData;
|
||
|
SrcData.Reset(Width * Height * 4);
|
||
|
for (int hi = 0; hi < Height; hi++)
|
||
|
{
|
||
|
for (int wi = 0; wi < Width; wi++)
|
||
|
{
|
||
|
int index = hi * Width + wi;
|
||
|
|
||
|
if (S[index * 2] + S[index * 2 + 1] > 0)
|
||
|
{
|
||
|
SrcData.Push(S[index * 2]);
|
||
|
SrcData.Push(S[index * 2 + 1]);
|
||
|
SrcData.Push(0x00);
|
||
|
SrcData.Push(0xff);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
SrcData.Push(0x00);
|
||
|
SrcData.Push(0x00);
|
||
|
SrcData.Push(0xff);
|
||
|
SrcData.Push(0xff);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
FTextureResource* TextureResource = InflaredTexture->Resource;
|
||
|
auto Region = FUpdateTextureRegion2D(0, 0, 0, 0, Width, Height);
|
||
|
|
||
|
ENQUEUE_RENDER_COMMAND(UpdateTextureData)(
|
||
|
[TextureResource, Region, SrcData](FRHICommandListImmediate& RHICmdList) {
|
||
|
FTexture2DRHIRef Texture2D = TextureResource->TextureRHI ? TextureResource->TextureRHI->GetTexture2D() : nullptr;
|
||
|
if (!Texture2D)
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
RHIUpdateTexture2D(Texture2D, 0, Region, 4 * Region.Width, SrcData.GetData());
|
||
|
});
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
void UAzureKinectDevice::CaptureBodyIndexImage(const k4abt::frame& BodyFrame)
|
||
|
{
|
||
|
k4a::image BodyIndexMap = BodyFrame.get_body_index_map();
|
||
|
|
||
|
int32 Width = BodyIndexMap.get_width_pixels(), Height = BodyIndexMap.get_height_pixels();
|
||
|
if (Width == 0 || Height == 0) return;
|
||
|
|
||
|
if (BodyIndexTexture->GetSurfaceWidth() != Width || BodyIndexTexture->GetSurfaceHeight() != Height)
|
||
|
{
|
||
|
BodyIndexTexture->InitCustomFormat(Width, Height, EPixelFormat::PF_R8G8B8A8, true);
|
||
|
BodyIndexTexture->RenderTargetFormat = ETextureRenderTargetFormat::RTF_RGBA8;
|
||
|
BodyIndexTexture->UpdateResource();
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
uint8* S = BodyIndexMap.get_buffer();
|
||
|
TArray<uint8> SrcData;
|
||
|
SrcData.Reset(Width * Height * 4);
|
||
|
for (int i = 0; i < Width * Height; i++)
|
||
|
{
|
||
|
SrcData.Push(S[i]);
|
||
|
SrcData.Push(S[i]);
|
||
|
SrcData.Push(S[i]);
|
||
|
SrcData.Push(0xff);
|
||
|
}
|
||
|
|
||
|
FTextureResource* TextureResource = BodyIndexTexture->Resource;
|
||
|
auto Region = FUpdateTextureRegion2D(0, 0, 0, 0, Width, Height);
|
||
|
|
||
|
ENQUEUE_RENDER_COMMAND(UpdateTextureData)(
|
||
|
[TextureResource, Region, SrcData](FRHICommandListImmediate& RHICmdList) {
|
||
|
FTexture2DRHIRef Texture2D = TextureResource->TextureRHI ? TextureResource->TextureRHI->GetTexture2D() : nullptr;
|
||
|
if (!Texture2D)
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
RHIUpdateTexture2D(Texture2D, 0, Region, 4 * Region.Width, SrcData.GetData());
|
||
|
|
||
|
});
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
void UAzureKinectDevice::UpdateSkeletons()
|
||
|
{
|
||
|
|
||
|
k4abt::frame BodyFrame = nullptr;
|
||
|
TArray<int32> BodyIDs;
|
||
|
|
||
|
try
|
||
|
{
|
||
|
if (!BodyTracker.enqueue_capture(Capture, FrameTime))
|
||
|
{
|
||
|
UE_LOG(AzureKinectDeviceLog, Warning, TEXT("Failed adding capture to tracker process queue"));
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (!BodyTracker.pop_result(&BodyFrame, FrameTime))
|
||
|
{
|
||
|
UE_LOG(AzureKinectDeviceLog, Warning, TEXT("Failed Tracker pop body frame"));
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
catch (const k4a::error& Err)
|
||
|
{
|
||
|
FString Msg(ANSI_TO_TCHAR(Err.what()));
|
||
|
UE_LOG(AzureKinectDeviceLog, Error, TEXT("Couldn't get Body Frame: %s"), *Msg);
|
||
|
}
|
||
|
|
||
|
if (BodyIndexTexture)
|
||
|
{
|
||
|
CaptureBodyIndexImage(BodyFrame);
|
||
|
}
|
||
|
|
||
|
{
|
||
|
FScopeLock Lock(Thread->GetCriticalSection());
|
||
|
|
||
|
NumTrackedSkeletons = BodyFrame.get_num_bodies();
|
||
|
Skeletons.Reset(NumTrackedSkeletons);
|
||
|
|
||
|
for (int32 i = 0; i < NumTrackedSkeletons; i++)
|
||
|
{
|
||
|
k4abt_body_t Body;
|
||
|
FAzureKinectSkeleton Skeleton;
|
||
|
|
||
|
BodyFrame.get_body_skeleton(i, Body.skeleton);
|
||
|
Skeleton.ID = BodyFrame.get_body_id(i);
|
||
|
|
||
|
Skeleton.Joints.Reset(K4ABT_JOINT_COUNT);
|
||
|
|
||
|
for (int32 j = 0; j < K4ABT_JOINT_COUNT; j++)
|
||
|
{
|
||
|
Skeleton.Joints.Push(JointToTransform(Body.skeleton.joints[j], j));
|
||
|
}
|
||
|
|
||
|
Skeletons.Push(Skeleton);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
BodyFrame.reset();
|
||
|
|
||
|
}
|
||
|
|
||
|
FTransform UAzureKinectDevice::JointToTransform(const k4abt_joint_t& Joint, int32 Index)
|
||
|
{
|
||
|
|
||
|
// This transform algorithm is introdeced from
|
||
|
// https://github.com/secretlocation/azure-kinect-unreal/
|
||
|
// Still there is room to refactor...
|
||
|
|
||
|
/**
|
||
|
* Convert Azure Kinect Depth and Color camera co-ordinate system
|
||
|
* to Unreal co-ordinate system
|
||
|
* @see https://docs.microsoft.com/en-us/azure/kinect-dk/coordinate-systems
|
||
|
*
|
||
|
* Kinect [mm] Unreal [cm]
|
||
|
* --------------------------------------
|
||
|
* +ve X-axis Right +ve Y-axis
|
||
|
* +ve Y-axis Down -ve Z-axis
|
||
|
* +ve Z-axis Forward +ve X-axis
|
||
|
*/
|
||
|
FVector Position(Joint.position.xyz.z, Joint.position.xyz.x, - Joint.position.xyz.y);
|
||
|
Position *= 0.1f;
|
||
|
|
||
|
/**
|
||
|
* Convert the Orientation from Kinect co-ordinate system to Unreal co-ordinate system.
|
||
|
* We negate the x, y components of the JointQuaternion since we are converting from
|
||
|
* Kinect's Right Hand orientation to Unreal's Left Hand orientation.
|
||
|
*/
|
||
|
FQuat Quat(
|
||
|
-Joint.orientation.wxyz.x,
|
||
|
-Joint.orientation.wxyz.y,
|
||
|
Joint.orientation.wxyz.z,
|
||
|
Joint.orientation.wxyz.w
|
||
|
);
|
||
|
|
||
|
return FTransform(Quat, Position);
|
||
|
}
|
||
|
|
||
|
void UAzureKinectDevice::CalcFrameCount()
|
||
|
{
|
||
|
float FrameTimeInMilli = 0.0f;
|
||
|
switch (Fps)
|
||
|
{
|
||
|
case EKinectFps::PER_SECOND_5:
|
||
|
FrameTimeInMilli = 1000.f / 5.f;
|
||
|
break;
|
||
|
case EKinectFps::PER_SECOND_15:
|
||
|
FrameTimeInMilli = 1000.f / 15.f;
|
||
|
break;
|
||
|
case EKinectFps::PER_SECOND_30:
|
||
|
FrameTimeInMilli = 1000.f / 30.f;
|
||
|
break;
|
||
|
default:
|
||
|
break;
|
||
|
}
|
||
|
FrameTime = std::chrono::milliseconds(FMath::CeilToInt(FrameTimeInMilli));
|
||
|
}
|