세션 풀 초기화 및 모델 로딩 로직을 개선하고, CUDA 자동 감지 기능을 추가하였습니다. 상태 JSON 파일의 워커 수를 4로 업데이트하고, 로그 파일에 서버 시작 및 상태 저장 관련 메시지를 기록하였습니다. README.md에 Jetson Xavier에서의 ONNX Runtime GPU 설치 절차를 추가하였으며, 요구 사항 파일에서 numpy 버전을 1.24.4로 업데이트하였습니다. 스크립트에서 자동 설치 및 실행 기능을 개선하고, x86 시스템에 대한 설치 스크립트를 수정하였습니다.

This commit is contained in:
AGX 2025-08-29 21:37:33 +09:00
parent b5a0098eb7
commit 83b24d4e05
26 changed files with 5734 additions and 1500 deletions

View File

@ -69,9 +69,12 @@ FastAPI와 딥러닝을 활용한 병렬 처리 인페인팅 서버입니다. Si
- **CUDA**: 11.8 - **CUDA**: 11.8
- **cuDNN**: 8 - **cuDNN**: 8
- **TensorRT**: 8.6 - **TensorRT**: 8.6
- **GCC**: 11 이상 (ONNX Runtime GPU 호환성용)
- **RAM**: 4GB 이상 권장 - **RAM**: 4GB 이상 권장
- **저장공간**: 10GB 이상 - **저장공간**: 10GB 이상
> **중요**: Jetson Xavier에서 GPU 가속을 위해서는 GCC 11과 특별한 ONNX Runtime 버전이 필요합니다. 자동 설치 스크립트가 이를 자동으로 처리합니다.
### x86_64 시스템 ### x86_64 시스템
- **OS**: Ubuntu 18.04 이상 - **OS**: Ubuntu 18.04 이상
- **Python**: 3.8 이상 (3.10 권장) - **Python**: 3.8 이상 (3.10 권장)
@ -104,34 +107,58 @@ cd inpaintServer
chmod +x scripts/*.sh chmod +x scripts/*.sh
``` ```
### 2. 원클릭 설치 및 실행 ### 2. 🚀 원클릭 자동 설치 (권장)
#### Jetson Xavier (ARM64) #### 플랫폼 자동 감지 설치
```bash ```bash
# Jetson 전용 설치 및 실행 # 🎯 플랫폼 자동 감지 후 최적 설치
bash scripts/setup_and_run.sh bash scripts/install.sh
``` ```
#### x86_64 시스템 (RTX 3060 12GB 등) #### 플랫폼별 직접 설치
**🚀 Jetson Xavier (ARM64):**
```bash ```bash
# x86 전용 설치 및 실행 # Jetson Xavier 전용 최적화 설치
bash scripts/setup_jetson.sh
```
**🖥️ x86-64 Desktop:**
```bash
# x86-64 데스크톱 최적화 설치
bash scripts/setup_x86.sh bash scripts/setup_x86.sh
``` ```
```bash ### 📁 가상환경 설정 방식
# 시스템 자동 감지 (권장)
./scripts/setup_and_run.sh
# 또는 시스템별 지정 이 프로젝트는 **유연한 가상환경 설정**을 지원합니다:
./scripts/setup_and_run.sh --jetson # Jetson Xavier
./scripts/setup_and_run.sh --x86 # x86_64 시스템 #### 방식 1: 표준 venv 디렉토리 (기본)
```bash
python3 -m venv venv
source venv/bin/activate
``` ```
**이 스크립트가 자동으로 수행하는 작업:** #### 방식 2: 프로젝트 자체를 가상환경으로 사용
- ✅ 가상환경 생성 (`python -m venv venv`) ```bash
- ✅ 의존성 설치 (`requirements.txt`) # 프로젝트 디렉토리에서 직접 가상환경 생성
- ✅ 모델 파일 확인 및 안내 python3 -m venv .
- ✅ 서버 시작 (메인 + 모니터링) source bin/activate
```
> **💡 스마트 감지**: 설치 스크립트가 자동으로 감지하고 처리합니다
> - `venv/` 디렉토리 존재 → 표준 venv 사용
> - `pyvenv.cfg` 파일 존재 → 프로젝트 자체가 가상환경
> - 둘 다 없음 → 새로운 `venv/` 디렉토리 생성
**자동 설치가 수행하는 작업:**
- ✅ 플랫폼 자동 감지 (Jetson Xavier vs x86-64)
- ✅ 가상환경 자동 생성/감지
- ✅ GPU 최적화 의존성 설치
- ✅ ONNX Runtime GPU 설치 (플랫폼별)
- ✅ PyTorch CUDA 설치
- ✅ 모델 호환성 확인
- ✅ 설치 검증 및 테스트
### 3. 수동 설치 (고급 사용자) ### 3. 수동 설치 (고급 사용자)
@ -638,6 +665,34 @@ ls -la app/models/onnx/
### Jetson 전용 문제 ### Jetson 전용 문제
#### ONNX Runtime GPU 설치 (GLIBCXX 호환성 문제)
Jetson Xavier에서 ONNX Runtime GPU를 사용하려면 특별한 절차가 필요합니다:
```bash
# 1. GCC 11 업그레이드 (필수)
sudo apt install software-properties-common
sudo add-apt-repository ppa:ubuntu-toolchain-r/test
sudo apt update
sudo apt install gcc-11 g++-11
# 2. Jetson 전용 ONNX Runtime GPU 휠 다운로드 및 설치
wget https://nvidia.box.com/shared/static/zostg6agm00fb6t5uisw51qi6kpcuwzd.whl \
-O onnxruntime_gpu-1.17.0-cp38-cp38-linux_aarch64.whl
pip install --force-reinstall onnxruntime_gpu-1.17.0-cp38-cp38-linux_aarch64.whl
# 3. 설치 확인
python -c "import onnxruntime as ort; print('ONNX Runtime 버전:', ort.__version__); print('Available providers:', ort.get_available_providers())"
```
**예상 출력**:
```
ONNX Runtime 버전: 1.17.0
Available providers: ['TensorrtExecutionProvider', 'CUDAExecutionProvider', 'CPUExecutionProvider']
```
> **참고**: 자동 설치 스크립트를 사용하면 위 과정이 자동으로 수행됩니다.
#### 전력 모드 설정 #### 전력 모드 설정
```bash ```bash
# MAXN 모드로 설정 # MAXN 모드로 설정

View File

@ -3,7 +3,7 @@ Configuration settings for the inpainting server
""" """
import os import os
import platform import platform
from typing import Dict, Any from typing import Dict, Any, Optional
from pydantic_settings import BaseSettings from pydantic_settings import BaseSettings
@ -17,9 +17,16 @@ class Settings(BaseSettings):
PORT: int = 8008 PORT: int = 8008
WORKERS: int = 1 WORKERS: int = 1
# GPU settings # GPU settings (Jetson Xavier 최적화)
CUDA_DEVICE: int = 0 CUDA_DEVICE: int = 0
FP16_ENABLED: bool = True USE_CUDA: bool = True # CUDA 사용 여부 (Jetson에서 항상 True)
USE_FP16: bool = True # FP16 사용 여부 (Jetson 최적화)
FP16_ENABLED: bool = True # 기존 호환성
# ONNX Runtime 최적화 설정
USE_TENSORRT: bool = True # TensorRT 사용 여부 (최고 성능)
TENSORRT_FP16: bool = True # TensorRT FP16 사용
TENSORRT_WORKSPACE_SIZE: int = 2 * 1024 * 1024 * 1024 # 2GB
# Jetson specific settings # Jetson specific settings
JETSON_MODE: bool = IS_JETSON JETSON_MODE: bool = IS_JETSON
@ -27,29 +34,38 @@ class Settings(BaseSettings):
JETSON_FAN_CONTROL: bool = True JETSON_FAN_CONTROL: bool = True
JETSON_TEMP_THRESHOLD: int = 75 # Celsius JETSON_TEMP_THRESHOLD: int = 75 # Celsius
# Session pool settings # Session pool settings (Jetson Xavier는 32GB 통합 메모리로 더 많은 세션 가능)
SIMPLE_LAMA_SESSIONS: int = 2 if IS_JETSON else 4 SIMPLE_LAMA_SESSIONS: int = 4 if IS_JETSON else 2 # Jetson: 통합 32GB vs 데스크톱: VRAM 제한
MIGAN_SESSIONS: int = 2 if IS_JETSON else 4 MIGAN_SESSIONS: int = 4 if IS_JETSON else 2 # Jetson이 더 많은 세션 운영 가능
REMBG_SESSIONS: int = 1 if IS_JETSON else 3 REMBG_SESSIONS: int = 3 if IS_JETSON else 1 # 메모리 공유 방식의 이점 활용
# Worker settings (Jetson은 더 적은 워커 사용) # Worker settings (Jetson은 통합 메모리로 효율적)
MAX_WORKERS: int = 6 if IS_JETSON else 16 MAX_WORKERS: int = 8 if IS_JETSON else 6 # Jetson: 메모리 오버헤드 적음
MIN_WORKERS: int = 2 if IS_JETSON else 8 MIN_WORKERS: int = 4 if IS_JETSON else 2 # 통합 메모리 활용
WORKER_TIMEOUT: int = 120 # 2 minutes WORKER_TIMEOUT: int = 120 # 2 minutes
# VRAM management (Jetson은 더 보수적인 설정) # 메모리 관리 (Jetson은 32GB 통합 메모리로 여유로움)
VRAM_THRESHOLD_HIGH: float = 0.7 if IS_JETSON else 0.85 # 70% for Jetson, 85% for x86 VRAM_THRESHOLD_HIGH: float = 0.85 if IS_JETSON else 0.75 # Jetson: 32GB 통합 메모리
VRAM_THRESHOLD_LOW: float = 0.3 if IS_JETSON else 0.25 # 30% for Jetson, 25% for x86 VRAM_THRESHOLD_LOW: float = 0.4 if IS_JETSON else 0.3 # 데스크톱: VRAM 제한
VRAM_CHECK_INTERVAL: int = 20 if IS_JETSON else 20 # More frequent for both VRAM_CHECK_INTERVAL: int = 30 if IS_JETSON else 15 # Jetson은 덜 자주 체크
# Model paths # Model paths
SIMPLE_LAMA_MODEL_PATH: str = "models/simple-lama" SIMPLE_LAMA_MODEL_PATH: str = "app/models/pt/big-lama.pt"
MIGAN_MODEL_PATH: str = "models/migan" MIGAN_MODEL_PATH: str = "app/models/onnx/migan_pipeline_v2.onnx"
REMBG_MODEL_PATH: str = "models/rembg" REMBG_MODEL_PATH: str = "app/models/onnx/birefnet-general-lite.onnx"
# Upload settings # MIGAN ONNX settings
MAX_FILE_SIZE: int = 25 * 1024 * 1024 if IS_JETSON else 100 * 1024 * 1024 # 25MB for Jetson, 100MB for x86 MIGAN_ONNX_PATH: Optional[str] = "app/models/onnx/migan_pipeline_v2.onnx" # 커스텀 ONNX 파일 경로
MAX_IMAGE_SIZE: int = 2048 if IS_JETSON else 8192 # Maximum image dimension for Jetson, 8K for x86 MIGAN_INTRA_THREADS: int = 0
MIGAN_INTER_THREADS: int = 0
# REMBG settings (자동 다운로드 방식)
REMBG_MODEL_NAME: str = "birefnet-general-lite" # 고품질 경량 모델
LOCAL_REMBG_MODEL_PATH: Optional[str] = None # 로컬 파일 사용 안함
# Upload settings (Jetson Xavier는 32GB 메모리로 대용량 처리 가능)
MAX_FILE_SIZE: int = 100 * 1024 * 1024 if IS_JETSON else 50 * 1024 * 1024 # Jetson: 100MB, 데스크톱: 50MB
MAX_IMAGE_SIZE: int = 4096 if IS_JETSON else 3072 # Jetson: 4K, 데스크톱: 3K (VRAM 고려)
ALLOWED_EXTENSIONS: set = {".jpg", ".jpeg", ".png", ".bmp", ".tiff"} ALLOWED_EXTENSIONS: set = {".jpg", ".jpeg", ".png", ".bmp", ".tiff"}
# Monitoring # Monitoring

View File

@ -105,21 +105,62 @@ class SessionPool:
async def _load_simple_lama_model(self): async def _load_simple_lama_model(self):
"""Simple LAMA 모델을 로드합니다.""" """Simple LAMA 모델을 로드합니다."""
# Placeholder - 실제 모델 로딩 로직으로 대체 from ..models.simple_lama import SimpleLamaInpainter
await asyncio.sleep(0.1) # 시뮬레이션 from ..core.config import settings
return {"model": "simple_lama", "loaded": True}
try:
model = SimpleLamaInpainter(
model_path=settings.SIMPLE_LAMA_MODEL_PATH,
device="cuda" if settings.USE_CUDA else "cpu",
fp16=settings.USE_FP16
)
await model.load_model()
logger.info("Simple LAMA 모델 세션 로드 완료")
return model
except Exception as e:
logger.error(f"Simple LAMA 모델 로드 실패: {e}")
raise
async def _load_migan_model(self): async def _load_migan_model(self):
"""MIGAN 모델을 로드합니다.""" """MIGAN 모델을 로드합니다."""
# Placeholder - 실제 모델 로딩 로직으로 대체 from ..models.migan import MiganInpainter
await asyncio.sleep(0.1) # 시뮬레이션 from ..core.config import settings
return {"model": "migan", "loaded": True}
try:
# MIGAN 모델 생성 - ONNX Runtime이 자동으로 CUDA 감지
model = MiganInpainter(
model_path=getattr(settings, 'MIGAN_ONNX_PATH', settings.MIGAN_MODEL_PATH),
device="cuda" if settings.USE_CUDA else "cpu",
fp16=settings.USE_FP16,
use_cuda=settings.USE_CUDA
)
await model.load_model()
logger.info("MIGAN 모델 세션 로드 완료")
return model
except Exception as e:
logger.error(f"MIGAN 모델 로드 실패: {e}")
raise
async def _load_rembg_model(self): async def _load_rembg_model(self):
"""REMBG 모델을 로드합니다.""" """REMBG 모델을 로드합니다."""
# Placeholder - 실제 모델 로딩 로직으로 대체 from ..models.rembg_model import RembgProcessor
await asyncio.sleep(0.1) # 시뮬레이션 from ..core.config import settings
return {"model": "rembg", "loaded": True}
try:
# RemBG 모델 생성 - 자동으로 CUDA 감지
model = RembgProcessor(
model_name=getattr(settings, 'REMBG_MODEL_NAME', 'birefnet-general-lite'),
device="cuda" if settings.USE_CUDA else "cpu",
fp16=settings.USE_FP16,
local_rembg_model_path=getattr(settings, 'LOCAL_REMBG_MODEL_PATH', None)
)
# 프리로드 강제: 실패 시 서버 기동 실패로 처리 (원인 파악을 위함)
await model.load_model()
logger.info("REMBG 모델 세션 로드 완료")
return model
except Exception as e:
logger.error(f"REMBG 모델 로드 실패: {e}")
raise
@asynccontextmanager @asynccontextmanager
async def get_session(self, model_type: ModelType): async def get_session(self, model_type: ModelType):
@ -240,5 +281,10 @@ class SessionPool:
return status_by_model return status_by_model
# 전역 세션 풀 인스턴스 # 전역 세션 풀 인스턴스 (설정값으로 초기화)
session_pool = SessionPool() from ..core.config import settings
session_pool = SessionPool(
simple_lama_count=settings.SIMPLE_LAMA_SESSIONS,
migan_count=settings.MIGAN_SESSIONS,
rembg_count=settings.REMBG_SESSIONS
)

View File

@ -6,7 +6,7 @@ import asyncio
import logging import logging
import time import time
import uuid import uuid
from typing import Dict, List, Optional, Callable, Any from typing import Dict, List, Optional, Callable, Any, Tuple
from dataclasses import dataclass from dataclasses import dataclass
from enum import Enum from enum import Enum
from concurrent.futures import ThreadPoolExecutor from concurrent.futures import ThreadPoolExecutor
@ -330,49 +330,50 @@ class WorkerManager:
async def process_inpaint(self, **kwargs) -> Optional[np.ndarray]: async def process_inpaint(self, **kwargs) -> Optional[np.ndarray]:
"""인페인팅 작업을 처리합니다.""" """인페인팅 작업을 처리합니다."""
try: try:
# 간단한 시뮬레이션 (실제로는 세션 풀에서 모델을 가져와 처리) from ..core.session_pool import session_pool, ModelType
from ..models.simple_lama import SimpleLamaInpainter
from ..models.migan import MiganInpainter
model_name = kwargs.get('model_name', 'simple-lama') model_name = kwargs.get('model_name', 'simple-lama')
# 모델명에 따라 세션 타입 결정
if model_name == 'simple-lama': if model_name == 'simple-lama':
model = SimpleLamaInpainter() model_type = ModelType.SIMPLE_LAMA
elif model_name == 'migan': elif model_name == 'migan':
model = MiganInpainter() model_type = ModelType.MIGAN
else: else:
model = SimpleLamaInpainter() # 기본값 model_type = ModelType.SIMPLE_LAMA # 기본값
# 모델 처리 (실제로는 세션 풀에서 가져온 모델 사용) # 세션 풀에서 모델 세션 가져와서 처리
result = await model.inpaint( async with session_pool.get_session(model_type) as session:
image=kwargs['image'], # session.model 에서 실제 모델 객체의 메서드를 호출해야 함
mask=kwargs['mask'] result = await session.model.inpaint(
) image=kwargs['image'],
mask=kwargs['mask']
)
return result return result
except Exception as e: except Exception as e:
logger.error(f"인페인팅 처리 실패: {e}") logger.error(f"인페인팅 처리 실패: {e}", exc_info=True)
return None return None
async def process_remove_bg(self, **kwargs) -> Optional[np.ndarray]: async def process_remove_bg(self, **kwargs) -> Optional[Tuple[np.ndarray, np.ndarray]]:
"""배경 제거 작업을 처리합니다.""" """배경 제거 작업을 처리합니다."""
try: try:
# 간단한 시뮬레이션 (실제로는 세션 풀에서 모델을 가져와 처리) from ..core.session_pool import session_pool, ModelType
from ..models.rembg_model import RembgProcessor
model = RembgProcessor() # 세션 풀에서 REMBG 모델 세션 가져와서 처리
async with session_pool.get_session(ModelType.REMBG) as session:
# 모델 처리 (실제로는 세션 풀에서 가져온 모델 사용) # session.model 에서 실제 모델 객체의 메서드를 호출해야 함
result = await model.remove_background( result = await session.model.remove_background(
image=kwargs['image'] image=kwargs['image'],
) model_name=kwargs.get('model_name', 'u2net')
)
return result return result
except Exception as e: except Exception as e:
logger.error(f"배경 제거 처리 실패: {e}") logger.error(f"배경 제거 처리 실패: {e}", exc_info=True)
return None return None, None
# 전역 워커 매니저 인스턴스 # 전역 워커 매니저 인스턴스

View File

@ -1,190 +1,285 @@
""" """
MIGAN 인페인팅 모델 구현 MIGAN ONNX 인페인팅 모델 구현 (실제 ONNX 파이프라인 사용)
""" """
import torch import os
import numpy as np import time
import cv2
from PIL import Image
import logging import logging
from typing import Union, Tuple from typing import Optional, Union
import asyncio import cv2
import numpy as np
import onnxruntime as ort
from PIL import Image
# OpenCV 내부 최적화 off
cv2.setUseOptimized(False)
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
def _np_uint8_2d(arr, name="mask"):
if arr is None:
raise ValueError(f"{name} is None")
if not isinstance(arr, np.ndarray):
raise TypeError(f"{name} must be np.ndarray, got {type(arr)}")
if arr.ndim != 2:
raise ValueError(f"{name} must be 2D, got shape={arr.shape}")
if arr.dtype != np.uint8:
# 안전 변환
arr = arr.astype(np.uint8, copy=False)
return arr
class MiganInpainter: class MiganInpainter:
def __init__(self, model_path: str = None, device: str = "cuda", fp16: bool = True): """
MIGAN ONNX 파이프라인 래퍼
- 입력: image_path(str), mask(gray uint8 HxW) 텍스트영역=255
- 내부에서 mask를 (이진화반전)하여 MI-GAN 규칙(255=known, 0=hole)으로 맞춤
- 출력: BGR uint8(H,W,3)
"""
def __init__(self,
model_path: str = None,
device: str = "cuda",
fp16: bool = True,
use_cuda: bool = False,
intra_threads: int = 0,
inter_threads: int = 0):
self.model_path = model_path self.model_path = model_path
self.device = device self.device = device
self.fp16 = fp16 self.fp16 = fp16
self.model = None self.use_cuda = bool(use_cuda)
self.intra_threads = int(intra_threads or 0)
self.inter_threads = int(inter_threads or 0)
self.loaded = False self.loaded = False
if not model_path or not os.path.exists(model_path):
logger.error(f"MIGAN ONNX 파일을 찾을 수 없습니다: {model_path}")
raise FileNotFoundError(f"MIGAN ONNX 파일이 없습니다: {model_path}")
self.session = None
self._session = None
self.in_image = None
self.in_mask = None
self.out_name = None
async def _get_or_create_session(self):
"""ONNX 런타임 세션을 생성하거나 기존 세션을 반환합니다."""
if self._session is None:
try:
logger.info("MIGAN ONNX 런타임 세션 생성 시도...")
import onnxruntime as ort
so = ort.SessionOptions()
if self.intra_threads > 0:
so.intra_op_num_threads = self.intra_threads
if self.inter_threads > 0:
so.inter_op_num_threads = self.inter_threads
providers = []
if self.use_cuda:
providers = ['CUDAExecutionProvider', 'CPUExecutionProvider']
logger.info(f"MIGAN ONNX providers 설정: {providers}")
else:
providers = ['CPUExecutionProvider']
logger.info("MIGAN ONNX CPU-only mode 로 설정")
self._session = ort.InferenceSession(
self.model_path,
sess_options=so,
providers=providers
)
logger.info(f"MIGAN ONNX 세션 생성 완료. Providers: {self._session.get_providers()}")
except Exception as e:
logger.error(f"MIGAN ONNX 세션 초기화 실패: {e}", exc_info=True)
if 'ort' in locals():
logger.error(f"사용 가능한 providers: {ort.get_available_providers()}")
raise RuntimeError(f"MIGAN 모델 초기화 실패: {e}")
return self._session
async def load_model(self): async def load_model(self):
"""모델을 비동기적으로 로드합니다.""" """모델을 비동기적으로 로드합니다."""
if self.loaded: if self.loaded:
return return
try: try:
logger.info("Loading MIGAN model...") logger.info("Loading MIGAN ONNX model...")
# 실제 구현에서는 MIGAN 모델을 로드 self.session = await self._get_or_create_session()
# 여기서는 플레이스홀더로 구현 ins = self.session.get_inputs()
await asyncio.sleep(0.1) # 모델 로딩 시뮬레이션 outs = self.session.get_outputs()
self.in_image = ins[0].name
self.in_mask = ins[1].name
self.out_name = outs[0].name
for i, inp in enumerate(ins):
logger.debug(f"MIGAN 입력 {i}: {inp.name}, 형태: {inp.shape}, 타입: {inp.type}")
for i, out in enumerate(outs):
logger.debug(f"MIGAN 출력 {i}: {out.name}, 형태: {out.shape}, 타입: {out.type}")
logger.debug(f"MIGAN 세션 준비 완료. providers={self.session.get_providers()}")
# TODO: 실제 모델 로딩 로직
# self.model = load_migan_model(self.model_path, device=self.device)
self.model = {"type": "migan", "device": self.device, "fp16": self.fp16}
self.loaded = True self.loaded = True
logger.info("MIGAN ONNX model loaded successfully")
logger.info("MIGAN model loaded successfully")
except Exception as e: except Exception as e:
logger.error(f"Failed to load MIGAN model: {e}") logger.error(f"Failed to load MIGAN model: {e}", exc_info=True)
raise raise
def preprocess_image(self, image: Union[Image.Image, np.ndarray]) -> torch.Tensor: async def inpaint(self, image: Union[str, Image.Image, np.ndarray],
"""이미지를 전처리합니다."""
if isinstance(image, Image.Image):
image = np.array(image)
# RGB로 변환
if image.shape[2] == 4: # RGBA
image = cv2.cvtColor(image, cv2.COLOR_RGBA2RGB)
elif image.shape[2] == 3 and image.dtype == np.uint8:
image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
# 크기 조정 (MIGAN은 특정 크기를 선호할 수 있음)
height, width = image.shape[:2]
if height != 512 or width != 512:
image = cv2.resize(image, (512, 512), interpolation=cv2.INTER_LANCZOS4)
# 정규화 (-1 to 1, MIGAN 스타일)
image = image.astype(np.float32) / 127.5 - 1.0
# 텐서로 변환 (B, C, H, W)
tensor = torch.from_numpy(image).permute(2, 0, 1).unsqueeze(0)
if self.fp16:
tensor = tensor.half()
return tensor.to(self.device)
def preprocess_mask(self, mask: Union[Image.Image, np.ndarray]) -> torch.Tensor:
"""마스크를 전처리합니다."""
if isinstance(mask, Image.Image):
mask = np.array(mask)
# 그레이스케일로 변환
if len(mask.shape) == 3:
mask = cv2.cvtColor(mask, cv2.COLOR_RGB2GRAY)
# 크기 조정
if mask.shape[0] != 512 or mask.shape[1] != 512:
mask = cv2.resize(mask, (512, 512), interpolation=cv2.INTER_NEAREST)
# 이진화 (0 또는 1)
mask = (mask > 127).astype(np.float32)
# 텐서로 변환 (B, 1, H, W)
tensor = torch.from_numpy(mask).unsqueeze(0).unsqueeze(0)
if self.fp16:
tensor = tensor.half()
return tensor.to(self.device)
def postprocess_result(self, tensor: torch.Tensor, original_size: Tuple[int, int]) -> np.ndarray:
"""결과를 후처리합니다."""
# CPU로 이동하고 numpy로 변환
if tensor.is_cuda:
tensor = tensor.cpu()
if tensor.dtype == torch.float16:
tensor = tensor.float()
result = tensor.squeeze(0).permute(1, 2, 0).numpy()
# -1 to 1 범위에서 0-255로 변환
result = ((result + 1.0) * 127.5).clip(0, 255).astype(np.uint8)
# 원본 크기로 복원
if result.shape[:2] != original_size:
result = cv2.resize(result, (original_size[1], original_size[0]),
interpolation=cv2.INTER_LANCZOS4)
return result
async def inpaint(self, image: Union[Image.Image, np.ndarray],
mask: Union[Image.Image, np.ndarray]) -> np.ndarray: mask: Union[Image.Image, np.ndarray]) -> np.ndarray:
"""인페인팅을 수행합니다.""" """
인페인팅을 수행합니다.
Args:
image: 원본 이미지 (파일 경로, PIL Image, 또는 numpy array)
mask: 마스크 (PIL Image 또는 numpy array, 텍스트영역=255)
Returns:
인페인팅된 이미지 (BGR numpy array)
"""
if not self.loaded: if not self.loaded:
await self.load_model() await self.load_model()
try: try:
# 원본 크기 저장 # 1) 입력 이미지 로드
if isinstance(image, Image.Image): if isinstance(image, str):
original_size = image.size[::-1] # (height, width) bgr = cv2.imread(image, cv2.IMREAD_COLOR)
if bgr is None:
logger.error(f"MIGAN 이미지 로드 실패: {image}")
return None
elif isinstance(image, Image.Image):
bgr = cv2.cvtColor(np.array(image), cv2.COLOR_RGB2BGR)
elif isinstance(image, np.ndarray):
if image.shape[2] == 3:
bgr = image.copy()
else:
logger.error(f"MIGAN 지원하지 않는 이미지 형태: {image.shape}")
return None
else: else:
original_size = image.shape[:2] logger.error(f"MIGAN 지원하지 않는 이미지 타입: {type(image)}")
return None
H, W = bgr.shape[:2]
# 2) 마스크 정규화: (이진화 → 반전) 해서 255=known, 0=hole 맞추기
if isinstance(mask, Image.Image):
mask_array = np.array(mask)
else:
mask_array = mask
# 전처리 mask_normalized = _np_uint8_2d(mask_array, name="mask")
image_tensor = self.preprocess_image(image) if mask_normalized.shape != (H, W):
mask_tensor = self.preprocess_mask(mask) logger.error(f"MIGAN 마스크 크기 불일치: mask={mask_normalized.shape}, img={(H,W)}")
return None
# 이진화: 128 threshold 기준
_, mask_bin = cv2.threshold(mask_normalized, 128, 255, cv2.THRESH_BINARY)
# 마스크 반전: 텍스트영역 255 -> 0 (hole), 배경 0 -> 255 (known)
mask_known255 = 255 - mask_bin
# 3) RGB 변환 (파이프라인 입력은 RGB uint8)
rgb = cv2.cvtColor(bgr, cv2.COLOR_BGR2RGB)
# 4) ONNX 추론 - 배치 차원 추가 및 차원 순서 변경
start = time.time()
# ONNX 모델 입력 형태:
# - image: (1, 3, H, W) - 배치, 채널, 높이, 너비 순서
# - mask: (1, 1, H, W) - 배치, 채널(1), 높이, 너비 순서
# 추론 (실제 구현에서는 모델 추론) # 이미지: (H, W, 3) -> (1, 3, H, W)
with torch.no_grad(): rgb_batch = np.expand_dims(rgb, 0).transpose(0, 3, 1, 2)
# TODO: 실제 모델 추론 로직
# result = self.model(image_tensor, mask_tensor)
# 플레이스홀더: 더 정교한 인페인팅 시뮬레이션
result = await self._simulate_advanced_inpainting(image_tensor, mask_tensor)
# 후처리
result_np = self.postprocess_result(result, original_size)
return result_np # 마스크: (H, W) -> (1, 1, H, W)
mask_batch = np.expand_dims(mask_known255, (0, 1))
logger.debug(f"MIGAN 입력 형태 - 이미지: {rgb_batch.shape}, 마스크: {mask_batch.shape}")
out = self.session.run(
[self.out_name],
{self.in_image: rgb_batch, self.in_mask: mask_batch}
)[0] # expect RGB uint8(1,3,H,W)
# 출력 차원 처리: (1,3,H,W) -> (H,W,3)
if out.ndim == 4 and out.shape[0] == 1:
out = out[0].transpose(1, 2, 0) # (1,3,H,W) -> (3,H,W) -> (H,W,3)
elif out.ndim == 3 and out.shape[0] == 3: # (3,H,W) -> (H,W,3)
out = out.transpose(1, 2, 0)
logger.debug(f"MIGAN 출력 형태: {out.shape}, dtype: {out.dtype}")
if not isinstance(out, np.ndarray) or out.ndim != 3 or out.dtype != np.uint8:
logger.error(f"MIGAN ONNX 출력 형식 오류: type={type(out)}, shape={getattr(out,'shape',None)}, dtype={getattr(out,'dtype',None)}")
return None
elapsed = (time.time() - start) * 1000.0
logger.debug(f"MIGAN 추론 완료: {elapsed:.2f} ms")
# 5) BGR로 되돌려 반환
bgr_out = cv2.cvtColor(out, cv2.COLOR_RGB2BGR)
return bgr_out
except Exception as e: except Exception as e:
logger.error(f"MIGAN inpainting failed: {e}") error_msg = str(e).lower()
raise if "invalid rank" in error_msg or "invalid argument" in error_msg:
logger.error(f"MIGAN ONNX 입력 차원 오류: {e}")
async def _simulate_advanced_inpainting(self, image_tensor: torch.Tensor, logger.error(f"MIGAN 입력 이미지 형태: {rgb_batch.shape if 'rgb_batch' in locals() else 'N/A'}")
mask_tensor: torch.Tensor) -> torch.Tensor: logger.error(f"MIGAN 입력 마스크 형태: {mask_batch.shape if 'mask_batch' in locals() else 'N/A'}")
"""고급 인페인팅 시뮬레이션 (실제 구현에서는 제거)""" else:
# 비동기 처리 시뮬레이션 logger.error(f"MIGAN inpaint 예외: {e}", exc_info=True)
await asyncio.sleep(0.15) # MIGAN은 더 오래 걸린다고 가정 return None
result = image_tensor.clone()
mask_bool = mask_tensor.bool()
# 더 정교한 인페인팅 시뮬레이션: 주변 픽셀의 가중 평균
if mask_bool.any():
# 간단한 inpainting 시뮬레이션
for c in range(3):
channel = result[0, c]
mask_2d = mask_bool[0, 0]
# 마스크 영역의 경계에서 값을 가져와서 보간
kernel = torch.ones(3, 3, device=self.device) / 9.0
if self.fp16:
kernel = kernel.half()
# 간단한 convolution 기반 인페인팅
padded_channel = torch.nn.functional.pad(channel.unsqueeze(0).unsqueeze(0), (1, 1, 1, 1), mode='replicate')
smoothed = torch.nn.functional.conv2d(padded_channel, kernel.unsqueeze(0).unsqueeze(0), padding=0)
result[0, c][mask_2d] = smoothed[0, 0][mask_2d]
return result
def get_model_info(self) -> dict: def get_model_info(self) -> dict:
"""모델 정보를 반환합니다.""" """모델 정보를 반환합니다."""
return { return {
"model_type": "migan", "model_type": "migan",
"model_path": self.model_path,
"device": self.device, "device": self.device,
"fp16": self.fp16, "fp16": self.fp16,
"use_cuda": self.use_cuda,
"loaded": self.loaded, "loaded": self.loaded,
"model_path": self.model_path, "providers": self.session.get_providers() if self.session else None
"input_size": (512, 512)
} }
# 편의 함수: 설정으로부터 MIGAN 인스턴스 생성
def build_migan_from_config(config: dict, logger: Optional[object] = None, gpu_manager: Optional[object] = None) -> MiganInpainter:
"""
설정으로부터 MiganInpainter 인스턴스를 생성.
필수 :
- migan_onnx_path
선택 :
- migan_use_cuda (bool)
- migan_intra_threads (int)
- migan_inter_threads (int)
"""
onnx_path = config.get("migan_onnx_path", "")
if not onnx_path:
raise ValueError("config['migan_onnx_path'] 가 필요합니다.")
use_cuda = bool(config.get("migan_use_cuda", False))
intra = int(config.get("migan_intra_threads", 0) or 0)
inter = int(config.get("migan_inter_threads", 0) or 0)
inpainter = MiganInpainter(
model_path=onnx_path,
device="cuda" if use_cuda else "cpu",
fp16=False, # ONNX에서는 fp16 사용하지 않음
use_cuda=use_cuda,
intra_threads=intra,
inter_threads=inter,
)
# GPU 관리자를 인페인터 객체에 연결
if gpu_manager:
inpainter.gpu_manager = gpu_manager
if logger:
logger.log(f"MIGAN GPU 관리자 연결 완료: {type(gpu_manager).__name__}", level=logging.DEBUG)
else:
if logger:
logger.log(f"MIGAN GPU 관리자 없음: gpu_manager={gpu_manager}", level=logging.DEBUG)
# 디버깅: gpu_manager 속성 확인
if logger:
logger.log(f"MIGAN 인페인터 gpu_manager 속성: {hasattr(inpainter, 'gpu_manager')}, 값: {getattr(inpainter, 'gpu_manager', None)}", level=logging.DEBUG)
return inpainter

View File

@ -1,25 +1,139 @@
""" """
REMBG 배경 제거 모델 구현 REMBG 배경 제거 모델 구현 (실제 rembg 라이브러리 사용)
""" """
import torch import os
import numpy as np
import cv2 import cv2
from PIL import Image from PIL import Image
import logging import logging
from typing import Union, Tuple import numpy as np
import onnxruntime # ONNX 런타임 직접 사용을 위해 임포트
from typing import Union, Tuple, Optional
import asyncio import asyncio
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class RembgProcessor: class RembgProcessor:
def __init__(self, model_name: str = "u2net", device: str = "cuda", fp16: bool = True): """
rembg 기반 배경제거 모듈 (안전한 의존성 처리)
"""
# 사용하시려는 birefnet 모델을 지원 목록에 추가합니다.
SUPPORTED_MODELS = {
"u2net": "범용 배경제거 | 빠름 | 사람/사물 모두 양호 (기본값)",
"u2netp": "u2net 경량화 | 매우 빠름 | 실시간, 저사양PC",
"u2net_human_seg": "인물 전용 | 빠름 | 사람 경계 정밀",
"u2net_cloth_seg": "옷 전용 | 빠름 | 패션/의류 특화",
"isnet-general-use": "범용 고품질 | 느림 | 디테일 중시, 대용량",
"sam": "SAM 최고 품질 | 매우 느림 | 고성능PC 권장",
"sam-mobile": "SAM 경량화 | 보통 | 모바일, 중간성능",
"birefnet-general-lite": "BiRefNet 경량 모델 | 고품질 저용량 (로컬)"
}
# SUPPORTED_MODELS 키와 실제 rembg sessions 키 간의 매핑
MODEL_NAME_MAPPING = {
"u2net": "u2net",
"u2netp": "u2netp",
"u2net_human_seg": "u2net-human-seg",
"u2net_cloth_seg": "u2net-cloth-seg",
"isnet-general-use": "dis-general-use",
"sam": "sam",
"sam-mobile": "sam", # sam-mobile은 sam과 동일하게 처리
"birefnet-general-lite": "birefnet-general-lite"
}
def __init__(self, model_name: str = "u2net", device: str = "cuda", fp16: bool = True,
local_rembg_model_path: str = None):
self.model_name = model_name self.model_name = model_name
self.device = device self.device = device
self.fp16 = fp16 self.fp16 = fp16
self.model = None self.local_rembg_model_path = local_rembg_model_path
self.sessions = {}
self.loaded = False self.loaded = False
self._rembg_available = None
self._init_error = None
self._cuda_providers_tested = False
def _check_rembg_availability(self):
"""rembg 모듈 사용 가능 여부를 확인하고 캐시.
세션을 생성하지 않아 모델 다운로드를 유발하지 않도록 ."""
if self._rembg_available is not None:
return self._rembg_available
try:
import rembg # noqa: F401
self._rembg_available = True
logger.info("rembg 모듈 임포트 성공 (세션 생성은 지연 로딩)")
return True
except ImportError as e:
self._init_error = f"rembg 모듈이 설치되지 않음: {e}"
self._rembg_available = False
except Exception as e:
self._init_error = f"rembg 모듈 초기화 실패 (의존성/하드웨어 문제): {e}"
self._rembg_available = False
logger.error(self._init_error)
return False
def get_session(self, model_name, timeout_seconds: int = 90):
"""
모델별 세션을 캐싱하여 반환 (로컬 모델 경로 CUDA 지원 포함)
"""
if not self._check_rembg_availability():
logger.error(f"rembg 사용 불가로 세션 생성 실패: {self._init_error}")
return None
# device 설정에 따라 CUDA 사용 여부 결정 (간소화)
cuda_enabled = self.device == "cuda"
# 실제 모델명을 세션 키에 사용
actual_model_name = self.MODEL_NAME_MAPPING.get(model_name, model_name)
session_key = f"{actual_model_name}_cuda_{cuda_enabled}"
if session_key not in self.sessions:
logger.info(f"🔧 rembg 새 세션 생성 필요: {session_key}")
try:
import rembg
try:
from rembg.sessions import sessions
except ImportError:
# rembg 버전에 따라 import 경로가 다를 수 있음
sessions = None
logger.warning("rembg.sessions import 실패, 기본 방식 사용")
# Jetson 환경에서 TensorRT 충돌을 피하기 위해 프로바이더 명시
providers = ['CUDAExecutionProvider', 'CPUExecutionProvider']
logger.info(f"rembg 세션 생성 providers: {providers}")
session = rembg.new_session(
model_name=actual_model_name,
providers=providers
)
self.sessions[session_key] = session
# 실제 사용된 provider 확인 및 로깅 (가드 처리)
actual_providers = []
try:
inner = getattr(session, 'inner_session', None)
if inner and hasattr(inner, 'get_providers'):
actual_providers = inner.get_providers() or []
except Exception as prov_err:
logger.debug(f"rembg provider 확인 실패: {prov_err}")
is_gpu = any(('CUDA' in p) or ('Tensorrt' in p) for p in actual_providers)
status = "GPU 가속" if is_gpu else "CPU 모드"
logger.info(
f"✅ rembg '{actual_model_name}' {status}로 동작 (providers: {actual_providers or '알 수 없음'})"
)
except Exception as e:
logger.error(f"rembg 세션 생성 실패 ('{actual_model_name}'): {e}", exc_info=True)
return None
else:
logger.debug(f"♻️ rembg 기존 세션 재사용: {session_key}")
return self.sessions.get(session_key)
async def load_model(self): async def load_model(self):
"""모델을 비동기적으로 로드합니다.""" """모델을 비동기적으로 로드합니다."""
if self.loaded: if self.loaded:
@ -28,141 +142,141 @@ class RembgProcessor:
try: try:
logger.info(f"Loading REMBG model ({self.model_name})...") logger.info(f"Loading REMBG model ({self.model_name})...")
# 실제 구현에서는 rembg 라이브러리를 사용 # rembg 사용 가능성 확인
# 여기서는 플레이스홀더로 구현 if not self._check_rembg_availability():
await asyncio.sleep(0.1) # 모델 로딩 시뮬레이션 raise RuntimeError(f"REMBG 사용 불가: {self._init_error}")
# TODO: 실제 모델 로딩 로직 # 세션 생성
# from rembg import new_session session = self.get_session(self.model_name)
# self.model = new_session(self.model_name) if session is None:
raise RuntimeError(f"REMBG 세션 생성 실패: {self.model_name}")
self.model = {
"type": "rembg",
"model_name": self.model_name,
"device": self.device,
"fp16": self.fp16
}
self.loaded = True self.loaded = True
logger.info(f"REMBG model ({self.model_name}) loaded successfully") logger.info(f"REMBG model ({self.model_name}) loaded successfully")
except Exception as e: except Exception as e:
logger.error(f"Failed to load REMBG model: {e}") logger.error(f"Failed to load REMBG model: {e}")
raise raise
def preprocess_image(self, image: Union[Image.Image, np.ndarray]) -> np.ndarray: def to_white_background(self, img: Image.Image) -> Image.Image:
"""이미지를 전처리합니다.""" """RGBA 이미지를 흰 배경으로 변환"""
if isinstance(image, Image.Image): if img.mode in ("RGBA", "BGRA"):
image = np.array(image) bg = Image.new("RGB", img.size, (255, 255, 255))
bg.paste(img, mask=img.split()[-1])
# RGB로 변환 return bg
if image.shape[2] == 4: # RGBA else:
image = cv2.cvtColor(image, cv2.COLOR_RGBA2RGB) return img.convert("RGB")
elif len(image.shape) == 3 and image.shape[2] == 3:
image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB) async def remove_background(self, image: Union[str, Image.Image, np.ndarray],
model_name: str = None, **kwargs) -> Tuple[np.ndarray, np.ndarray]:
return image """
배경을 제거하고 결과 이미지와 마스크를 반환합니다.
def create_mask_from_alpha(self, rgba_image: np.ndarray) -> np.ndarray: Args:
"""RGBA 이미지에서 알파 채널을 마스크로 변환합니다.""" image: 입력 이미지 (파일 경로, PIL Image, 또는 numpy array)
if rgba_image.shape[2] != 4: model_name: 사용할 모델명 (없으면 기본 모델 사용)
raise ValueError("Input image must have 4 channels (RGBA)") **kwargs: 추가 옵션 (alpha_matting )
# 알파 채널을 마스크로 사용 Returns:
alpha_channel = rgba_image[:, :, 3] (result_rgb, mask): 결과 이미지(RGB) 마스크
"""
# 0-255 범위의 마스크 생성
mask = alpha_channel.astype(np.uint8)
return mask
async def remove_background(self, image: Union[Image.Image, np.ndarray]) -> Tuple[np.ndarray, np.ndarray]:
"""배경을 제거하고 결과 이미지와 마스크를 반환합니다."""
if not self.loaded: if not self.loaded:
await self.load_model() await self.load_model()
try: try:
# 전처리 # 이미지 로드 및 변환
processed_image = self.preprocess_image(image) if isinstance(image, str):
original_shape = processed_image.shape if not os.path.exists(image):
logger.error(f"입력 이미지가 존재하지 않습니다: {image}")
return None, None
img = cv2.imread(image)
if img is None:
logger.error(f"이미지 로드 실패: {image}")
return None, None
img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
elif isinstance(image, Image.Image):
img_rgb = np.array(image.convert('RGB'))
elif isinstance(image, np.ndarray):
if len(image.shape) == 3 and image.shape[2] == 3:
# BGR to RGB
img_rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
else:
img_rgb = image
else:
logger.error(f"지원하지 않는 이미지 타입: {type(image)}")
return None, None
# 사용할 모델명 결정
effective_model_name = model_name or self.model_name
# 배경 제거 (실제 구현에서는 rembg 사용) if effective_model_name not in self.SUPPORTED_MODELS:
# TODO: 실제 모델 추론 로직 logger.warning(f"지원하지 않는 모델명: {effective_model_name}. u2net으로 대체 사용")
# from rembg import remove effective_model_name = "u2net"
# result_rgba = remove(self.model, processed_image)
session = self.get_session(effective_model_name)
if session is None:
return None, None
import rembg
import time
# 플레이스홀더: 배경 제거 시뮬레이션 start_time = time.time()
result_rgba = await self._simulate_background_removal(processed_image) result = rembg.remove(img_rgb, session=session, alpha_matting=kwargs.get("alpha_matting", False))
end_time = time.time()
# 결과에서 RGB 이미지와 마스크 분리 if not isinstance(result, Image.Image):
result_rgb = result_rgba[:, :, :3] result = Image.fromarray(result)
mask = self.create_mask_from_alpha(result_rgba)
# RGBA 이미지에서 RGB와 마스크 분리
result_rgba = np.array(result)
if result_rgba.shape[2] == 4:
result_rgb = result_rgba[:, :, :3]
mask = result_rgba[:, :, 3]
else:
result_rgb = result_rgba
# 간단한 마스크 생성 (배경이 검은색이라고 가정)
gray = cv2.cvtColor(result_rgb, cv2.COLOR_RGB2GRAY)
mask = (gray > 10).astype(np.uint8) * 255
processing_time = end_time - start_time
# provider 기반 상태 로깅 (세션에서 확인 시도)
try:
sess = self.sessions.get(f"{self.MODEL_NAME_MAPPING.get(effective_model_name, effective_model_name)}_cuda_{self.device == 'cuda'}")
providers = []
if sess and getattr(sess, 'inner_session', None) and hasattr(sess.inner_session, 'get_providers'):
providers = sess.inner_session.get_providers() or []
cuda_status = "CUDA" if any('CUDA' in p or 'Tensorrt' in p for p in providers) else "CPU"
except Exception:
cuda_status = "알 수 없음"
logger.info(f"✅ 배경 제거 성공: {effective_model_name} ({cuda_status}, {processing_time:.2f}초)")
return result_rgb, mask return result_rgb, mask
except Exception as e: except Exception as e:
logger.error(f"Background removal failed: {e}") logger.error(f"배경 제거 처리 중 오류 ({model_name}): {e}", exc_info=True)
raise return None, None
async def _simulate_background_removal(self, image: np.ndarray) -> np.ndarray: def set_default_model(self, model_name):
"""배경 제거 시뮬레이션 (실제 구현에서는 제거)""" if model_name not in self.SUPPORTED_MODELS:
# 비동기 처리 시뮬레이션 raise ValueError(f"지원하지 않는 모델명: {model_name}")
await asyncio.sleep(0.08) # REMBG는 상대적으로 빠르다고 가정 self.model_name = model_name
logger.info(f"rembg 기본 모델이 '{model_name}'(으)로 변경됨")
height, width = image.shape[:2]
def get_default_model(self):
# 간단한 전경/배경 분리 시뮬레이션 return self.model_name
# 중앙 영역을 전경으로, 가장자리를 배경으로 가정
center_x, center_y = width // 2, height // 2 def get_supported_models(self):
return self.SUPPORTED_MODELS.copy()
# 타원형 마스크 생성
y, x = np.ogrid[:height, :width] def get_model_description(self, model_name):
mask = ((x - center_x) ** 2 / (width * 0.3) ** 2 + return self.SUPPORTED_MODELS.get(model_name, "모델 설명 없음")
(y - center_y) ** 2 / (height * 0.4) ** 2) <= 1
def is_available(self):
# 부드러운 가장자리를 위한 가우시안 블러 return self._check_rembg_availability()
mask_float = mask.astype(np.float32)
mask_blurred = cv2.GaussianBlur(mask_float, (51, 51), 20) def get_init_error(self):
return self._init_error
# RGBA 이미지 생성
result_rgba = np.zeros((height, width, 4), dtype=np.uint8)
result_rgba[:, :, :3] = image # RGB 채널
result_rgba[:, :, 3] = (mask_blurred * 255).astype(np.uint8) # 알파 채널
return result_rgba
async def apply_new_background(self, foreground: np.ndarray, mask: np.ndarray,
background: Union[np.ndarray, tuple]) -> np.ndarray:
"""새로운 배경을 적용합니다."""
try:
height, width = foreground.shape[:2]
# 배경 준비
if isinstance(background, tuple):
# 단색 배경
bg = np.full((height, width, 3), background, dtype=np.uint8)
else:
# 이미지 배경
if isinstance(background, Image.Image):
background = np.array(background)
bg = cv2.resize(background, (width, height))
if len(bg.shape) == 3 and bg.shape[2] == 4:
bg = bg[:, :, :3] # RGBA에서 RGB로
# 마스크를 0-1 범위로 정규화
mask_norm = mask.astype(np.float32) / 255.0
mask_3ch = np.stack([mask_norm] * 3, axis=-1)
# 알파 블렌딩
result = (foreground.astype(np.float32) * mask_3ch +
bg.astype(np.float32) * (1 - mask_3ch))
return result.astype(np.uint8)
except Exception as e:
logger.error(f"Background application failed: {e}")
raise
def get_model_info(self) -> dict: def get_model_info(self) -> dict:
"""모델 정보를 반환합니다.""" """모델 정보를 반환합니다."""
return { return {
@ -170,5 +284,8 @@ class RembgProcessor:
"model_name": self.model_name, "model_name": self.model_name,
"device": self.device, "device": self.device,
"fp16": self.fp16, "fp16": self.fp16,
"loaded": self.loaded "loaded": self.loaded,
} "available": self.is_available(),
"supported_models": list(self.SUPPORTED_MODELS.keys()),
"local_model_path": self.local_rembg_model_path
}

View File

@ -29,17 +29,22 @@ class SimpleLamaInpainter:
try: try:
logger.info("Loading Simple LAMA model...") logger.info("Loading Simple LAMA model...")
# 실제 구현에서는 simple-lama-inpainting 라이브러리를 사용 # 실제 simple-lama-inpainting 라이브러리 사용
# 여기서는 플레이스홀더로 구현 try:
await asyncio.sleep(0.1) # 모델 로딩 시뮬레이션 from simple_lama_inpainting import SimpleLama
self.model = SimpleLama(device=self.device)
logger.info("실제 SimpleLama 모델 로딩 완료")
except ImportError as e:
logger.warning(f"SimpleLama 라이브러리 import 실패: {e}")
logger.info("fallback 모드로 전환합니다...")
# fallback으로 시뮬레이션 모드 사용
self.model = {"type": "simple_lama_fallback", "device": self.device, "fp16": self.fp16}
except Exception as e:
logger.error(f"SimpleLama 모델 초기화 실패: {e}")
logger.info("fallback 모드로 전환합니다...")
self.model = {"type": "simple_lama_fallback", "device": self.device, "fp16": self.fp16}
# TODO: 실제 모델 로딩 로직
# from simple_lama_inpainting import SimpleLama
# self.model = SimpleLama(device=self.device)
self.model = {"type": "simple_lama", "device": self.device, "fp16": self.fp16}
self.loaded = True self.loaded = True
logger.info("Simple LAMA model loaded successfully") logger.info("Simple LAMA model loaded successfully")
except Exception as e: except Exception as e:
@ -114,18 +119,34 @@ class SimpleLamaInpainter:
image_tensor = self.preprocess_image(image) image_tensor = self.preprocess_image(image)
mask_tensor = self.preprocess_mask(mask) mask_tensor = self.preprocess_mask(mask)
# 추론 (실제 구현에서는 모델 추론) # 실제 모델 추론
with torch.no_grad(): with torch.no_grad():
# TODO: 실제 모델 추론 로직 if hasattr(self.model, '__call__') and not isinstance(self.model, dict):
# result = self.model(image_tensor, mask_tensor) # 실제 SimpleLama 모델 사용
logger.info("실제 SimpleLama 모델로 인페인팅 수행")
# 플레이스홀더: 마스크 영역을 평균 색상으로 채우기
result = await self._simulate_inpainting(image_tensor, mask_tensor) # SimpleLama는 PIL Image를 받으므로 변환
if isinstance(image, np.ndarray):
# 후처리 pil_image = Image.fromarray(image)
result_np = self.postprocess_result(result) else:
pil_image = image
return result_np
if isinstance(mask, np.ndarray):
pil_mask = Image.fromarray(mask)
else:
pil_mask = mask
# 실제 추론 수행
result_pil = self.model(pil_image, pil_mask)
result_np = np.array(result_pil)
return result_np
else:
# Fallback: 시뮬레이션 모드
logger.warning("Fallback 모드: 시뮬레이션 인페인팅 사용")
result = await self._simulate_inpainting(image_tensor, mask_tensor)
result_np = self.postprocess_result(result)
return result_np
except Exception as e: except Exception as e:
logger.error(f"Inpainting failed: {e}") logger.error(f"Inpainting failed: {e}")

View File

@ -109,28 +109,21 @@ class MonitoringData:
logger.info("워커 상태가 비어있어 기본값 사용") logger.info("워커 상태가 비어있어 기본값 사용")
worker_status = self._get_default_worker_status() worker_status = self._get_default_worker_status()
# 실제 세션 풀 상태 가져오기 (status.json보다 우선) # 항상 실제 세션 풀 상태 사용 (동적 데이터)
try: try:
logger.info("실제 세션 풀 상태 수집 시작")
real_session_status = session_pool.get_status() real_session_status = session_pool.get_status()
session_status = real_session_status
logger.debug(f"실시간 세션 풀 상태 사용: {real_session_status}")
# 세션 풀이 초기화되었는지 확인 (모든 total이 0이 아닌지 체크)
total_sessions = sum(pool_info.get("total", 0) for pool_info in real_session_status.values())
if real_session_status and total_sessions > 0:
session_status = real_session_status
logger.info(f"실제 세션 풀 상태 사용: {real_session_status}")
else:
logger.info(f"세션 풀이 아직 초기화되지 않음 (총 세션: {total_sessions}), status.json 사용")
# status.json에서 가져온 값을 그대로 사용
if not session_status:
logger.info("세션 상태가 비어있어 기본값 사용")
session_status = self._get_default_session_status()
except Exception as e:
logger.warning(f"실제 세션 풀 상태 조회 실패: {e}")
if not session_status: if not session_status:
logger.info("세션 상태가 비어있어 기본값 사용") logger.info("세션 상태가 비어있어 기본값 사용")
session_status = self._get_default_session_status() session_status = self._get_default_session_status()
except Exception as e:
logger.warning(f"실제 세션 풀 상태 조회 실패: {e}")
session_status = status.get("session_status", {})
if not session_status:
logger.info("status.json 세션 상태도 비어있어 기본값 사용")
session_status = self._get_default_session_status()
# GPU 정보 (안전하게 가져오기) # GPU 정보 (안전하게 가져오기)
gpu_info = {} gpu_info = {}
@ -895,6 +888,44 @@ HTML_TEMPLATE = """
</div> </div>
</div> </div>
<!-- 로그 뷰어 -->
<div class="card">
<h3>📝 최근 로그 (최근 50)</h3>
<div style="background: #f8f9fa; border-radius: 5px; padding: 15px; height: 300px; overflow-y: auto; font-family: 'Courier New', monospace; font-size: 12px;" id="logs-container">
로딩 ...
</div>
<div style="margin-top: 10px; text-align: right;">
<button onclick="refreshLogs()" style="padding: 5px 15px; background: #667eea; color: white; border: none; border-radius: 3px; cursor: pointer;">새로고침</button>
</div>
</div>
<!-- 성능 통계 -->
<div class="card">
<h3> 모델 로딩 성능 통계</h3>
<div id="performance-stats-container">
로딩 ...
</div>
</div>
<!-- 모델 사용 통계 -->
<div class="card">
<h3>📊 모델별 사용 통계</h3>
<div id="model-usage-stats-container">
로딩 ...
</div>
</div>
<!-- 시스템 경고 -->
<div class="card">
<h3>🚨 실시간 시스템 경고</h3>
<div id="system-alerts-container">
로딩 ...
</div>
<div style="margin-top: 10px; text-align: right;">
<button onclick="refreshSystemAlerts()" style="padding: 5px 15px; background: #dc3545; color: white; border: none; border-radius: 3px; cursor: pointer;">경고 새로고침</button>
</div>
</div>
<!-- 차트 --> <!-- 차트 -->
<div class="chart-container"> <div class="chart-container">
<h3>📈 실시간 성능 차트</h3> <h3>📈 실시간 성능 차트</h3>
@ -1291,6 +1322,203 @@ HTML_TEMPLATE = """
performanceChart.update(); performanceChart.update();
gpuChart.update(); gpuChart.update();
} }
// 로그 새로고침 함수
function refreshLogs() {
fetch('/api/logs?lines=50')
.then(response => response.json())
.then(data => {
const container = document.getElementById('logs-container');
if (data.logs && data.logs.length > 0) {
let logHtml = '';
data.logs.forEach(log => {
if (log.raw) {
logHtml += `<div style="margin-bottom: 3px; color: #666;">${escapeHtml(log.raw)}</div>`;
} else {
const levelColor = getLevelColor(log.level);
logHtml += `<div style="margin-bottom: 3px;">
<span style="color: #888; font-size: 11px;">${log.timestamp}</span>
<span style="color: ${levelColor}; font-weight: bold; margin: 0 5px;">[${log.level}]</span>
<span style="color: #666; margin-right: 5px;">${log.module}:</span>
<span>${escapeHtml(log.message)}</span>
</div>`;
}
});
container.innerHTML = logHtml;
container.scrollTop = container.scrollHeight; // 스크롤을 아래로
} else {
container.innerHTML = '<div style="color: #999;">로그가 없습니다.</div>';
}
})
.catch(error => {
console.error('로그 로딩 실패:', error);
document.getElementById('logs-container').innerHTML = '<div style="color: #dc3545;">로그 로딩 실패</div>';
});
}
// 성능 통계 새로고침 함수
function refreshPerformanceStats() {
fetch('/api/performance-stats')
.then(response => response.json())
.then(data => {
const container = document.getElementById('performance-stats-container');
if (data.stats && Object.keys(data.stats).length > 0) {
let statsHtml = '<div class="grid" style="grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 15px;">';
Object.entries(data.stats).forEach(([modelName, stats]) => {
statsHtml += `<div style="background: #f8f9fa; padding: 15px; border-radius: 5px;">
<h4 style="margin-top: 0; color: #667eea;">${modelName}</h4>
<div class="metric">
<span class="metric-label">평균 로딩 시간:</span>
<span class="metric-value">${stats.avg_ms.toFixed(1)}ms</span>
</div>
<div class="metric">
<span class="metric-label">최소/최대:</span>
<span class="metric-value">${stats.min_ms.toFixed(1)}ms / ${stats.max_ms.toFixed(1)}ms</span>
</div>
<div class="metric">
<span class="metric-label"> 로딩 횟수:</span>
<span class="metric-value">${stats.count}</span>
</div>
</div>`;
});
statsHtml += '</div>';
container.innerHTML = statsHtml;
} else {
container.innerHTML = '<div style="color: #999; text-align: center; padding: 20px;">성능 데이터가 없습니다.</div>';
}
})
.catch(error => {
console.error('성능 통계 로딩 실패:', error);
document.getElementById('performance-stats-container').innerHTML = '<div style="color: #dc3545;">성능 통계 로딩 실패</div>';
});
}
// 헬퍼 함수들
function escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
function getLevelColor(level) {
switch(level) {
case 'ERROR': return '#dc3545';
case 'WARNING': return '#ffc107';
case 'INFO': return '#17a2b8';
case 'DEBUG': return '#6c757d';
default: return '#333';
}
}
// 모델 사용 통계 새로고침 함수
function refreshModelUsageStats() {
fetch('/api/model-usage-stats')
.then(response => response.json())
.then(data => {
const container = document.getElementById('model-usage-stats-container');
if (data.model_usage && Object.keys(data.model_usage).length > 0) {
let statsHtml = '<div class="grid" style="grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 15px;">';
// 모델별 사용량
statsHtml += '<div style="background: #e3f2fd; padding: 15px; border-radius: 5px;">';
statsHtml += '<h4 style="margin-top: 0; color: #1976d2;">🎯 모델별 요청</h4>';
Object.entries(data.model_usage).forEach(([model, count]) => {
statsHtml += `<div class="metric">
<span class="metric-label">${model}:</span>
<span class="metric-value">${count}</span>
</div>`;
});
statsHtml += '</div>';
// 엔드포인트별 사용량
if (data.endpoint_usage && Object.keys(data.endpoint_usage).length > 0) {
statsHtml += '<div style="background: #f3e5f5; padding: 15px; border-radius: 5px;">';
statsHtml += '<h4 style="margin-top: 0; color: #7b1fa2;">🔗 엔드포인트별</h4>';
Object.entries(data.endpoint_usage).forEach(([endpoint, count]) => {
statsHtml += `<div class="metric">
<span class="metric-label">${endpoint}:</span>
<span class="metric-value">${count}</span>
</div>`;
});
statsHtml += '</div>';
}
// 요청
statsHtml += '<div style="background: #e8f5e8; padding: 15px; border-radius: 5px;">';
statsHtml += '<h4 style="margin-top: 0; color: #388e3c;">📈 총계</h4>';
statsHtml += `<div class="metric">
<span class="metric-label"> 요청:</span>
<span class="metric-value">${data.total_requests}</span>
</div>`;
statsHtml += `<div class="metric">
<span class="metric-label">분석 범위:</span>
<span class="metric-value">${data.analysis_window}</span>
</div>`;
statsHtml += '</div>';
statsHtml += '</div>';
container.innerHTML = statsHtml;
} else {
container.innerHTML = '<div style="color: #999; text-align: center; padding: 20px;">사용 데이터가 없습니다.</div>';
}
})
.catch(error => {
console.error('모델 사용 통계 로딩 실패:', error);
document.getElementById('model-usage-stats-container').innerHTML = '<div style="color: #dc3545;">통계 로딩 실패</div>';
});
}
// 시스템 경고 새로고침 함수
function refreshSystemAlerts() {
fetch('/api/system-alerts')
.then(response => response.json())
.then(data => {
const container = document.getElementById('system-alerts-container');
if (data.alerts && data.alerts.length > 0) {
let alertsHtml = '';
data.alerts.forEach(alert => {
const levelColor = alert.level === 'critical' ? '#dc3545' :
alert.level === 'warning' ? '#ffc107' : '#28a745';
const levelIcon = alert.level === 'critical' ? '🚨' :
alert.level === 'warning' ? '⚠️' : '';
alertsHtml += `<div style="background: ${levelColor}20; border-left: 4px solid ${levelColor}; padding: 15px; margin-bottom: 10px; border-radius: 5px;">
<div style="font-weight: bold; color: ${levelColor};">
${levelIcon} ${alert.level.toUpperCase()}
</div>
<div style="margin-top: 5px;">${alert.message}</div>
<div style="font-size: 12px; color: #666; margin-top: 5px;">
${new Date(alert.timestamp).toLocaleString()}
</div>
</div>`;
});
container.innerHTML = alertsHtml;
} else {
container.innerHTML = '<div style="color: #28a745; text-align: center; padding: 20px;">✅ 현재 시스템 경고가 없습니다.</div>';
}
})
.catch(error => {
console.error('시스템 경고 로딩 실패:', error);
document.getElementById('system-alerts-container').innerHTML = '<div style="color: #dc3545;">경고 로딩 실패</div>';
});
}
// 페이지 로드 초기화
document.addEventListener('DOMContentLoaded', function() {
// 로그 성능 통계 초기 로딩
refreshLogs();
refreshPerformanceStats();
refreshModelUsageStats();
refreshSystemAlerts();
// 30초마다 로그 성능 통계 자동 새로고침
setInterval(refreshLogs, 30000);
setInterval(refreshPerformanceStats, 30000);
setInterval(refreshModelUsageStats, 15000); // 15초마다
setInterval(refreshSystemAlerts, 10000); // 10초마다
});
</script> </script>
</body> </body>
</html> </html>
@ -1330,7 +1558,259 @@ async def get_simple_status():
logger.error(f"간단한 상태 조회 실패: {e}") logger.error(f"간단한 상태 조회 실패: {e}")
return {"error": str(e)} return {"error": str(e)}
@api_router.get("/logs")
async def get_recent_logs(lines: int = 100):
"""최근 로그 반환"""
try:
import os
log_file = "logs/main.log"
if not os.path.exists(log_file):
return {"logs": [], "message": "로그 파일이 없습니다"}
# 최근 lines 줄 읽기
with open(log_file, 'r', encoding='utf-8') as f:
all_lines = f.readlines()
recent_lines = all_lines[-lines:] if len(all_lines) > lines else all_lines
# 로그 파싱
parsed_logs = []
for line in recent_lines:
line = line.strip()
if line and " - " in line:
try:
# 시간, 모듈, 레벨, 메시지 분리
parts = line.split(" - ", 3)
if len(parts) >= 4:
parsed_logs.append({
"timestamp": parts[0],
"module": parts[1],
"level": parts[2],
"message": parts[3]
})
else:
parsed_logs.append({"raw": line})
except:
parsed_logs.append({"raw": line})
return {"logs": parsed_logs, "total": len(parsed_logs)}
except Exception as e:
logger.error(f"로그 조회 실패: {e}")
return {"logs": [], "error": str(e)}
@api_router.get("/model-usage-stats")
async def get_model_usage_stats():
"""모델별 사용 통계 반환"""
try:
import os
import re
from collections import defaultdict
from datetime import datetime, timedelta
log_file = "logs/main.log"
if not os.path.exists(log_file):
return {"stats": {}, "message": "로그 파일이 없습니다"}
# 최근 1시간 데이터만 분석
one_hour_ago = datetime.now() - timedelta(hours=1)
# 모델별 요청 통계
model_requests = defaultdict(int)
endpoint_requests = defaultdict(int)
hourly_requests = defaultdict(int)
with open(log_file, 'r', encoding='utf-8') as f:
lines = f.readlines()[-2000:] # 최근 2000줄만
for line in lines:
try:
# API 요청 로그 패턴
if '"POST /api/v1/inpaint' in line:
model_requests['inpaint'] += 1
elif '"POST /api/v1/remove_bg' in line:
model_requests['remove_bg'] += 1
elif '"GET /api/v1/model' in line:
endpoint_requests['health_check'] += 1
# 시간대별 분석
time_match = re.search(r'(\d{2}:\d{2}):\d{2}', line)
if time_match:
hour_minute = time_match.group(1)
hourly_requests[hour_minute] += 1
except Exception:
continue
return {
"model_usage": dict(model_requests),
"endpoint_usage": dict(endpoint_requests),
"hourly_distribution": dict(hourly_requests),
"total_requests": sum(model_requests.values()) + sum(endpoint_requests.values()),
"analysis_window": "최근 2000줄"
}
except Exception as e:
logger.error(f"모델 사용 통계 조회 실패: {e}")
return {"stats": {}, "error": str(e)}
@api_router.get("/performance-stats")
async def get_performance_stats():
"""성능 통계 반환"""
try:
import os
import re
from collections import defaultdict
log_file = "logs/main.log"
if not os.path.exists(log_file):
return {"stats": {}, "message": "로그 파일이 없습니다"}
# 모델 로딩 시간 분석
model_load_times = defaultdict(list)
# 최근 1000줄만 분석
with open(log_file, 'r', encoding='utf-8') as f:
lines = f.readlines()[-1000:]
# 모델 로딩 시간 분석
for i, line in enumerate(lines):
if "Loading" in line and "model" in line:
loading_time = None
success_time = None
# 현재 라인에서 시간 추출
try:
time_match = re.search(r'(\d{2}:\d{2}:\d{2}),(\d{3})', line)
if time_match:
loading_time = f"{time_match.group(1)}.{time_match.group(2)}"
# 다음 몇 줄에서 "loaded successfully" 찾기
for j in range(i+1, min(i+5, len(lines))):
if "loaded successfully" in lines[j]:
success_match = re.search(r'(\d{2}:\d{2}:\d{2}),(\d{3})', lines[j])
if success_match:
success_time = f"{success_match.group(1)}.{success_match.group(2)}"
# 시간 차이 계산 (초 단위)
loading_parts = loading_time.split(':')
success_parts = success_time.split(':')
loading_total = float(loading_parts[0])*3600 + float(loading_parts[1])*60 + float(loading_parts[2])
success_total = float(success_parts[0])*3600 + float(success_parts[1])*60 + float(success_parts[2])
duration_ms = (success_total - loading_total) * 1000
if 0 < duration_ms < 10000: # 0-10초 범위만 유효
model_name = "Simple LAMA" # 기본값
if "simple_lama" in line.lower():
model_name = "Simple LAMA"
elif "migan" in line.lower():
model_name = "MIGAN"
elif "rembg" in line.lower():
model_name = "RemBG"
model_load_times[model_name].append(duration_ms)
break
except Exception as parse_error:
continue
# 통계 계산
stats = {}
for model_name, times in model_load_times.items():
if times:
stats[model_name] = {
"count": len(times),
"avg_ms": round(sum(times) / len(times), 1),
"min_ms": round(min(times), 1),
"max_ms": round(max(times), 1),
"recent_times": [round(t, 1) for t in times[-10:]] # 최근 10개
}
return {"stats": stats, "analysis_lines": len(lines)}
except Exception as e:
logger.error(f"성능 통계 조회 실패: {e}")
return {"stats": {}, "error": str(e)}
# 테스트용 엔드포인트 # 테스트용 엔드포인트
@api_router.get("/system-alerts")
async def get_system_alerts():
"""시스템 알림 반환"""
try:
alerts = []
# CPU 사용률 체크
cpu_percent = psutil.cpu_percent(interval=1)
if cpu_percent > 90:
alerts.append({
"level": "critical",
"message": f"CPU 사용률이 매우 높습니다: {cpu_percent:.1f}%",
"category": "system",
"timestamp": datetime.now().isoformat()
})
elif cpu_percent > 75:
alerts.append({
"level": "warning",
"message": f"CPU 사용률이 높습니다: {cpu_percent:.1f}%",
"category": "system",
"timestamp": datetime.now().isoformat()
})
# 메모리 사용률 체크
memory = psutil.virtual_memory()
if memory.percent > 90:
alerts.append({
"level": "critical",
"message": f"메모리 사용률이 매우 높습니다: {memory.percent:.1f}%",
"category": "system",
"timestamp": datetime.now().isoformat()
})
elif memory.percent > 80:
alerts.append({
"level": "warning",
"message": f"메모리 사용률이 높습니다: {memory.percent:.1f}%",
"category": "system",
"timestamp": datetime.now().isoformat()
})
# 디스크 사용률 체크
disk = psutil.disk_usage('/')
disk_percent = (disk.used / disk.total) * 100
if disk_percent > 90:
alerts.append({
"level": "critical",
"message": f"디스크 사용률이 매우 높습니다: {disk_percent:.1f}%",
"category": "system",
"timestamp": datetime.now().isoformat()
})
elif disk_percent > 80:
alerts.append({
"level": "warning",
"message": f"디스크 사용률이 높습니다: {disk_percent:.1f}%",
"category": "system",
"timestamp": datetime.now().isoformat()
})
# GPU 메모리 체크 (가능한 경우)
try:
gpu_info = gpu_monitor.get_gpu_memory_info()
if gpu_info.get("usage_percent", 0) > 95:
alerts.append({
"level": "critical",
"message": f"GPU 메모리 사용률이 매우 높습니다: {gpu_info['usage_percent']:.1f}%",
"category": "gpu",
"timestamp": datetime.now().isoformat()
})
except Exception:
pass
return {"alerts": alerts, "count": len(alerts)}
except Exception as e:
logger.error(f"시스템 알림 조회 실패: {e}")
return {"alerts": [], "error": str(e)}
@api_router.get("/test") @api_router.get("/test")
async def test_endpoint(): async def test_endpoint():
"""테스트용 엔드포인트입니다.""" """테스트용 엔드포인트입니다."""

File diff suppressed because it is too large Load Diff

View File

@ -1,629 +1,148 @@
INFO: Started server process [1338234] INFO: Started server process [35210]
2025-08-29 02:46:04,218 - uvicorn.error - INFO - Started server process [1338234] 2025-08-29 21:32:06,424 - uvicorn.error - INFO - Started server process [35210]
INFO: Waiting for application startup. INFO: Waiting for application startup.
2025-08-29 02:46:04,219 - uvicorn.error - INFO - Waiting for application startup. 2025-08-29 21:32:06,424 - uvicorn.error - INFO - Waiting for application startup.
2025-08-29 02:46:04,220 - main - INFO - 🚀 인페인팅 서버 시작 중... 2025-08-29 21:32:06,425 - main - INFO - 🚀 인페인팅 서버 시작 중...
2025-08-29 02:46:04,220 - main - INFO - ✅ 공유 객체를 app.state에 저장 완료 2025-08-29 21:32:06,426 - main - INFO - ✅ 공유 객체를 app.state에 저장 완료
2025-08-29 02:46:04,220 - main - INFO - 🔄 상태 저장 백그라운드 작업 생성 중... 2025-08-29 21:32:06,426 - main - INFO - 🔄 상태 저장 백그라운드 작업 생성 중...
2025-08-29 02:46:04,220 - main - INFO - ✅ 상태 저장 백그라운드 작업 생성 완료 2025-08-29 21:32:06,426 - main - INFO - ✅ 상태 저장 백그라운드 작업 생성 완료
2025-08-29 02:46:04,220 - app.core.session_pool - INFO - Initializing session pools... 2025-08-29 21:32:06,427 - main - INFO - 🚀 세션 풀 초기화 (CUDA 자동 감지)
2025-08-29 02:46:04,221 - app.core.session_pool - INFO - Initializing 2 sessions for simple_lama 2025-08-29 21:32:06,427 - app.core.session_pool - INFO - Initializing session pools...
2025-08-29 02:46:04,221 - main - INFO - 🔄 상태 저장 백그라운드 작업 시작됨 2025-08-29 21:32:06,427 - app.core.session_pool - INFO - Initializing 4 sessions for simple_lama
2025-08-29 02:46:04,222 - main - INFO - 상태 저장 완료 #1: 02:46:04 2025-08-29 21:32:09,609 - app.models.simple_lama - INFO - Loading Simple LAMA model...
2025-08-29 02:46:04,322 - app.core.session_pool - INFO - Created session simple_lama_0 2025-08-29 21:32:13,763 - app.models.simple_lama - INFO - 실제 SimpleLama 모델 로딩 완료
2025-08-29 02:46:04,423 - app.core.session_pool - INFO - Created session simple_lama_1 2025-08-29 21:32:13,764 - app.models.simple_lama - INFO - Simple LAMA model loaded successfully
2025-08-29 02:46:04,424 - app.core.session_pool - INFO - Initializing 2 sessions for migan 2025-08-29 21:32:13,765 - app.core.session_pool - INFO - Simple LAMA 모델 세션 로드 완료
2025-08-29 02:46:04,525 - app.core.session_pool - INFO - Created session migan_0 2025-08-29 21:32:13,766 - app.core.session_pool - INFO - Created session simple_lama_0
2025-08-29 02:46:04,626 - app.core.session_pool - INFO - Created session migan_1 2025-08-29 21:32:13,766 - app.models.simple_lama - INFO - Loading Simple LAMA model...
2025-08-29 02:46:04,627 - app.core.session_pool - INFO - Initializing 1 sessions for rembg 2025-08-29 21:32:15,606 - app.models.simple_lama - INFO - 실제 SimpleLama 모델 로딩 완료
2025-08-29 02:46:04,728 - app.core.session_pool - INFO - Created session rembg_0 2025-08-29 21:32:15,606 - app.models.simple_lama - INFO - Simple LAMA model loaded successfully
2025-08-29 02:46:04,728 - app.core.session_pool - INFO - Session pools initialized successfully 2025-08-29 21:32:15,607 - app.core.session_pool - INFO - Simple LAMA 모델 세션 로드 완료
2025-08-29 02:46:04,728 - main - INFO - ✅ 세션 풀 초기화 완료 2025-08-29 21:32:15,607 - app.core.session_pool - INFO - Created session simple_lama_1
2025-08-29 02:46:04,729 - app.core.worker_manager - INFO - Starting worker manager... 2025-08-29 21:32:15,608 - app.models.simple_lama - INFO - Loading Simple LAMA model...
2025-08-29 02:46:04,729 - app.core.worker_manager - INFO - Worker manager started with 2 workers 2025-08-29 21:32:17,289 - app.models.simple_lama - INFO - 실제 SimpleLama 모델 로딩 완료
2025-08-29 02:46:04,730 - main - INFO - ✅ 워커 매니저 시작 완료 2025-08-29 21:32:17,290 - app.models.simple_lama - INFO - Simple LAMA model loaded successfully
2025-08-29 02:46:04,730 - main - INFO - 🎉 인페인팅 서버 시작 완료! 2025-08-29 21:32:17,290 - app.core.session_pool - INFO - Simple LAMA 모델 세션 로드 완료
2025-08-29 21:32:17,291 - app.core.session_pool - INFO - Created session simple_lama_2
2025-08-29 21:32:17,291 - app.models.simple_lama - INFO - Loading Simple LAMA model...
2025-08-29 21:32:18,958 - app.models.simple_lama - INFO - 실제 SimpleLama 모델 로딩 완료
2025-08-29 21:32:18,959 - app.models.simple_lama - INFO - Simple LAMA model loaded successfully
2025-08-29 21:32:18,959 - app.core.session_pool - INFO - Simple LAMA 모델 세션 로드 완료
2025-08-29 21:32:18,960 - app.core.session_pool - INFO - Created session simple_lama_3
2025-08-29 21:32:18,960 - app.core.session_pool - INFO - Initializing 4 sessions for migan
2025-08-29 21:32:19,025 - app.models.migan - INFO - Loading MIGAN ONNX model...
2025-08-29 21:32:19,026 - app.models.migan - INFO - MIGAN ONNX 런타임 세션 생성 시도...
2025-08-29 21:32:19,026 - app.models.migan - INFO - MIGAN ONNX providers 설정: ['CUDAExecutionProvider', 'CPUExecutionProvider']
2025-08-29 21:32:20.460420837 [W:onnxruntime:, transformer_memcpy.cc:74 ApplyImpl] 17 Memcpy nodes are added to the graph main_graph for CUDAExecutionProvider. It might have negative impact on performance (including unable to run CUDA graph). Set session_options.log_severity_level=1 to see the detail logs before this message.
2025-08-29 21:32:21,873 - app.models.migan - INFO - MIGAN ONNX 세션 생성 완료. Providers: ['CUDAExecutionProvider', 'CPUExecutionProvider']
2025-08-29 21:32:21,874 - app.models.migan - INFO - MIGAN ONNX model loaded successfully
2025-08-29 21:32:21,874 - app.core.session_pool - INFO - MIGAN 모델 세션 로드 완료
2025-08-29 21:32:21,874 - app.core.session_pool - INFO - Created session migan_0
2025-08-29 21:32:21,875 - app.models.migan - INFO - Loading MIGAN ONNX model...
2025-08-29 21:32:21,875 - app.models.migan - INFO - MIGAN ONNX 런타임 세션 생성 시도...
2025-08-29 21:32:21,876 - app.models.migan - INFO - MIGAN ONNX providers 설정: ['CUDAExecutionProvider', 'CPUExecutionProvider']
2025-08-29 21:32:22.968792052 [W:onnxruntime:, transformer_memcpy.cc:74 ApplyImpl] 17 Memcpy nodes are added to the graph main_graph for CUDAExecutionProvider. It might have negative impact on performance (including unable to run CUDA graph). Set session_options.log_severity_level=1 to see the detail logs before this message.
2025-08-29 21:32:23,134 - app.models.migan - INFO - MIGAN ONNX 세션 생성 완료. Providers: ['CUDAExecutionProvider', 'CPUExecutionProvider']
2025-08-29 21:32:23,135 - app.models.migan - INFO - MIGAN ONNX model loaded successfully
2025-08-29 21:32:23,135 - app.core.session_pool - INFO - MIGAN 모델 세션 로드 완료
2025-08-29 21:32:23,136 - app.core.session_pool - INFO - Created session migan_1
2025-08-29 21:32:23,136 - app.models.migan - INFO - Loading MIGAN ONNX model...
2025-08-29 21:32:23,137 - app.models.migan - INFO - MIGAN ONNX 런타임 세션 생성 시도...
2025-08-29 21:32:23,137 - app.models.migan - INFO - MIGAN ONNX providers 설정: ['CUDAExecutionProvider', 'CPUExecutionProvider']
2025-08-29 21:32:24.203228802 [W:onnxruntime:, transformer_memcpy.cc:74 ApplyImpl] 17 Memcpy nodes are added to the graph main_graph for CUDAExecutionProvider. It might have negative impact on performance (including unable to run CUDA graph). Set session_options.log_severity_level=1 to see the detail logs before this message.
2025-08-29 21:32:24,367 - app.models.migan - INFO - MIGAN ONNX 세션 생성 완료. Providers: ['CUDAExecutionProvider', 'CPUExecutionProvider']
2025-08-29 21:32:24,368 - app.models.migan - INFO - MIGAN ONNX model loaded successfully
2025-08-29 21:32:24,368 - app.core.session_pool - INFO - MIGAN 모델 세션 로드 완료
2025-08-29 21:32:24,369 - app.core.session_pool - INFO - Created session migan_2
2025-08-29 21:32:24,369 - app.models.migan - INFO - Loading MIGAN ONNX model...
2025-08-29 21:32:24,370 - app.models.migan - INFO - MIGAN ONNX 런타임 세션 생성 시도...
2025-08-29 21:32:24,370 - app.models.migan - INFO - MIGAN ONNX providers 설정: ['CUDAExecutionProvider', 'CPUExecutionProvider']
2025-08-29 21:32:25.439378113 [W:onnxruntime:, transformer_memcpy.cc:74 ApplyImpl] 17 Memcpy nodes are added to the graph main_graph for CUDAExecutionProvider. It might have negative impact on performance (including unable to run CUDA graph). Set session_options.log_severity_level=1 to see the detail logs before this message.
2025-08-29 21:32:25,605 - app.models.migan - INFO - MIGAN ONNX 세션 생성 완료. Providers: ['CUDAExecutionProvider', 'CPUExecutionProvider']
2025-08-29 21:32:25,606 - app.models.migan - INFO - MIGAN ONNX model loaded successfully
2025-08-29 21:32:25,607 - app.core.session_pool - INFO - MIGAN 모델 세션 로드 완료
2025-08-29 21:32:25,607 - app.core.session_pool - INFO - Created session migan_3
2025-08-29 21:32:25,608 - app.core.session_pool - INFO - Initializing 3 sessions for rembg
2025-08-29 21:32:25,610 - app.models.rembg_model - INFO - Loading REMBG model (birefnet-general-lite)...
2025-08-29 21:32:27,663 - app.models.rembg_model - INFO - rembg 모듈 임포트 성공 (세션 생성은 지연 로딩)
2025-08-29 21:32:27,664 - app.models.rembg_model - INFO - 🔧 rembg 새 세션 생성 필요: birefnet-general-lite_cuda_True
2025-08-29 21:32:27,664 - app.models.rembg_model - WARNING - rembg.sessions import 실패, 기본 방식 사용
2025-08-29 21:32:27,665 - app.models.rembg_model - INFO - rembg 세션 생성 providers: ['CUDAExecutionProvider', 'CPUExecutionProvider']
2025-08-29 21:32:40,606 - app.models.rembg_model - INFO - ✅ rembg 'birefnet-general-lite' GPU 가속로 동작 (providers: ['CUDAExecutionProvider', 'CPUExecutionProvider'])
2025-08-29 21:32:40,606 - app.models.rembg_model - INFO - REMBG model (birefnet-general-lite) loaded successfully
2025-08-29 21:32:40,607 - app.core.session_pool - INFO - REMBG 모델 세션 로드 완료
2025-08-29 21:32:40,607 - app.core.session_pool - INFO - Created session rembg_0
2025-08-29 21:32:40,608 - app.models.rembg_model - INFO - Loading REMBG model (birefnet-general-lite)...
2025-08-29 21:32:40,608 - app.models.rembg_model - INFO - rembg 모듈 임포트 성공 (세션 생성은 지연 로딩)
2025-08-29 21:32:40,608 - app.models.rembg_model - INFO - 🔧 rembg 새 세션 생성 필요: birefnet-general-lite_cuda_True
2025-08-29 21:32:40,609 - app.models.rembg_model - WARNING - rembg.sessions import 실패, 기본 방식 사용
2025-08-29 21:32:40,609 - app.models.rembg_model - INFO - rembg 세션 생성 providers: ['CUDAExecutionProvider', 'CPUExecutionProvider']
2025-08-29 21:32:53,362 - app.models.rembg_model - INFO - ✅ rembg 'birefnet-general-lite' GPU 가속로 동작 (providers: ['CUDAExecutionProvider', 'CPUExecutionProvider'])
2025-08-29 21:32:53,363 - app.models.rembg_model - INFO - REMBG model (birefnet-general-lite) loaded successfully
2025-08-29 21:32:53,364 - app.core.session_pool - INFO - REMBG 모델 세션 로드 완료
2025-08-29 21:32:53,364 - app.core.session_pool - INFO - Created session rembg_1
2025-08-29 21:32:53,365 - app.models.rembg_model - INFO - Loading REMBG model (birefnet-general-lite)...
2025-08-29 21:32:53,365 - app.models.rembg_model - INFO - rembg 모듈 임포트 성공 (세션 생성은 지연 로딩)
2025-08-29 21:32:53,366 - app.models.rembg_model - INFO - 🔧 rembg 새 세션 생성 필요: birefnet-general-lite_cuda_True
2025-08-29 21:32:53,366 - app.models.rembg_model - WARNING - rembg.sessions import 실패, 기본 방식 사용
2025-08-29 21:32:53,367 - app.models.rembg_model - INFO - rembg 세션 생성 providers: ['CUDAExecutionProvider', 'CPUExecutionProvider']
2025-08-29 21:33:05,746 - app.models.rembg_model - INFO - ✅ rembg 'birefnet-general-lite' GPU 가속로 동작 (providers: ['CUDAExecutionProvider', 'CPUExecutionProvider'])
2025-08-29 21:33:05,747 - app.models.rembg_model - INFO - REMBG model (birefnet-general-lite) loaded successfully
2025-08-29 21:33:05,747 - app.core.session_pool - INFO - REMBG 모델 세션 로드 완료
2025-08-29 21:33:05,748 - app.core.session_pool - INFO - Created session rembg_2
2025-08-29 21:33:05,748 - app.core.session_pool - INFO - Session pools initialized successfully
2025-08-29 21:33:05,749 - main - INFO - ✅ 세션 풀 초기화 완료
2025-08-29 21:33:05,749 - app.core.worker_manager - INFO - Starting worker manager...
2025-08-29 21:33:05,750 - app.core.worker_manager - INFO - Worker manager started with 4 workers
2025-08-29 21:33:05,750 - main - INFO - ✅ 워커 매니저 시작 완료
2025-08-29 21:33:05,750 - main - INFO - 🎉 인페인팅 서버 시작 완료!
2025-08-29 21:33:05,751 - main - INFO - 🔄 상태 저장 백그라운드 작업 시작됨
INFO: Application startup complete. INFO: Application startup complete.
2025-08-29 02:46:04,730 - uvicorn.error - INFO - Application startup complete. 2025-08-29 21:33:05,753 - uvicorn.error - INFO - Application startup complete.
INFO: Uvicorn running on http://0.0.0.0:8008 (Press CTRL+C to quit) INFO: Uvicorn running on http://0.0.0.0:8008 (Press CTRL+C to quit)
2025-08-29 02:46:04,732 - uvicorn.error - INFO - Uvicorn running on http://0.0.0.0:8008 (Press CTRL+C to quit) 2025-08-29 21:33:05,754 - uvicorn.error - INFO - Uvicorn running on http://0.0.0.0:8008 (Press CTRL+C to quit)
2025-08-29 02:46:05,226 - main - INFO - 상태 저장 완료 #2: 02:46:05 INFO: 127.0.0.1:37912 - "GET /api/v1/health HTTP/1.1" 404 Not Found
INFO: 127.0.0.1:60054 - "GET /health HTTP/1.1" 200 OK INFO: 127.0.0.1:37916 - "GET /api/v1/health HTTP/1.1" 404 Not Found
2025-08-29 02:46:06,230 - main - INFO - 상태 저장 완료 #3: 02:46:06 INFO: 192.168.0.119:54788 - "GET /health HTTP/1.1" 200 OK
2025-08-29 02:46:07,234 - main - INFO - 상태 저장 완료 #4: 02:46:07 INFO: 192.168.0.119:54910 - "GET /api/v1/model HTTP/1.1" 200 OK
2025-08-29 02:46:08,238 - main - INFO - 상태 저장 완료 #5: 02:46:08 2025-08-29 21:34:22,942 - app.models.simple_lama - INFO - 실제 SimpleLama 모델로 인페인팅 수행
2025-08-29 02:46:09,243 - main - INFO - 상태 저장 완료 #6: 02:46:09 INFO: 192.168.0.119:54911 - "POST /api/v1/inpaint HTTP/1.1" 200 OK
2025-08-29 02:46:10,248 - main - INFO - 상태 저장 완료 #7: 02:46:10 INFO: 192.168.0.119:54959 - "GET /api/v1/model HTTP/1.1" 200 OK
2025-08-29 02:46:11,253 - main - INFO - 상태 저장 완료 #8: 02:46:11 2025-08-29 21:34:33,468 - app.models.simple_lama - INFO - 실제 SimpleLama 모델로 인페인팅 수행
2025-08-29 02:46:12,257 - main - INFO - 상태 저장 완료 #9: 02:46:12 INFO: 192.168.0.119:54960 - "POST /api/v1/inpaint HTTP/1.1" 200 OK
2025-08-29 02:46:13,261 - main - INFO - 상태 저장 완료 #10: 02:46:13 INFO: 192.168.0.119:55054 - "GET /api/v1/model HTTP/1.1" 200 OK
2025-08-29 02:46:14,266 - main - INFO - 상태 저장 완료 #11: 02:46:14 2025-08-29 21:34:54,879 - app.models.simple_lama - INFO - 실제 SimpleLama 모델로 인페인팅 수행
2025-08-29 02:46:15,270 - main - INFO - 상태 저장 완료 #12: 02:46:15 INFO: 192.168.0.119:55055 - "POST /api/v1/inpaint HTTP/1.1" 200 OK
2025-08-29 02:46:16,275 - main - INFO - 상태 저장 완료 #13: 02:46:16 INFO: 192.168.0.119:55078 - "GET /api/v1/model HTTP/1.1" 200 OK
2025-08-29 02:46:17,280 - main - INFO - 상태 저장 완료 #14: 02:46:17 2025-08-29 21:35:00,881 - app.models.simple_lama - INFO - 실제 SimpleLama 모델로 인페인팅 수행
2025-08-29 02:46:18,284 - main - INFO - 상태 저장 완료 #15: 02:46:18 INFO: 192.168.0.119:55091 - "POST /api/v1/inpaint HTTP/1.1" 200 OK
2025-08-29 02:46:19,289 - main - INFO - 상태 저장 완료 #16: 02:46:19 INFO: 192.168.0.119:55106 - "GET /api/v1/model HTTP/1.1" 200 OK
2025-08-29 02:46:20,293 - main - INFO - 상태 저장 완료 #17: 02:46:20 2025-08-29 21:35:06,470 - app.models.simple_lama - INFO - 실제 SimpleLama 모델로 인페인팅 수행
2025-08-29 02:46:21,297 - main - INFO - 상태 저장 완료 #18: 02:46:21 INFO: 192.168.0.119:55107 - "POST /api/v1/inpaint HTTP/1.1" 200 OK
2025-08-29 02:46:22,301 - main - INFO - 상태 저장 완료 #19: 02:46:22 INFO: 192.168.0.119:55137 - "GET /api/v1/model HTTP/1.1" 200 OK
2025-08-29 02:46:23,305 - main - INFO - 상태 저장 완료 #20: 02:46:23 2025-08-29 21:35:15,091 - app.models.simple_lama - INFO - 실제 SimpleLama 모델로 인페인팅 수행
2025-08-29 02:46:24,309 - main - INFO - 상태 저장 완료 #21: 02:46:24 INFO: 192.168.0.119:55138 - "POST /api/v1/inpaint HTTP/1.1" 200 OK
2025-08-29 02:46:25,313 - main - INFO - 상태 저장 완료 #22: 02:46:25 INFO: 192.168.0.119:55172 - "GET /api/v1/model HTTP/1.1" 200 OK
2025-08-29 02:46:26,317 - main - INFO - 상태 저장 완료 #23: 02:46:26 2025-08-29 21:35:24,091 - app.models.simple_lama - INFO - 실제 SimpleLama 모델로 인페인팅 수행
2025-08-29 02:46:27,321 - main - INFO - 상태 저장 완료 #24: 02:46:27 INFO: 192.168.0.119:55173 - "POST /api/v1/inpaint HTTP/1.1" 200 OK
2025-08-29 02:46:28,325 - main - INFO - 상태 저장 완료 #25: 02:46:28 INFO: 192.168.0.119:55212 - "GET /api/v1/model HTTP/1.1" 200 OK
2025-08-29 02:46:29,330 - main - INFO - 상태 저장 완료 #26: 02:46:29 2025-08-29 21:35:34,140 - app.models.simple_lama - INFO - 실제 SimpleLama 모델로 인페인팅 수행
2025-08-29 02:46:30,334 - main - INFO - 상태 저장 완료 #27: 02:46:30 INFO: 192.168.0.119:55213 - "POST /api/v1/inpaint HTTP/1.1" 200 OK
2025-08-29 02:46:31,340 - main - INFO - 상태 저장 완료 #28: 02:46:31 INFO: 192.168.0.119:55228 - "GET /api/v1/model HTTP/1.1" 200 OK
2025-08-29 02:46:32,344 - main - INFO - 상태 저장 완료 #29: 02:46:32 2025-08-29 21:35:39,477 - app.models.simple_lama - INFO - 실제 SimpleLama 모델로 인페인팅 수행
2025-08-29 02:46:33,349 - main - INFO - 상태 저장 완료 #30: 02:46:33 INFO: 192.168.0.119:55229 - "POST /api/v1/inpaint HTTP/1.1" 200 OK
2025-08-29 02:46:34,353 - main - INFO - 상태 저장 완료 #31: 02:46:34 INFO: 192.168.0.119:55262 - "GET /api/v1/model HTTP/1.1" 200 OK
2025-08-29 02:46:35,358 - main - INFO - 상태 저장 완료 #32: 02:46:35 2025-08-29 21:35:45,252 - app.models.simple_lama - INFO - 실제 SimpleLama 모델로 인페인팅 수행
2025-08-29 02:46:36,363 - main - INFO - 상태 저장 완료 #33: 02:46:36 INFO: 192.168.0.119:55263 - "POST /api/v1/inpaint HTTP/1.1" 200 OK
2025-08-29 02:46:37,368 - main - INFO - 상태 저장 완료 #34: 02:46:37 INFO: 192.168.0.119:55478 - "GET /api/v1/model HTTP/1.1" 200 OK
2025-08-29 02:46:38,372 - main - INFO - 상태 저장 완료 #35: 02:46:38 2025-08-29 21:36:44,752 - app.models.simple_lama - INFO - 실제 SimpleLama 모델로 인페인팅 수행
2025-08-29 02:46:39,377 - main - INFO - 상태 저장 완료 #36: 02:46:39 INFO: 192.168.0.119:55479 - "POST /api/v1/inpaint HTTP/1.1" 200 OK
2025-08-29 02:46:40,382 - main - INFO - 상태 저장 완료 #37: 02:46:40 INFO: 192.168.0.119:55521 - "GET /api/v1/model HTTP/1.1" 200 OK
2025-08-29 02:46:41,387 - main - INFO - 상태 저장 완료 #38: 02:46:41 2025-08-29 21:36:56,862 - app.models.simple_lama - INFO - 실제 SimpleLama 모델로 인페인팅 수행
2025-08-29 02:46:42,392 - main - INFO - 상태 저장 완료 #39: 02:46:42 INFO: 192.168.0.119:55526 - "POST /api/v1/inpaint HTTP/1.1" 200 OK
2025-08-29 02:46:43,397 - main - INFO - 상태 저장 완료 #40: 02:46:43 INFO: 192.168.0.119:55551 - "GET /api/v1/model HTTP/1.1" 200 OK
2025-08-29 02:46:44,400 - main - INFO - 상태 저장 완료 #41: 02:46:44 2025-08-29 21:37:03,465 - app.models.simple_lama - INFO - 실제 SimpleLama 모델로 인페인팅 수행
2025-08-29 02:46:45,403 - main - INFO - 상태 저장 완료 #42: 02:46:45 INFO: 192.168.0.119:55553 - "POST /api/v1/inpaint HTTP/1.1" 200 OK
2025-08-29 02:46:46,406 - main - INFO - 상태 저장 완료 #43: 02:46:46 INFO: 192.168.0.119:55575 - "GET /api/v1/model HTTP/1.1" 200 OK
2025-08-29 02:46:47,409 - main - INFO - 상태 저장 완료 #44: 02:46:47 2025-08-29 21:37:09,535 - app.models.simple_lama - INFO - 실제 SimpleLama 모델로 인페인팅 수행
2025-08-29 02:46:48,413 - main - INFO - 상태 저장 완료 #45: 02:46:48 INFO: 192.168.0.119:55576 - "POST /api/v1/inpaint HTTP/1.1" 200 OK
2025-08-29 02:46:49,416 - main - INFO - 상태 저장 완료 #46: 02:46:49
2025-08-29 02:46:50,419 - main - INFO - 상태 저장 완료 #47: 02:46:50
2025-08-29 02:46:51,423 - main - INFO - 상태 저장 완료 #48: 02:46:51
2025-08-29 02:46:52,428 - main - INFO - 상태 저장 완료 #49: 02:46:52
2025-08-29 02:46:53,434 - main - INFO - 상태 저장 완료 #50: 02:46:53
2025-08-29 02:46:54,438 - main - INFO - 상태 저장 완료 #51: 02:46:54
2025-08-29 02:46:55,443 - main - INFO - 상태 저장 완료 #52: 02:46:55
2025-08-29 02:46:56,449 - main - INFO - 상태 저장 완료 #53: 02:46:56
2025-08-29 02:46:57,454 - main - INFO - 상태 저장 완료 #54: 02:46:57
2025-08-29 02:46:58,459 - main - INFO - 상태 저장 완료 #55: 02:46:58
2025-08-29 02:46:59,464 - main - INFO - 상태 저장 완료 #56: 02:46:59
2025-08-29 02:47:00,468 - main - INFO - 상태 저장 완료 #57: 02:47:00
2025-08-29 02:47:01,473 - main - INFO - 상태 저장 완료 #58: 02:47:01
2025-08-29 02:47:02,478 - main - INFO - 상태 저장 완료 #59: 02:47:02
2025-08-29 02:47:03,482 - main - INFO - 상태 저장 완료 #60: 02:47:03
2025-08-29 02:47:04,487 - main - INFO - 상태 저장 완료 #61: 02:47:04
2025-08-29 02:47:07,774 - main - INFO - 상태 저장 완료 #62: 02:47:07
2025-08-29 02:47:08,779 - main - INFO - 상태 저장 완료 #63: 02:47:08
2025-08-29 02:47:09,783 - main - INFO - 상태 저장 완료 #64: 02:47:09
2025-08-29 02:47:10,787 - main - INFO - 상태 저장 완료 #65: 02:47:10
2025-08-29 02:47:11,791 - main - INFO - 상태 저장 완료 #66: 02:47:11
2025-08-29 02:47:12,795 - main - INFO - 상태 저장 완료 #67: 02:47:12
2025-08-29 02:47:13,800 - main - INFO - 상태 저장 완료 #68: 02:47:13
2025-08-29 02:47:14,804 - main - INFO - 상태 저장 완료 #69: 02:47:14
2025-08-29 02:47:15,809 - main - INFO - 상태 저장 완료 #70: 02:47:15
2025-08-29 02:47:16,814 - main - INFO - 상태 저장 완료 #71: 02:47:16
2025-08-29 02:47:17,818 - main - INFO - 상태 저장 완료 #72: 02:47:17
2025-08-29 02:47:18,823 - main - INFO - 상태 저장 완료 #73: 02:47:18
2025-08-29 02:47:19,828 - main - INFO - 상태 저장 완료 #74: 02:47:19
2025-08-29 02:47:20,833 - main - INFO - 상태 저장 완료 #75: 02:47:20
2025-08-29 02:47:21,837 - main - INFO - 상태 저장 완료 #76: 02:47:21
2025-08-29 02:47:22,841 - main - INFO - 상태 저장 완료 #77: 02:47:22
2025-08-29 02:47:23,849 - main - INFO - 상태 저장 완료 #78: 02:47:23
2025-08-29 02:47:24,853 - main - INFO - 상태 저장 완료 #79: 02:47:24
2025-08-29 02:47:25,857 - main - INFO - 상태 저장 완료 #80: 02:47:25
2025-08-29 02:47:26,861 - main - INFO - 상태 저장 완료 #81: 02:47:26
2025-08-29 02:47:30,810 - main - INFO - 상태 저장 완료 #82: 02:47:30
2025-08-29 02:47:31,815 - main - INFO - 상태 저장 완료 #83: 02:47:31
2025-08-29 02:47:32,819 - main - INFO - 상태 저장 완료 #84: 02:47:32
2025-08-29 02:47:33,824 - main - INFO - 상태 저장 완료 #85: 02:47:33
2025-08-29 02:47:34,828 - main - INFO - 상태 저장 완료 #86: 02:47:34
2025-08-29 02:47:35,833 - main - INFO - 상태 저장 완료 #87: 02:47:35
2025-08-29 02:47:36,837 - main - INFO - 상태 저장 완료 #88: 02:47:36
2025-08-29 02:47:37,840 - main - INFO - 상태 저장 완료 #89: 02:47:37
2025-08-29 02:47:38,845 - main - INFO - 상태 저장 완료 #90: 02:47:38
2025-08-29 02:47:39,852 - main - INFO - 상태 저장 완료 #91: 02:47:39
2025-08-29 02:47:40,856 - main - INFO - 상태 저장 완료 #92: 02:47:40
2025-08-29 02:47:41,861 - main - INFO - 상태 저장 완료 #93: 02:47:41
2025-08-29 02:47:42,865 - main - INFO - 상태 저장 완료 #94: 02:47:42
2025-08-29 02:47:43,869 - main - INFO - 상태 저장 완료 #95: 02:47:43
2025-08-29 02:47:44,877 - main - INFO - 상태 저장 완료 #96: 02:47:44
2025-08-29 02:47:45,880 - main - INFO - 상태 저장 완료 #97: 02:47:45
2025-08-29 02:47:46,884 - main - INFO - 상태 저장 완료 #98: 02:47:46
2025-08-29 02:47:47,889 - main - INFO - 상태 저장 완료 #99: 02:47:47
2025-08-29 02:47:48,893 - main - INFO - 상태 저장 완료 #100: 02:47:48
2025-08-29 02:47:49,901 - main - INFO - 상태 저장 완료 #101: 02:47:49
2025-08-29 02:47:53,846 - main - INFO - 상태 저장 완료 #102: 02:47:53
2025-08-29 02:47:54,851 - main - INFO - 상태 저장 완료 #103: 02:47:54
2025-08-29 02:47:55,856 - main - INFO - 상태 저장 완료 #104: 02:47:55
2025-08-29 02:47:56,860 - main - INFO - 상태 저장 완료 #105: 02:47:56
2025-08-29 02:47:57,865 - main - INFO - 상태 저장 완료 #106: 02:47:57
2025-08-29 02:47:58,869 - main - INFO - 상태 저장 완료 #107: 02:47:58
2025-08-29 02:47:59,873 - main - INFO - 상태 저장 완료 #108: 02:47:59
2025-08-29 02:48:00,878 - main - INFO - 상태 저장 완료 #109: 02:48:00
2025-08-29 02:48:01,882 - main - INFO - 상태 저장 완료 #110: 02:48:01
2025-08-29 02:48:02,886 - main - INFO - 상태 저장 완료 #111: 02:48:02
2025-08-29 02:48:03,890 - main - INFO - 상태 저장 완료 #112: 02:48:03
2025-08-29 02:48:04,895 - main - INFO - 상태 저장 완료 #113: 02:48:04
2025-08-29 02:48:05,899 - main - INFO - 상태 저장 완료 #114: 02:48:05
2025-08-29 02:48:06,903 - main - INFO - 상태 저장 완료 #115: 02:48:06
2025-08-29 02:48:07,908 - main - INFO - 상태 저장 완료 #116: 02:48:07
2025-08-29 02:48:08,913 - main - INFO - 상태 저장 완료 #117: 02:48:08
2025-08-29 02:48:09,918 - main - INFO - 상태 저장 완료 #118: 02:48:09
2025-08-29 02:48:10,924 - main - INFO - 상태 저장 완료 #119: 02:48:10
2025-08-29 02:48:11,928 - main - INFO - 상태 저장 완료 #120: 02:48:11
2025-08-29 02:48:12,933 - main - INFO - 상태 저장 완료 #121: 02:48:12
2025-08-29 02:48:16,876 - main - INFO - 상태 저장 완료 #122: 02:48:16
2025-08-29 02:48:17,883 - main - INFO - 상태 저장 완료 #123: 02:48:17
2025-08-29 02:48:18,887 - main - INFO - 상태 저장 완료 #124: 02:48:18
2025-08-29 02:48:19,891 - main - INFO - 상태 저장 완료 #125: 02:48:19
2025-08-29 02:48:20,895 - main - INFO - 상태 저장 완료 #126: 02:48:20
2025-08-29 02:48:21,900 - main - INFO - 상태 저장 완료 #127: 02:48:21
2025-08-29 02:48:22,903 - main - INFO - 상태 저장 완료 #128: 02:48:22
2025-08-29 02:48:23,907 - main - INFO - 상태 저장 완료 #129: 02:48:23
2025-08-29 02:48:24,912 - main - INFO - 상태 저장 완료 #130: 02:48:24
2025-08-29 02:48:25,917 - main - INFO - 상태 저장 완료 #131: 02:48:25
2025-08-29 02:48:26,922 - main - INFO - 상태 저장 완료 #132: 02:48:26
2025-08-29 02:48:27,926 - main - INFO - 상태 저장 완료 #133: 02:48:27
2025-08-29 02:48:28,931 - main - INFO - 상태 저장 완료 #134: 02:48:28
2025-08-29 02:48:29,935 - main - INFO - 상태 저장 완료 #135: 02:48:29
2025-08-29 02:48:30,940 - main - INFO - 상태 저장 완료 #136: 02:48:30
2025-08-29 02:48:31,945 - main - INFO - 상태 저장 완료 #137: 02:48:31
2025-08-29 02:48:32,949 - main - INFO - 상태 저장 완료 #138: 02:48:32
2025-08-29 02:48:33,954 - main - INFO - 상태 저장 완료 #139: 02:48:33
2025-08-29 02:48:34,956 - main - INFO - 상태 저장 완료 #140: 02:48:34
2025-08-29 02:48:35,961 - main - INFO - 상태 저장 완료 #141: 02:48:35
2025-08-29 02:48:39,911 - main - INFO - 상태 저장 완료 #142: 02:48:39
2025-08-29 02:48:40,915 - main - INFO - 상태 저장 완료 #143: 02:48:40
2025-08-29 02:48:41,920 - main - INFO - 상태 저장 완료 #144: 02:48:41
2025-08-29 02:48:42,924 - main - INFO - 상태 저장 완료 #145: 02:48:42
2025-08-29 02:48:43,928 - main - INFO - 상태 저장 완료 #146: 02:48:43
2025-08-29 02:48:44,934 - main - INFO - 상태 저장 완료 #147: 02:48:44
2025-08-29 02:48:45,938 - main - INFO - 상태 저장 완료 #148: 02:48:45
2025-08-29 02:48:46,942 - main - INFO - 상태 저장 완료 #149: 02:48:46
2025-08-29 02:48:47,946 - main - INFO - 상태 저장 완료 #150: 02:48:47
2025-08-29 02:48:48,951 - main - INFO - 상태 저장 완료 #151: 02:48:48
2025-08-29 02:48:49,955 - main - INFO - 상태 저장 완료 #152: 02:48:49
2025-08-29 02:48:50,960 - main - INFO - 상태 저장 완료 #153: 02:48:50
2025-08-29 02:48:51,964 - main - INFO - 상태 저장 완료 #154: 02:48:51
2025-08-29 02:48:52,969 - main - INFO - 상태 저장 완료 #155: 02:48:52
2025-08-29 02:48:53,973 - main - INFO - 상태 저장 완료 #156: 02:48:53
2025-08-29 02:48:54,977 - main - INFO - 상태 저장 완료 #157: 02:48:54
2025-08-29 02:48:55,981 - main - INFO - 상태 저장 완료 #158: 02:48:55
2025-08-29 02:48:56,985 - main - INFO - 상태 저장 완료 #159: 02:48:56
2025-08-29 02:48:57,990 - main - INFO - 상태 저장 완료 #160: 02:48:57
2025-08-29 02:48:58,995 - main - INFO - 상태 저장 완료 #161: 02:48:58
2025-08-29 02:49:02,945 - main - INFO - 상태 저장 완료 #162: 02:49:02
2025-08-29 02:49:03,949 - main - INFO - 상태 저장 완료 #163: 02:49:03
2025-08-29 02:49:04,954 - main - INFO - 상태 저장 완료 #164: 02:49:04
2025-08-29 02:49:05,958 - main - INFO - 상태 저장 완료 #165: 02:49:05
2025-08-29 02:49:06,961 - main - INFO - 상태 저장 완료 #166: 02:49:06
2025-08-29 02:49:07,966 - main - INFO - 상태 저장 완료 #167: 02:49:07
2025-08-29 02:49:08,970 - main - INFO - 상태 저장 완료 #168: 02:49:08
2025-08-29 02:49:09,975 - main - INFO - 상태 저장 완료 #169: 02:49:09
2025-08-29 02:49:10,979 - main - INFO - 상태 저장 완료 #170: 02:49:10
2025-08-29 02:49:11,985 - main - INFO - 상태 저장 완료 #171: 02:49:11
2025-08-29 02:49:12,990 - main - INFO - 상태 저장 완료 #172: 02:49:12
2025-08-29 02:49:13,995 - main - INFO - 상태 저장 완료 #173: 02:49:13
2025-08-29 02:49:15,000 - main - INFO - 상태 저장 완료 #174: 02:49:14
2025-08-29 02:49:16,005 - main - INFO - 상태 저장 완료 #175: 02:49:16
2025-08-29 02:49:17,010 - main - INFO - 상태 저장 완료 #176: 02:49:17
2025-08-29 02:49:18,015 - main - INFO - 상태 저장 완료 #177: 02:49:18
2025-08-29 02:49:19,020 - main - INFO - 상태 저장 완료 #178: 02:49:19
2025-08-29 02:49:20,024 - main - INFO - 상태 저장 완료 #179: 02:49:20
2025-08-29 02:49:21,029 - main - INFO - 상태 저장 완료 #180: 02:49:21
2025-08-29 02:49:22,034 - main - INFO - 상태 저장 완료 #181: 02:49:22
2025-08-29 02:49:25,980 - main - INFO - 상태 저장 완료 #182: 02:49:25
2025-08-29 02:49:26,985 - main - INFO - 상태 저장 완료 #183: 02:49:26
2025-08-29 02:49:27,989 - main - INFO - 상태 저장 완료 #184: 02:49:27
2025-08-29 02:49:28,993 - main - INFO - 상태 저장 완료 #185: 02:49:28
2025-08-29 02:49:29,997 - main - INFO - 상태 저장 완료 #186: 02:49:29
2025-08-29 02:49:31,001 - main - INFO - 상태 저장 완료 #187: 02:49:30
2025-08-29 02:49:32,005 - main - INFO - 상태 저장 완료 #188: 02:49:32
2025-08-29 02:49:33,009 - main - INFO - 상태 저장 완료 #189: 02:49:33
2025-08-29 02:49:34,013 - main - INFO - 상태 저장 완료 #190: 02:49:34
2025-08-29 02:49:35,016 - main - INFO - 상태 저장 완료 #191: 02:49:35
2025-08-29 02:49:36,021 - main - INFO - 상태 저장 완료 #192: 02:49:36
2025-08-29 02:49:37,025 - main - INFO - 상태 저장 완료 #193: 02:49:37
2025-08-29 02:49:38,030 - main - INFO - 상태 저장 완료 #194: 02:49:38
2025-08-29 02:49:39,035 - main - INFO - 상태 저장 완료 #195: 02:49:39
2025-08-29 02:49:40,039 - main - INFO - 상태 저장 완료 #196: 02:49:40
2025-08-29 02:49:41,044 - main - INFO - 상태 저장 완료 #197: 02:49:41
2025-08-29 02:49:42,048 - main - INFO - 상태 저장 완료 #198: 02:49:42
2025-08-29 02:49:43,052 - main - INFO - 상태 저장 완료 #199: 02:49:43
2025-08-29 02:49:44,057 - main - INFO - 상태 저장 완료 #200: 02:49:44
2025-08-29 02:49:45,061 - main - INFO - 상태 저장 완료 #201: 02:49:45
2025-08-29 02:49:49,015 - main - INFO - 상태 저장 완료 #202: 02:49:49
2025-08-29 02:49:50,020 - main - INFO - 상태 저장 완료 #203: 02:49:50
2025-08-29 02:49:51,025 - main - INFO - 상태 저장 완료 #204: 02:49:51
2025-08-29 02:49:52,029 - main - INFO - 상태 저장 완료 #205: 02:49:52
2025-08-29 02:49:53,033 - main - INFO - 상태 저장 완료 #206: 02:49:53
2025-08-29 02:49:54,037 - main - INFO - 상태 저장 완료 #207: 02:49:54
2025-08-29 02:49:55,041 - main - INFO - 상태 저장 완료 #208: 02:49:55
2025-08-29 02:49:56,045 - main - INFO - 상태 저장 완료 #209: 02:49:56
2025-08-29 02:49:57,049 - main - INFO - 상태 저장 완료 #210: 02:49:57
2025-08-29 02:49:58,053 - main - INFO - 상태 저장 완료 #211: 02:49:58
2025-08-29 02:49:59,057 - main - INFO - 상태 저장 완료 #212: 02:49:59
2025-08-29 02:50:00,059 - main - INFO - 상태 저장 완료 #213: 02:50:00
2025-08-29 02:50:01,063 - main - INFO - 상태 저장 완료 #214: 02:50:01
2025-08-29 02:50:02,067 - main - INFO - 상태 저장 완료 #215: 02:50:02
2025-08-29 02:50:03,069 - main - INFO - 상태 저장 완료 #216: 02:50:03
2025-08-29 02:50:04,073 - main - INFO - 상태 저장 완료 #217: 02:50:04
2025-08-29 02:50:05,078 - main - INFO - 상태 저장 완료 #218: 02:50:05
2025-08-29 02:50:06,083 - main - INFO - 상태 저장 완료 #219: 02:50:06
2025-08-29 02:50:07,087 - main - INFO - 상태 저장 완료 #220: 02:50:07
2025-08-29 02:50:08,091 - main - INFO - 상태 저장 완료 #221: 02:50:08
2025-08-29 02:50:12,049 - main - INFO - 상태 저장 완료 #222: 02:50:12
2025-08-29 02:50:13,054 - main - INFO - 상태 저장 완료 #223: 02:50:13
2025-08-29 02:50:14,058 - main - INFO - 상태 저장 완료 #224: 02:50:14
2025-08-29 02:50:15,063 - main - INFO - 상태 저장 완료 #225: 02:50:15
2025-08-29 02:50:16,067 - main - INFO - 상태 저장 완료 #226: 02:50:16
2025-08-29 02:50:17,071 - main - INFO - 상태 저장 완료 #227: 02:50:17
2025-08-29 02:50:18,076 - main - INFO - 상태 저장 완료 #228: 02:50:18
2025-08-29 02:50:19,081 - main - INFO - 상태 저장 완료 #229: 02:50:19
2025-08-29 02:50:20,085 - main - INFO - 상태 저장 완료 #230: 02:50:20
2025-08-29 02:50:21,090 - main - INFO - 상태 저장 완료 #231: 02:50:21
2025-08-29 02:50:22,094 - main - INFO - 상태 저장 완료 #232: 02:50:22
2025-08-29 02:50:23,098 - main - INFO - 상태 저장 완료 #233: 02:50:23
2025-08-29 02:50:24,102 - main - INFO - 상태 저장 완료 #234: 02:50:24
2025-08-29 02:50:25,106 - main - INFO - 상태 저장 완료 #235: 02:50:25
2025-08-29 02:50:26,111 - main - INFO - 상태 저장 완료 #236: 02:50:26
2025-08-29 02:50:27,115 - main - INFO - 상태 저장 완료 #237: 02:50:27
2025-08-29 02:50:28,120 - main - INFO - 상태 저장 완료 #238: 02:50:28
2025-08-29 02:50:29,124 - main - INFO - 상태 저장 완료 #239: 02:50:29
2025-08-29 02:50:30,129 - main - INFO - 상태 저장 완료 #240: 02:50:30
2025-08-29 02:50:31,134 - main - INFO - 상태 저장 완료 #241: 02:50:31
2025-08-29 02:50:35,082 - main - INFO - 상태 저장 완료 #242: 02:50:35
2025-08-29 02:50:36,087 - main - INFO - 상태 저장 완료 #243: 02:50:36
2025-08-29 02:50:37,092 - main - INFO - 상태 저장 완료 #244: 02:50:37
2025-08-29 02:50:38,097 - main - INFO - 상태 저장 완료 #245: 02:50:38
2025-08-29 02:50:39,101 - main - INFO - 상태 저장 완료 #246: 02:50:39
2025-08-29 02:50:40,106 - main - INFO - 상태 저장 완료 #247: 02:50:40
2025-08-29 02:50:41,111 - main - INFO - 상태 저장 완료 #248: 02:50:41
2025-08-29 02:50:42,115 - main - INFO - 상태 저장 완료 #249: 02:50:42
2025-08-29 02:50:43,120 - main - INFO - 상태 저장 완료 #250: 02:50:43
2025-08-29 02:50:44,125 - main - INFO - 상태 저장 완료 #251: 02:50:44
2025-08-29 02:50:45,129 - main - INFO - 상태 저장 완료 #252: 02:50:45
2025-08-29 02:50:46,134 - main - INFO - 상태 저장 완료 #253: 02:50:46
2025-08-29 02:50:47,137 - main - INFO - 상태 저장 완료 #254: 02:50:47
2025-08-29 02:50:48,141 - main - INFO - 상태 저장 완료 #255: 02:50:48
2025-08-29 02:50:49,146 - main - INFO - 상태 저장 완료 #256: 02:50:49
2025-08-29 02:50:50,150 - main - INFO - 상태 저장 완료 #257: 02:50:50
2025-08-29 02:50:51,154 - main - INFO - 상태 저장 완료 #258: 02:50:51
2025-08-29 02:50:52,159 - main - INFO - 상태 저장 완료 #259: 02:50:52
2025-08-29 02:50:53,164 - main - INFO - 상태 저장 완료 #260: 02:50:53
2025-08-29 02:50:54,168 - main - INFO - 상태 저장 완료 #261: 02:50:54
2025-08-29 02:50:58,116 - main - INFO - 상태 저장 완료 #262: 02:50:58
2025-08-29 02:50:59,121 - main - INFO - 상태 저장 완료 #263: 02:50:59
2025-08-29 02:51:00,126 - main - INFO - 상태 저장 완료 #264: 02:51:00
2025-08-29 02:51:01,130 - main - INFO - 상태 저장 완료 #265: 02:51:01
2025-08-29 02:51:02,133 - main - INFO - 상태 저장 완료 #266: 02:51:02
2025-08-29 02:51:03,140 - main - INFO - 상태 저장 완료 #267: 02:51:03
2025-08-29 02:51:04,144 - main - INFO - 상태 저장 완료 #268: 02:51:04
2025-08-29 02:51:05,148 - main - INFO - 상태 저장 완료 #269: 02:51:05
2025-08-29 02:51:06,153 - main - INFO - 상태 저장 완료 #270: 02:51:06
2025-08-29 02:51:07,156 - main - INFO - 상태 저장 완료 #271: 02:51:07
2025-08-29 02:51:08,161 - main - INFO - 상태 저장 완료 #272: 02:51:08
2025-08-29 02:51:09,165 - main - INFO - 상태 저장 완료 #273: 02:51:09
2025-08-29 02:51:10,170 - main - INFO - 상태 저장 완료 #274: 02:51:10
2025-08-29 02:51:11,174 - main - INFO - 상태 저장 완료 #275: 02:51:11
2025-08-29 02:51:12,178 - main - INFO - 상태 저장 완료 #276: 02:51:12
2025-08-29 02:51:13,182 - main - INFO - 상태 저장 완료 #277: 02:51:13
2025-08-29 02:51:14,187 - main - INFO - 상태 저장 완료 #278: 02:51:14
2025-08-29 02:51:15,191 - main - INFO - 상태 저장 완료 #279: 02:51:15
2025-08-29 02:51:16,196 - main - INFO - 상태 저장 완료 #280: 02:51:16
2025-08-29 02:51:17,200 - main - INFO - 상태 저장 완료 #281: 02:51:17
2025-08-29 02:51:21,149 - main - INFO - 상태 저장 완료 #282: 02:51:21
2025-08-29 02:51:22,154 - main - INFO - 상태 저장 완료 #283: 02:51:22
2025-08-29 02:51:23,159 - main - INFO - 상태 저장 완료 #284: 02:51:23
2025-08-29 02:51:24,163 - main - INFO - 상태 저장 완료 #285: 02:51:24
2025-08-29 02:51:25,167 - main - INFO - 상태 저장 완료 #286: 02:51:25
2025-08-29 02:51:26,172 - main - INFO - 상태 저장 완료 #287: 02:51:26
2025-08-29 02:51:27,177 - main - INFO - 상태 저장 완료 #288: 02:51:27
2025-08-29 02:51:28,181 - main - INFO - 상태 저장 완료 #289: 02:51:28
2025-08-29 02:51:29,185 - main - INFO - 상태 저장 완료 #290: 02:51:29
2025-08-29 02:51:30,191 - main - INFO - 상태 저장 완료 #291: 02:51:30
2025-08-29 02:51:31,197 - main - INFO - 상태 저장 완료 #292: 02:51:31
2025-08-29 02:51:32,202 - main - INFO - 상태 저장 완료 #293: 02:51:32
2025-08-29 02:51:33,206 - main - INFO - 상태 저장 완료 #294: 02:51:33
2025-08-29 02:51:34,211 - main - INFO - 상태 저장 완료 #295: 02:51:34
2025-08-29 02:51:35,215 - main - INFO - 상태 저장 완료 #296: 02:51:35
2025-08-29 02:51:36,219 - main - INFO - 상태 저장 완료 #297: 02:51:36
2025-08-29 02:51:37,224 - main - INFO - 상태 저장 완료 #298: 02:51:37
2025-08-29 02:51:38,229 - main - INFO - 상태 저장 완료 #299: 02:51:38
2025-08-29 02:51:39,233 - main - INFO - 상태 저장 완료 #300: 02:51:39
2025-08-29 02:51:40,237 - main - INFO - 상태 저장 완료 #301: 02:51:40
2025-08-29 02:51:44,186 - main - INFO - 상태 저장 완료 #302: 02:51:44
2025-08-29 02:51:45,191 - main - INFO - 상태 저장 완료 #303: 02:51:45
2025-08-29 02:51:46,195 - main - INFO - 상태 저장 완료 #304: 02:51:46
2025-08-29 02:51:47,200 - main - INFO - 상태 저장 완료 #305: 02:51:47
2025-08-29 02:51:48,205 - main - INFO - 상태 저장 완료 #306: 02:51:48
2025-08-29 02:51:49,209 - main - INFO - 상태 저장 완료 #307: 02:51:49
2025-08-29 02:51:50,213 - main - INFO - 상태 저장 완료 #308: 02:51:50
2025-08-29 02:51:51,218 - main - INFO - 상태 저장 완료 #309: 02:51:51
2025-08-29 02:51:52,222 - main - INFO - 상태 저장 완료 #310: 02:51:52
2025-08-29 02:51:53,226 - main - INFO - 상태 저장 완료 #311: 02:51:53
2025-08-29 02:51:54,230 - main - INFO - 상태 저장 완료 #312: 02:51:54
2025-08-29 02:51:55,232 - main - INFO - 상태 저장 완료 #313: 02:51:55
2025-08-29 02:51:56,237 - main - INFO - 상태 저장 완료 #314: 02:51:56
2025-08-29 02:51:57,241 - main - INFO - 상태 저장 완료 #315: 02:51:57
2025-08-29 02:51:58,245 - main - INFO - 상태 저장 완료 #316: 02:51:58
2025-08-29 02:51:59,250 - main - INFO - 상태 저장 완료 #317: 02:51:59
2025-08-29 02:52:00,254 - main - INFO - 상태 저장 완료 #318: 02:52:00
2025-08-29 02:52:01,258 - main - INFO - 상태 저장 완료 #319: 02:52:01
2025-08-29 02:52:02,261 - main - INFO - 상태 저장 완료 #320: 02:52:02
2025-08-29 02:52:03,265 - main - INFO - 상태 저장 완료 #321: 02:52:03
2025-08-29 02:52:07,219 - main - INFO - 상태 저장 완료 #322: 02:52:07
2025-08-29 02:52:08,224 - main - INFO - 상태 저장 완료 #323: 02:52:08
2025-08-29 02:52:09,229 - main - INFO - 상태 저장 완료 #324: 02:52:09
2025-08-29 02:52:10,233 - main - INFO - 상태 저장 완료 #325: 02:52:10
2025-08-29 02:52:11,237 - main - INFO - 상태 저장 완료 #326: 02:52:11
2025-08-29 02:52:12,241 - main - INFO - 상태 저장 완료 #327: 02:52:12
2025-08-29 02:52:13,245 - main - INFO - 상태 저장 완료 #328: 02:52:13
2025-08-29 02:52:14,250 - main - INFO - 상태 저장 완료 #329: 02:52:14
2025-08-29 02:52:15,254 - main - INFO - 상태 저장 완료 #330: 02:52:15
2025-08-29 02:52:16,258 - main - INFO - 상태 저장 완료 #331: 02:52:16
2025-08-29 02:52:17,262 - main - INFO - 상태 저장 완료 #332: 02:52:17
2025-08-29 02:52:18,266 - main - INFO - 상태 저장 완료 #333: 02:52:18
2025-08-29 02:52:19,271 - main - INFO - 상태 저장 완료 #334: 02:52:19
2025-08-29 02:52:20,275 - main - INFO - 상태 저장 완료 #335: 02:52:20
2025-08-29 02:52:21,280 - main - INFO - 상태 저장 완료 #336: 02:52:21
2025-08-29 02:52:22,284 - main - INFO - 상태 저장 완료 #337: 02:52:22
2025-08-29 02:52:23,289 - main - INFO - 상태 저장 완료 #338: 02:52:23
2025-08-29 02:52:24,294 - main - INFO - 상태 저장 완료 #339: 02:52:24
2025-08-29 02:52:25,298 - main - INFO - 상태 저장 완료 #340: 02:52:25
2025-08-29 02:52:26,303 - main - INFO - 상태 저장 완료 #341: 02:52:26
2025-08-29 02:52:30,253 - main - INFO - 상태 저장 완료 #342: 02:52:30
2025-08-29 02:52:31,258 - main - INFO - 상태 저장 완료 #343: 02:52:31
2025-08-29 02:52:32,263 - main - INFO - 상태 저장 완료 #344: 02:52:32
2025-08-29 02:52:33,267 - main - INFO - 상태 저장 완료 #345: 02:52:33
2025-08-29 02:52:34,272 - main - INFO - 상태 저장 완료 #346: 02:52:34
2025-08-29 02:52:35,276 - main - INFO - 상태 저장 완료 #347: 02:52:35
2025-08-29 02:52:36,280 - main - INFO - 상태 저장 완료 #348: 02:52:36
2025-08-29 02:52:37,285 - main - INFO - 상태 저장 완료 #349: 02:52:37
2025-08-29 02:52:38,288 - main - INFO - 상태 저장 완료 #350: 02:52:38
2025-08-29 02:52:39,292 - main - INFO - 상태 저장 완료 #351: 02:52:39
2025-08-29 02:52:40,297 - main - INFO - 상태 저장 완료 #352: 02:52:40
2025-08-29 02:52:41,302 - main - INFO - 상태 저장 완료 #353: 02:52:41
2025-08-29 02:52:42,306 - main - INFO - 상태 저장 완료 #354: 02:52:42
2025-08-29 02:52:43,310 - main - INFO - 상태 저장 완료 #355: 02:52:43
2025-08-29 02:52:44,314 - main - INFO - 상태 저장 완료 #356: 02:52:44
2025-08-29 02:52:45,317 - main - INFO - 상태 저장 완료 #357: 02:52:45
2025-08-29 02:52:46,322 - main - INFO - 상태 저장 완료 #358: 02:52:46
2025-08-29 02:52:47,327 - main - INFO - 상태 저장 완료 #359: 02:52:47
2025-08-29 02:52:48,332 - main - INFO - 상태 저장 완료 #360: 02:52:48
2025-08-29 02:52:49,337 - main - INFO - 상태 저장 완료 #361: 02:52:49
2025-08-29 02:52:53,288 - main - INFO - 상태 저장 완료 #362: 02:52:53
2025-08-29 02:52:54,294 - main - INFO - 상태 저장 완료 #363: 02:52:54
2025-08-29 02:52:55,297 - main - INFO - 상태 저장 완료 #364: 02:52:55
2025-08-29 02:52:56,301 - main - INFO - 상태 저장 완료 #365: 02:52:56
2025-08-29 02:52:57,306 - main - INFO - 상태 저장 완료 #366: 02:52:57
2025-08-29 02:52:58,310 - main - INFO - 상태 저장 완료 #367: 02:52:58
2025-08-29 02:52:59,314 - main - INFO - 상태 저장 완료 #368: 02:52:59
2025-08-29 02:53:00,319 - main - INFO - 상태 저장 완료 #369: 02:53:00
2025-08-29 02:53:01,323 - main - INFO - 상태 저장 완료 #370: 02:53:01
2025-08-29 02:53:02,327 - main - INFO - 상태 저장 완료 #371: 02:53:02
2025-08-29 02:53:03,331 - main - INFO - 상태 저장 완료 #372: 02:53:03
2025-08-29 02:53:04,336 - main - INFO - 상태 저장 완료 #373: 02:53:04
2025-08-29 02:53:05,340 - main - INFO - 상태 저장 완료 #374: 02:53:05
2025-08-29 02:53:06,344 - main - INFO - 상태 저장 완료 #375: 02:53:06
2025-08-29 02:53:07,348 - main - INFO - 상태 저장 완료 #376: 02:53:07
2025-08-29 02:53:08,353 - main - INFO - 상태 저장 완료 #377: 02:53:08
2025-08-29 02:53:09,357 - main - INFO - 상태 저장 완료 #378: 02:53:09
2025-08-29 02:53:10,361 - main - INFO - 상태 저장 완료 #379: 02:53:10
2025-08-29 02:53:11,365 - main - INFO - 상태 저장 완료 #380: 02:53:11
2025-08-29 02:53:12,369 - main - INFO - 상태 저장 완료 #381: 02:53:12
2025-08-29 02:53:16,321 - main - INFO - 상태 저장 완료 #382: 02:53:16
2025-08-29 02:53:17,327 - main - INFO - 상태 저장 완료 #383: 02:53:17
2025-08-29 02:53:18,332 - main - INFO - 상태 저장 완료 #384: 02:53:18
2025-08-29 02:53:19,337 - main - INFO - 상태 저장 완료 #385: 02:53:19
2025-08-29 02:53:20,341 - main - INFO - 상태 저장 완료 #386: 02:53:20
2025-08-29 02:53:21,346 - main - INFO - 상태 저장 완료 #387: 02:53:21
2025-08-29 02:53:22,350 - main - INFO - 상태 저장 완료 #388: 02:53:22
2025-08-29 02:53:23,354 - main - INFO - 상태 저장 완료 #389: 02:53:23
2025-08-29 02:53:24,359 - main - INFO - 상태 저장 완료 #390: 02:53:24
2025-08-29 02:53:25,364 - main - INFO - 상태 저장 완료 #391: 02:53:25
2025-08-29 02:53:26,367 - main - INFO - 상태 저장 완료 #392: 02:53:26
2025-08-29 02:53:27,371 - main - INFO - 상태 저장 완료 #393: 02:53:27
2025-08-29 02:53:28,375 - main - INFO - 상태 저장 완료 #394: 02:53:28
2025-08-29 02:53:29,377 - main - INFO - 상태 저장 완료 #395: 02:53:29
2025-08-29 02:53:30,381 - main - INFO - 상태 저장 완료 #396: 02:53:30
2025-08-29 02:53:31,385 - main - INFO - 상태 저장 완료 #397: 02:53:31
2025-08-29 02:53:32,389 - main - INFO - 상태 저장 완료 #398: 02:53:32
2025-08-29 02:53:33,393 - main - INFO - 상태 저장 완료 #399: 02:53:33
2025-08-29 02:53:34,398 - main - INFO - 상태 저장 완료 #400: 02:53:34
2025-08-29 02:53:35,402 - main - INFO - 상태 저장 완료 #401: 02:53:35
2025-08-29 02:53:39,355 - main - INFO - 상태 저장 완료 #402: 02:53:39
2025-08-29 02:53:40,361 - main - INFO - 상태 저장 완료 #403: 02:53:40
2025-08-29 02:53:41,365 - main - INFO - 상태 저장 완료 #404: 02:53:41
2025-08-29 02:53:42,369 - main - INFO - 상태 저장 완료 #405: 02:53:42
2025-08-29 02:53:43,374 - main - INFO - 상태 저장 완료 #406: 02:53:43
2025-08-29 02:53:44,378 - main - INFO - 상태 저장 완료 #407: 02:53:44
2025-08-29 02:53:45,382 - main - INFO - 상태 저장 완료 #408: 02:53:45
2025-08-29 02:53:46,387 - main - INFO - 상태 저장 완료 #409: 02:53:46
2025-08-29 02:53:47,392 - main - INFO - 상태 저장 완료 #410: 02:53:47
2025-08-29 02:53:48,398 - main - INFO - 상태 저장 완료 #411: 02:53:48
2025-08-29 02:53:49,402 - main - INFO - 상태 저장 완료 #412: 02:53:49
2025-08-29 02:53:50,406 - main - INFO - 상태 저장 완료 #413: 02:53:50
2025-08-29 02:53:51,411 - main - INFO - 상태 저장 완료 #414: 02:53:51
2025-08-29 02:53:52,415 - main - INFO - 상태 저장 완료 #415: 02:53:52
2025-08-29 02:53:53,419 - main - INFO - 상태 저장 완료 #416: 02:53:53
2025-08-29 02:53:54,424 - main - INFO - 상태 저장 완료 #417: 02:53:54
2025-08-29 02:53:55,428 - main - INFO - 상태 저장 완료 #418: 02:53:55
2025-08-29 02:53:56,432 - main - INFO - 상태 저장 완료 #419: 02:53:56
2025-08-29 02:53:57,436 - main - INFO - 상태 저장 완료 #420: 02:53:57
2025-08-29 02:53:58,441 - main - INFO - 상태 저장 완료 #421: 02:53:58
2025-08-29 02:54:02,388 - main - INFO - 상태 저장 완료 #422: 02:54:02
2025-08-29 02:54:03,393 - main - INFO - 상태 저장 완료 #423: 02:54:03
2025-08-29 02:54:04,398 - main - INFO - 상태 저장 완료 #424: 02:54:04
2025-08-29 02:54:05,402 - main - INFO - 상태 저장 완료 #425: 02:54:05
2025-08-29 02:54:06,406 - main - INFO - 상태 저장 완료 #426: 02:54:06
2025-08-29 02:54:07,411 - main - INFO - 상태 저장 완료 #427: 02:54:07
2025-08-29 02:54:08,415 - main - INFO - 상태 저장 완료 #428: 02:54:08
2025-08-29 02:54:09,419 - main - INFO - 상태 저장 완료 #429: 02:54:09
2025-08-29 02:54:10,424 - main - INFO - 상태 저장 완료 #430: 02:54:10
2025-08-29 02:54:11,428 - main - INFO - 상태 저장 완료 #431: 02:54:11
2025-08-29 02:54:12,433 - main - INFO - 상태 저장 완료 #432: 02:54:12
2025-08-29 02:54:13,437 - main - INFO - 상태 저장 완료 #433: 02:54:13
2025-08-29 02:54:14,442 - main - INFO - 상태 저장 완료 #434: 02:54:14
2025-08-29 02:54:15,446 - main - INFO - 상태 저장 완료 #435: 02:54:15
2025-08-29 02:54:16,450 - main - INFO - 상태 저장 완료 #436: 02:54:16
2025-08-29 02:54:17,453 - main - INFO - 상태 저장 완료 #437: 02:54:17
2025-08-29 02:54:18,457 - main - INFO - 상태 저장 완료 #438: 02:54:18
2025-08-29 02:54:19,461 - main - INFO - 상태 저장 완료 #439: 02:54:19
2025-08-29 02:54:20,465 - main - INFO - 상태 저장 완료 #440: 02:54:20
2025-08-29 02:54:21,470 - main - INFO - 상태 저장 완료 #441: 02:54:21
2025-08-29 02:54:25,425 - main - INFO - 상태 저장 완료 #442: 02:54:25
2025-08-29 02:54:26,430 - main - INFO - 상태 저장 완료 #443: 02:54:26
2025-08-29 02:54:27,434 - main - INFO - 상태 저장 완료 #444: 02:54:27
2025-08-29 02:54:28,438 - main - INFO - 상태 저장 완료 #445: 02:54:28
2025-08-29 02:54:29,441 - main - INFO - 상태 저장 완료 #446: 02:54:29
2025-08-29 02:54:30,448 - main - INFO - 상태 저장 완료 #447: 02:54:30
2025-08-29 02:54:31,452 - main - INFO - 상태 저장 완료 #448: 02:54:31
2025-08-29 02:54:32,457 - main - INFO - 상태 저장 완료 #449: 02:54:32
2025-08-29 02:54:33,461 - main - INFO - 상태 저장 완료 #450: 02:54:33
2025-08-29 02:54:34,466 - main - INFO - 상태 저장 완료 #451: 02:54:34
2025-08-29 02:54:35,470 - main - INFO - 상태 저장 완료 #452: 02:54:35
2025-08-29 02:54:36,475 - main - INFO - 상태 저장 완료 #453: 02:54:36
2025-08-29 02:54:37,479 - main - INFO - 상태 저장 완료 #454: 02:54:37
2025-08-29 02:54:38,484 - main - INFO - 상태 저장 완료 #455: 02:54:38
2025-08-29 02:54:39,488 - main - INFO - 상태 저장 완료 #456: 02:54:39
2025-08-29 02:54:40,492 - main - INFO - 상태 저장 완료 #457: 02:54:40
2025-08-29 02:54:41,496 - main - INFO - 상태 저장 완료 #458: 02:54:41
2025-08-29 02:54:42,501 - main - INFO - 상태 저장 완료 #459: 02:54:42
2025-08-29 02:54:43,505 - main - INFO - 상태 저장 완료 #460: 02:54:43
2025-08-29 02:54:44,509 - main - INFO - 상태 저장 완료 #461: 02:54:44
2025-08-29 02:54:48,455 - main - INFO - 상태 저장 완료 #462: 02:54:48
2025-08-29 02:54:49,459 - main - INFO - 상태 저장 완료 #463: 02:54:49
2025-08-29 02:54:50,464 - main - INFO - 상태 저장 완료 #464: 02:54:50
2025-08-29 02:54:51,468 - main - INFO - 상태 저장 완료 #465: 02:54:51
2025-08-29 02:54:52,472 - main - INFO - 상태 저장 완료 #466: 02:54:52
2025-08-29 02:54:53,477 - main - INFO - 상태 저장 완료 #467: 02:54:53
2025-08-29 02:54:54,482 - main - INFO - 상태 저장 완료 #468: 02:54:54
2025-08-29 02:54:55,486 - main - INFO - 상태 저장 완료 #469: 02:54:55
2025-08-29 02:54:56,491 - main - INFO - 상태 저장 완료 #470: 02:54:56
2025-08-29 02:54:57,495 - main - INFO - 상태 저장 완료 #471: 02:54:57
2025-08-29 02:54:58,501 - main - INFO - 상태 저장 완료 #472: 02:54:58
2025-08-29 02:54:59,505 - main - INFO - 상태 저장 완료 #473: 02:54:59
2025-08-29 02:55:00,509 - main - INFO - 상태 저장 완료 #474: 02:55:00
2025-08-29 02:55:01,513 - main - INFO - 상태 저장 완료 #475: 02:55:01
2025-08-29 02:55:02,515 - main - INFO - 상태 저장 완료 #476: 02:55:02
2025-08-29 02:55:03,519 - main - INFO - 상태 저장 완료 #477: 02:55:03
2025-08-29 02:55:04,523 - main - INFO - 상태 저장 완료 #478: 02:55:04
2025-08-29 02:55:05,527 - main - INFO - 상태 저장 완료 #479: 02:55:05
2025-08-29 02:55:06,531 - main - INFO - 상태 저장 완료 #480: 02:55:06
2025-08-29 02:55:07,536 - main - INFO - 상태 저장 완료 #481: 02:55:07
2025-08-29 02:55:11,489 - main - INFO - 상태 저장 완료 #482: 02:55:11
2025-08-29 02:55:12,495 - main - INFO - 상태 저장 완료 #483: 02:55:12
2025-08-29 02:55:13,498 - main - INFO - 상태 저장 완료 #484: 02:55:13
2025-08-29 02:55:14,502 - main - INFO - 상태 저장 완료 #485: 02:55:14
2025-08-29 02:55:15,506 - main - INFO - 상태 저장 완료 #486: 02:55:15
2025-08-29 02:55:16,510 - main - INFO - 상태 저장 완료 #487: 02:55:16
2025-08-29 02:55:17,514 - main - INFO - 상태 저장 완료 #488: 02:55:17
2025-08-29 02:55:18,519 - main - INFO - 상태 저장 완료 #489: 02:55:18
2025-08-29 02:55:19,523 - main - INFO - 상태 저장 완료 #490: 02:55:19
2025-08-29 02:55:20,527 - main - INFO - 상태 저장 완료 #491: 02:55:20
2025-08-29 02:55:21,531 - main - INFO - 상태 저장 완료 #492: 02:55:21
2025-08-29 02:55:22,536 - main - INFO - 상태 저장 완료 #493: 02:55:22
2025-08-29 02:55:23,540 - main - INFO - 상태 저장 완료 #494: 02:55:23
2025-08-29 02:55:24,544 - main - INFO - 상태 저장 완료 #495: 02:55:24
2025-08-29 02:55:25,549 - main - INFO - 상태 저장 완료 #496: 02:55:25
2025-08-29 02:55:26,553 - main - INFO - 상태 저장 완료 #497: 02:55:26
2025-08-29 02:55:27,557 - main - INFO - 상태 저장 완료 #498: 02:55:27
2025-08-29 02:55:28,561 - main - INFO - 상태 저장 완료 #499: 02:55:28
2025-08-29 02:55:29,565 - main - INFO - 상태 저장 완료 #500: 02:55:29
2025-08-29 02:55:30,569 - main - INFO - 상태 저장 완료 #501: 02:55:30
2025-08-29 02:55:34,523 - main - INFO - 상태 저장 완료 #502: 02:55:34
2025-08-29 02:55:35,528 - main - INFO - 상태 저장 완료 #503: 02:55:35
2025-08-29 02:55:36,533 - main - INFO - 상태 저장 완료 #504: 02:55:36
2025-08-29 02:55:37,537 - main - INFO - 상태 저장 완료 #505: 02:55:37
2025-08-29 02:55:38,542 - main - INFO - 상태 저장 완료 #506: 02:55:38
2025-08-29 02:55:39,547 - main - INFO - 상태 저장 완료 #507: 02:55:39
2025-08-29 02:55:40,551 - main - INFO - 상태 저장 완료 #508: 02:55:40
2025-08-29 02:55:41,556 - main - INFO - 상태 저장 완료 #509: 02:55:41
2025-08-29 02:55:42,560 - main - INFO - 상태 저장 완료 #510: 02:55:42
2025-08-29 02:55:43,564 - main - INFO - 상태 저장 완료 #511: 02:55:43
2025-08-29 02:55:44,572 - main - INFO - 상태 저장 완료 #512: 02:55:44
2025-08-29 02:55:45,576 - main - INFO - 상태 저장 완료 #513: 02:55:45
2025-08-29 02:55:46,580 - main - INFO - 상태 저장 완료 #514: 02:55:46
2025-08-29 02:55:47,584 - main - INFO - 상태 저장 완료 #515: 02:55:47
2025-08-29 02:55:48,589 - main - INFO - 상태 저장 완료 #516: 02:55:48
2025-08-29 02:55:49,597 - main - INFO - 상태 저장 완료 #517: 02:55:49
2025-08-29 02:55:50,600 - main - INFO - 상태 저장 완료 #518: 02:55:50
2025-08-29 02:55:51,603 - main - INFO - 상태 저장 완료 #519: 02:55:51
2025-08-29 02:55:52,608 - main - INFO - 상태 저장 완료 #520: 02:55:52
2025-08-29 02:55:53,612 - main - INFO - 상태 저장 완료 #521: 02:55:53
2025-08-29 02:55:57,558 - main - INFO - 상태 저장 완료 #522: 02:55:57
2025-08-29 02:55:58,562 - main - INFO - 상태 저장 완료 #523: 02:55:58
2025-08-29 02:55:59,566 - main - INFO - 상태 저장 완료 #524: 02:55:59
2025-08-29 02:56:00,570 - main - INFO - 상태 저장 완료 #525: 02:56:00
2025-08-29 02:56:01,574 - main - INFO - 상태 저장 완료 #526: 02:56:01
2025-08-29 02:56:02,578 - main - INFO - 상태 저장 완료 #527: 02:56:02
2025-08-29 02:56:03,582 - main - INFO - 상태 저장 완료 #528: 02:56:03
2025-08-29 02:56:04,586 - main - INFO - 상태 저장 완료 #529: 02:56:04
2025-08-29 02:56:05,590 - main - INFO - 상태 저장 완료 #530: 02:56:05
2025-08-29 02:56:06,595 - main - INFO - 상태 저장 완료 #531: 02:56:06
2025-08-29 02:56:07,599 - main - INFO - 상태 저장 완료 #532: 02:56:07
2025-08-29 02:56:08,604 - main - INFO - 상태 저장 완료 #533: 02:56:08
2025-08-29 02:56:09,608 - main - INFO - 상태 저장 완료 #534: 02:56:09
2025-08-29 02:56:10,612 - main - INFO - 상태 저장 완료 #535: 02:56:10
2025-08-29 02:56:11,616 - main - INFO - 상태 저장 완료 #536: 02:56:11
2025-08-29 02:56:12,620 - main - INFO - 상태 저장 완료 #537: 02:56:12
2025-08-29 02:56:13,625 - main - INFO - 상태 저장 완료 #538: 02:56:13
2025-08-29 02:56:14,629 - main - INFO - 상태 저장 완료 #539: 02:56:14
2025-08-29 02:56:15,633 - main - INFO - 상태 저장 완료 #540: 02:56:15
2025-08-29 02:56:16,637 - main - INFO - 상태 저장 완료 #541: 02:56:16
2025-08-29 02:56:20,593 - main - INFO - 상태 저장 완료 #542: 02:56:20
2025-08-29 02:56:21,599 - main - INFO - 상태 저장 완료 #543: 02:56:21
2025-08-29 02:56:22,604 - main - INFO - 상태 저장 완료 #544: 02:56:22
2025-08-29 02:56:23,608 - main - INFO - 상태 저장 완료 #545: 02:56:23
2025-08-29 02:56:24,613 - main - INFO - 상태 저장 완료 #546: 02:56:24
2025-08-29 02:56:25,617 - main - INFO - 상태 저장 완료 #547: 02:56:25
2025-08-29 02:56:26,622 - main - INFO - 상태 저장 완료 #548: 02:56:26
2025-08-29 02:56:27,626 - main - INFO - 상태 저장 완료 #549: 02:56:27
2025-08-29 02:56:28,630 - main - INFO - 상태 저장 완료 #550: 02:56:28
2025-08-29 02:56:29,635 - main - INFO - 상태 저장 완료 #551: 02:56:29
2025-08-29 02:56:30,639 - main - INFO - 상태 저장 완료 #552: 02:56:30
2025-08-29 02:56:31,644 - main - INFO - 상태 저장 완료 #553: 02:56:31
2025-08-29 02:56:32,649 - main - INFO - 상태 저장 완료 #554: 02:56:32
2025-08-29 02:56:33,654 - main - INFO - 상태 저장 완료 #555: 02:56:33
2025-08-29 02:56:34,659 - main - INFO - 상태 저장 완료 #556: 02:56:34
2025-08-29 02:56:35,663 - main - INFO - 상태 저장 완료 #557: 02:56:35
2025-08-29 02:56:36,667 - main - INFO - 상태 저장 완료 #558: 02:56:36
2025-08-29 02:56:37,670 - main - INFO - 상태 저장 완료 #559: 02:56:37
2025-08-29 02:56:38,675 - main - INFO - 상태 저장 완료 #560: 02:56:38
2025-08-29 02:56:39,679 - main - INFO - 상태 저장 완료 #561: 02:56:39
2025-08-29 02:56:43,627 - main - INFO - 상태 저장 완료 #562: 02:56:43
2025-08-29 02:56:44,631 - main - INFO - 상태 저장 완료 #563: 02:56:44
2025-08-29 02:56:45,633 - main - INFO - 상태 저장 완료 #564: 02:56:45
2025-08-29 02:56:46,637 - main - INFO - 상태 저장 완료 #565: 02:56:46
2025-08-29 02:56:47,642 - main - INFO - 상태 저장 완료 #566: 02:56:47
2025-08-29 02:56:48,646 - main - INFO - 상태 저장 완료 #567: 02:56:48
2025-08-29 02:56:49,650 - main - INFO - 상태 저장 완료 #568: 02:56:49
2025-08-29 02:56:50,655 - main - INFO - 상태 저장 완료 #569: 02:56:50
2025-08-29 02:56:51,659 - main - INFO - 상태 저장 완료 #570: 02:56:51
2025-08-29 02:56:52,663 - main - INFO - 상태 저장 완료 #571: 02:56:52
2025-08-29 02:56:53,667 - main - INFO - 상태 저장 완료 #572: 02:56:53
2025-08-29 02:56:54,671 - main - INFO - 상태 저장 완료 #573: 02:56:54
2025-08-29 02:56:55,676 - main - INFO - 상태 저장 완료 #574: 02:56:55
2025-08-29 02:56:56,679 - main - INFO - 상태 저장 완료 #575: 02:56:56
2025-08-29 02:56:57,683 - main - INFO - 상태 저장 완료 #576: 02:56:57
2025-08-29 02:56:58,687 - main - INFO - 상태 저장 완료 #577: 02:56:58
2025-08-29 02:56:59,691 - main - INFO - 상태 저장 완료 #578: 02:56:59
2025-08-29 02:57:00,697 - main - INFO - 상태 저장 완료 #579: 02:57:00
2025-08-29 02:57:01,701 - main - INFO - 상태 저장 완료 #580: 02:57:01
2025-08-29 02:57:02,705 - main - INFO - 상태 저장 완료 #581: 02:57:02
2025-08-29 02:57:06,661 - main - INFO - 상태 저장 완료 #582: 02:57:06
2025-08-29 02:57:07,665 - main - INFO - 상태 저장 완료 #583: 02:57:07
2025-08-29 02:57:08,670 - main - INFO - 상태 저장 완료 #584: 02:57:08
2025-08-29 02:57:09,674 - main - INFO - 상태 저장 완료 #585: 02:57:09
2025-08-29 02:57:10,679 - main - INFO - 상태 저장 완료 #586: 02:57:10
2025-08-29 02:57:11,684 - main - INFO - 상태 저장 완료 #587: 02:57:11
2025-08-29 02:57:12,689 - main - INFO - 상태 저장 완료 #588: 02:57:12
2025-08-29 02:57:13,693 - main - INFO - 상태 저장 완료 #589: 02:57:13
2025-08-29 02:57:14,697 - main - INFO - 상태 저장 완료 #590: 02:57:14
2025-08-29 02:57:15,702 - main - INFO - 상태 저장 완료 #591: 02:57:15
2025-08-29 02:57:16,706 - main - INFO - 상태 저장 완료 #592: 02:57:16
2025-08-29 02:57:17,711 - main - INFO - 상태 저장 완료 #593: 02:57:17
2025-08-29 02:57:18,716 - main - INFO - 상태 저장 완료 #594: 02:57:18
2025-08-29 02:57:19,719 - main - INFO - 상태 저장 완료 #595: 02:57:19
2025-08-29 02:57:20,721 - main - INFO - 상태 저장 완료 #596: 02:57:20
2025-08-29 02:57:21,723 - main - INFO - 상태 저장 완료 #597: 02:57:21
2025-08-29 02:57:22,728 - main - INFO - 상태 저장 완료 #598: 02:57:22
2025-08-29 02:57:23,732 - main - INFO - 상태 저장 완료 #599: 02:57:23
2025-08-29 02:57:24,736 - main - INFO - 상태 저장 완료 #600: 02:57:24

View File

@ -1 +1 @@
1338234 35210

View File

@ -1,39 +1,111 @@
INFO: Started server process [1338258] INFO: Started server process [35583]
INFO: Waiting for application startup. INFO: Waiting for application startup.
Fan control not available Fan control not available
INFO: Application startup complete. INFO: Application startup complete.
INFO: Uvicorn running on http://0.0.0.0:8888 (Press CTRL+C to quit) INFO: Uvicorn running on http://0.0.0.0:8888 (Press CTRL+C to quit)
INFO: 127.0.0.1:51772 - "GET /api/simple HTTP/1.1" 200 OK INFO: 192.168.0.119:54536 - "GET / HTTP/1.1" 200 OK
INFO: 127.0.0.1:37588 - "GET /api/status HTTP/1.1" 200 OK INFO: 192.168.0.119:54536 - "GET /api/logs?lines=50 HTTP/1.1" 200 OK
INFO: 127.0.0.1:56018 - "GET /api/status HTTP/1.1" 200 OK INFO: 192.168.0.119:54537 - "GET /api/performance-stats HTTP/1.1" 200 OK
INFO: 127.0.0.1:56030 - "GET / HTTP/1.1" 200 OK INFO: 192.168.0.119:54536 - "GET /api/system-alerts HTTP/1.1" 200 OK
INFO: ('127.0.0.1', 57392) - "WebSocket /ws" [accepted] INFO: 192.168.0.119:54537 - "GET /favicon.ico HTTP/1.1" 404 Not Found
INFO: 192.168.0.119:54544 - "GET /api/model-usage-stats HTTP/1.1" 200 OK
INFO: 127.0.0.1:60704 - "GET /api/simple HTTP/1.1" 200 OK
INFO: ('192.168.0.119', 54527) - "WebSocket /ws" [accepted]
INFO: connection open INFO: connection open
INFO: 127.0.0.1:56030 - "GET /favicon.ico HTTP/1.1" 404 Not Found INFO: 192.168.0.119:54557 - "GET /favicon.ico HTTP/1.1" 404 Not Found
INFO: ('127.0.0.1', 40458) - "WebSocket /ws" [accepted] INFO: 192.168.0.119:54544 - "GET /api/system-alerts HTTP/1.1" 200 OK
INFO: 192.168.0.119:54536 - "GET /api/model-usage-stats HTTP/1.1" 200 OK
INFO: 192.168.0.119:54538 - "GET / HTTP/1.1" 200 OK
INFO: 192.168.0.119:54584 - "GET /favicon.ico HTTP/1.1" 404 Not Found
INFO: 192.168.0.119:54643 - "GET / HTTP/1.1" 200 OK
INFO: ('192.168.0.119', 54586) - "WebSocket /ws" [accepted]
INFO: connection closed
INFO: 192.168.0.119:54643 - "GET /api/logs?lines=50 HTTP/1.1" 200 OK
INFO: 192.168.0.119:54644 - "GET /api/performance-stats HTTP/1.1" 200 OK
INFO: 127.0.0.1:48206 - "GET / HTTP/1.1" 200 OK
INFO: connection closed
INFO: 192.168.0.119:54722 - "GET /api/model-usage-stats HTTP/1.1" 200 OK
INFO: 192.168.0.119:54723 - "GET /api/system-alerts HTTP/1.1" 200 OK
INFO: 192.168.0.119:54735 - "GET /favicon.ico HTTP/1.1" 404 Not Found
INFO: ('192.168.0.119', 54717) - "WebSocket /ws" [accepted]
INFO: ('192.168.0.119', 54802) - "WebSocket /ws" [accepted]
INFO: connection open INFO: connection open
INFO: connection open
INFO: 192.168.0.119:54644 - "GET /api/model-usage-stats HTTP/1.1" 200 OK
INFO: 127.0.0.1:48206 - "GET /api/logs?lines=50 HTTP/1.1" 200 OK
INFO: 127.0.0.1:48234 - "GET /api/model-usage-stats HTTP/1.1" 200 OK
INFO: 127.0.0.1:48218 - "GET /api/performance-stats HTTP/1.1" 200 OK
INFO: 192.168.0.119:54722 - "GET /api/system-alerts HTTP/1.1" 200 OK
INFO: 192.168.0.119:54723 - "GET /api/logs?lines=50 HTTP/1.1" 200 OK
INFO: 192.168.0.119:54643 - "GET /api/performance-stats HTTP/1.1" 200 OK
INFO: 192.168.0.119:54644 - "GET /api/model-usage-stats HTTP/1.1" 200 OK
INFO: 127.0.0.1:48206 - "GET /api/model-usage-stats HTTP/1.1" 200 OK
INFO: 192.168.0.119:54722 - "GET /api/system-alerts HTTP/1.1" 200 OK
INFO: 127.0.0.1:48218 - "GET /api/system-alerts HTTP/1.1" 200 OK
INFO: 127.0.0.1:48234 - "GET /api/logs?lines=50 HTTP/1.1" 200 OK
INFO: 127.0.0.1:42808 - "GET /api/system-alerts HTTP/1.1" 200 OK
INFO: 127.0.0.1:42822 - "GET /favicon.ico HTTP/1.1" 404 Not Found
INFO: 192.168.0.119:54834 - "GET /favicon.ico HTTP/1.1" 404 Not Found
INFO: ('127.0.0.1', 42798) - "WebSocket /ws" [accepted]
INFO: 192.168.0.119:54643 - "GET /api/model-usage-stats HTTP/1.1" 200 OK
INFO: 127.0.0.1:48206 - "GET /api/model-usage-stats HTTP/1.1" 200 OK
INFO: 192.168.0.119:54644 - "GET /api/system-alerts HTTP/1.1" 200 OK
INFO: connection open
INFO: 192.168.0.119:54900 - "GET /api/system-alerts HTTP/1.1" 200 OK
INFO: 127.0.0.1:42344 - "GET /api/performance-stats HTTP/1.1" 200 OK
INFO: 127.0.0.1:48234 - "GET /api/system-alerts HTTP/1.1" 200 OK
INFO: 192.168.0.119:54722 - "GET /api/logs?lines=50 HTTP/1.1" 200 OK
INFO: 127.0.0.1:48218 - "GET /api/system-alerts HTTP/1.1" 200 OK
INFO: connection closed INFO: connection closed
브로드캐스트 오류: list.remove(x): x not in list 브로드캐스트 오류: list.remove(x): x not in list
INFO: ('127.0.0.1', 41220) - "WebSocket /ws" [accepted] INFO: 192.168.0.119:54643 - "GET /api/model-usage-stats HTTP/1.1" 200 OK
INFO: connection open INFO: 127.0.0.1:42822 - "GET /api/model-usage-stats HTTP/1.1" 200 OK
INFO: connection closed INFO: 192.168.0.119:54644 - "GET /api/system-alerts HTTP/1.1" 200 OK
브로드캐스트 오류: list.remove(x): x not in list INFO: 127.0.0.1:48206 - "GET /api/logs?lines=50 HTTP/1.1" 200 OK
INFO: ('127.0.0.1', 43988) - "WebSocket /ws" [accepted] INFO: 127.0.0.1:42808 - "GET /api/system-alerts HTTP/1.1" 200 OK
INFO: connection open INFO: 192.168.0.119:54900 - "GET /api/system-alerts HTTP/1.1" 200 OK
INFO: connection closed INFO: 127.0.0.1:42344 - "GET /api/system-alerts HTTP/1.1" 200 OK
브로드캐스트 오류: list.remove(x): x not in list INFO: 127.0.0.1:48234 - "GET /api/performance-stats HTTP/1.1" 200 OK
INFO: ('127.0.0.1', 50666) - "WebSocket /ws" [accepted] INFO: 192.168.0.119:54722 - "GET /api/model-usage-stats HTTP/1.1" 200 OK
INFO: connection open INFO: 127.0.0.1:48218 - "GET /api/model-usage-stats HTTP/1.1" 200 OK
INFO: connection closed
브로드캐스트 오류: list.remove(x): x not in list
INFO: ('127.0.0.1', 44104) - "WebSocket /ws" [accepted]
INFO: connection open
INFO: connection closed
INFO: ('127.0.0.1', 56574) - "WebSocket /ws" [accepted]
INFO: connection open
INFO: connection closed
브로드캐스트 오류: list.remove(x): x not in list
INFO: ('127.0.0.1', 39494) - "WebSocket /ws" [accepted]
INFO: connection open
INFO: connection closed INFO: connection closed
INFO: ('192.168.0.119', 54983) - "WebSocket /ws" [accepted]
INFO: 192.168.0.119:54643 - "GET /api/logs?lines=50 HTTP/1.1" 200 OK
INFO: 127.0.0.1:42822 - "GET /api/system-alerts HTTP/1.1" 200 OK
INFO: 192.168.0.119:54644 - "GET /api/system-alerts HTTP/1.1" 200 OK
INFO: 127.0.0.1:48206 - "GET /api/system-alerts HTTP/1.1" 200 OK
INFO: 127.0.0.1:42808 - "GET /api/model-usage-stats HTTP/1.1" 200 OK
INFO: connection open
INFO: 192.168.0.119:55051 - "GET /api/system-alerts HTTP/1.1" 200 OK
INFO: 192.168.0.119:55129 - "GET /api/performance-stats HTTP/1.1" 200 OK
INFO: 192.168.0.119:54900 - "GET /api/model-usage-stats HTTP/1.1" 200 OK
INFO: 127.0.0.1:42344 - "GET /api/logs?lines=50 HTTP/1.1" 200 OK
INFO: 192.168.0.119:54722 - "GET /api/system-alerts HTTP/1.1" 200 OK
INFO: ('127.0.0.1', 38388) - "WebSocket /ws" [accepted]
INFO: 127.0.0.1:48218 - "GET /api/performance-stats HTTP/1.1" 200 OK
INFO: 127.0.0.1:48234 - "GET /api/system-alerts HTTP/1.1" 200 OK
INFO: 192.168.0.119:54643 - "GET /api/performance-stats HTTP/1.1" 200 OK
INFO: 127.0.0.1:42822 - "GET /api/model-usage-stats HTTP/1.1" 200 OK
INFO: 192.168.0.119:54644 - "GET /api/system-alerts HTTP/1.1" 200 OK
INFO: 127.0.0.1:48206 - "GET /api/system-alerts HTTP/1.1" 200 OK
INFO: 127.0.0.1:42808 - "GET /api/system-alerts HTTP/1.1" 200 OK
INFO: connection open
INFO: 192.168.0.119:55051 - "GET /api/model-usage-stats HTTP/1.1" 200 OK
INFO: 192.168.0.119:55129 - "GET /api/logs?lines=50 HTTP/1.1" 200 OK
INFO: 192.168.0.119:54900 - "GET /api/system-alerts HTTP/1.1" 200 OK
INFO: 127.0.0.1:42344 - "GET /api/system-alerts HTTP/1.1" 200 OK
INFO: 192.168.0.119:54722 - "GET /api/performance-stats HTTP/1.1" 200 OK
INFO: 127.0.0.1:48218 - "GET /api/model-usage-stats HTTP/1.1" 200 OK
INFO: 127.0.0.1:48234 - "GET /api/logs?lines=50 HTTP/1.1" 200 OK
INFO: 192.168.0.119:54643 - "GET /api/model-usage-stats HTTP/1.1" 200 OK
INFO: 127.0.0.1:42822 - "GET /api/performance-stats HTTP/1.1" 200 OK
INFO: 192.168.0.119:54644 - "GET /api/system-alerts HTTP/1.1" 200 OK
INFO: 127.0.0.1:48206 - "GET /api/model-usage-stats HTTP/1.1" 200 OK
INFO: 127.0.0.1:42808 - "GET /api/system-alerts HTTP/1.1" 200 OK
INFO: 192.168.0.119:55051 - "GET /api/system-alerts HTTP/1.1" 200 OK
INFO: 192.168.0.119:55129 - "GET /api/model-usage-stats HTTP/1.1" 200 OK
INFO: 192.168.0.119:54900 - "GET /api/performance-stats HTTP/1.1" 200 OK
INFO: 127.0.0.1:42344 - "GET /api/logs?lines=50 HTTP/1.1" 200 OK
INFO: 192.168.0.119:54722 - "GET /api/model-usage-stats HTTP/1.1" 200 OK
브로드캐스트 오류: list.remove(x): x not in list 브로드캐스트 오류: list.remove(x): x not in list
INFO: ('192.168.0.119', 55354) - "WebSocket /ws" [accepted]

View File

@ -1 +1 @@
1338258 35583

52
main.py
View File

@ -15,15 +15,53 @@ import uvicorn
from app.core.config import settings from app.core.config import settings
from app.core.worker_manager import worker_manager from app.core.worker_manager import worker_manager
from app.core.session_pool import session_pool from app.core.session_pool import SessionPool, session_pool
from app.api.endpoints import router from app.api.endpoints import router
from app.monitoring.dashboard import monitor_app from app.monitoring.dashboard import monitor_app
# 로깅 설정 # 로깅 설정
logging.basicConfig( import logging.handlers
level=logging.INFO, import os
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s"
# 로그 디렉토리 생성
log_dir = "logs"
os.makedirs(log_dir, exist_ok=True)
# 로그 회전 설정: 최대 10MB, 7일 보관
rotating_handler = logging.handlers.TimedRotatingFileHandler(
filename=os.path.join(log_dir, "main.log"),
when="D", # 일별 회전
interval=1, # 1일마다
backupCount=7, # 7일 보관
encoding="utf-8"
) )
rotating_handler.setFormatter(logging.Formatter(
"%(asctime)s - %(name)s - %(levelname)s - %(message)s"
))
# 파일 크기 제한 핸들러 추가 (10MB)
size_handler = logging.handlers.RotatingFileHandler(
filename=os.path.join(log_dir, "main_size.log"),
maxBytes=10*1024*1024, # 10MB
backupCount=5, # 최대 5개 파일
encoding="utf-8"
)
size_handler.setFormatter(logging.Formatter(
"%(asctime)s - %(name)s - %(levelname)s - %(message)s"
))
# 콘솔 핸들러
console_handler = logging.StreamHandler()
console_handler.setFormatter(logging.Formatter(
"%(asctime)s - %(name)s - %(levelname)s - %(message)s"
))
# 루트 로거 설정
root_logger = logging.getLogger()
root_logger.setLevel(logging.INFO)
root_logger.addHandler(rotating_handler)
root_logger.addHandler(size_handler)
root_logger.addHandler(console_handler)
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -152,7 +190,7 @@ async def save_status_periodically():
with open("status.json", "w") as f: with open("status.json", "w") as f:
json.dump(status, f, indent=2) json.dump(status, f, indent=2)
logger.info(f"상태 저장 완료 #{iteration}: {time.strftime('%H:%M:%S')}") logger.debug(f"상태 저장 완료 #{iteration}: {time.strftime('%H:%M:%S')}")
except Exception as e: except Exception as e:
logger.error(f"상태 저장 실패 #{iteration}: {e}") logger.error(f"상태 저장 실패 #{iteration}: {e}")
@ -178,7 +216,9 @@ async def lifespan(app: FastAPI):
logger.info("✅ 상태 저장 백그라운드 작업 생성 완료") logger.info("✅ 상태 저장 백그라운드 작업 생성 완료")
try: try:
# 세션 풀 초기화 # ONNX Runtime과 RemBG가 자동으로 CUDA 감지
logger.info("🚀 세션 풀 초기화 (CUDA 자동 감지)")
await session_pool.initialize() await session_pool.initialize()
logger.info("✅ 세션 풀 초기화 완료") logger.info("✅ 세션 풀 초기화 완료")

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

View File

@ -2,7 +2,7 @@ fastapi==0.104.1
uvicorn[standard]==0.24.0 uvicorn[standard]==0.24.0
python-multipart==0.0.6 python-multipart==0.0.6
pillow==10.0.1 pillow==10.0.1
numpy==1.24.3 numpy==1.24.4
opencv-python==4.8.1.78 opencv-python==4.8.1.78
pydantic-settings==2.8.1 pydantic-settings==2.8.1
psutil==5.9.6 psutil==5.9.6
@ -10,11 +10,11 @@ requests==2.31.0
# PyTorch - Jetson Xavier (ARM64) 지원 # PyTorch - Jetson Xavier (ARM64) 지원
# Jetson의 경우 torch==2.0.1+nv23.11-torch2.0.0 사용 권장 # Jetson의 경우 torch==2.0.1+nv23.11-torch2.0.0 사용 권장
torch==2.0.1+cu118 #torch==2.0.1+cu118
torchvision==0.15.2+cu118 #torchvision==0.15.2+cu118
# TensorRT 및 CUDA 관련 # TensorRT 및 CUDA 관련
tensorrt==8.6.1 #tensorrt==8.6.1
pycuda==2022.2.2 pycuda==2022.2.2
# 인페인팅 모델들 # 인페인팅 모델들
@ -31,5 +31,7 @@ pynvml==11.5.0
nvidia-ml-py3==7.352.0 nvidia-ml-py3==7.352.0
# 추가 최적화 패키지들 # 추가 최적화 패키지들
onnxruntime-gpu==1.16.3 # ONNX Runtime - Jetson Xavier용 특별 설치 필요 (install_deps.sh 참조)
# onnxruntime-gpu==1.17.0 # Jetson용 휠 파일로 수동 설치
#onnxruntime==1.19.2 # CPU 전용 (Jetson에서는 GPU 휠로 교체됨)
tensorflow-gpu==2.13.0 tensorflow-gpu==2.13.0

271
scripts/install.sh Executable file
View File

@ -0,0 +1,271 @@
#!/bin/bash
# 인페인팅 서버 자동 설치 스크립트
# 플랫폼 자동 감지 및 최적 설치 방법 선택
# Usage: ./install.sh
set -e
# 색상 코드
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
CYAN='\033[0;36m'
MAGENTA='\033[0;35m'
NC='\033[0m' # No Color
# 로고 출력
print_logo() {
echo -e "${CYAN}"
echo " ██║ ███╗ ██╗██████╗ █████╗ ██╗███╗ ██╗████████╗"
echo " ██║ ████╗ ██║██╔══██╗██╔══██╗██║████╗ ██║╚══██╔══╝"
echo " ██║ ██╔██╗ ██║██████╔╝███████║██║██╔██╗ ██║ ██║ "
echo " ██║ ██║╚██╗██║██╔═══╝ ██╔══██║██║██║╚██╗██║ ██║ "
echo " ██║ ██║ ╚████║██║ ██║ ██║██║██║ ╚████║ ██║ "
echo " ╚═╝ ╚═╝ ╚═══╝╚═╝ ╚═╝ ╚═╝╚═╝╚═╝ ╚═══╝ ╚═╝ "
echo ""
echo " 🎨 AI 인페인팅 서버 - 자동 설치 도구"
echo -e "${NC}"
}
# 로그 함수들
log_info() {
echo -e "${BLUE}[INFO]${NC} $1"
}
log_success() {
echo -e "${GREEN}[SUCCESS]${NC} $1"
}
log_warning() {
echo -e "${YELLOW}[WARNING]${NC} $1"
}
log_error() {
echo -e "${RED}[ERROR]${NC} $1"
}
log_step() {
echo -e "${CYAN}[STEP]${NC} $1"
}
log_platform() {
echo -e "${MAGENTA}[PLATFORM]${NC} $1"
}
# 플랫폼 감지
detect_platform() {
local arch=$(uname -m)
local os_info=$(uname -a)
log_step "플랫폼 감지 중..."
if [[ "$arch" == "aarch64" ]] && echo "$os_info" | grep -q "tegra"; then
PLATFORM="jetson"
PLATFORM_NAME="NVIDIA Jetson Xavier"
INSTALL_SCRIPT="setup_jetson.sh"
log_platform "🚀 $PLATFORM_NAME 감지됨"
elif [[ "$arch" == "x86_64" ]] || [[ "$arch" == "amd64" ]]; then
PLATFORM="x86"
PLATFORM_NAME="x86-64 Desktop"
INSTALL_SCRIPT="setup_x86.sh"
log_platform "🖥️ $PLATFORM_NAME 감지됨"
elif [[ "$arch" == "aarch64" ]]; then
PLATFORM="arm64"
PLATFORM_NAME="ARM64 (Generic)"
INSTALL_SCRIPT="setup_jetson.sh"
log_platform "💻 $PLATFORM_NAME 감지됨 (Jetson 설치 방법 사용)"
else
log_error "지원하지 않는 아키텍처: $arch"
echo "지원 플랫폼:"
echo " - x86-64 (Intel/AMD)"
echo " - ARM64 (NVIDIA Jetson Xavier)"
exit 1
fi
echo "감지된 정보:"
echo " 아키텍처: $arch"
echo " 플랫폼: $PLATFORM_NAME"
echo " 설치 스크립트: $INSTALL_SCRIPT"
echo ""
}
# GPU 정보 표시
show_gpu_info() {
log_step "GPU 정보 확인 중..."
if command -v nvidia-smi >/dev/null 2>&1; then
echo "=== NVIDIA GPU 정보 ==="
nvidia-smi --query-gpu=name,driver_version,memory.total --format=csv,noheader 2>/dev/null | while read line; do
echo " GPU: $line"
done
echo ""
else
log_warning "NVIDIA GPU 또는 드라이버가 설치되지 않았습니다."
echo ""
fi
}
# 시스템 요구사항 확인
check_requirements() {
log_step "시스템 요구사항 확인 중..."
local errors=0
# Python 3.8+ 확인
if command -v python3 >/dev/null 2>&1; then
local python_version=$(python3 --version 2>&1 | awk '{print $2}')
if python3 -c "import sys; exit(0 if sys.version_info >= (3, 8) else 1)" 2>/dev/null; then
log_success "Python $python_version 확인됨"
else
log_error "Python 3.8 이상이 필요합니다. 현재: $python_version"
errors=$((errors + 1))
fi
else
log_error "Python3가 설치되지 않았습니다."
errors=$((errors + 1))
fi
# Git 확인
if command -v git >/dev/null 2>&1; then
log_success "Git 확인됨"
else
log_warning "Git이 설치되지 않았습니다. 설치 과정에서 자동으로 설치됩니다."
fi
# 메모리 확인
if [ "$PLATFORM" = "jetson" ]; then
local total_mem=$(free -g | grep '^Mem:' | awk '{print $2}')
if [ "$total_mem" -ge 16 ]; then
log_success "메모리 ${total_mem}GB 확인됨 (Jetson 권장: 16GB+)"
else
log_warning "메모리 ${total_mem}GB (권장: 16GB+)"
fi
else
local total_mem=$(free -g | grep '^Mem:' | awk '{print $2}')
if [ "$total_mem" -ge 8 ]; then
log_success "메모리 ${total_mem}GB 확인됨"
else
log_warning "메모리 ${total_mem}GB (권장: 8GB+)"
fi
fi
# 디스크 공간 확인
local free_space=$(df -BG . | tail -1 | awk '{print $4}' | sed 's/G//')
if [ "$free_space" -ge 10 ]; then
log_success "디스크 여유 공간 ${free_space}GB 확인됨"
else
log_warning "디스크 여유 공간 ${free_space}GB (권장: 10GB+)"
fi
if [ $errors -gt 0 ]; then
log_error "필수 요구사항이 충족되지 않았습니다. 위 문제를 해결한 후 다시 시도하세요."
exit 1
fi
echo ""
}
# 설치 옵션 표시
show_installation_options() {
echo "=== 설치 옵션 ==="
echo ""
if [ "$PLATFORM" = "jetson" ]; then
echo "🚀 Jetson Xavier 최적화 설정:"
echo " - ONNX Runtime GPU (ARM64 특화)"
echo " - 32GB 통합 메모리 최적화"
echo " - CUDA + TensorRT 가속"
echo " - 세션 풀: LAMA(4), MIGAN(4), REMBG(3)"
echo " - 워커: 8개 (자동 조정)"
else
echo "🖥️ x86-64 데스크톱 설정:"
echo " - ONNX Runtime GPU (x86-64)"
echo " - RTX GPU 최적화"
echo " - CUDA + TensorRT 가속"
echo " - 세션 풀: LAMA(3), MIGAN(3), REMBG(2)"
echo " - 워커: 4-8개 (GPU 메모리에 따라)"
fi
echo ""
}
# 사용자 확인
confirm_installation() {
echo "=== 설치 확인 ==="
echo "다음 설정으로 설치를 진행합니다:"
echo " 플랫폼: $PLATFORM_NAME"
echo " 설치 스크립트: $INSTALL_SCRIPT"
echo ""
read -p "계속 진행하시겠습니까? (y/N): " -r
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
echo "설치가 취소되었습니다."
exit 0
fi
echo ""
}
# 메인 실행
main() {
clear
print_logo
# 스크립트 디렉토리로 이동
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(dirname "$SCRIPT_DIR")"
cd "$SCRIPT_DIR"
detect_platform
show_gpu_info
check_requirements
show_installation_options
confirm_installation
# 플랫폼별 설치 스크립트 실행
log_step "플랫폼별 설치 스크립트 실행 중..."
if [ ! -f "$INSTALL_SCRIPT" ]; then
log_error "설치 스크립트를 찾을 수 없습니다: $INSTALL_SCRIPT"
exit 1
fi
chmod +x "$INSTALL_SCRIPT"
bash "$INSTALL_SCRIPT"
# 설치 완료
echo ""
echo "=========================================="
log_success "🎉 인페인팅 서버 설치 완료!"
echo "=========================================="
echo ""
echo "다음 단계:"
echo " 1. 서버 시작: bash scripts/start_server.sh"
echo " 2. 서버 상태 확인: bash scripts/status.sh"
echo " 3. 서버 중지: bash scripts/stop_server.sh"
echo ""
echo "접속 주소:"
echo " 🖥️ 모니터링 대시보드: http://localhost:8009"
echo " 🔗 API 엔드포인트: http://localhost:8008"
echo " 📊 헬스체크: curl http://localhost:8008/api/v1/health"
echo ""
if [ "$PLATFORM" = "jetson" ]; then
echo "Jetson Xavier 참고사항:"
echo " - tegrastats로 시스템 상태 모니터링 가능"
echo " - 32GB 통합 메모리로 다중 세션 최적화됨"
echo " - GPU 온도/클럭 정보가 모니터링 대시보드에 표시됨"
else
echo "x86-64 참고사항:"
echo " - nvidia-smi로 GPU 상태 확인 가능"
echo " - GPU 메모리에 따라 세션 수 자동 조정됨"
echo " - TensorRT 사용 시 최고 성능"
fi
echo ""
}
# 스크립트 실행
main "$@"

View File

@ -525,8 +525,63 @@ install_gpu_libraries() {
if [ "$SYSTEM_TYPE" = "jetson" ]; then if [ "$SYSTEM_TYPE" = "jetson" ]; then
log_info "Jetson 전용 GPU 라이브러리 설치 중..." log_info "Jetson 전용 GPU 라이브러리 설치 중..."
# Jetson 최적화 라이브러리들 # GCC 버전 확인 및 업그레이드 (ONNX Runtime GPU용)
pip install onnxruntime-gpu || pip install onnxruntime log_info "GCC 버전 확인 중..."
if ! gcc-11 --version &> /dev/null; then
log_warning "GCC-11이 설치되지 않았습니다. ONNX Runtime GPU 설치를 위해 설치 중..."
# GCC 11 설치
sudo apt install -y software-properties-common
sudo add-apt-repository -y ppa:ubuntu-toolchain-r/test
sudo apt update
sudo apt install -y gcc-11 g++-11
log_success "GCC-11 설치 완료"
else
log_info "GCC-11이 이미 설치되어 있습니다"
fi
# Jetson Xavier용 ONNX Runtime GPU 설치
log_info "Jetson Xavier용 ONNX Runtime GPU 설치 중..."
# 기존 onnxruntime 제거
pip uninstall -y onnxruntime onnxruntime-gpu 2>/dev/null || true
# ONNX Runtime GPU 휠 파일 다운로드
ONNX_WHEEL_NAME="onnxruntime_gpu-1.17.0-cp38-cp38-linux_aarch64.whl"
if [ ! -f "$PROJECT_ROOT/$ONNX_WHEEL_NAME" ]; then
log_info "ONNX Runtime GPU 휠 파일 다운로드 중..."
cd "$PROJECT_ROOT"
wget https://nvidia.box.com/shared/static/zostg6agm00fb6t5uisw51qi6kpcuwzd.whl \
-O "$ONNX_WHEEL_NAME"
else
log_info "ONNX Runtime GPU 휠 파일이 이미 존재합니다"
fi
# ONNX Runtime GPU 설치
log_info "ONNX Runtime GPU 설치 중..."
pip install --force-reinstall "$PROJECT_ROOT/$ONNX_WHEEL_NAME"
# 설치 확인
if python -c "import onnxruntime as ort; print('Available providers:', ort.get_available_providers())" 2>/dev/null; then
log_success "ONNX Runtime GPU 설치 및 확인 완료"
python -c "
import onnxruntime as ort
providers = ort.get_available_providers()
print('ONNX Runtime 버전:', ort.__version__)
print('사용 가능한 프로바이더:')
for provider in providers:
print(' -', provider)
if 'TensorrtExecutionProvider' in providers:
print('✅ TensorRT 프로바이더 사용 가능')
if 'CUDAExecutionProvider' in providers:
print('✅ CUDA 프로바이더 사용 가능')
"
else
log_error "ONNX Runtime GPU 설치 실패"
log_info "CPU 버전으로 폴백 설치 중..."
pip install onnxruntime==1.19.2
fi
# Jetson 전용 패키지들 # Jetson 전용 패키지들
pip install jetson-stats || log_warning "jetson-stats 설치 실패" pip install jetson-stats || log_warning "jetson-stats 설치 실패"
@ -534,6 +589,13 @@ install_gpu_libraries() {
else else
log_info "x86용 GPU 라이브러리 설치 중..." log_info "x86용 GPU 라이브러리 설치 중..."
# x86용 ONNX Runtime GPU 설치
log_info "x86용 ONNX Runtime GPU 설치 중..."
pip install onnxruntime-gpu || {
log_warning "ONNX Runtime GPU 설치 실패, CPU 버전으로 폴백"
pip install onnxruntime==1.19.2
}
# 추가 GPU 가속 라이브러리 # 추가 GPU 가속 라이브러리
if [ "$INSTALL_EXTRAS" = true ]; then if [ "$INSTALL_EXTRAS" = true ]; then
pip install cupy-cuda118 || { pip install cupy-cuda118 || {

View File

@ -1,326 +0,0 @@
#!/bin/bash
# 인페인팅 서버 자동 설치 및 실행 스크립트
# 다른 머신에서 git clone 후 바로 실행할 수 있도록 합니다.
set -e
# 색상 코드
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# 로그 함수들
log_info() {
echo -e "${BLUE}[INFO]${NC} $1"
}
log_success() {
echo -e "${GREEN}[SUCCESS]${NC} $1"
}
log_warning() {
echo -e "${YELLOW}[WARNING]${NC} $1"
}
log_error() {
echo -e "${RED}[ERROR]${NC} $1"
}
# 도움말 표시
show_help() {
echo "인페인팅 서버 자동 설치 및 실행 스크립트"
echo ""
echo "사용법: $0 [옵션]"
echo ""
echo "옵션:"
echo " --jetson Jetson Xavier 최적화 설치"
echo " --x86 x86_64 시스템용 설치"
echo " --no-venv 기존 가상환경 사용 (새로 생성하지 않음)"
echo " --no-models 모델 파일 다운로드 건너뛰기"
echo " --no-start 설치만 하고 서버 시작하지 않음"
echo " -h, --help 이 도움말 표시"
echo ""
echo "예시:"
echo " $0 # 시스템 자동 감지하여 설치 및 실행"
echo " $0 --jetson # Jetson Xavier 최적화 설치"
echo " $0 --x86 # x86_64 시스템용 설치"
}
# 기본 설정
JETSON_MODE=false
X86_MODE=false
CREATE_VENV=true
DOWNLOAD_MODELS=true
START_SERVER=true
PROJECT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
# 명령줄 인자 처리
while [[ $# -gt 0 ]]; do
case $1 in
--jetson)
JETSON_MODE=true
shift
;;
--x86)
X86_MODE=true
shift
;;
--no-venv)
CREATE_VENV=false
shift
;;
--no-models)
DOWNLOAD_MODELS=false
shift
;;
--no-start)
START_SERVER=false
shift
;;
-h|--help)
show_help
exit 0
;;
*)
log_error "알 수 없는 옵션: $1"
show_help
exit 1
;;
esac
done
# 시스템 자동 감지
detect_system() {
if [ "$JETSON_MODE" = true ]; then
SYSTEM_TYPE="jetson"
log_info "🚁 Jetson Xavier 모드로 설정됨"
elif [ "$X86_MODE" = true ]; then
SYSTEM_TYPE="x86"
log_info "🖥️ x86_64 모드로 설정됨"
else
# 자동 감지
if [ "$(uname -m)" = "aarch64" ] && uname -a | grep -q "tegra"; then
SYSTEM_TYPE="jetson"
log_info "🚁 Jetson Xavier (ARM64) 시스템 자동 감지"
elif [ "$(uname -m)" = "x86_64" ]; then
SYSTEM_TYPE="x86"
log_info "🖥️ x86_64 시스템 자동 감지"
else
log_error "지원되지 않는 시스템 아키텍처: $(uname -m)"
exit 1
fi
fi
}
# Python 버전 확인
check_python() {
log_info "Python 환경 확인 중..."
if ! command -v python3 &> /dev/null; then
log_error "Python3가 설치되지 않았습니다"
log_info "Ubuntu/Debian: sudo apt install python3 python3-pip python3-venv"
exit 1
fi
PYTHON_VERSION=$(python3 --version | cut -d' ' -f2)
log_info "Python 버전: $PYTHON_VERSION"
# Python 3.8 이상 확인
if ! python3 -c "import sys; exit(0 if sys.version_info >= (3, 8) else 1)"; then
log_error "Python 3.8 이상이 필요합니다. 현재 버전: $PYTHON_VERSION"
exit 1
fi
log_success "Python 환경 확인 완료"
}
# 가상환경 설정
setup_venv() {
if [ "$CREATE_VENV" = false ]; then
log_info "기존 가상환경 사용"
return 0
fi
log_info "가상환경 설정 중..."
cd "$PROJECT_ROOT"
# 기존 가상환경 제거 (있다면)
if [ -d "venv" ]; then
log_warning "기존 가상환경 제거 중..."
rm -rf venv
fi
# 새 가상환경 생성
python3 -m venv venv
# 가상환경 활성화
source venv/bin/activate
# pip 업그레이드
pip install --upgrade pip
log_success "가상환경 설정 완료"
}
# 의존성 설치
install_dependencies() {
log_info "의존성 설치 중..."
cd "$PROJECT_ROOT"
# 가상환경 활성화 확인
if [ "$CREATE_VENV" = true ]; then
source venv/bin/activate
fi
# requirements.txt 확인
if [ "$SYSTEM_TYPE" = "x86" ]; then
REQ_FILE="requirements_x86.txt"
else
REQ_FILE="requirements.txt"
fi
if [ ! -f "$REQ_FILE" ]; then
log_error "$REQ_FILE 파일을 찾을 수 없습니다"
exit 1
fi
# 시스템별 최적화 설치
if [ "$SYSTEM_TYPE" = "jetson" ]; then
log_info "Jetson Xavier 최적화 패키지 설치 중..."
# Jetson 전용 패키지가 있다면 여기에 추가
fi
# 기본 패키지 설치
pip install -r "$REQ_FILE"
log_success "의존성 설치 완료"
}
# 모델 파일 확인 및 다운로드
setup_models() {
if [ "$DOWNLOAD_MODELS" = false ]; then
log_info "모델 파일 다운로드 건너뛰기"
return 0
fi
log_info "모델 파일 확인 중..."
cd "$PROJECT_ROOT"
# 모델 디렉토리 생성
mkdir -p app/models/onnx
mkdir -p app/models/pt
# 필요한 모델 파일들 확인
MODEL_FILES_MISSING=false
if [ ! -f "app/models/onnx/migan_pipeline_v2.onnx" ]; then
log_warning "MIGAN 모델 파일이 없습니다: app/models/onnx/migan_pipeline_v2.onnx"
MODEL_FILES_MISSING=true
fi
if [ ! -f "app/models/pt/big-lama.pt" ]; then
log_warning "LAMA 모델 파일이 없습니다: app/models/pt/big-lama.pt"
MODEL_FILES_MISSING=true
fi
if [ "$MODEL_FILES_MISSING" = true ]; then
log_warning "일부 모델 파일이 없습니다"
log_info "모델 파일들을 수동으로 다운로드하여 다음 위치에 배치하세요:"
log_info " - MIGAN: app/models/onnx/migan_pipeline_v2.onnx"
log_info " - LAMA: app/models/pt/big-lama.pt"
log_info "서버는 사용 가능한 모델로만 동작합니다"
else
log_success "모든 모델 파일 확인 완료"
fi
}
# 환경 설정
setup_environment() {
log_info "환경 설정 중..."
cd "$PROJECT_ROOT"
# .env 파일 생성 (없다면)
if [ ! -f ".env" ] && [ -f ".env.example" ]; then
cp .env.example .env
log_info ".env 파일 생성됨"
fi
log_success "환경 설정 완료"
}
# 서버 시작
start_server() {
if [ "$START_SERVER" = false ]; then
log_info "서버 시작 건너뛰기"
return 0
fi
log_info "서버 시작 중..."
cd "$PROJECT_ROOT"
# start_server.sh 실행 권한 확인
if [ ! -x "scripts/start_server.sh" ]; then
chmod +x scripts/start_server.sh
fi
# 시스템별 최적화 옵션
if [ "$SYSTEM_TYPE" = "jetson" ]; then
./scripts/start_server.sh --jetson-optimize
else
./scripts/start_server.sh
fi
}
# 설치 완료 정보 출력
print_completion_info() {
echo ""
echo "=========================================="
echo "🎉 인페인팅 서버 설치 완료!"
echo "=========================================="
echo "시스템 타입: $SYSTEM_TYPE"
echo "프로젝트 경로: $PROJECT_ROOT"
if [ "$START_SERVER" = true ]; then
echo ""
echo "🚀 서버 접속 정보:"
echo " - 메인 API 서버: http://localhost:8008"
echo " - API 문서: http://localhost:8008/docs"
echo " - 모니터링 대시보드: http://localhost:8888"
echo " - 헬스 체크: http://localhost:8008/health"
else
echo ""
echo "서버를 시작하려면 다음 명령을 실행하세요:"
echo " cd $PROJECT_ROOT"
echo " ./scripts/start_server.sh"
fi
echo ""
echo "🛠️ 유용한 명령어:"
echo " - 서버 중지: ./scripts/stop_server.sh"
echo " - 서버 상태: ./scripts/status.sh"
echo " - API 테스트: python tests/scripts/test_api.py"
echo ""
echo "📚 더 많은 정보는 README.md를 참고하세요"
echo "=========================================="
}
# 메인 실행 함수
main() {
echo "🚀 인페인팅 서버 자동 설치 시작"
echo "=========================================="
detect_system
check_python
setup_venv
install_dependencies
setup_models
setup_environment
start_server
print_completion_info
}
# 스크립트 실행
main "$@"

226
scripts/setup_jetson.sh Executable file
View File

@ -0,0 +1,226 @@
#!/bin/bash
# Jetson Xavier 전용 인페인팅 서버 설치 스크립트
# ARM64 아키텍처 + NVIDIA Jetson Xavier + 32GB 통합 메모리 최적화
# Usage: ./setup_jetson.sh
set -e
# 색상 코드
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
CYAN='\033[0;36m'
NC='\033[0m' # No Color
# 로그 함수들
log_info() {
echo -e "${BLUE}[JETSON]${NC} $1"
}
log_success() {
echo -e "${GREEN}[SUCCESS]${NC} $1"
}
log_warning() {
echo -e "${YELLOW}[WARNING]${NC} $1"
}
log_error() {
echo -e "${RED}[ERROR]${NC} $1"
}
log_step() {
echo -e "${CYAN}[STEP]${NC} $1"
}
# 스크립트 시작
clear
echo "======================================"
echo "🚀 Jetson Xavier 인페인팅 서버 설치"
echo "======================================"
echo ""
# 1단계: 시스템 확인
log_step "1단계: Jetson Xavier 시스템 확인"
# ARM64 아키텍처 확인
if [ "$(uname -m)" != "aarch64" ]; then
log_error "이 스크립트는 Jetson Xavier (ARM64)용입니다. x86 시스템은 setup_x86.sh를 사용하세요."
exit 1
fi
# Jetson 확인
if [ ! -f "/etc/nv_tegra_release" ]; then
log_warning "Jetson 시스템이 아닌 것 같습니다. 계속 진행하시겠습니까? (y/N)"
read -r response
if [[ ! "$response" =~ ^[Yy]$ ]]; then
exit 1
fi
fi
log_success "Jetson Xavier ARM64 시스템 확인됨"
# 2단계: 가상환경 설정
log_step "2단계: Python 가상환경 설정"
# 프로젝트 루트로 이동
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(dirname "$SCRIPT_DIR")"
cd "$PROJECT_ROOT"
# 가상환경 생성 함수
create_venv() {
local venv_path="$1"
if [ ! -d "$venv_path" ]; then
log_info "가상환경 생성 중: $venv_path"
python3 -m venv "$venv_path"
log_success "가상환경 생성 완료: $venv_path"
else
log_info "기존 가상환경 발견: $venv_path"
fi
}
# venv 또는 현재 디렉토리에 가상환경 확인/생성
if [ -d "venv" ]; then
VENV_PATH="venv"
log_info "기존 venv 가상환경 사용"
elif [ -f "pyvenv.cfg" ]; then
VENV_PATH="."
log_info "프로젝트 자체가 가상환경으로 설정됨"
else
VENV_PATH="venv"
log_info "새로운 venv 가상환경 생성"
create_venv "$VENV_PATH"
fi
# 가상환경 활성화
if [ "$VENV_PATH" = "." ]; then
# 현재 디렉토리가 가상환경인 경우
if [ -z "$VIRTUAL_ENV" ]; then
log_info "프로젝트 디렉토리 가상환경 활성화"
source bin/activate 2>/dev/null || {
log_error "가상환경 활성화 실패. venv를 새로 생성합니다."
VENV_PATH="venv"
create_venv "$VENV_PATH"
source "$VENV_PATH/bin/activate"
}
fi
else
source "$VENV_PATH/bin/activate"
fi
log_success "가상환경 활성화: $VIRTUAL_ENV"
# 3단계: 시스템 패키지 업데이트
log_step "3단계: 시스템 패키지 업데이트"
sudo apt update && sudo apt upgrade -y
# 4단계: 필수 도구 설치
log_step "4단계: Jetson 개발 도구 설치"
sudo apt install -y \
python3-dev \
python3-pip \
python3-venv \
build-essential \
cmake \
git \
curl \
wget \
unzip \
pkg-config \
libssl-dev \
libffi-dev \
libjpeg-dev \
libpng-dev \
libtiff-dev \
libopencv-dev \
nvidia-jetpack
# 5단계: GCC 11 설치 (ONNX Runtime GPU 호환성)
log_step "5단계: GCC 11 설치 (ONNX Runtime GPU 필수)"
sudo apt install -y gcc-11 g++-11
sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-11 100
sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-11 100
# 6단계: Python 패키지 업그레이드
log_step "6단계: Python 패키지 매니저 업그레이드"
pip install --upgrade pip setuptools wheel
# 7단계: Jetson 특화 ONNX Runtime GPU 설치
log_step "7단계: Jetson ONNX Runtime GPU 설치"
log_info "Jetson Xavier용 ONNX Runtime GPU 다운로드 중..."
# 기존 onnxruntime 제거
pip uninstall -y onnxruntime onnxruntime-gpu 2>/dev/null || true
# Jetson Xavier 전용 ONNX Runtime GPU 설치
ONNX_WHEEL_URL="https://nvidia.box.com/shared/static/zostg6agm00fb6t5uisw51qi6kpcuwzd.whl"
ONNX_WHEEL_FILE="onnxruntime_gpu-1.17.0-cp38-cp38-linux_aarch64.whl"
wget -O "$ONNX_WHEEL_FILE" "$ONNX_WHEEL_URL"
pip install --force-reinstall "$ONNX_WHEEL_FILE"
rm -f "$ONNX_WHEEL_FILE"
log_success "Jetson ONNX Runtime GPU 설치 완료"
# 8단계: PyTorch 설치 (Jetson 최적화)
log_step "8단계: Jetson 최적화 PyTorch 설치"
pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118
# 9단계: 프로젝트 의존성 설치
log_step "9단계: 프로젝트 의존성 설치"
pip install -r requirements.txt
# 10단계: numpy 버전 조정 (호환성)
log_step "10단계: 호환성 패키지 조정"
pip install numpy==1.23.5
# 11단계: 설치 검증
log_step "11단계: 설치 검증"
echo "=== Python 환경 ==="
python --version
echo "가상환경: $VIRTUAL_ENV"
echo -e "\n=== ONNX Runtime 확인 ==="
python -c "
import onnxruntime as ort
print(f'ONNX Runtime 버전: {ort.__version__}')
providers = ort.get_available_providers()
print(f'사용 가능한 providers: {providers}')
if 'CUDAExecutionProvider' in providers:
print('✅ CUDA 지원 확인됨')
if 'TensorrtExecutionProvider' in providers:
print('✅ TensorRT 지원 확인됨')
" || log_warning "ONNX Runtime 확인 실패"
echo -e "\n=== PyTorch CUDA 확인 ==="
python -c "
import torch
print(f'PyTorch 버전: {torch.__version__}')
print(f'CUDA 사용 가능: {torch.cuda.is_available()}')
if torch.cuda.is_available():
print(f'CUDA 버전: {torch.version.cuda}')
print(f'GPU 개수: {torch.cuda.device_count()}')
" || log_warning "PyTorch 확인 실패"
echo -e "\n=== Jetson 시스템 정보 ==="
tegrastats --verbose 2>/dev/null | head -1 || echo "tegrastats 정보 없음"
# 완료
echo ""
echo "======================================"
log_success "🎉 Jetson Xavier 설치 완료!"
echo "======================================"
echo ""
echo "다음 명령으로 서버를 시작하세요:"
echo " bash scripts/start_server.sh"
echo ""
echo "모니터링 대시보드:"
echo " http://localhost:8009"
echo ""
echo "API 테스트:"
echo " curl http://localhost:8008/api/v1/health"
echo ""

View File

@ -1,7 +1,7 @@
#!/bin/bash #!/bin/bash
# x86 시스템용 인페인팅 서버 자동 설치 스크립트 # x86-64 시스템용 인페인팅 서버 설치 스크립트
# RTX 3060 12GB 등 x86 GPU를 지원합니다. # RTX 3060 12GB, RTX 4080 등 x86 GPU 최적화
# Usage: ./setup_x86.sh # Usage: ./setup_x86.sh
set -e set -e
@ -11,11 +11,12 @@ RED='\033[0;31m'
GREEN='\033[0;32m' GREEN='\033[0;32m'
YELLOW='\033[1;33m' YELLOW='\033[1;33m'
BLUE='\033[0;34m' BLUE='\033[0;34m'
CYAN='\033[0;36m'
NC='\033[0m' # No Color NC='\033[0m' # No Color
# 로그 함수들 # 로그 함수들
log_info() { log_info() {
echo -e "${BLUE}[INFO]${NC} $1" echo -e "${BLUE}[X86]${NC} $1"
} }
log_success() { log_success() {
@ -30,120 +31,178 @@ log_error() {
echo -e "${RED}[ERROR]${NC} $1" echo -e "${RED}[ERROR]${NC} $1"
} }
# 기본 설정 log_step() {
PROJECT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" echo -e "${CYAN}[STEP]${NC} $1"
VENV_PATH="$PROJECT_ROOT/venv" }
REQUIREMENTS_FILE="$PROJECT_ROOT/requirements_x86.txt"
log_info "🚀 x86 시스템용 인페인팅 서버 자동 설치 시작" # 스크립트 시작
echo "==========================================" clear
echo "======================================"
echo "🖥️ x86-64 인페인팅 서버 설치"
echo "======================================"
echo ""
# 시스템 감지 # 1단계: 시스템 확인
if [ "$(uname -m)" = "x86_64" ]; then log_step "1단계: x86-64 시스템 확인"
log_info "🖥️ x86_64 시스템 자동 감지"
# x86-64 아키텍처 확인
ARCH=$(uname -m)
if [[ "$ARCH" != "x86_64" && "$ARCH" != "amd64" ]]; then
log_error "이 스크립트는 x86-64용입니다. Jetson Xavier는 setup_jetson.sh를 사용하세요."
exit 1
fi
log_success "x86-64 시스템 확인됨"
# 2단계: 가상환경 설정
log_step "2단계: Python 가상환경 설정"
# 프로젝트 루트로 이동
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(dirname "$SCRIPT_DIR")"
cd "$PROJECT_ROOT"
# 가상환경 생성 함수
create_venv() {
local venv_path="$1"
if [ ! -d "$venv_path" ]; then
log_info "가상환경 생성 중: $venv_path"
python3 -m venv "$venv_path"
log_success "가상환경 생성 완료: $venv_path"
else
log_info "기존 가상환경 발견: $venv_path"
fi
}
# venv 또는 현재 디렉토리에 가상환경 확인/생성
if [ -d "venv" ]; then
VENV_PATH="venv"
log_info "기존 venv 가상환경 사용"
elif [ -f "pyvenv.cfg" ]; then
VENV_PATH="."
log_info "프로젝트 자체가 가상환경으로 설정됨"
else else
log_error "❌ x86_64 시스템이 아닙니다. 이 스크립트는 x86 시스템에서만 실행해야 합니다." VENV_PATH="venv"
exit 1 log_info "새로운 venv 가상환경 생성"
fi create_venv "$VENV_PATH"
# Python 환경 확인
log_info "Python 환경 확인 중..."
if ! command -v python3 &> /dev/null; then
log_error "❌ Python3가 설치되지 않았습니다."
exit 1
fi
PYTHON_VERSION=$(python3 --version 2>&1 | awk '{print $2}')
log_info "Python 버전: $PYTHON_VERSION"
# Python 3.10 이상 확인
if ! python3 -c "import sys; exit(0 if sys.version_info >= (3, 10) else 1)" 2>/dev/null; then
log_error "❌ Python 3.10 이상이 필요합니다. 현재 버전: $PYTHON_VERSION"
exit 1
fi
log_success "Python 환경 확인 완료"
# 가상환경 설정
log_info "가상환경 설정 중..."
if [ ! -d "$VENV_PATH" ]; then
log_info "새로운 가상환경을 생성합니다..."
python3 -m venv "$VENV_PATH"
fi fi
# 가상환경 활성화 # 가상환경 활성화
source "$VENV_PATH/bin/activate" if [ "$VENV_PATH" = "." ]; then
# 현재 디렉토리가 가상환경인 경우
# pip 업그레이드 if [ -z "$VIRTUAL_ENV" ]; then
log_info "pip 업그레이드 중..." log_info "프로젝트 디렉토리 가상환경 활성화"
pip install --upgrade pip source bin/activate 2>/dev/null || {
log_error "가상환경 활성화 실패. venv를 새로 생성합니다."
log_success "가상환경 설정 완료" VENV_PATH="venv"
create_venv "$VENV_PATH"
# 의존성 설치 source "$VENV_PATH/bin/activate"
log_info "의존성 설치 중..." }
if [ ! -f "$REQUIREMENTS_FILE" ]; then fi
log_error "❌ requirements_x86.txt 파일을 찾을 수 없습니다." else
exit 1 source "$VENV_PATH/bin/activate"
fi fi
# PyTorch 먼저 설치 (CUDA 11.8) log_success "가상환경 활성화: $VIRTUAL_ENV"
log_info "PyTorch 설치 중 (CUDA 11.8)..."
pip install torch==2.0.1+cu118 torchvision==0.15.2+cu118 --index-url https://download.pytorch.org/whl/cu118
# TensorFlow 설치 (Python 3.10 호환) # 3단계: 시스템 패키지 업데이트 (Ubuntu/Debian)
log_info "TensorFlow 설치 중 (Python 3.10 호환)..." log_step "3단계: 시스템 패키지 업데이트"
pip install tensorflow-gpu==2.12.0 if command -v apt >/dev/null 2>&1; then
sudo apt update && sudo apt upgrade -y
sudo apt install -y \
python3-dev \
python3-pip \
python3-venv \
build-essential \
cmake \
git \
curl \
wget \
unzip \
pkg-config \
libssl-dev \
libffi-dev \
libjpeg-dev \
libpng-dev \
libtiff-dev \
libopencv-dev
elif command -v yum >/dev/null 2>&1; then
sudo yum update -y
sudo yum groupinstall -y "Development Tools"
sudo yum install -y python3-devel python3-pip cmake git curl wget unzip
else
log_warning "패키지 매니저를 찾을 수 없습니다. 수동으로 개발 도구를 설치해주세요."
fi
# 나머지 의존성 설치 # 4단계: Python 패키지 업그레이드
log_info "기타 의존성 설치 중..." log_step "4단계: Python 패키지 매니저 업그레이드"
pip install -r "$REQUIREMENTS_FILE" pip install --upgrade pip setuptools wheel
log_success "의존성 설치 완료" # 5단계: x86 ONNX Runtime GPU 설치
log_step "5단계: x86 ONNX Runtime GPU 설치"
log_info "x86-64용 ONNX Runtime GPU 설치 중..."
# 모델 다운로드 # 기존 onnxruntime 제거
log_info "AI 모델 다운로드 중..." pip uninstall -y onnxruntime onnxruntime-gpu 2>/dev/null || true
cd "$PROJECT_ROOT"
# 모델 디렉토리 생성 # x86-64 ONNX Runtime GPU 설치
mkdir -p models/simple-lama models/migan models/rembg pip install onnxruntime-gpu
# Simple LAMA 모델 다운로드 log_success "x86 ONNX Runtime GPU 설치 완료"
#if [ ! -f "models/simple-lama/big-lama.pt" ]; then
# log_info "Simple LAMA 모델 다운로드 중..."
# wget -O models/simple-lama/big-lama.pt https://github.com/Saafke/Simple-LAMA/releases/download/v1.0/big-lama.pt
#fi
# MIGAN 모델 다운로드 # 6단계: PyTorch 설치 (x86 CUDA)
#if [ ! -f "models/migan/migan.pt" ]; then log_step "6단계: x86 CUDA PyTorch 설치"
# log_info "MIGAN 모델 다운로드 중..." pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118
# wget -O models/migan/migan.pt https://github.com/open-mmlab/mmediting/releases/download/v1.0.0/migan_256x256_celeba-hq_20220629-3b7c8c9f.pth
#fi
#log_success "모델 다운로드 완료" # 7단계: 프로젝트 의존성 설치
log_step "7단계: 프로젝트 의존성 설치"
pip install -r requirements.txt
# 권한 설정 # 8단계: 설치 검증
log_info "권한 설정 중..." log_step "8단계: 설치 검증"
chmod +x scripts/*.sh
log_success "권한 설정 완료" echo "=== Python 환경 ==="
python --version
echo "가상환경: $VIRTUAL_ENV"
# 설치 완료 echo -e "\n=== ONNX Runtime 확인 ==="
echo "==========================================" python -c "
log_success "🎉 x86 시스템용 인페인팅 서버 설치가 완료되었습니다!" import onnxruntime as ort
print(f'ONNX Runtime 버전: {ort.__version__}')
providers = ort.get_available_providers()
print(f'사용 가능한 providers: {providers}')
if 'CUDAExecutionProvider' in providers:
print('✅ CUDA 지원 확인됨')
if 'TensorrtExecutionProvider' in providers:
print('✅ TensorRT 지원 확인됨')
" || log_warning "ONNX Runtime 확인 실패"
echo -e "\n=== PyTorch CUDA 확인 ==="
python -c "
import torch
print(f'PyTorch 버전: {torch.__version__}')
print(f'CUDA 사용 가능: {torch.cuda.is_available()}')
if torch.cuda.is_available():
print(f'CUDA 버전: {torch.version.cuda}')
print(f'GPU 개수: {torch.cuda.device_count()}')
for i in range(torch.cuda.device_count()):
print(f'GPU {i}: {torch.cuda.get_device_name(i)}')
" || log_warning "PyTorch 확인 실패"
echo -e "\n=== NVIDIA GPU 확인 ==="
nvidia-smi 2>/dev/null || log_warning "nvidia-smi 정보 없음"
# 완료
echo "" echo ""
log_info "다음 명령어로 서버를 시작할 수 있습니다:" echo "======================================"
echo " cd $PROJECT_ROOT" log_success "🎉 x86-64 설치 완료!"
echo "======================================"
echo ""
echo "다음 명령으로 서버를 시작하세요:"
echo " bash scripts/start_server.sh" echo " bash scripts/start_server.sh"
echo "" echo ""
log_info "또는 자동 설정 및 실행:" echo "모니터링 대시보드:"
echo " bash scripts/setup_and_run.sh" echo " http://localhost:8009"
echo "" echo ""
log_info "서버 포트:" echo "API 테스트:"
echo " 메인 서버: http://localhost:8008" echo " curl http://localhost:8008/api/v1/health"
echo " 모니터링: http://localhost:8888"
echo ""
log_info "GPU 설정:"
echo " RTX 3060 12GB 권장 설정이 자동으로 적용됩니다"
echo " 워커 수: 4-8개 (자동 조정)"
echo " 세션 수: Simple LAMA 4개, MIGAN 4개, REMBG 2개"

View File

@ -40,6 +40,7 @@ LOG_DIR="$PROJECT_ROOT/logs"
# 가상환경 경로 자동 감지 # 가상환경 경로 자동 감지
detect_venv_path() { detect_venv_path() {
local possible_paths=( local possible_paths=(
"$PROJECT_ROOT/" # 일반적인 경로
"$PROJECT_ROOT/venv" # 일반적인 venv 경로 "$PROJECT_ROOT/venv" # 일반적인 venv 경로
"$PROJECT_ROOT/.venv" # 숨김 venv 경로 "$PROJECT_ROOT/.venv" # 숨김 venv 경로
"$PROJECT_ROOT/env" # env 경로 "$PROJECT_ROOT/env" # env 경로
@ -364,8 +365,8 @@ setup_jetson_optimization() {
# 전력 모드 설정 # 전력 모드 설정
if command -v nvpmodel &> /dev/null; then if command -v nvpmodel &> /dev/null; then
log_info "전력 모드를 MAXN으로 설정 중..." log_info "전력 모드를 MAXN으로 설정 중..."
sudo nvpmodel -m 0 # MAXN 모드 sudo nvpmodel -m 0 2>/dev/null || log_warning "MAXN 모드 설정 실패"
sudo nvpmodel -q sudo nvpmodel -q 2>/dev/null || log_warning "전력 모드 확인 실패"
else else
log_warning "nvpmodel을 찾을 수 없습니다" log_warning "nvpmodel을 찾을 수 없습니다"
fi fi
@ -458,13 +459,35 @@ start_servers() {
echo $! > "$LOG_DIR/main_server.pid" echo $! > "$LOG_DIR/main_server.pid"
fi fi
sleep 3 # 메인 서버 시작 대기 (모델 로딩 시간 고려)
log_info "메인 서버 초기화 대기 중... (최대 120초)"
local timeout=120
local elapsed=0
local interval=5
# 메인 서버 상태 확인 while [ $elapsed -lt $timeout ]; do
if curl -s "http://localhost:$MAIN_SERVER_PORT/health" > /dev/null; then sleep $interval
log_success "메인 서버 시작 완료 (PID: $(cat $LOG_DIR/main_server.pid))" elapsed=$((elapsed + interval))
else
log_error "메인 서버 시작 실패" # 올바른 헬스체크 엔드포인트 사용
if curl -s "http://localhost:$MAIN_SERVER_PORT/api/v1/health" > /dev/null 2>&1; then
log_success "메인 서버 시작 완료 (PID: $(cat $LOG_DIR/main_server.pid)) - ${elapsed}초 소요"
break
fi
# 진행 상황 로깅 (더 자주)
if [ $((elapsed % 10)) -eq 0 ]; then
log_info "메인 서버 초기화 중... (${elapsed}/${timeout}초) - 모델 로딩 진행 중"
fi
done
# 최종 확인
if ! curl -s "http://localhost:$MAIN_SERVER_PORT/api/v1/health" > /dev/null 2>&1; then
log_error "메인 서버 시작 실패 (${timeout}초 타임아웃)"
log_info "모델 로딩이 진행 중일 수 있습니다. 로그를 확인하세요:"
log_info " tail -f $LOG_DIR/main_server.log"
log_info "서버 프로세스가 실행 중인지 확인하세요:"
log_info " ps aux | grep uvicorn"
exit 1 exit 1
fi fi

View File

@ -1,19 +1,33 @@
{ {
"worker_status": { "worker_status": {
"running": true, "running": true,
"total_workers": 2, "total_workers": 4,
"queue_size": 0, "queue_size": 0,
"workers_by_status": { "workers_by_status": {
"idle": [ "idle": [
{ {
"id": "worker_c91d2f9c", "id": "worker_4a61796f",
"status": "idle", "status": "idle",
"task_count": 0, "task_count": 0,
"error_count": 0, "error_count": 0,
"last_task_at": null "last_task_at": null
}, },
{ {
"id": "worker_ba00e1d4", "id": "worker_ef99e89d",
"status": "idle",
"task_count": 0,
"error_count": 0,
"last_task_at": null
},
{
"id": "worker_da813d18",
"status": "idle",
"task_count": 0,
"error_count": 0,
"last_task_at": null
},
{
"id": "worker_ed362e96",
"status": "idle", "status": "idle",
"task_count": 0, "task_count": 0,
"error_count": 0, "error_count": 0,
@ -28,46 +42,70 @@
}, },
"session_status": { "session_status": {
"simple_lama": { "simple_lama": {
"total": 2, "total": 4,
"in_use": 0, "in_use": 0,
"available": 2 "available": 4
}, },
"migan": { "migan": {
"total": 2, "total": 4,
"in_use": 0, "in_use": 0,
"available": 2 "available": 4
}, },
"rembg": { "rembg": {
"total": 1, "total": 3,
"in_use": 0, "in_use": 0,
"available": 1 "available": 3
} }
}, },
"api_stats": { "api_stats": {
"total_requests": 1, "total_requests": 31,
"successful_requests": 1, "successful_requests": 29,
"failed_requests": 0, "failed_requests": 2,
"success_rate": 100.0, "success_rate": 93.54838709677419,
"endpoint_usage": { "endpoint_usage": {
"GET /health": 1 "GET /api/v1/health": 2,
"GET /health": 1,
"GET /api/v1/model": 14,
"POST /api/v1/inpaint": 14
}, },
"endpoint_stats": { "endpoint_stats": {
"GET /api/v1/health": {
"count": 2,
"avg_time": 0.001275777816772461,
"min_time": 0.000980377197265625,
"max_time": 0.0015711784362792969,
"current_concurrent": 0
},
"GET /health": { "GET /health": {
"count": 1, "count": 1,
"avg_time": 0.001737356185913086, "avg_time": 0.0077762603759765625,
"min_time": 0.001737356185913086, "min_time": 0.0077762603759765625,
"max_time": 0.001737356185913086, "max_time": 0.0077762603759765625,
"current_concurrent": 0
},
"GET /api/v1/model": {
"count": 14,
"avg_time": 0.004632132393973214,
"min_time": 0.0023491382598876953,
"max_time": 0.007782697677612305,
"current_concurrent": 0
},
"POST /api/v1/inpaint": {
"count": 14,
"avg_time": 3.2447635446275984,
"min_time": 1.2617759704589844,
"max_time": 14.294722318649292,
"current_concurrent": 0 "current_concurrent": 0
} }
}, },
"average_response_time": 0.001737356185913086, "average_response_time": 1.4678021707842428,
"min_response_time": 0.001737356185913086, "min_response_time": 0.000980377197265625,
"max_response_time": 0.001737356185913086, "max_response_time": 14.294722318649292,
"current_concurrent": 0, "current_concurrent": 0,
"max_concurrent": 1, "max_concurrent": 1,
"requests_per_second": 0.0014694498527868262, "requests_per_second": 0.09576884328690756,
"uptime": 680.5267958641052, "uptime": 323.69608879089355,
"recent_errors": [] "recent_errors": []
}, },
"timestamp": 1756403844.7342515 "timestamp": 1756471050.1084073
} }

1
test_lama_result.png Normal file
View File

@ -0,0 +1 @@
Internal Server Error

106
tests/rembg_test.py Normal file
View File

@ -0,0 +1,106 @@
#!/usr/bin/env python3
import os
import sys
import time
from datetime import datetime
def log(msg: str):
ts = datetime.now().strftime('%H:%M:%S')
print(f"[{ts}] {msg}", flush=True)
def main():
# 환경 설정
u2net_home = os.path.join(os.path.expanduser('~'), '.u2net')
os.makedirs(u2net_home, exist_ok=True)
os.environ.setdefault('U2NET_HOME', u2net_home)
# 캐시 파일이 있으면 체크섬 스킵
if any(f.endswith('.onnx') for f in os.listdir(u2net_home)):
os.environ.setdefault('MODEL_CHECKSUM_DISABLED', '1')
# ORT 상세로그 (원인 추적)
os.environ.setdefault('ORT_LOG_SEVERITY_LEVEL', '0')
os.environ.setdefault('ORT_LOG_VERBOSITY_LEVEL', '1')
model_name = os.environ.get('REMBG_TEST_MODEL', 'birefnet-general-lite')
custom_path = os.environ.get('REMBG_MODEL_PATH')
log(f"U2NET_HOME={os.environ.get('U2NET_HOME')}")
log(f"MODEL_CHECKSUM_DISABLED={os.environ.get('MODEL_CHECKSUM_DISABLED')}")
log(f"REMBG_TEST_MODEL={model_name}, REMBG_MODEL_PATH={custom_path}")
import onnxruntime as ort
log(f"ORT version={getattr(ort, '__version__', 'unknown')}")
log(f"ORT device={ort.get_device()}")
log(f"ORT available providers={ort.get_available_providers()}")
import rembg
log(f"rembg version={getattr(rembg, '__version__', 'unknown')}")
# 세션 생성
# 일부 버전에서 sessions dict가 공개되지 않을 수 있어 안전하게 시도
try:
from rembg.sessions import sessions as rembg_sessions
log(f"rembg sessions registered (count)={len(rembg_sessions)}")
if model_name not in rembg_sessions and model_name != 'ben_custom':
log(f"WARN: session class not registered for '{model_name}'. Using available: {list(rembg_sessions.keys())[:10]} ...")
except Exception as e:
log(f"rembg.sessions registry read skipped: {e}")
args = []
kwargs = {}
if model_name == 'ben_custom':
if not custom_path:
print("ERROR: REMBG_MODEL_PATH must be set for ben_custom", file=sys.stderr)
sys.exit(2)
kwargs['model_path'] = custom_path
# Jetson + ONNX 1.17 조합에서 TensorRT 충돌을 피하기 위해 CUDA 명시
providers = ['CUDAExecutionProvider', 'CPUExecutionProvider']
log(f"Using providers: {providers}")
kwargs['providers'] = providers
log("Creating rembg session...")
t0 = time.time()
sess = rembg.new_session(model_name, *args, **kwargs)
t1 = time.time()
prov = []
try:
if hasattr(sess, 'inner_session') and hasattr(sess.inner_session, 'get_providers'):
prov = sess.inner_session.get_providers() or []
except Exception as e:
log(f"provider read failed: {e}")
log(f"Session created in {t1 - t0:.2f}s, providers={prov}")
# 테스트 이미지 생성 (단색 배경 + 전경 사각형)
from PIL import Image, ImageDraw
W, H = 512, 384
img = Image.new('RGB', (W, H), (200, 220, 240))
d = ImageDraw.Draw(img)
d.rectangle([W//4, H//4, 3*W//4, 3*H//4], fill=(180, 50, 50))
# 배경제거 수행
from rembg import remove
log("Calling rembg.remove()...")
t2 = time.time()
out = remove(img, session=sess)
t3 = time.time()
log(f"remove() finished in {t3 - t2:.2f}s")
# 결과 저장
out_dir = os.path.join(os.getcwd(), 'outputs')
os.makedirs(out_dir, exist_ok=True)
out_path = os.path.join(out_dir, f'rembg_test_{model_name}.png')
try:
if hasattr(out, 'save'):
out.save(out_path)
else:
from PIL import Image as PILImage
PILImage.open(out).save(out_path)
log(f"Result saved: {out_path}")
except Exception as e:
log(f"Result save failed: {e}")
if __name__ == '__main__':
main()