레이블이 그래픽스인 게시물을 표시합니다. 모든 게시물 표시
레이블이 그래픽스인 게시물을 표시합니다. 모든 게시물 표시

2026년 10월 4일 일요일

GPT6 Codex 기반 바이브 디자인 및 영상 제작 관련 테스트 결과

이 글은 GPT6 Codex 기반 바이브 디자인 및 영상 제작 관련 테스트 결과를 간략히 기록해 남긴다. 

테스트 대상은 서울 선릉역 인근의 가상 대지에 계획한 지상 5층, 지하 2층 규모의 미술관·문화공간이다. 자하 하디드와 프랭크 게리의 조형 언어를 참고해 금속 곡면 외피와 유리 입면, 아트리움을 중심으로 구성했다. 

코덱스를 띄우고 Astra 모델에 프롬프트로 지시하자 Computer Use 하며 모델링, 렌더링, 애니메이션쉬지않고 작업한다. 레빗 등 실행하며 중간 에러나니 자가 수정 후 재시도하고 있다.ㅎ 

Rhino, Revit 을 이용해 개념 모델에서 시작해 Blender로 모델링과 재질, 조명, 카메라 동선을 작업했다. 상공에서 건물을 조망하다가 점차 내려와 입구를 통과하고, 로비와 전시 공간을 둘러보는 32초 애니메이션을 제작했다. 주요 장면은 별도의 이미지로 렌더링했다.

이후 힉스필드를 연결해 영상의 실사화도 테스트했다. 작업 화면 녹화본에서 외부 전경과 입구 접근 부분을 추출하고, 화면 UI를 잘라낸 뒤 짧은 영상으로 변환했다. 10크레딧 예산을 고려해 해상도와 길이를 제한했으며, 최종 결과는 약 8.4초, 960×512 해상도였다. 실제 사용량은 8.46크레딧이었다.

실사화 과정에서 금속 외피와 유리 반사, 조경, 주변 건물의 표현은 한층 풍부해졌다. 다만 일부 건물 형상과 디테일이 원본과 달라지는 현상도 확인했다. 설계 형태의 정확성을 검토할 때는 원본 모델과 Blender 렌더링을 기준으로 삼고, 생성형 영상은 분위기와 공간 경험을 전달하는 용도로 활용하는 편이 적절해 보였다.

이번 테스트에서는 자연어 지시를 통해 개념 모델링부터 카메라 애니메이션, 주요 장면 렌더링, 영상 실사화까지 이어지는 작업 흐름을 확인했다. 

초기 설계안을 빠르게 시각화하고 클라이언트와 공유하는 데 활용 가능성이 있지만, 설계 정확도와 영상의 시각적 완성도는 AI가 설계 영상 의도를 정확히 추론할 수 있도록 적절한 컨텐츠(도면, 설계치수, 배경, 목적 등)와 프롬프트 하네스 작업에 크게 영향을 받는다.

이 작업은 다른 일을 하면서 두시간 정도에 걸쳐 진행되었고, 개념 설계 정도로 시작하였다. 참고로, 상세 프롬프트는 다음과 같다(사실 이 테스트에서 내가 준 컨텐츠는 거의 없었다).

> 건축가 프랭크 게리 스타일로 Revit을 이용해 모델링해 줘. 지상 5층, 지하 2층 규모의 럭셔리 미술관 및 문화공간으로 기획하고 진행해 줘. 위치는 서울 강남구 선릉역 근처야. 가상 대지로 개념설계 진행.

> 클라이언트 입장에서 다양한 뷰로 렌더링해 줘. 건물을 설명하는 도면뿐 아니라, 외관과 공간 분위기를 이해할 수 있는 여러 시점의 이미지를 요청.

작업 과정을 화면에서 확인하기 위해 다음 지시도 추가했다.

> 모델링 과정을 Blender의 해당 위치 뷰로 줌·팬하면서 보여 줘.

> 모델링이 끝나면 스카이 뷰로 위에서 마치 새가 날아오는 것처럼 건물을 조망해 줘. 점점 카메라를 아래로 내려 입구까지 들어간 다음, 실내를 한 번 훑어보는 식으로 Blender 애니메이션을 작성해 줘. 주요 장면도 캡처해서 렌더링해 줘.

카메라의 출발점과 이동 순서, 최종 도착 공간을 자연어로 지정했다. 결과물은 상공 조망에서 입구와 실내로 이어지는 32초 영상과 주요 장면 이미지로 구성했다.

> 이 애니메이션 영상을 힉스필드로 연결해서 영상 제작해. 힉스필드를 이용해서 사실적으로 영상을 제작해 봐. 단, 해상도나 작업량은 사용량을 고려해서 진행해 줘. 현재 힉스필드 크레딧은 10개 내로만 사용해.

> 첨부 영상의 외부 건물 전경에서 들어가는 부분만 추출해 짧게 실사화해 줘. 

최종 결과는 약 8.4초, 960×512 였으며, 8.46크레딧을 사용했다. 형태 보존을 요청했음에도 일부 디테일은 달라졌으므로, 생성 결과를 원본 설계와 비교하는 과정은 필요하다.

2026년 5월 24일 일요일

OpenUSD 그래프 구조, 객체지향 메커니즘 및 실제 포맷 예시

이 글은 OpenUSD의 그래프 구조, 객체지향 특성, 실행 가능 스크립트 및 실제 포맷 형식에 대한 조사 내용을 요약 정리한다.

OpenUSD는 단순한 static 3D 데이터 포맷이 아니다. 계층적 씬 그래프 구조 위에 객체지향 데이터 모델과 동작 메커니즘까지 포함 가능한 확장형 포맷이다.

