카테고리 없음
Unreal Engine에서 실시간 3D Scene Reconstruction 구현하기 (ONNX 변환 공부하기) [기록용]
zmo
2025. 6. 28. 23:01
1. Unreal Engine은 PyTorch를 직접 실행할 수 없다
- PyTorch는 Python 기반 프레임워크
- Unreal Engine은 C++ 기반 게임/AR/VR 엔진으로, Python 모델을 직접 로드하거나 실행할 수 없다
- 따라서 PyTorch에서 훈련한 모델을 Unreal이 이해할 수 있는 중간 표현(ONNX) 으로 변환
2. ONNX는 범용 모델 포맷이다
- ONNX(Open Neural Network Exchange)는 PyTorch, TensorFlow 등에서 공통으로 모델을 표현하기 위한 표준 포맷.
- 다양한 플랫폼에서 추론 가능:
- Unreal (C++에서 ONNX Runtime 사용)
- Web (ONNX.js)
- 모바일 (ONNX → CoreML or TFLite)
- NVIDIA GPU (ONNX → TensorRT)
- 한 번 변환하면 어디든 쓸 수 있는 "중립 포맷"으로써 사용할수 있게 세팅하기
흐름
[PyTorch에서 모델 훈련 or 불러오기] ↓ [ONNX로 변환] ↓ [Unreal Engine 내 ONNX Runtime 연동 (C++ 또는 Python via Plugin)] ↓ [실시간 입력 이미지 → Depth Map 추정 → 결과 사용]
PyTorch → ONNX 변환
import torch
from midas.model_loader import load_model
import torch.onnx
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
model, transform = load_model("DPT_Large", device)
model.eval()
# Dummy 입력 생성 (B, C, H, W)
dummy_input = torch.randn(1, 3, 384, 384).to(device)
# ONNX 변환
torch.onnx.export(model, dummy_input, "midas_depth.onnx",
input_names=["input"], output_names=["output"],
opset_version=11)
opset_version은 Unreal 호환성을 위해 11~15 정도로 설정
#include <onnxruntime_cxx_api.h>
Ort::Env env(ORT_LOGGING_LEVEL_WARNING, "DepthApp");
Ort::SessionOptions session_options;
Ort::Session session(env, "midas_depth.onnx", session_options);
// 입력 및 출력 이름 확인
Ort::AllocatorWithDefaultOptions allocator;
auto input_name = session.GetInputName(0, allocator);
auto output_name = session.GetOutputName(0, allocator);
// 입력 텐서 생성 및 실행
std::vector<float> input_tensor_values = ... // 입력 이미지 텐서로 변환
Ort::Value input_tensor = Ort::Value::CreateTensor<float>(...);
auto output_tensors = session.Run(...);
Unreal 내에서 실시간 카메라 이미지 → Tensor 변환이 필요하며, [OpenCV + UE 연동] 또는 [MediaTexture 추출] 방식으로 구현 가능
실시간 최적화
- ONNX 모델을 TensorRT로 변환하여 속도 향상 (NVIDIA GPU 사용 시)
- 깊이 맵 해상도 축소 → 보간 → 실시간성 확보
- 카메라 프레임 중 일부만 추론하고 나머지는 추정 보간 처리
파일 구조는?
/DepthApp
├── midas_depth.onnx
├── DepthRuntime.cpp (ONNX 호출 코드)
├── PythonServer/ (선택?)
│ └── flask_onnx_server.py
├── UEProject/
│ ├── Plugins/
│ │ └── ONNXRuntime/
│ ├── Blueprints/
│ │ └── DepthReceiverBP.uasset
Unreal Engine의 C++ 클래스에서 ONNX Runtime을 사용해 midas_depth.onnx 모델을 로딩하고 추론하기
ONNXDepthComponent.h
#pragma once
#include "CoreMinimal.h"
#include "Components/ActorComponent.h"
#include "onnxruntime_cxx_api.h" // ONNX Runtime C++ 헤더
#include "ONNXDepthComponent.generated.h"
// UE5에서 사용할 수 있는 Actor 컴포넌트 클래스 정의
// 이 컴포넌트는 AI 추론 기능만 전담함
UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) )
class YOURPROJECT_API UONNXDepthComponent : public UActorComponent
{
GENERATED_BODY()
public:
// 생성자
UONNXDepthComponent();
// 게임 시작 시 자동 호출되는 함수
virtual void BeginPlay() override;
// 실제로 모델을 실행해서 Depth를 추론하는 함수
void RunDepthInference(const std::vector<float>& InputData, int Width, int Height);
private:
// ONNX Runtime 관련 객체들
Ort::Env* Env; // 환경 객체: 로그 설정 등 초기화
Ort::Session* Session; // 모델 세션: 모델 로딩 및 실행 담당
Ort::SessionOptions* SessionOptions; // 세션 설정: 멀티스레드 등 옵션 조정
};
ONNXDepthComponent.cpp
#include "ONNXDepthComponent.h"
UONNXDepthComponent::UONNXDepthComponent()
{
// 컴포넌트는 기본적으로 매 프레임 Tick을 수행하지 않음
PrimaryComponentTick.bCanEverTick = false;
// ONNX Runtime 환경 생성 (로깅 레벨, 애플리케이션 이름 지정)
Env = new Ort::Env(ORT_LOGGING_LEVEL_WARNING, "DepthInference");
// 세션 옵션 초기화 (추론 설정)
SessionOptions = new Ort::SessionOptions();
SessionOptions->SetIntraOpNumThreads(1); // 스레드 수 설정 (성능 조정)
// ONNX 모델 파일 경로를 주고 세션 생성
Session = new Ort::Session(*Env, TEXT("midas_depth.onnx"), *SessionOptions);
}
void UONNXDepthComponent::BeginPlay()
{
Super::BeginPlay();
// BeginPlay에서 추론 로직을 바로 실행해도 되고,
// 외부에서 함수 호출로 처리해도 됨
}
void UONNXDepthComponent::RunDepthInference(const std::vector<float>& InputData, int Width, int Height)
{
// ONNX Runtime에서 메모리 할당을 쉽게 하기 위한 기본 할당자
Ort::AllocatorWithDefaultOptions Allocator;
// 입력 텐서의 차원 정의: 1x3xHxW (배치 1, RGB, 높이, 너비)
std::vector<int64_t> InputDims = {1, 3, Height, Width};
// 전체 입력 텐서의 원소 개수 계산
size_t InputTensorSize = 1 * 3 * Height * Width;
// 입력 데이터로부터 ONNX Runtime 텐서 생성
Ort::Value InputTensor = Ort::Value::CreateTensor<float>(
Allocator, // 메모리 관리 도구
const_cast<float*>(InputData.data()), // 입력 데이터 포인터 (const 제거)
InputTensorSize, // 총 원소 개수
InputDims.data(), // 차원 정보 포인터
InputDims.size() // 차원 수
);
// ONNX 모델의 입력 및 출력 이름 설정
const char* InputNames[] = {"input"}; // 모델 입력 이름
const char* OutputNames[] = {"output"}; // 모델 출력 이름
// 실제 추론 수행 (Run)
auto OutputTensors = Session->Run(
Ort::RunOptions{nullptr}, // 실행 옵션 (기본값)
InputNames, &InputTensor, 1, // 입력 이름 및 텐서
OutputNames, 1 // 출력 이름
);
// 출력 텐서에서 결과 데이터 가져오기 (float* 형태)
float* OutputData = OutputTensors.front().GetTensorMutableData<float>();
// 출력은 1x1xHxW로 나오므로 원소 수는 Height * Width
int OutputSize = 1 * 1 * Height * Width;
// 예시: 첫 번째 Depth 값을 UE 로그로 출력
UE_LOG(LogTemp, Warning, TEXT("First depth value: %f"), OutputData[0]);
// 이후: Depth 결과를 시각화하거나 저장할 수 있음
}