그렇다면 언리얼 엔진에서 어떻게 사용할 것인가?
언리얼에서 물체의 실제 위치(AActor::GetActorLocation())는 월드 좌표계에 존재한다.
하지만 화면에 표시하거나, 이미지로 라벨링하려면 2D 화면 좌표(Screen Space) 로 변환해야 한다.
이 변환은 수학적으로 다음 행렬 연산으로 표현된다.
ScreenCoord = ProjectionMatrix × ViewMatrix × WorldCoord
언리얼에서는 이 과정을 직접 행렬로 계산할 필요 없이, UGameplayStatics::ProjectWorldToScreen() 함수를 사용하면 된다.
함수로 행렬 계산하기
// WorldToScreenExample.cpp
#include "WorldToScreenExample.h"
#include "Kismet/GameplayStatics.h"
#include "GameFramework/PlayerController.h"
#include "Engine/Engine.h"
void UWorldToScreenExample::ConvertActorToScreen(AActor* TargetActor)
{
if (!TargetActor) return;
// 현재 플레이어 컨트롤러 가져오기
APlayerController* PlayerController = UGameplayStatics::GetPlayerController(GetWorld(), 0);
if (!PlayerController) return;
// 월드 좌표 얻기
FVector WorldLocation = TargetActor->GetActorLocation();
// 화면 좌표로 변환
FVector2D ScreenPosition;
bool bIsOnScreen = PlayerController->ProjectWorldLocationToScreen(WorldLocation, ScreenPosition);
if (bIsOnScreen)
{
UE_LOG(LogTemp, Warning, TEXT("Actor %s is on screen at: X=%.1f, Y=%.1f"),
*TargetActor->GetName(), ScreenPosition.X, ScreenPosition.Y);
}
else
{
UE_LOG(LogTemp, Warning, TEXT("Actor %s is off screen."), *TargetActor->GetName());
}
}
이미지의 특정 픽셀 위치 (u, v)와 그 지점의 뎁스 맵 값 D(u,v)를 알 때, 카메라 좌표계 기준 3D 포인트 (Xc, Yc, Zc)는 다음 공식으로 계산할 수 있다.
Z_c = D(u, v)
Xc=(u−cx)×Zc/fx
Yc=(v−cy)×Zc/fy
이 공식의 기하학적 의미는 '삼각형의 닮음' 원리이다. 이미지 평면에서 픽셀이 중심점(cx, cy)으로부터 떨어진 거리는, 실제 3D 공간에서 해당 포인트가 카메라 중심축으로부터 떨어진 거리와 비례한다. 초점 거리 fx, fy와 깊이 Zc가 바로 그 비례상수 역할을 한다.
이 과정을 통해 우리는 2D 이미지 한 장으로부터 3D 공간 정보를 복원하고, 언리얼 엔진 안에서 실시간으로 시각화하거나 물리적으로 상호작용하는 객체를 만들어낼 수 있게 된다.
//'뎁스(Depth)'는 관찰자(보통 카메라)로부터 3D 공간에 있는 객체의 표면까지의 거리를 의미
AI 객체 탐지 모델은 이미지 속에서 객체의 위치를 2D Bounding Box (사각형 좌표)로 알려준다. 하지만 이건 그냥 화면 위의 네모일 뿐, 언리얼 엔진의 3D 월드에서는 아무런 의미가 없을 것이다.
그러므로 이 2D 사각형에 깊이(Depth) 정보를 결합하여 실제 3D 공간의 Bounding Box로 "복원"하는 방법을 알아보자. 이 과정을 통해 AI를 3D 공간을 인지하고 상호작용할 수 있게 만들어줄 것이다.
카메라에서 뻗어 나가는 시야 영역을 절두체(Frustum)라고 한다. 2D Bounding Box의 네 꼭짓점을 3D 공간으로 역투영(Deproject)하면, 카메라에서 시작되는 4개의 광선(Ray)이 만들어진다.
여기에 특정 거리(Depth)에 가상의 평면을 놓으면, 4개의 광선이 그 평면과 만나는 4개의 3D 점이 생기고, 이 4개의 점이 바로 우리가 찾던 3D Bounding Box의 '앞면'이 된다.
입력:
과정:
void AMyActor::Create3DBoxFrom2DBox(const FBox2D& Box2D, float Depth, float BoxThickness)
{
APlayerController* PC = GetWorld()->GetFirstPlayerController();
if (!PC) return;
// 2D Box의 네 꼭짓점 좌표 배열
FVector2D Corners[4] = {
FVector2D(Box2D.Min.X, Box2D.Min.Y), // Top-Left
FVector2D(Box2D.Max.X, Box2D.Min.Y), // Top-Right
FVector2D(Box2D.Min.X, Box2D.Max.Y), // Bottom-Left
FVector2D(Box2D.Max.X, Box2D.Max.Y) // Bottom-Right
};
FVector Vertices[8]; // 최종 3D Box의 8개 정점
int32 VertexIndex = 0;
// 앞면 (Depth) 과 뒷면 (Depth + Thickness) 계산
for (float CurrentDepth : { Depth, Depth + BoxThickness })
{
for (const FVector2D& Corner : Corners)
{
FVector WorldLocation, WorldDirection;
bool bSuccess = PC->DeprojectScreenPositionToWorld(
Corner.X,
Corner.Y,
WorldLocation,
WorldDirection
);
if (bSuccess)
{
// Ray 시작점(카메라 위치)에서 Depth 만큼 떨어진 지점을 계산
Vertices[VertexIndex++] = WorldLocation + WorldDirection * CurrentDepth;
}
}
}
// 8개의 정점으로 3D Bounding Box의 중심과 범위를 계산
FBox BoundingBox(Vertices, 8);
// 디버그용으로 월드에 3D Box 그리기
DrawDebugBox(
GetWorld(),
BoundingBox.GetCenter(),
BoundingBox.GetExtent(),
FColor::Green,
false,
5.0f,
0,
10.0f
);
}
| Unreal Engine Point Cloud 실시간 렌더링하기 (0) | 2025.10.25 |
|---|---|
| Unreal Engine 좌표변환 이해하기(1) (0) | 2025.10.20 |
| 완전히 새로 정리된 Unreal + ONNX 통합 코드 (객체 감지 + 지형 파악) Part1 (0) | 2025.09.20 |
| Unreal Engine에서 학습한 ONNX 모델 불러와 실시간 추론하기 (0) | 2025.09.13 |
| Unreal Engine에서 CUDA와 OpenCV 사용 시행착오 정리하기 (5) | 2025.08.30 |