USD Scene Graph 계층구조 및 References 인스턴싱 개념도

USD의 계층적 씬 그래프 구조

USD의 기본 데이터 모델은 씬 그래프 구조를 바탕으로 작동한다.

1) Stage와 Prim 그래프 계층

  • Stage: 씬 전체 데이터를 관리하는 그래프의 루트 컨테이너이다.
  • Prim: 그래프를 구성하는 개별 노드이다. 자식 Prim을 가질 수 있는 트리 구조 형태를 띤다.
  • Relationship 및 Attribute: 노드 간의 논리적 연결선 역할을 수행한다. 하나의 Prim이 다른 Prim의 재질, 물리 속성, 센서 데이터를 참조할 때 DAG 그래프 형태로 확장된다.

2) 객체지향(OOP) 스키마 구조 지원

USD는 객체지향 프로그래밍의 클래스, 상속, 캡슐화 개념을 데이터 모델 수준에서 지원한다.

  • Typed Schema: C++ 및 Python 클래스에 1:1로 매핑되는 스키마이다. 상속 구조를 가지며 예로 UsdGeomGprim 클래스를 상속받아 UsdGeomMesh가 정의된다.
  • API Schema: 기존 Prim에 특정 기능만 추가하는 다중 상속 형태의 스키마이다. 예로 일반 Mesh Prim에 UsdPhysicsRigidBodyAPI를 적용하여 물리 속성을 가미한다.
  • Class Prim과 Inherits: USD 파일 내부에서 class 키워드로 추상 클래스를 정의하고 inherits 속성으로 이를 상속받아 유산 속성을 재사용한다.

파이썬 스크립트 및 동작 메커니즘 포함 기능

USD 포맷은 형상, 재질, 물리, 센서 데이터 같은 정적 속성 외에 가동형 로직 및 스크립트 실행 메커니즘도 포함한다.

1) OmniActionGraph 및 Scripting Node

Omniverse 환경과 결합된 USD 포맷은 ActionGraph 노드 내부에 파이썬 스크립트를 직접 저장할 수 있다. 시뮬레이션 프레임마다 파이썬 메서드가 호출되어 로봇 제어 및 이벤트 처리가 구동된다.

2) AssetInfo 및 Custom Data 기반 바인딩

Prim 내부에 customData나 파이썬 실행 모듈 경로를 메타데이터로 매핑할 수 있다. 로드 시점에 파이썬 로직을 바인딩하여 실행하는 동적 객체 구조 구현이 가능하다.

USD 그래프 구조 실제 형식 예시

텍스트 포맷인 USDA 형식의 실제 예시이다. 객체 상속, 물리 속성 적용, 센서 설정 및 로직 매핑 구조를 확인할 수 있다.

#usda 1.0

# 1. 추상 클래스 정의 (Class Prim)
class "_BaseRobot"
{
    color3f[] primvars:displayColor = [(0.2, 0.2, 0.2)]
    float physics:mass = 10.0
}

# 2. 상속 및 객체 생성 (Inherits & Typed Schema)
def Xform "SpotRobot" (
    inherits = </_BaseRobot>
)
{
    # API Schema를 통한 물리 속성 다중 상속
    prepend apiSchemas = ["PhysicsRigidBodyAPI"]

    # 3. 자식 Prim 구성 (Mesh 형상)
    def Mesh "Body"
    {
        float3[] points = [(-1, -1, 0), (1, -1, 0), (1, 1, 0), (-1, 1, 0)]
        int[] faceVertexCounts = [4]
        int[] faceVertexIndices = [0, 1, 2, 3]
    }

    # 4. 센서 데이터 포함 (Camera Sensor)
    def Camera "FrontCamera"
    {
        float focalLength = 24.0
        float2 clippingRange = (0.1, 100.0)
    }

    # 5. 파이썬 노드 및 동작 로직 포함 예시 (OmniActionGraph)
    def Omnigraph "RobotControlGraph"
    {
        def OmnigraphNode "PythonController"
        {
            token nodeType = "omni.graph.action.PythonScript"
            string inputs:script = "def compute(db):\n    # 로봇 제어 파이썬 메서드\n    pass"
        }
    }
}

마무리

USD는 단순 데이터 저장 포맷이 아닌 객체지향적 스키마와 계층적 DAG 그래프를 결합한 포맷이다.
형상과 재질 외에도 물리, 센서, 파이썬 기반 제어 로직까지 단일 씬 그래프 안에 포함할 수 있다.
클래스 상속 및 지연 로딩 메커니즘을 바탕으로 복잡한 피지컬 AI 및 시뮬레이션 환경의 표준 데이터 모델 역할을 수행한다.

2026년 4월 23일 목요일

개발자 도구 캐시 정리 및 용량 줄이는 방법

이 글은 개발자 도구 캐시 정리 및 용량 줄이는 방법을 간략히 나눔한다. 

기본적으로 허깅페이스, PIP 등을 사용하는 개발자로 가정한다.

명령창을 띄워 다음과 같이 실행한다. 
pip cache purge
conda clean --all -y
huggingface-cli delete-cache
wandb sync --clean
npm cache clean --force
yarn cache clean
pnpm store prune
docker system prune -a --volumes
docker builder prune -a
docker image prune -a

파이썬에서 다음을 실행한다.
import torch
torch.cuda.empty_cache()

앱 개발시에는 다음을 실행한다.
flutter clean 
cd android 
.\gradlew clean

# 다운로드된 의존성·빌드 캐시
Remove-Item -Recurse -Force "$env:USERPROFILE\.gradle\caches"
# 구버전 Gradle 배포판들 (버전별로 수백 MB씩 쌓임)
Remove-Item -Recurse -Force "$env:USERPROFILE\.gradle\wrapper\dists"
# 좀비 데몬 로그
Remove-Item -Recurse -Force "$env:USERPROFILE\.gradle\daemon"

flutter pub cache clean
dart pub cache repair 
 
Remove-Item -Recurse -Force "$env:USERPROFILE\.android\avd\*.avd\snapshots"
Remove-Item -Recurse -Force "$env:LOCALAPPDATA\Google\AndroidStudio*\caches"

Write-Host "Gradle caches..." 
Remove-Item -Recurse -Force "$env:USERPROFILE\.gradle\caches" -ErrorAction SilentlyContinue
Remove-Item -Recurse -Force "$env:USERPROFILE\.gradle\daemon" -ErrorAction SilentlyContinue
Write-Host "Pub cache..."
flutter pub cache clean
  
임시 폴더를 삭제한다.
C:\Windows\Temp\
C:\Windows\SoftwareDistribution\Download\

도커 이미지 모두 삭제하려면 다음 실행한다.
docker rmi -f $(docker images -q)

다음과 같이 배치파일을 작성해 한번에 처리할 수도 있다.

@echo off
chcp 65001 >nul
cls

:: 1. 개발 패키지 및 AI 캐시 정리
where pip >nul 2>&1 && echo [*] pip cache... && pip cache purge >nul 2>&1
where conda >nul 2>&1 && echo [*] conda cache... && call conda clean --all --yes >nul 2>&1
where npm >nul 2>&1 && echo [*] npm cache... && call npm cache clean --force >nul 2>&1

where huggingface-cli >nul 2>&1 && echo [*] Hugging Face cli cache... && huggingface-cli delete-cache --quiet >nul 2>&1
if exist "C:\Users\KSW\.cache\huggingface\hub" (
    echo [*] Cleaning Hugging Face incomplete downloads...
    del /f /s /q "C:\Users\KSW\.cache\huggingface\hub\version.txt" >nul 2>&1
)

:: 2. 도커(Docker) 캐시 및 끊어진 이미지 정리 
where docker >nul 2>&1 && echo [*] Docker system pruning... && docker system prune -f >nul 2>&1

:: 3. 임시 폴더(Temp) 파일 정리
echo [*] Cleaning Temp folders...
if exist "C:\Users\KSW\AppData\Local\Temp" (
    del /f /s /q "C:\Users\KSW\AppData\Local\Temp\*.*" >nul 2>&1
    for /d %%p in ("C:\Users\KSW\AppData\Local\Temp\*") do rmdir /s /q "%%p" >nul 2>&1
)
if exist "%SystemRoot%\Temp" (
    del /f /s /q "%SystemRoot%\Temp\*.*" >nul 2>&1
    for /d %%p in ("%SystemRoot%\Temp\*") do rmdir /s /q "%%p" >nul 2>&1
)
if exist "%SystemRoot%\Prefetch" (
    del /f /s /q "%SystemRoot%\Prefetch\*.*" >nul 2>&1
)

echo [+] All caches cleared.
timeout /t 3

2026년 2월 24일 화요일

ViT 메커니즘 이해 및 코드 스크래치하기

이 글은 ViT 모델을 스크래치하는 방법을 정리한다.
VLM 개념도

현재 시점(2026.3)에서 최상위 오픈소스 소형 VLM 중 가성비 제일 좋은 것은 gemma-4, qwen vlm 모델이다. 단 gemma-4 는 아무리 작은 모델도 파인튜닝을 위해선는 32GB 이상 VRAM이 필요하다. 
개인 로컬 GPU 에서는 SmolVLM, nanoVLM, 팔마 등이 그나마 적절한 VRAM(16GB) 에서 동작한다.

여기서는 VLM의 기반이 되는 ViT모델을 프롬 스크래치해본다. 가장 일반적인 예제인 CIFAR10 이미지 데이터셋을 이용해 VIT를 학습해 보기로 한다. 이 예시는 적은 VRAM(8GB)이내에서 가볍게 학습 방법을 알아보기 위한 목적으로 프롬 스크래치한다. 각 단계별로 주요 부분을 다음과 같이 코딩한다. 

모델 하이퍼파라메터 설정
import os, torch
import matplotlib.pyplot as plt
import numpy as np
import torch.nn as nn
from torch.nn import functional as F
import torchvision
import torchvision.transforms as transforms

# 1. 하이퍼파라미터 설정
batch_size = 128        # 범위(32~128),
learning_rate = 1e-4    # Adam 추천값(0.001~0.0001)
epochs = 50             # 범위(10~50)
device = 'cuda' if torch.cuda.is_available() else 'cpu'

# 모델 아키텍처 사이즈  
img_size = 96
patch_size = 16
n_embd = 256        # 표현력 (n_head=8로 나눠떨어짐)
n_head = 8
num_blks = 6        
emb_dropout = blk_dropout = 0.2  
num_classes = 10    # CIFAR-10이므로 10개 클래스


데이터셋 로딩
# CIFAR-10 원본은 32x32 사이즈지만, 우리가 만든 VLM 구조(96x96)와
# 완벽하게 호환시키기 위해 Resize(96)을 적용.
# 학습 데이터에는 데이터 증강(Augmentation)을 추가하여 일반화 성능 향상
train_transform = transforms.Compose([
    # CIFAR-10(32x32)을 96x96으로 업스케일: LANCZOS 보간으로 경계(edge) 흐림 최소화
    transforms.Resize((img_size, img_size), interpolation=transforms.InterpolationMode.LANCZOS),
    transforms.RandomCrop(img_size, padding=12),     #  패딩 후 랜덤 크롭: 위치 변화에 강건
    transforms.RandomHorizontalFlip(p=0.5),          # 좌우 반전
    transforms.ColorJitter(brightness=0.2, contrast=0.2, saturation=0.2),  # 색상 증강
    transforms.ToTensor(),
    transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5)),
    transforms.RandomErasing(p=0.2, scale=(0.02, 0.2)),  # 랜덤 패치 제거: 특정 영역 과의존 방지
])

# 테스트 데이터는 증강 없이 원본 그대로 사용 (동일한 LANCZOS 보간 적용)
test_transform = transforms.Compose([
    transforms.Resize((img_size, img_size), interpolation=transforms.InterpolationMode.LANCZOS),
    transforms.ToTensor(),
    transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))
])

print("데이터셋을 다운로드하고 준비합니다...")
trainset = torchvision.datasets.CIFAR10(root='./data', train=True, download=True, transform=train_transform)
trainloader = torch.utils.data.DataLoader(trainset, batch_size=batch_size, shuffle=True)

testset = torchvision.datasets.CIFAR10(root='./data', train=False, download=True, transform=test_transform)
testloader = torch.utils.data.DataLoader(testset, batch_size=batch_size, shuffle=False)

classes = ('plane', 'car', 'bird', 'cat', 'deer', 'dog', 'frog', 'horse', 'ship', 'truck')

# 데이터셋 샘플 몇개 저장
dataiter = iter(trainloader)
images, labels = next(dataiter)
fig, axes = plt.subplots(1, 5, figsize=(15, 3))
for i in range(5):
    img = images[i].numpy()
    img = img * 0.5 + 0.5
    img = np.transpose(img, (1, 2, 0))
    axes[i].imshow(img)
    axes[i].set_title(f"Label: {classes[labels[i]]}")
    axes[i].axis('off')
   
ouptut_fname = "./cifar10_samples.png"
plt.savefig(ouptut_fname)
plt.show()


로딩된 데이터셋 일부 예시는 다음과 같다. 

ViT 모델 구조 정의
# 이미지 패치를 임베딩
class PatchEmbeddings(nn.Module):
    def __init__(self, img_size, patch_size, hidden_dim):
        super().__init__()
        self.conv = nn.Conv2d(in_channels=3, out_channels=hidden_dim, kernel_size=patch_size, stride=patch_size)

    def forward(self, X):
        X = self.conv(X)            # shape = (B, hidden_dim, H/patch_size, W/patch_size). (128, 256, 6, 6)
        X = X.flatten(2)            # flatten(2) means flattening the last two dimensions. shape = (128, 256, 36)
        X = X.transpose(1, 2)       # shape = (128, 36, 256)
        return X

patch_emb = PatchEmbeddings(img_size, patch_size, n_embd).to(device)
print("입력 이미지 크기:", images.shape)
print("패치 임베딩 출력 크기:", patch_emb.forward(images.to(device)).shape)

ViT는 트랜스포머 구조를 그대로 활용한다. 이를 아래 ViT와 연계해 사용한다. 
class ViT(nn.Module): # 비전 트랜스포머 모델
    def __init__(self, img_size, patch_size, num_hiddens, num_heads, num_blks, emb_dropout, blk_dropout, num_classes):
        super().__init__()
        self.patch_embedding = PatchEmbeddings(img_size, patch_size, num_hiddens)   # 이미지 패치를 임베딩 벡터로 변환
        self.cls_token = nn.Parameter(torch.zeros(1, 1, num_hiddens))               # 클래스 토큰 초기화 (학습 가능한 파라미터)
        num_patches = (img_size // patch_size) ** 2
        self.pos_embedding = nn.Parameter(torch.randn(1, num_patches + 1, num_hiddens))     # 위치 임베딩 초기화 (학습 가능한 파라미터)
        self.dropout = nn.Dropout(emb_dropout)                                      
       
        self.blocks = nn.ModuleList([Block(num_hiddens, num_heads, blk_dropout, is_decoder=False) for _ in range(num_blks)]) # 트랜스포머 블록 여러 개 쌓기
        self.layer_norm = nn.LayerNorm(num_hiddens) # 최종 레이어 정규화
        self.classifier = nn.Linear(num_hiddens, num_classes) # 분류기(클래스 토큰을 최종적으로 클래스 확률로 변환)

    def forward(self, X):
        x = self.patch_embedding(X)     # shape = (B, num_patches, num_hiddens) from patch embedding. (128, 3, 96, 96) -> (128, 36, 256)
        cls_tokens = self.cls_token.expand(x.shape[0], -1, -1)  # shape = (B, 1, 256)
        x = torch.cat((cls_tokens, x), dim=1)                   # shape = (B, num_patches + 1, num_hiddens). (128, 37, 256)
        x += self.pos_embedding     # 위치 임베딩 추가. 초기값은 랜덤이지만 학습을 통해 최적화됨. (128, 37, 256)
        x = self.dropout(x)
       
      for block in self.blocks:
            x = block(x)
           
        cls_output = self.layer_norm(x[:, 0])   # 클래스 토큰의 최종 표현을 추출 (shape = (B, num_hiddens). (128, 256))
        logits = self.classifier(cls_output)    # 클래스 토큰을 최종적으로 클래스 확률로 변환 (shape = (B, num_classes). (128, 10))
        return logits  

모델 학습
print(f"1. 모델 초기화 (디바이스: {device})")
model = ViT(img_size, patch_size, n_embd, n_head, num_blks, emb_dropout, blk_dropout, num_classes).to(device)
# Adam에 weight decay 추가 (일반화 성능 향상)
optimizer = torch.optim.Adam(model.parameters(), lr=learning_rate, weight_decay=1e-4)
criterion = nn.CrossEntropyLoss()

# [개선] CosineAnnealingLR: 코사인 곡선으로 학습률 감소 → ReduceLROnPlateau 대비 안정적 수렴
scheduler = torch.optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=epochs, eta_min=1e-6)

print("2. CIFAR-10 학습 시작")
best_val_loss = float('inf')
patience = 10  # [개선] 3→10: 코사인 스케줄러 환경에서 일시적 val loss 증가 허용
patience_counter = 0
for epoch in range(epochs):
    model.train()
    running_loss = 0.0
    for i, data in enumerate(trainloader, 0):
        inputs, labels = data
        inputs, labels = inputs.to(device), labels.to(device)

        optimizer.zero_grad()
        outputs = model(inputs)
        loss = criterion(outputs, labels)
        loss.backward()
        optimizer.step()

        running_loss += loss.item()
        # 100 미니배치마다 로그 출력
        if i % 100 == 99:    
            print(f"[Epoch {epoch + 1}, Batch {i + 1}] Loss: {running_loss / 100:.3f}")
            running_loss = 0.0

    # validation loss 계산 (테스트 데이터 사용)
    model.eval()
    val_loss = 0.0
    with torch.no_grad():
        for val_inputs, val_labels in testloader:
            val_inputs, val_labels = val_inputs.to(device), val_labels.to(device)
            val_outputs = model(val_inputs)
            val_loss += criterion(val_outputs, val_labels).item()
    val_loss /= len(testloader)
    print(f"[Epoch {epoch + 1}] Validation Loss: {val_loss:.4f}")
   
    # CosineAnnealingLR 스케줄러 업데이트 (매 epoch 호출)
    scheduler.step()

    # early stopping
    if val_loss < best_val_loss:
        best_val_loss = val_loss
        patience_counter = 0
        # 모델 저장 (optional)
        torch.save(model.state_dict(), 'best_vit_model.pth')
    else:
        patience_counter += 1
        if patience_counter >= patience:
            print("Early stopping triggered.")
            break


학습 결과
학습된 결과는 다음과 같다. 
1. 모델 초기화 (디바이스: cuda)
2. CIFAR-10 학습 시작
[Epoch 1, Batch 100] Loss: 2.161
[Epoch 1, Batch 200] Loss: 2.026
[Epoch 1, Batch 300] Loss: 1.967
[Epoch 1] Validation Loss: 1.8028
[Epoch 2, Batch 100] Loss: 1.856
[Epoch 2, Batch 200] Loss: 1.797
[Epoch 2, Batch 300] Loss: 1.745

유사하게 정답을 맞추는 것을 확인할 수 있다. 자세한 코드 구현은 다음 링크에서 확인할 수 있다. 

VLM 파인튜닝 레퍼런스
VLM 스크래치 레퍼런스
오픈소스 라이브러리

ViT 개념 설명 레퍼런스

2026년 2월 20일 금요일

캐드 역설계 지원 ParSeNet, HPNet 딥러닝 모델 구조 분석

ParSeNet이나 HPNet과 같은 역설계(Scan-to-CAD) 모델의 핵심 '눈(Eye)' 역할을 하는 백본(Backbone) 아키텍처는 주로 3D 점군(Point Cloud)의 기하학적 특징을 추출하는 딥러닝 네트워크로 구성된다.


백본 모델(Backbone Model)의 구조와 기능
이러한 파이프라인의 백본은 2D 이미지의 픽셀을 처리하는 CNN(ResNet 등)과 달리, 순서가 없고 불규칙하게 흩어진 3차원 좌표의 집합을 처리해야 한다. 이를 위해 주로 다음과 같은 3가지 아키텍처가 백본으로 결합되어 사용된다.

PointNet++ (가장 표준적인 백본)
  • 구조: 다층 퍼셉트론(MLP)을 각 점에 독립적으로 적용한 뒤, 최대 풀링(Max Pooling)을 통해 입력 순서에 구애받지 않는 대칭 함수(Symmetric Function)를 구성한다. 여기에 계층적 샘플링(Furthest Point Sampling)과 지역 군집화(Ball Query) 기법을 더해 지역적 기하학(Local Geometry)을 캡처한다.
  • 기능: 단순한 좌표들을 엮어, "이 점 주변은 평평하다", "이 점은 날카로운 모서리에 있다"는 정보를 담은 고차원 특징 벡터로 변환한다.

DGCNN (Dynamic Graph CNN)
  • 구조: 점들 사이의 k-최근접 이웃(k-NN) 그래프를 구성하고, 네트워크 계층이 깊어질수록 특징 공간(Feature Space) 상에서 그래프의 연결을 동적으로 다시 계산하는 EdgeConv 연산을 수행한다.
  • 기능: 점과 점 사이의 '관계'를 학습하는 데 특화되어 있다. 곡률이 변하는 경계면(Boundary)이나 서로 맞닿아 있는 직교 평면의 특징을 뚜렷하게 잡아낸다.

Point Transformer (최신 SOTA 백본)
구조: 자연어 처리에서 쓰이는 셀프 어텐션(Self-attention) 메커니즘을 3D 점군에 맞게 변형하여 적용했다.
기능: 모델 전체의 전역적 맥락(Global Context)을 파악한다. CAD 모델 특유의 대칭성(Symmetry)이나 반복되는 구멍(Hole) 패턴의 특징을 매우 효과적으로 추출한다.

입력 데이터 (Input Data) 예시
백본 모델에 들어가는 입력값은 위상(Topology)이나 크기 정보가 없는 순수한 3D 좌표의 배열이다. 라이다(LiDAR) 스캐너나 가우시안 스플래팅 덩어리에서 추출된 표면 점 데이터가 이에 해당한다.
  • 형태: 실수 배열 (때로는 표면의 수직 방향을 나타내는 법선 벡터를 포함해 특징 배열로 입력됨).
  • 예시 데이터 (기계 부품의 표면 점 10,000개 추출):
  • [ [0.12, 1.55, -0.42], [0.13, 1.55, -0.40], ... [5.00, 2.10, 1.11] // 총 10,000개의 [x, y, z] 배열 ]

출력 데이터 (Output Data) 예시
백본에서 추출된 특징(Feature)은 여러 개의 서브 네트워크(Head)를 거쳐, 최종적으로 '분할 라벨(Segmentation Label)'과 수학적으로 정의된 '파라미터 수치(Parameter Vector)'로 나뉘어 출력된다.

형태 1: 점 단위 분할 확률 (Point-wise Segmentation)
각 점이 어떤 수학적 도형에 속하는지 분류한다.
예시: 점 $P_1$은 '원통(Cylinder)'일 확률 98%.

형태 2: 도형 파라미터 회귀 (Primitive Parameters)
분류된 덩어리에 대해 실수형 파라미터 방정식을 도출한다.
예시 A (평면 표면이 추출된 경우): Type: Plane
Normal_Vector (수직 벡터): [0.0, 0.0, 1.0]
Distance_from_Origin (원점 거리): 15.5

예시 B (드릴로 뚫린 구멍이 추출된 경우):
Type: Cylinder
Axis_Vector (중심축 방향): [0.0, 1.0, 0.0]
Center_Point (중심점): [10.0, 5.0, -2.0]
Radius (반지름 치수): 2.5 (이 수치가 설계 프로그램에서 조절 가능한 치수가 됨)

이러한 출력값들이 모여서 최종적으로 솔리드웍스(SolidWorks)나 인벤터(Inventor)에서 읽을 수 있는 매크로 스크립트나 STEP 형식의 트리 구조로 조립될 수 있다.

2025년 8월 22일 금요일

Text, wav 를 통한 입술 모양 이미지 생성 방법

이 글은 Text, wav 를 통한 입술 모양 이미지 생성 방법을 간략히 정리해 본다.

서론
디지털 휴먼 및 가상 아바타 기술의 발전에 따라, 텍스트 입력에 대한 실시간 립 애니메이션 생성 기술의 중요성이 증대되고 있다. 전통적인 방식은 텍스트로부터 완전한 오디오 파일을 생성한 후, 해당 파일을 기반으로 비디오 프레임을 합성하는 배치(Batch) 처리 방식을 채택한다. 그러나 이 방식은 오디오 파일 생성과 비디오 렌더링에 소요되는 시간으로 인해 상당한 지연(Latency)이 발생하며, 실시간 상호작용 애플리케이션에는 부적합하다. 본 글은 실시간 텍스트립싱크를 구현하기 위한 핵심 기술 요소를 분석한다.

실시간 립싱크 구현을 위한 핵심 파이프라인
실시간 립싱크 시스템은 단일 모델이 아닌, 두 가지 핵심 기술이 순차적으로 결합된 파이프라인(Pipeline) 구조로 구현된다.

1.  스트리밍 텍스트음성 변환 (Streaming TexttoSpeech, TTS)
실시간성을 확보하기 위한 첫 번째 단계는 스트리밍 TTS 엔진이다. 이는 입력 텍스트를 완성된 오디오 파일로 변환하는 것이 아니라, 연속적인 오디오 데이터 스트림(Stream)으로 즉시 생성하는 기술이다. 텍스트가 입력되는 즉시 오디오 청크(Chunk)가 생성되어 파이프라인의 후속 단계로 전달되므로, 전체 문장이 끝날 때까지 기다릴 필요가 없다. 이는 전체 지연 시간을 최소화하는 데 결정적인 역할을 한다.

2.  저지연 얼굴 애니메이션 (Lowlatency Facial Animation)
두 번째 단계는 스트리밍 TTS로부터 전달받은 오디오 청크를 입력받아, 이에 상응하는 입 모양 애니메이션을 즉각적으로 생성하는 모델이다. 이 모델은 오디오 파형, 음소(Phoneme), 또는 음성 특징(Feature)을 분석하여 얼굴 모델의 특정 파라미터를 제어한다. 여기서 핵심은 최소한의 연산으로 최대한 자연스러운 움직임을 생성하여, 오디오와 시각적 출력 사이의 동기화를 유지하는 것이다.

구현 방식에 따른 기술적 접근
실시간 립싱크는 요구되는 성능과 사용 가능한 하드웨어 자원에 따라 다양한 접근 방식이 존재한다.

1. 고성능 GPU 기반 솔루션: NVIDIA Riva 및 Audio2Face
NVIDIA에서 제공하는 이 솔루션은 현재 가장 높은 수준의 실시간성과 품질을 제공하는 산업 표준으로 평가된다. NVIDIA Riva는 고성종 스트리밍 TTS 엔진의 역할을 수행하며, Audio2Face는 Riva로부터 생성된 오디오 스트림을 입력받아 3D 아바타의 얼굴 메쉬(Mesh)를 실시간으로 정교하게 제어한다. 이 방식은 RTX 시리즈 이상의 고성능 GPU를 요구하지만, 매우 낮은 지연 시간과 사실적인 표정 변화를 구현할 수 있다는 장점이 있다.

2. 경량화 오픈소스 모델 조합
제한된 하드웨어 환경에서는 경량화된 오픈소스 모델을 조합하여 시스템을 구축할 수 있다. 예를 들어, 빠른 추론 속도를 보이는 LivePortrait 또는 Wav2Lip과 같은 얼굴 애니메이션 모델과 PiperTTS와 같은 경량 스트리밍 TTS 엔진을 결합하는 방식이다. 이 접근법은 시스템의 전체적인 연산량을 줄여 소비자용 GPU 또는 CPU 환경에서도 실시간 처리를 가능하게 하는 것을 목표로 한다. 다만, 각 구성 요소를 연결하고 최적화하는 추가적인 개발 과정이 요구된다.

MediaPipe
MediaPipe 라이브러리는 립싱크 시스템 구축에 있어 중요한 기반 기술을 제공한다. MediaPipe의 Face Landmarker 기능은 이미지나 비디오 프레임에서 478개의 3D 얼굴 랜드마크와 52개의 블렌드셰이프(Blendshapes)를 정밀하게 추출한다. 블렌드셰이프는 '입 벌리기', '미소' 등 특정 표정의 강도를 수치화한 데이터로, 3D 모델을 제어하는 표준 파라미터로 사용된다.

MediaPipe 자체는 오디오 데이터를 해석하여 립싱크 애니메이션을 생성하는 기능을 포함하고 있지 않다. MediaPipe는 단지 얼굴의 기하학적 구조와 표정을 '표현'하고 '측정'하는 도구일 뿐이다. 

따라서 MediaPipe를 활용한 립싱크 시스템을 구축하기 위해서는, 오디오 스트림을 입력받아 이에 상응하는 블렌드셰이프 값을 예측하는 별도의 '오디오투블렌드셰이프(AudiotoBlendshape)' 변환 모델이 반드시 필요하다. 이 모델이 오디오 분석 엔진의 역할을 수행하며, MediaPipe는 그 결과를 받아 시각적으로 렌더링하는 후처리단에 위치하게 된다.

결론
실시간 텍스트 기반 립싱크는 단순한 모델 하나가 아닌, 스트리밍 TTS와 저지연 얼굴 애니메이션 모델이 유기적으로 결합된 파이프라인을 통해 구현되는 복합적인 기술이다. 고성능 환경에서는 NVIDIA의 솔루션이, 자원이 제한된 환경에서는 경량화된 오픈소스 모델들의 조합이 효과적인 대안이 될 수 있다. 

MediaPipe와 같은 라이브러리는 얼굴 애니메이션의 최종 출력단을 담당하는 핵심적인 구성 요소이지만, 그 자체만으로는 완전한 립싱크 솔루션이 될 수 없으며 오디오를 해석하는 별도의 AI 모델과의 연동이 필수적이다. 

레퍼런스

2025년 8월 21일 목요일

Manim. 코드로 수학적 애니메이션을 만드는 방법

Manim은 복잡한 수학적 개념을 명료하고 아름다운 애니메이션으로 시각화하기 위해 설계된 파이썬(Python) 라이브러리이다.

Manim 개요

Manim(Mathematical Animation Engine)은 코드를 통해 프로그래밍 방식으로 정밀한 2D 애니메이션을 생성하는 데 사용되는 오픈소스 프레임워크이다. 사용자는 파이썬 클래스와 메서드를 활용하여 도형, 텍스트, 그래프, 공식 등 다양한 시각적 요소를 정의하고, 이들의 생성, 변형, 소멸 등 다채로운 움직임을 제어할 수 있다.

단순한 키프레임(Keyframe) 기반의 애니메이션 도구와 달리, Manim은 모든 시각적 요소를 객체(Object)로 다룬다. 따라서 좌표, 크기, 색상, 투명도 등 객체의 모든 속성을 수치적으로 정밀하게 제어하는 것이 가능하다. 이러한 특징 덕분에 미적분, 선형대수, 물리학 공식 등 추상적인 개념을 직관적으로 이해할 수 있도록 설명하는 영상 콘텐츠 제작에 매우 강력한 성능을 보인다.

더 자세한 정보는 Manim 웹사이트에서 확인할 수 있다.


개발 배경

Manim은 저명한 수학 유튜버인 그랜트 샌더슨(Grant Sanderson)이 자신의 유튜브 채널 '3Blue1Brown'의 콘텐츠를 제작하기 위해 직접 개발한 도구에서 시작되었다. 그는 복잡한 수학 이론을 설명하는 데 기존의 시각 자료나 애니메이션 소프트웨어로는 한계가 있다고 느꼈다. 특히, 수학적 개념의 본질적인 아름다움과 논리적 흐름을 정확하게 표현하기 위해서는 프로그래밍을 통한 완전한 제어권이 필수적이라고 생각했다.

이러한 필요에 의해 2015년부터 Manim을 개인 프로젝트로 개발하기 시작했으며, 그의 영상이 큰 인기를 얻으면서 Manim 역시 전 세계의 교육자, 개발자, 아티스트들로부터 주목받게 되었다. 이후, 그의 독창적인 버전을 기반으로 개발자 커뮤니티가 주도하는 보다 범용적이고 사용하기 쉬운 'Manim Community Edition'이 파생되어 활발하게 발전하고 있다. Manim의 소스코드는 커뮤니티 GitHub 저장소에서 확인할 수 있다.


유사 도구

Manim과 유사하게 코드를 통해 시각적 결과물을 생성하는 라이브러리는 여러 가지가 있으며, 각각 다른 목적과 특징을 가지고 있다.

  • Processing: 시각 예술, 데이터 시각화, 인터랙티브 아트 분야에서 널리 사용되는 오픈소스 프로그래밍 언어 및 환경이다. Java를 기반으로 하며, 초보자가 쉽게 그래픽 프로그래밍에 입문할 수 있도록 설계되었다. 애니메이션 기능도 지원하지만, Manim만큼 수학적 표현에 특화되어 있지는 않다. (Processing 웹사이트)

  • D3.js: 웹 브라우저에서 동적인 데이터 기반 문서를 만들기 위한 자바스크립트(JavaScript) 라이브러리이다. SVG, HTML, CSS를 활용하여 데이터 시각화, 특히 인터랙티브 차트와 그래프를 제작하는 데 매우 강력한 기능을 제공한다. 웹 기반이라는 점에서 Manim과 차이가 있다. (D3.js 웹사이트)

  • Pygame: 파이썬으로 2D 게임을 개발하기 위해 설계된 라이브러리이다. 실시간 사용자 입력 처리와 게임 루프 관리에 중점을 두고 있어, 정해진 스크립트에 따라 렌더링되는 Manim과는 작동 방식과 목적이 다르다. (Pygame 웹사이트)


Manim은 MIT 라이선스를 따르는 완전한 오픈소스 소프트웨어이다. 이는 개인, 교육 기관, 상업적 목적을 포함한 어떠한 용도로든 무료로 사용할 수 있음을 의미한다. 별도의 가입 절차나 유료 플랜 없이 누구나 자유롭게 다운로드하여 설치하고, 소스코드를 수정하거나 재배포하는 것이 가능하다. 모든 기능은 커뮤니티의 자발적인 기여를 통해 개발되고 유지보수된다.


설치 및 사용 방법

Manim을 사용하기 위해서는 파이썬과 몇 가지 시스템 의존성 패키지를 먼저 설치해야 한다.

  1. 파이썬 설치: 공식 웹사이트에서 파이썬 3.8 이상의 버전을 다운로드하여 설치한다.

  2. 시스템 의존성 설치: FFmpeg는 애니메이션을 영상 파일로 렌더링하는 데 필수적인 도구이다. FFmpeg 공식 웹사이트에서 운영체제에 맞게 설치한다. LaTeX는 수식을 렌더링하기 위해 필요하다. MiKTeX(Windows), MacTeX(macOS), TeX Live(Linux) 등을 설치한다.

  1. Manim 라이브러리 설치: 터미널 또는 명령 프롬프트에서 아래의 pip 명령어를 실행하여 Manim을 설치한다.
    pip install manim

  2. 코드 작성: 텍스트 편집기나 IDE(통합 개발 환경)를 열어 파이썬 스크립트(.py) 파일을 생성하고, Manim 문법에 맞게 애니메이션 코드를 작성한다.

  3. 렌더링 실행: 터미널에서 아래와 같은 명령어를 사용하여 작성한 파이썬 파일을 실행하면, 코드가 렌더링 되어 영상 파일(기본적으로 .mp4)이 생성된다.
    manim -pql your_script_name.py YourSceneClassName

-pql은 미리보기(preview) 와 낮은 품질(low quality) 로 빠르게 렌더링하라는 옵션이다.

예제 코드: SquareToCircle

아래 코드는 사각형이 원으로 변형되는 간단한 애니메이션을 생성하는 Manim 스크립트이다.

from manim import *

class SquareToCircle(Scene):
    def construct(self):
        circle = Circle()
        square = Square()
        square.flip(RIGHT)
        square.rotate(-3 * TAU / 8)
        circle.set_fill(PINK, opacity=0.5)

        self.play(Create(square))
        self.play(Transform(square, circle))
        self.play(FadeOut(square))


  • from manim import *: Manim 라이브러리의 모든 구성요소(클래스, 함수 등)를 현재 스크립트로 가져온다.

  • class SquareToCircle(Scene):: SquareToCircle이라는 이름의 새로운 애니메이션 장면(Scene)을 정의한다. Manim의 모든 애니메이션은 이 Scene 클래스를 상속받아 만들어진다.

  • def construct(self):: 이 메서드 안에 애니메이션의 구체적인 동작과 순서를 기술한다. Manim이 장면을 렌더링할 때 이 construct 메서드를 자동으로 실행한다.

  • circle = Circle() / square = Square(): 화면에 표시될 원(Circle)과 사각형(Square) 객체를 생성하여 각각 circle, square 변수에 할당한다. 아직 화면에 나타나지는 않는다.

  • square.flip(RIGHT): square 객체를 오른쪽 방향(RIGHT 축)을 기준으로 뒤집는다.

  • square.rotate(-3 * TAU / 8): square 객체를 회전시킨다. TAU는 2π (360도)를 의미하는 Manim의 상수이며, 따라서 −135도 만큼 시계 방향으로 회전시킨다.

  • circle.set_fill(PINK, opacity=0.5): circle 객체의 내부를 분홍색(PINK)으로 채우고, 투명도를 50%(0.5)로 설정한다.

  • self.play(Create(square)): 첫 번째 애니메이션을 실행한다. Create는 객체가 점차 그려지며 나타나는 효과이다. 이 줄이 실행되면 앞서 설정된 모양의 사각형이 화면에 그려진다.

  • self.play(Transform(square, circle)): 두 번째 애니메이션을 실행한다. Transform은 첫 번째 객체(square)가 두 번째 객체(circle)의 모양과 속성으로 점진적으로 변형되는 효과를 만든다. 이 줄이 실행되면 사각형이 원으로 변신한다.

  • self.play(FadeOut(square)): 세 번째 애니메이션을 실행한다. FadeOut은 객체가 서서히 투명해지며 사라지는 효과이다. square 객체(이제 원의 모습을 하고 있음)가 화면에서 사라지며 애니메이션이 종료된다.