인페인팅 방법을 MIGAN으로 변경하고 관련 설정 추가. TensorRT 최적화 및 메모리 관리 기능 개선. Dockerfile 및 의존성 파일 업데이트. 전체적인 코드 정리 및 주석 보강.

This commit is contained in:
7600M 2025-08-23 09:07:19 +09:00
parent 1b72426779
commit e3bd4bb50b
10 changed files with 1448 additions and 45 deletions

View File

@ -1,5 +1,6 @@
# CUDA 11.8 + Ubuntu 22.04 (cudnn 필요 없으면 -runtime- 만 사용)
#FROM nvidia/cuda:11.8.0-cudnn8-runtime-ubuntu22.04
#FROM nvcr.io/nvidia/tensorrt:23.10-py3
FROM nvidia/cuda:11.8.0-cudnn8-devel-ubuntu22.04
# ---------- 시스템 의존성 (Pango/Cairo/HarfBuzz/Fonts 포함) ----------

View File

@ -0,0 +1,62 @@
# CUDA 11.8 + Ubuntu 22.04 + cuDNN8 + TensorRT 8.6 + Python3.10
FROM nvcr.io/nvidia/tensorrt:23.10-py3
#FROM nvidia/cuda:11.8.0-cudnn8-devel-ubuntu22.04
# ---------- 시스템 의존성 (Pango/Cairo/HarfBuzz/Fonts 포함) ----------
RUN apt-get update && apt-get install -y --no-install-recommends \
python3.10 python3-pip python3.10-distutils \
# OpenCV 런타임
libgl1-mesa-glx libgl1-mesa-dri libglib2.0-0 libgomp1 \
libsm6 libxext6 libxrender1 \
cuda-cudart-dev-11-8 \
# Pango / Cairo / HarfBuzz / GI 바인딩
libcairo2 libcairo2-dev \
libpango-1.0-0 libpangocairo-1.0-0 libpangoft2-1.0-0 \
libharfbuzz0b libharfbuzz-icu0 \
gobject-introspection libgirepository1.0-dev \
python3-gi python3-gi-cairo \
# 글꼴과 폰트 설정
fontconfig fonts-nanum fonts-noto-cjk fonts-dejavu-core \
# 기타
pkg-config \
&& rm -rf /var/lib/apt/lists/*
# ---------- Python 기본 설정 ----------
RUN ln -sf /usr/bin/python3.10 /usr/local/bin/python && \
python -m pip install --upgrade pip
WORKDIR /app
# ---------- 파이썬 패키지 ----------
COPY requirements_worker.txt .
COPY packages_worker/fastdeploy_gpu_python-1.0.7-cp310-cp310-manylinux1_x86_64.whl .
# constraints 복사
COPY constraints.txt .
RUN pip install --no-cache-dir --progress-bar off \
--index-url https://download.pytorch.org/whl/cu118 \
torch==2.3.1 torchvision==0.18.1 torchaudio==2.3.1
RUN pip install --no-cache-dir --progress-bar off -r requirements_worker.txt -c constraints.txt
RUN pip install --no-cache-dir --progress-bar off fastdeploy_gpu_python-1.0.7-cp310-cp310-manylinux1_x86_64.whl
# ---------- 메모리 추척 ----------
RUN pip install --no-cache-dir --progress-bar off pynvml
# ---------- 프로젝트 파일 ----------
COPY worker /app/worker
# COPY fonts /app/fonts
ENV PYTHONUNBUFFERED=1
ENV TEMP_STORAGE=/app/temp_files
RUN mkdir -p $TEMP_STORAGE
# (선택) TensorRT 런타임이 정말 필요하면 NVIDIA 레포 추가 후 libnvinfer8 설치 필요.
# 없다면 FastDeploy가 자동으로 Paddle Inference로 폴백하니 생략해도 동작은 함.
# 성능 이유로 TRT를 쓰려면 별도 설치 가이드대로 세팅 권장.
# ---------- Celery 엔트리포인트 ----------
# ★ 중요: -A 경로를 실제 celery_app 이 있는 모듈:변수 로 맞추세요.
# 예) celery_worker.py 안에 celery_app = Celery(...) 라면:
CMD ["celery", "-A", "worker.celery_worker:celery_app", "worker", "-l", "info", "--concurrency=1"]

View File

@ -31,10 +31,18 @@ requests==2.32.3
# loguru==0.7.2 # 사용시
python-dotenv==1.0.1 # .env 파싱시
# onnxruntime
# ONNX Runtime (MIGAN 인페인팅용)
onnxruntime-gpu==1.16.3
onnx==1.14.0
# 🔥 MIGAN 인페인팅 추가 패키지
# TensorRT (성능 최적화용 - NVIDIA GPU 환경)
# tensorrt>=8.6.0 # TensorRT 라이브러리 (별도 설치 필요)
# 텍스트 렌더링 품질 개선 (Cairo/Pango 경고 해결)
pycairo>=1.20.0 # Cairo 그래픽 라이브러리
PyGObject>=3.42.0 # GTK/Pango 바인딩
# Background removal
rembg[gpu]==2.0.67

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 MiB

After

Width:  |  Height:  |  Size: 951 KiB

View File

@ -37,14 +37,24 @@ unwanted_texts = {
toggle_states = {"inpaint_method": "lama:cuda", "min_masks_for_lama": 2, 'title': False, 'title_shuffle': False, 'title_trans_type': False, 'collect_method_combo': '쇼핑API', 'ocr': True, 'unwanted_words': {'할인': '', '무료': '', '증정': '', '이벤트': '', '특가': '', '세일': '', '사은품': '', '보증': '', '품절': '', '행사': '', '할인가': '', '무료배송': '', '가격설명': ''}, 'interval': 3.0, 'watingTime': 20, 'memo': False, 'memo_toggle_exposer': False, 'memo_toggle_order': False, 'optionTrnas': True, 'optionTrnas_method': True, 'optionIMGTrans': True, 'optionIMGTrans_type': '자체서버', 'optionAutoSelect': True, 'price': False, 'tag': False, 'tag_ai': False, 'thumb': False, 'thumb_trans_type': 'CPU', 'thumb_nukki': False, 'remove_background_white': True, 'detail_Option': False, 'detail_IMGTrans': True, 'detail_IMGTrans_type': '자체서버', 'debug_mode': True, 'ed_mode': False, 'discord': False, 'is_localServer': False, 'watermark_toggle': False, 'clientID': '', 'clientSecret': '', 'discord_webhook': '', 'watermark_text': '', 'thumb_rmb_count': 3, 'max_option_count': 6, 'opacity_percent': 20, 'group_index': 4, 'remove_overprice': False, 'cat_rec': False, 'fixed_keywords': False, 'fixed_keywords_count': 2, 'title_length_limit': 27, 'base_dir': 'C:\\Program Files\\Edit_PartTimer\\lib\\src', 'TEMP_IMAGE_DIR': 'C:\\Program Files\\Edit_PartTimer\\lib\\src\\temp_images', 'ERROR_SCREENSHOT_DIR': 'C:\\Program Files\\Edit_PartTimer\\lib\\src\\error_screenshots', 'image_font_path': 'C:\\Program Files\\Edit_PartTimer\\lib\\src\\fonts\\HakgyoansimDunggeunmisoTTFB.ttf', 'watermark_font_path': 'C:\\Program Files\\Edit_PartTimer\\lib\\src\\fonts\\HakgyoansimDunggeunmisoTTFB.ttf', 'request_inpainting_server_url': 'http://171.101.232.45:50205', 'request_rembg_server_url': 'http://171.101.232.45:50205', 'request_rembg_server_url_local': 'http://192.168.0.150:35756', 'membership_level': 'premium', 'image_worker_restart_every': 10, 'image_worker_restart_count': 0, 'products_per_context_restart': 19, 'is_admin': False, 'admin_id': 'matia0514@naver.com', 'admin_pw': '', 'user_id': 'dreamm8985', 'user_pw': '112233', 'unwanted_words_button': False, 'font_type': '폰트5', 'cmb_button': False, 'detail_text_button': False, 'watermark': False}
toggle_states.update({
"use_roi_optimized_mask": True, # True: 새 방식, False: 기존 방식
"enable_mask_refinement": False, # ROI 마스크 정제 비활성화
"context_expansion_ratio": 0.4, # 최소 확장
"blend_mode": "simple", # 단순 블렌딩
"performance_mode": True, # 빠른 경로 사용
"max_image_size": 1280, # 더 작은 크기 제한
"roi_area_high": 0.0 # 기본값: 0.60 → 0.0으로 변경 # 풀프레임 인페인팅 강제
# 🔥 MIGAN 인페인팅 사용 설정
"inpaint_method": "migan", # ROI → MIGAN 변경
"migan_use_cuda": True, # CUDA 사용
"migan_use_tensorrt": True, # TensorRT 최적화
"migan_trt_fp16_enable": True, # FP16 활성화
"migan_trt_engine_cache_enable": True, # 엔진 캐시 활성화
"migan_max_image_size": 1600, # 최대 이미지 크기
"migan_output_max_width": 800, # 출력 최대 가로
"migan_enable_output_resize": True, # 출력 리사이즈 활성화
# 기존 ROI 설정들 (MIGAN에서는 사용되지 않지만 호환성 유지)
"use_roi_optimized_mask": True, # True: 새 방식, False: 기존 방식
"enable_mask_refinement": False, # ROI 마스크 정제 비활성화
"context_expansion_ratio": 0.4, # 최소 확장
"blend_mode": "simple", # 단순 블렌딩
"performance_mode": True, # 빠른 경로 사용
"max_image_size": 1280, # 더 작은 크기 제한
"roi_area_high": 0.0 # 기본값: 0.60 → 0.0으로 변경 # 풀프레임 인페인팅 강제
})
def call_translate(img_path: pathlib.Path):
@ -58,7 +68,8 @@ def call_translate(img_path: pathlib.Path):
"toggle_states": json.dumps(toggle_states, ensure_ascii=False),
"unwanted_texts": json.dumps(unwanted_texts, ensure_ascii=False),
"ocr_method": "paddleocr",
"inpaint_method": "lama",
# "inpaint_method": "lama", # ← 이 줄을 제거하거나
"inpaint_method": "migan", # ← migan으로 변경
"sync": "true",
"wait": "90",
},

306
worker/MIGAN_SETUP.md Normal file
View File

@ -0,0 +1,306 @@
# MIGAN 인페인팅 모듈 설정 안내
## 개요
MIGAN ONNX 파이프라인을 사용한 전체 이미지 인페인팅 모듈이 추가되었습니다. 기존 ROI 기반 인페인팅과 함께 사용할 수 있으며, `toggle_states`를 통해 선택할 수 있습니다.
## 모델 파일 설정
### 1. 모델 디렉토리 생성
```bash
mkdir -p worker/models
```
### 2. MIGAN 모델 파일 배치
`migan_pipeline_v2.onnx` 파일을 다음 경로에 배치하세요:
```
worker/models/migan_pipeline_v2.onnx
```
### 3. TensorRT 캐시 디렉토리 (선택사항)
TensorRT 엔진 캐시를 위한 디렉토리:
```bash
mkdir -p /app/temp_files/trt_cache
```
## 사용 방법
### 인페인팅 방법 선택
`toggle_states`에서 `inpaint_method` 키를 통해 인페인팅 방법을 선택할 수 있습니다:
```python
# ROI 기반 인페인팅 (기본값)
toggle_states = {
"inpaint_method": "roi"
}
# MIGAN 전체 이미지 인페인팅
toggle_states = {
"inpaint_method": "migan"
}
```
### MIGAN 설정 옵션
#### 기본 설정
```python
toggle_states = {
"inpaint_method": "migan",
"migan_onnx_path": "/app/worker/models/migan_pipeline_v2.onnx",
"migan_use_cuda": True,
"migan_use_tensorrt": True,
"migan_trt_fp16_enable": True,
"migan_trt_engine_cache_enable": True,
"migan_max_image_size": 1600,
"migan_output_max_width": 800,
"migan_enable_output_resize": True
}
```
#### 설정 옵션 설명
**ONNX 모델 설정:**
- `migan_onnx_path`: ONNX 모델 파일 경로
- `migan_use_cuda`: CUDA 사용 여부 (기본: True)
- `migan_intra_threads`: ONNX Runtime 스레드 수 (기본: 0 = 자동)
- `migan_inter_threads`: ONNX Runtime 스레드 수 (기본: 0 = 자동)
**TensorRT 최적화:**
- `migan_use_tensorrt`: TensorRT 사용 여부 (기본: True)
- `migan_trt_fp16_enable`: FP16 최적화 활성화 (기본: True)
- `migan_trt_engine_cache_enable`: 엔진 캐시 활성화 (기본: True)
**이미지 처리:**
- `migan_max_image_size`: 최대 이미지 크기 제한 (긴 변 기준, 기본: 1600px)
- `migan_output_max_width`: 출력 최대 가로 크기 (기본: 800px)
- `migan_enable_output_resize`: 출력 크기 조정 활성화 (기본: True)
#### 직접 모듈 사용 (개발/테스트용)
```python
from worker.migan_inpainting_module import MIGANInpaintingModule
# ⚠️ 프로덕션에서는 celery_worker의 전역 인스턴스 사용 권장
inpainter = MIGANInpaintingModule(config={
'use_cuda': True,
'max_image_size': 1600
})
# 인페인팅 실행
result = inpainter.inpaint_with_migan(image, mask)
# cleanup_memory() 호출하지 말 것 - 전역 인스턴스는 계속 유지
```
#### 전역 인스턴스 사용 (권장)
```python
# celery_worker.py에서 자동으로 처리됨
# toggle_states만 설정하면 됨
toggle_states = {
"inpaint_method": "migan",
"migan_use_tensorrt": True,
"migan_trt_fp16_enable": True,
"migan_max_image_size": 2048
}
# worker가 자동으로 전역 인스턴스를 사용하고 설정을 동적 업데이트
```
## 주요 특징
### MIGAN vs ROI 인페인팅 비교
| 항목 | MIGAN | ROI |
|------|-------|-----|
| 처리 방식 | 전체 이미지 | 영역별 분할 |
| 전처리 | 모델 내장 | 외부 처리 |
| ROI 분석 | 불필요 | 필수 |
| 메모리 사용 | 고정적 | 적응적 |
| 속도 | 일정 | 영역 수에 따라 변동 |
| 품질 | 일관적 | 영역별 최적화 |
### MIGAN 모듈 장점
1. **내장 전처리**: 리사이즈, 정규화 등이 ONNX 모델에 포함
2. **TensorRT 최적화**: FP16, 엔진 캐시를 통한 성능 향상
3. **세션 재사용**: 메모리 효율성 및 초기화 시간 단축
4. **자동 폴백**: MIGAN 실패 시 ROI 방식으로 자동 전환
5. **크기 제한**: 메모리 사용량 제어를 위한 이미지 크기 제한
6. **🔥 전역 인스턴스**: 모델은 한 번만 로딩되어 메모리에 상주
7. **🔥 동적 설정**: 실행 중 설정 변경 가능 (필요시 세션 재생성)
## 환경 변수
```bash
# TensorRT 엔진 캐시 디렉토리 (선택사항)
export TRT_ENGINE_CACHE_DIR="/app/temp_files/trt_cache"
# 디버그 아티팩트 저장 디렉토리
export DEBUG_DUMP_DIR="/app/temp_files/debug"
```
## 로그 분석
### 정상 처리 로그 예시
```
[MIGAN] ONNX Runtime 세션 생성 완료: ['TensorrtExecutionProvider', 'CUDAExecutionProvider', 'CPUExecutionProvider']
[INPAINT] MIGAN 처리 통계: 마스크 커버리지 15.3%, 최대 크기 제한: 1600px
이미지 스케일링: 2000x1500 → 1600x1200 (factor=0.800)
MIGAN 인페인팅 완료: 총 1.234s (추론: 0.987s)
```
### 에러 및 폴백 로그 예시
```
[MIGAN] ONNX 파일을 찾을 수 없습니다: /app/worker/models/migan_pipeline_v2.onnx
[INPAINT] MIGAN 인페인팅 실패, ROI로 폴백: FileNotFoundError
```
## 메모리 관리 및 성능
### 전역 인스턴스 방식
- **모델 로딩**: 워커 프로세스 시작 시 한 번만 로딩
- **메모리 상주**: 모델이 메모리에 계속 유지되어 초기화 오버헤드 없음
- **세션 재사용**: ONNX Runtime 세션이 캐시되어 반복 사용
- **동적 설정**: 필요시에만 세션 재생성 (경로 변경 등)
### 주의사항
- `cleanup_memory()` 메소드를 전역 인스턴스에서 호출하지 마세요
- 프로덕션 환경에서는 celery_worker의 전역 인스턴스를 사용하세요
- 개발/테스트에서만 직접 인스턴스 생성을 사용하세요
## 성능 최적화 팁
1. **TensorRT 활용**: GPU가 있는 환경에서는 TensorRT를 활성화하여 성능 향상
2. **엔진 캐시**: 첫 실행 후 생성되는 TensorRT 엔진을 캐시하여 재시작 시 빠른 로딩
3. **크기 제한**: 메모리 부족 시 `migan_max_image_size`를 조정하여 처리 가능한 크기로 제한
4. **출력 크기**: 클라이언트 요구사항에 맞춰 `migan_output_max_width` 조정
## 문제 해결
### 일반적인 문제들
1. **모델 파일 없음**
- 에러: `FileNotFoundError: ONNX 파일을 찾을 수 없습니다`
- 해결: `worker/models/migan_pipeline_v2.onnx` 파일 확인
2. **CUDA 메모리 부족**
- 에러: GPU 메모리 관련 오류
- 해결: `migan_max_image_size` 값을 줄이거나 `migan_use_cuda: False`로 설정
3. **TensorRT 초기화 실패**
- 에러: TensorRT Provider 초기화 실패
- 해결: `migan_use_tensorrt: False`로 설정하여 CUDA Provider만 사용
4. **세션 생성 실패**
- 에러: ONNX Runtime 세션 생성 실패
- 해결: CPU 모드로 폴백 (`migan_use_cuda: False`)
### 디버깅
1. **로그 레벨 조정**: 디버그 로그를 활성화하여 상세 정보 확인
2. **아티팩트 저장**: 디버그 이미지를 저장하여 각 단계별 결과 확인
3. **성능 모니터링**: GPU 메모리 사용량 및 처리 시간 모니터링
## 업데이트 및 확장
새로운 MIGAN 모델 버전을 사용하려면:
1. 새 ONNX 파일을 `worker/models/` 디렉토리에 배치
2. `toggle_states`에서 `migan_onnx_path` 업데이트
3. 필요시 입출력 형태에 맞춰 모듈 코드 수정

View File

@ -48,6 +48,7 @@ from worker.loggerModule import Logger
# from worker.inpaint_module import Inpainter, InpaintBackends
from worker.utils_debug import save_debug_artifacts, draw_ocr_overlay
from worker.roi_inpainting_module import ROIInpaintingModule
from worker.migan_inpainting_module import MIGANInpaintingModule
# from deep_translator import GoogleTranslator
@ -117,6 +118,7 @@ _mask: MaskModule | None = None
_text: TextRenderingModule | None = None
# _inpainter: Inpainter | None = None
_roi_inpainter: ROIInpaintingModule | None = None
_migan_inpainter: MIGANInpaintingModule | None = None
_translator = get_translator() # ✅ 워커 부팅 시 생성 & 재사용
# def get_lama():
@ -170,6 +172,15 @@ def get_roi_inpainter():
_gpu_tracker.log_snapshot(tag="after ROI Inpainter init")
return _roi_inpainter
def get_migan_inpainter():
"""MIGAN 인페인팅 모듈 초기화 및 반환"""
global _migan_inpainter
if _migan_inpainter is None:
_migan_inpainter = MIGANInpaintingModule(logger=clogger)
# MIGAN 인페인팅 초기화 직후 VRAM 스냅샷
_gpu_tracker.log_snapshot(tag="after MIGAN Inpainter init")
return _migan_inpainter
# 번역기 워밍업
try:
_ = _translator.translate_batch(["测试"], src="zh-CN", dest="ko")
@ -211,6 +222,9 @@ def _warm_up_models(**_):
roi_inpainter = get_roi_inpainter()
roi_inpainter._get_simple_lama() # SimpleLama 사전 로딩
# 🔥 MIGAN 인페인팅 모듈 사전 초기화 (옵션)
get_migan_inpainter() # 필요 시 주석 해제
logger.info("✅ 모델 사전 로딩 완료 (성능 최적화 포함)")
except Exception as e:
@ -251,6 +265,54 @@ def _parse_font_number_from_toggle(toggle_states: Dict[str, Any]) -> int | None:
logger.warning(f"[font] font_type 파싱 실패: {e}")
return None
def _parse_inpaint_method(toggle_states: Dict[str, Any]) -> str:
"""
toggle_states에서 인페인팅 방법을 파싱
Returns:
'roi': ROI 기반 인페인팅 (기본값)
'migan': MIGAN 전체 이미지 인페인팅
"""
method = str(toggle_states.get("inpaint_method", "roi")).lower().strip()
# MIGAN 방식 키워드들
migan_keywords = ["migan", "mi-gan", "full", "full_image", "onnx"]
if any(keyword in method for keyword in migan_keywords):
return "migan"
# 기본값은 ROI
return "roi"
def _lightweight_memory_cleanup(trace_id: Optional[str] = None):
"""
경량 메모리 정리 - 모델은 유지하고 중간 처리 데이터만 정리
Args:
trace_id: 트레이스 ID (로깅용)
"""
try:
import gc
import torch
# Python 가비지 컬렉션
collected = gc.collect()
# GPU 캐시 정리 (모델은 유지됨)
gpu_freed = 0
if torch.cuda.is_available():
gpu_freed = torch.cuda.memory_allocated() / 1024 / 1024 # MB
torch.cuda.empty_cache()
gpu_freed = gpu_freed - (torch.cuda.memory_allocated() / 1024 / 1024)
clogger.log(
f"[MEMORY][{trace_id or ''}] 경량 정리 완료: Python객체={collected}개, GPU캐시={gpu_freed:.1f}MB",
level=logging.DEBUG
)
except Exception as e:
clogger.log(f"[MEMORY][{trace_id or ''}] 메모리 정리 중 오류: {e}", level=logging.WARNING)
# def _parse_inpaint_backend(
# toggle_states: Dict[str, Any],
# *,
@ -570,41 +632,97 @@ def translate_task(self, *, image_b64: str, filename: str,
with track_phase("INPAINT", trace_id):
# 🔥 ROI 기반 인페인팅 최적화 (모듈화)
roi_inpainter = get_roi_inpainter()
# 🔥 인페인팅 방법 선택
inpaint_method = _parse_inpaint_method(toggle_states)
clogger.log(f"[TRACE][{trace_id}] 선택된 인페인팅 방법: {inpaint_method}", level=logging.INFO)
# ROI 처리 설정 (toggle_states에서 오버라이드 가능)
roi_config = {
'min_component_area': toggle_states.get('min_component_area', 100),
'merge_distance': toggle_states.get('merge_distance', 50),
'margin_ratio': toggle_states.get('margin_ratio', 0.15),
'large_mask_threshold': toggle_states.get('large_mask_threshold', 0.5),
# 🔥 마스크 정제 비활성화 (마스크 모듈에서 이미 최적화됨)
'enable_mask_refinement': toggle_states.get('enable_mask_refinement', False),
'mask_erosion_kernel': 0, # 비활성화
'mask_dilation_kernel': 0, # 비활성화
'mask_blur_kernel': 0, # 비활성화
'context_expansion_ratio': toggle_states.get('context_expansion_ratio', 0.1), # 줄임
'blend_mode': toggle_states.get('blend_mode', 'simple'), # 단순 블렌딩
'feather_blend_size': toggle_states.get('feather_blend_size', 5), # 줄임
# 🔥 형상 최적화 설정
'enable_shape_optimization': toggle_states.get('enable_shape_optimization', True),
'performance_tracking': toggle_states.get('performance_tracking', True),
}
# 처리 전 통계 로깅
stats = roi_inpainter.get_processing_stats(src_bgr, mask)
clogger.log(
f"[INPAINT] 처리 통계: {stats['num_components']}개 컴포넌트 → "
f"{stats['num_merged_rois']}개 ROI, 메모리 효율성: {stats['memory_efficiency']*100:.1f}%",
level=logging.INFO
)
# ROI 기반 인페인팅 실행
dst_bgr = roi_inpainter.inpaint_with_roi(src_bgr, mask, config=roi_config)
# 메모리 정리
roi_inpainter.cleanup_memory()
if inpaint_method == "migan":
# 🔥 MIGAN 전체 이미지 인페인팅
try:
migan_inpainter = get_migan_inpainter() # 전역 인스턴스 재사용
# MIGAN 설정 오버라이드 (toggle_states에서)
migan_config = {
'onnx_path': toggle_states.get('migan_onnx_path', '/app/worker/models/migan_pipeline_v2.onnx'),
'use_cuda': toggle_states.get('migan_use_cuda', True),
'use_tensorrt': toggle_states.get('migan_use_tensorrt', True),
'trt_fp16_enable': toggle_states.get('migan_trt_fp16_enable', True),
'trt_engine_cache_enable': toggle_states.get('migan_trt_engine_cache_enable', True),
'max_image_size': int(toggle_states.get('migan_max_image_size', 1600)),
'output_max_width': int(toggle_states.get('migan_output_max_width', 800)),
'enable_output_resize': toggle_states.get('migan_enable_output_resize', True),
}
# 처리 전 통계 로깅
stats = migan_inpainter.get_processing_stats(src_bgr, mask)
clogger.log(
f"[INPAINT] MIGAN 처리 통계: 마스크 커버리지 {stats['mask_coverage_ratio']*100:.1f}%, "
f"최대 크기 제한: {stats['max_size_limit']}px",
level=logging.INFO
)
# MIGAN 인페인팅 실행 (전역 인스턴스, 메모리 정리 안함)
dst_bgr = migan_inpainter.inpaint_with_migan(src_bgr, mask, config=migan_config)
# 🔥 경량 메모리 정리 - 중간 처리 데이터만 정리, 모델은 유지
_lightweight_memory_cleanup(trace_id)
except Exception as e:
clogger.log(f"[INPAINT] MIGAN 인페인팅 실패, ROI로 폴백: {e}", level=logging.WARNING)
# MIGAN 실패 시 ROI로 폴백
roi_inpainter = get_roi_inpainter()
roi_config = {
'min_component_area': toggle_states.get('min_component_area', 100),
'merge_distance': toggle_states.get('merge_distance', 50),
'margin_ratio': toggle_states.get('margin_ratio', 0.15),
'large_mask_threshold': toggle_states.get('large_mask_threshold', 0.5),
'enable_mask_refinement': toggle_states.get('enable_mask_refinement', False),
'context_expansion_ratio': toggle_states.get('context_expansion_ratio', 0.1),
'blend_mode': toggle_states.get('blend_mode', 'simple'),
'feather_blend_size': toggle_states.get('feather_blend_size', 5),
'enable_shape_optimization': toggle_states.get('enable_shape_optimization', True),
'performance_tracking': toggle_states.get('performance_tracking', True),
}
dst_bgr = roi_inpainter.inpaint_with_roi(src_bgr, mask, config=roi_config)
# 🔥 경량 메모리 정리 추가
_lightweight_memory_cleanup(trace_id)
else:
# 🔥 ROI 기반 인페인팅 (기본값)
roi_inpainter = get_roi_inpainter()
# ROI 처리 설정 (toggle_states에서 오버라이드 가능)
roi_config = {
'min_component_area': toggle_states.get('min_component_area', 100),
'merge_distance': toggle_states.get('merge_distance', 50),
'margin_ratio': toggle_states.get('margin_ratio', 0.15),
'large_mask_threshold': toggle_states.get('large_mask_threshold', 0.5),
# 🔥 마스크 정제 비활성화 (마스크 모듈에서 이미 최적화됨)
'enable_mask_refinement': toggle_states.get('enable_mask_refinement', False),
'mask_erosion_kernel': 0, # 비활성화
'mask_dilation_kernel': 0, # 비활성화
'mask_blur_kernel': 0, # 비활성화
'context_expansion_ratio': toggle_states.get('context_expansion_ratio', 0.1), # 줄임
'blend_mode': toggle_states.get('blend_mode', 'simple'), # 단순 블렌딩
'feather_blend_size': toggle_states.get('feather_blend_size', 5), # 줄임
# 🔥 형상 최적화 설정
'enable_shape_optimization': toggle_states.get('enable_shape_optimization', True),
'performance_tracking': toggle_states.get('performance_tracking', True),
}
# 처리 전 통계 로깅
stats = roi_inpainter.get_processing_stats(src_bgr, mask)
clogger.log(
f"[INPAINT] ROI 처리 통계: {stats['num_components']}개 컴포넌트 → "
f"{stats['num_merged_rois']}개 ROI, 메모리 효율성: {stats['memory_efficiency']*100:.1f}%",
level=logging.INFO
)
# ROI 기반 인페인팅 실행
dst_bgr = roi_inpainter.inpaint_with_roi(src_bgr, mask, config=roi_config)
# 🔥 경량 메모리 정리 - 중간 처리 데이터만 정리, 모델은 유지
_lightweight_memory_cleanup(trace_id)
# 인페인트 후 (렌더 전)
save_debug_artifacts(debug_dir, guid, inpaint=dst_bgr, _stage="INPAINT")

View File

@ -0,0 +1,585 @@
# -*- coding: utf-8 -*-
"""
MIGAN ONNX 파이프라인 인페인팅 모듈
- MIGAN ONNX 모델을 사용한 전체 이미지 인페인팅
- TensorRT 최적화 지원
- 세션 캐시 버퍼 재사용
- 이미지 크기 제한 후처리
"""
import os
import sys
import time
import logging
import tempfile
from typing import Optional, Dict, Any, Tuple
from pathlib import Path
import cv2
import numpy as np
import onnxruntime as ort
# OpenCV 내부 최적화 off (호환성)
cv2.setUseOptimized(False)
def _np_uint8_2d(arr, name="mask"):
"""2D uint8 배열 검증 및 변환"""
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
def _ensure_logger(logger: Optional[object]) -> logging.Logger:
"""로거 어댑터 생성"""
if logger and hasattr(logger, "log"):
return logger
pylogger = logging.getLogger("MIGAN")
if not pylogger.handlers:
pylogger.setLevel(logging.DEBUG)
h = logging.StreamHandler(stream=sys.stdout)
h.setFormatter(logging.Formatter("[%(asctime)s][%(levelname)s] %(message)s"))
pylogger.addHandler(h)
class _Adapter:
def __init__(self, _lg):
self._lg = _lg
def log(self, msg, level=logging.INFO, **kwargs):
self._lg.log(level, msg)
return _Adapter(pylogger)
class MIGANInpaintingModule:
"""
MIGAN ONNX 파이프라인 인페인팅 모듈
- 전체 이미지 처리 (ROI 비활성화)
- TensorRT 최적화 지원
- 세션 캐시 메모리 효율성
"""
_SESSION_CACHE = {} # 세션 캐시
_TRT_ENGINE_CACHE_DIR = None # TensorRT 엔진 캐시 디렉토리
def __init__(self, logger=None, config: Dict[str, Any] = None):
"""
MIGAN 인페인팅 모듈 초기화
Args:
logger: 로깅 객체
config: 설정 딕셔너리
"""
self.logger = _ensure_logger(logger)
# 기본 설정
self.default_config = {
'onnx_path': '/app/worker/models/migan_pipeline_v2.onnx',
'use_cuda': True,
'use_tensorrt': True,
'trt_fp16_enable': True,
'trt_engine_cache_enable': True,
'intra_threads': 0,
'inter_threads': 0,
'max_image_size': 1600, # 최대 한 변 제한
'output_max_width': 800, # 출력 가로 최대 크기
'enable_output_resize': True, # 출력 리사이즈 활성화
}
if config:
self.default_config.update(config)
# TensorRT 엔진 캐시 디렉토리 설정
if self.default_config['trt_engine_cache_enable']:
self._setup_trt_cache_dir()
self.session = None
self.input_names = None
self.output_names = None
self.logger.log("MIGAN 인페인팅 모듈 초기화 완료", level=logging.INFO)
def update_config(self, new_config: Dict[str, Any]):
"""
설정을 동적으로 업데이트
Args:
new_config: 새로운 설정 딕셔너리
"""
old_config = self.default_config.copy()
self.default_config.update(new_config)
# 세션에 영향을 주는 설정이 변경되었는지 확인
session_affecting_keys = {
'onnx_path', 'use_cuda', 'use_tensorrt',
'trt_fp16_enable', 'intra_threads', 'inter_threads'
}
config_changed = any(
old_config.get(key) != self.default_config.get(key)
for key in session_affecting_keys
)
if config_changed:
self.logger.log("설정 변경으로 인한 세션 재초기화 필요", level=logging.INFO)
# 기존 세션 정보 리셋 (새로운 세션이 생성되도록)
self.session = None
self.input_names = None
self.output_names = None
# TensorRT 캐시 디렉토리 업데이트
if self.default_config['trt_engine_cache_enable']:
self._setup_trt_cache_dir()
self.logger.log(f"설정 업데이트 완료: {list(new_config.keys())}", level=logging.INFO)
def _setup_trt_cache_dir(self):
"""TensorRT 엔진 캐시 디렉토리 설정"""
if self._TRT_ENGINE_CACHE_DIR is None:
cache_dir = os.getenv('TRT_ENGINE_CACHE_DIR', '/app/temp_files/trt_cache')
os.makedirs(cache_dir, exist_ok=True)
self._TRT_ENGINE_CACHE_DIR = cache_dir
self.logger.log(f"TensorRT 엔진 캐시 디렉토리: {cache_dir}", level=logging.INFO)
def _get_session_key(self, config: Dict[str, Any]) -> tuple:
"""세션 캐시 키 생성"""
return (
config['onnx_path'],
config['use_cuda'],
config['use_tensorrt'],
config['trt_fp16_enable'],
config['intra_threads'],
config['inter_threads']
)
def _create_session(self, config: Dict[str, Any]) -> ort.InferenceSession:
"""ONNX Runtime 세션 생성"""
onnx_path = config['onnx_path']
if not os.path.exists(onnx_path):
raise FileNotFoundError(f"ONNX 파일을 찾을 수 없습니다: {onnx_path}")
# 세션 옵션 설정
session_options = ort.SessionOptions()
if config['intra_threads'] > 0:
session_options.intra_op_num_threads = config['intra_threads']
if config['inter_threads'] > 0:
session_options.inter_op_num_threads = config['inter_threads']
# 최적화 레벨 설정
session_options.graph_optimization_level = ort.GraphOptimizationLevel.ORT_ENABLE_ALL
# Provider 설정
providers = []
provider_options = []
if config['use_cuda']:
if config['use_tensorrt']:
# TensorRT Provider 설정
trt_options = {
'device_id': 0,
'trt_fp16_enable': config['trt_fp16_enable'],
'trt_engine_cache_enable': config['trt_engine_cache_enable'],
}
if config['trt_engine_cache_enable'] and self._TRT_ENGINE_CACHE_DIR:
trt_options['trt_engine_cache_path'] = self._TRT_ENGINE_CACHE_DIR
providers.append('TensorrtExecutionProvider')
provider_options.append(trt_options)
self.logger.log(f"TensorRT 설정: FP16={config['trt_fp16_enable']}, 캐시={config['trt_engine_cache_enable']}", level=logging.INFO)
# CUDA Provider
cuda_options = {
'device_id': 0,
'arena_extend_strategy': 'kNextPowerOfTwo',
'gpu_mem_limit': 2 * 1024 * 1024 * 1024, # 2GB
'cudnn_conv_algo_search': 'EXHAUSTIVE',
'do_copy_in_default_stream': True,
}
providers.append('CUDAExecutionProvider')
provider_options.append(cuda_options)
# CPU Provider (폴백)
providers.append('CPUExecutionProvider')
provider_options.append({})
try:
if provider_options:
session = ort.InferenceSession(
onnx_path,
sess_options=session_options,
providers=providers,
provider_options=provider_options
)
else:
session = ort.InferenceSession(
onnx_path,
sess_options=session_options,
providers=providers
)
actual_providers = session.get_providers()
self.logger.log(f"ONNX Runtime 세션 생성 완료: {actual_providers}", level=logging.INFO)
return session
except Exception as e:
self.logger.log(f"GPU 세션 생성 실패, CPU로 폴백: {e}", level=logging.WARNING)
# CPU 전용 폴백
session = ort.InferenceSession(
onnx_path,
sess_options=session_options,
providers=['CPUExecutionProvider']
)
self.logger.log("CPU 세션으로 폴백 완료", level=logging.INFO)
return session
def _get_or_create_session(self, config: Dict[str, Any]) -> ort.InferenceSession:
"""세션 가져오기 또는 생성 (캐시 활용)"""
cache_key = self._get_session_key(config)
if cache_key in self._SESSION_CACHE:
self.logger.log("캐시된 ONNX 세션 재사용", level=logging.DEBUG)
return self._SESSION_CACHE[cache_key]
session = self._create_session(config)
self._SESSION_CACHE[cache_key] = session
return session
def _prepare_session(self, config: Dict[str, Any]):
"""세션 및 입출력 정보 준비"""
if self.session is None:
self.session = self._get_or_create_session(config)
# 입출력 정보 저장
inputs = self.session.get_inputs()
outputs = self.session.get_outputs()
if len(inputs) != 2:
raise ValueError(f"MIGAN 모델은 2개의 입력이 필요합니다. 현재: {len(inputs)}")
if len(outputs) != 1:
raise ValueError(f"MIGAN 모델은 1개의 출력이 필요합니다. 현재: {len(outputs)}")
self.input_names = [inp.name for inp in inputs]
self.output_names = [out.name for out in outputs]
# 입출력 형태 로깅
for i, inp in enumerate(inputs):
self.logger.log(f"입력 {i}: {inp.name}, 형태: {inp.shape}, 타입: {inp.type}", level=logging.DEBUG)
for i, out in enumerate(outputs):
self.logger.log(f"출력 {i}: {out.name}, 형태: {out.shape}, 타입: {out.type}", level=logging.DEBUG)
def scale_image_if_needed(self, image: np.ndarray, mask: np.ndarray,
max_size: int) -> Tuple[np.ndarray, np.ndarray, Dict]:
"""
이미지 크기 제한 적용
Args:
image: 입력 이미지
mask: 입력 마스크
max_size: 최대 크기 ( 기준)
Returns:
(스케일된 이미지, 스케일된 마스크, 스케일 정보)
"""
h, w = image.shape[:2]
max_dimension = max(h, w)
if max_dimension <= max_size:
return image, mask, {'scaled': False, 'original_size': (h, w)}
# 스케일 계산
scale_factor = max_size / max_dimension
new_h = int(h * scale_factor)
new_w = int(w * scale_factor)
# 8의 배수로 조정 (ONNX 모델 호환성)
new_h = ((new_h + 7) // 8) * 8
new_w = ((new_w + 7) // 8) * 8
# 리사이즈
scaled_image = cv2.resize(image, (new_w, new_h), interpolation=cv2.INTER_AREA)
scaled_mask = cv2.resize(mask, (new_w, new_h), interpolation=cv2.INTER_NEAREST)
self.logger.log(
f"이미지 스케일링: {w}x{h}{new_w}x{new_h} (factor={scale_factor:.3f})",
level=logging.INFO
)
return scaled_image, scaled_mask, {
'scaled': True,
'original_size': (h, w),
'scale_factor': scale_factor,
'scaled_size': (new_h, new_w)
}
def restore_original_scale(self, image: np.ndarray, scale_info: Dict) -> np.ndarray:
"""
처리된 이미지를 원본 크기로 복원
Args:
image: 처리된 이미지
scale_info: 스케일 정보
Returns:
복원된 이미지
"""
if not scale_info.get('scaled', False):
return image
original_h, original_w = scale_info['original_size']
restored = cv2.resize(image, (original_w, original_h), interpolation=cv2.INTER_CUBIC)
self.logger.log(
f"이미지 복원: {image.shape[1]}x{image.shape[0]}{original_w}x{original_h}",
level=logging.INFO
)
return restored
def resize_output_if_needed(self, image: np.ndarray, max_width: int) -> np.ndarray:
"""
출력 이미지 크기 조정 (가로 기준)
Args:
image: 출력 이미지
max_width: 최대 가로 크기
Returns:
크기 조정된 이미지
"""
h, w = image.shape[:2]
if w <= max_width:
return image
# 비율 유지하며 리사이즈
scale_factor = max_width / w
new_w = max_width
new_h = int(h * scale_factor)
resized = cv2.resize(image, (new_w, new_h), interpolation=cv2.INTER_AREA)
self.logger.log(
f"출력 리사이즈: {w}x{h}{new_w}x{new_h} (max_width={max_width})",
level=logging.INFO
)
return resized
def inpaint_with_migan(self, image: np.ndarray, mask: np.ndarray,
config: Dict[str, Any] = None) -> np.ndarray:
"""
MIGAN을 사용한 전체 이미지 인페인팅
Args:
image: 입력 이미지 (BGR)
mask: 마스크 (0~255, 텍스트영역=255)
config: 설정 오버라이드
Returns:
인페인팅된 이미지 (BGR)
"""
start_time = time.time()
# 🔥 동적 설정 업데이트 지원
if config is not None:
self.update_config(config)
effective_config = self.default_config
try:
# 1. 세션 준비
self._prepare_session(effective_config)
# 2. 이미지 크기 제한
max_size = effective_config['max_image_size']
scaled_image, scaled_mask, scale_info = self.scale_image_if_needed(
image, mask, max_size
)
# 3. 임시 파일로 이미지 저장 (MIGAN 모듈이 파일 경로를 요구함)
with tempfile.NamedTemporaryFile(suffix='.png', delete=False) as tmp_file:
temp_path = tmp_file.name
cv2.imwrite(temp_path, scaled_image)
try:
# 4. 마스크 전처리: 이진화 → 반전 (MIGAN 규칙에 맞춤)
mask_uint8 = _np_uint8_2d(scaled_mask, "mask")
_, mask_bin = cv2.threshold(mask_uint8, 128, 255, cv2.THRESH_BINARY)
mask_known255 = 255 - mask_bin # 텍스트영역(255) → hole(0)
# 5. 이미지 로드 및 RGB 변환
bgr = cv2.imread(temp_path, cv2.IMREAD_COLOR)
if bgr is None:
raise ValueError(f"임시 이미지 로드 실패: {temp_path}")
rgb = cv2.cvtColor(bgr, cv2.COLOR_BGR2RGB)
H, W = rgb.shape[:2]
if mask_known255.shape != (H, W):
raise ValueError(f"마스크 크기 불일치: mask={mask_known255.shape}, img={(H,W)}")
# 6. ONNX 추론을 위한 배치 차원 추가 및 차원 순서 변경
inference_start = time.time()
# 이미지: (H, W, 3) → (1, 3, H, W)
rgb_batch = np.expand_dims(rgb, 0).transpose(0, 3, 1, 2).astype(np.uint8)
# 마스크: (H, W) → (1, 1, H, W)
mask_batch = np.expand_dims(mask_known255, (0, 1)).astype(np.uint8)
self.logger.log(
f"추론 입력 - 이미지: {rgb_batch.shape}, 마스크: {mask_batch.shape}",
level=logging.DEBUG
)
# ONNX 추론 실행
inputs = {
self.input_names[0]: rgb_batch,
self.input_names[1]: mask_batch
}
outputs = self.session.run(self.output_names, inputs)
result = outputs[0] # (1, 3, H, W) 형태 예상
inference_time = time.time() - inference_start
# 7. 출력 후처리: (1, 3, H, W) → (H, W, 3)
if result.ndim == 4 and result.shape[0] == 1:
result = result[0].transpose(1, 2, 0) # (1,3,H,W) → (H,W,3)
elif result.ndim == 3 and result.shape[0] == 3:
result = result.transpose(1, 2, 0) # (3,H,W) → (H,W,3)
if not isinstance(result, np.ndarray) or result.ndim != 3:
raise ValueError(f"출력 형식 오류: shape={result.shape}, dtype={result.dtype}")
# uint8 보장
if result.dtype != np.uint8:
result = np.clip(result, 0, 255).astype(np.uint8)
# 8. BGR 변환
result_bgr = cv2.cvtColor(result, cv2.COLOR_RGB2BGR)
# 9. 원본 크기로 복원
if scale_info['scaled']:
result_bgr = self.restore_original_scale(result_bgr, scale_info)
# 10. 출력 크기 조정 (옵션)
if effective_config.get('enable_output_resize', True):
max_width = effective_config.get('output_max_width', 800)
result_bgr = self.resize_output_if_needed(result_bgr, max_width)
total_time = time.time() - start_time
self.logger.log(
f"MIGAN 인페인팅 완료: 총 {total_time:.3f}s (추론: {inference_time:.3f}s)",
level=logging.INFO
)
return result_bgr
finally:
# 임시 파일 정리
try:
os.unlink(temp_path)
except:
pass
except Exception as e:
self.logger.log(f"MIGAN 인페인팅 실패: {e}", level=logging.ERROR)
import traceback
self.logger.log(traceback.format_exc(), level=logging.DEBUG)
return image # 실패 시 원본 반환
def cleanup_memory(self):
"""
🔥 경고: 전역 인스턴스에서는 메소드를 호출하지 마세요!
메모리 정리 (개발/테스트 환경에서만 사용)
"""
self.logger.log("⚠️ cleanup_memory 호출됨 - 전역 인스턴스에서는 권장하지 않음", level=logging.WARNING)
import gc
gc.collect()
try:
# GPU 메모리 정리
if hasattr(self, 'session') and self.session:
# ONNX Runtime은 자동으로 메모리를 관리하므로 특별한 정리 불필요
pass
except Exception as e:
self.logger.log(f"메모리 정리 중 오류: {e}", level=logging.WARNING)
self.logger.log("MIGAN 메모리 정리 완료", level=logging.INFO)
def get_processing_stats(self, image: np.ndarray, mask: np.ndarray) -> Dict[str, Any]:
"""
MIGAN 처리 통계 정보 반환
Args:
image: 입력 이미지
mask: 입력 마스크
Returns:
처리 통계 딕셔너리
"""
total_area = image.shape[0] * image.shape[1]
mask_area = np.sum(mask > 128)
return {
'total_image_size': total_area,
'mask_area': mask_area,
'mask_coverage_ratio': mask_area / total_area if total_area > 0 else 0.0,
'processing_method': 'full_image_migan',
'roi_processing': False,
'max_size_limit': self.default_config['max_image_size'],
'output_resize_enabled': self.default_config['enable_output_resize']
}
# 편의 함수들
def create_migan_inpainter(logger=None, config=None):
"""MIGAN 인페인팅 모듈 팩토리 함수"""
return MIGANInpaintingModule(logger, config)
def build_migan_from_toggle(toggle_states: Dict[str, Any], logger=None) -> MIGANInpaintingModule:
"""
toggle_states로부터 설정을 읽어 MIGAN 인페인팅 모듈 생성
Args:
toggle_states: 설정 딕셔너리
logger: 로거 객체
Returns:
MIGAN 인페인팅 모듈 인스턴스
"""
config = {
'onnx_path': toggle_states.get('migan_onnx_path', '/app/worker/models/migan_pipeline_v2.onnx'),
'use_cuda': toggle_states.get('migan_use_cuda', True),
'use_tensorrt': toggle_states.get('migan_use_tensorrt', True),
'trt_fp16_enable': toggle_states.get('migan_trt_fp16_enable', True),
'trt_engine_cache_enable': toggle_states.get('migan_trt_engine_cache_enable', True),
'intra_threads': int(toggle_states.get('migan_intra_threads', 0) or 0),
'inter_threads': int(toggle_states.get('migan_inter_threads', 0) or 0),
'max_image_size': int(toggle_states.get('migan_max_image_size', 1600)),
'output_max_width': int(toggle_states.get('migan_output_max_width', 800)),
'enable_output_resize': toggle_states.get('migan_enable_output_resize', True),
}
return MIGANInpaintingModule(logger, config)
def quick_migan_inpaint(image: np.ndarray, mask: np.ndarray,
logger=None, config=None) -> np.ndarray:
"""간단한 MIGAN 인페인팅 수행"""
inpainter = create_migan_inpainter(logger, config)
result = inpainter.inpaint_with_migan(image, mask, config)
inpainter.cleanup_memory()
return result

Binary file not shown.

View File

@ -0,0 +1,312 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
MIGAN 인페인팅 모듈 테스트 스크립트
스크립트는 MIGAN 모듈의 기본 사용법과 설정을 보여줍니다.
실제 운영 환경에서는 celery_worker.py를 통해 사용하세요.
"""
import os
import sys
import numpy as np
import cv2
from pathlib import Path
# 현재 디렉토리를 Python 경로에 추가
current_dir = Path(__file__).parent
sys.path.insert(0, str(current_dir))
try:
from migan_inpainting_module import MIGANInpaintingModule, build_migan_from_toggle, quick_migan_inpaint
from loggerModule import Logger
except ImportError as e:
print(f"모듈 import 실패: {e}")
print("worker 디렉토리에서 실행하거나 PYTHONPATH를 설정하세요.")
sys.exit(1)
def create_test_image_and_mask():
"""테스트용 이미지와 마스크 생성"""
# 테스트 이미지 생성 (800x600, 컬러)
image = np.random.randint(0, 255, (600, 800, 3), dtype=np.uint8)
# 중앙에 텍스트 영역 시뮬레이션 (여러 개의 사각형)
mask = np.zeros((600, 800), dtype=np.uint8)
# 텍스트 영역들 (마스크에서 255는 텍스트 영역)
rectangles = [
(100, 100, 200, 50), # x, y, w, h
(300, 200, 150, 40),
(500, 300, 180, 60),
(200, 400, 220, 45),
]
for x, y, w, h in rectangles:
mask[y:y+h, x:x+w] = 255
return image, mask
def test_basic_usage():
"""기본 사용법 테스트"""
print("=== MIGAN 인페인팅 모듈 기본 테스트 ===")
# 로거 생성
logger = Logger()
# 테스트 이미지 및 마스크 생성
print("1. 테스트 이미지 및 마스크 생성...")
image, mask = create_test_image_and_mask()
print(f" 이미지 크기: {image.shape}")
print(f" 마스크 크기: {mask.shape}")
print(f" 마스크 영역: {np.sum(mask > 0)} 픽셀")
# MIGAN 모듈 초기화
print("\n2. MIGAN 모듈 초기화...")
try:
# 기본 설정으로 모듈 생성
config = {
'onnx_path': '/app/worker/models/migan_pipeline_v2.onnx',
'use_cuda': False, # CPU 모드로 테스트
'use_tensorrt': False,
'max_image_size': 1024,
'output_max_width': 600,
}
inpainter = MIGANInpaintingModule(logger=logger, config=config)
print(" MIGAN 모듈 초기화 성공")
# 처리 통계 확인
print("\n3. 처리 통계 분석...")
stats = inpainter.get_processing_stats(image, mask)
for key, value in stats.items():
print(f" {key}: {value}")
# 인페인팅 실행 (모델 파일이 없으면 실패할 것임)
print("\n4. MIGAN 인페인팅 실행...")
try:
result = inpainter.inpaint_with_migan(image, mask)
print(f" 인페인팅 성공! 결과 이미지 크기: {result.shape}")
except FileNotFoundError:
print(" ⚠️ 모델 파일을 찾을 수 없습니다: /app/worker/models/migan_pipeline_v2.onnx")
print(" 모델 파일을 올바른 위치에 배치한 후 다시 시도하세요.")
except Exception as e:
print(f" 인페인팅 실패: {e}")
# 메모리 정리
inpainter.cleanup_memory()
except Exception as e:
print(f" MIGAN 모듈 초기화 실패: {e}")
def test_toggle_states_usage():
"""toggle_states를 사용한 설정 테스트"""
print("\n=== toggle_states 설정 테스트 ===")
# 로거 생성
logger = Logger()
# toggle_states 설정 예시들
test_configs = [
{
"name": "CPU 모드",
"toggle_states": {
"inpaint_method": "migan",
"migan_use_cuda": False,
"migan_use_tensorrt": False,
"migan_max_image_size": 800,
"migan_output_max_width": 600,
}
},
{
"name": "GPU 모드 (TensorRT 비활성화)",
"toggle_states": {
"inpaint_method": "migan",
"migan_use_cuda": True,
"migan_use_tensorrt": False,
"migan_max_image_size": 1600,
"migan_output_max_width": 800,
}
},
{
"name": "최고 성능 모드 (TensorRT + FP16)",
"toggle_states": {
"inpaint_method": "migan",
"migan_use_cuda": True,
"migan_use_tensorrt": True,
"migan_trt_fp16_enable": True,
"migan_trt_engine_cache_enable": True,
"migan_max_image_size": 2048,
"migan_output_max_width": 1200,
}
}
]
for i, config in enumerate(test_configs, 1):
print(f"\n{i}. {config['name']} 테스트")
print(f" 설정: {config['toggle_states']}")
try:
# toggle_states로부터 MIGAN 모듈 생성
inpainter = build_migan_from_toggle(config['toggle_states'], logger)
print(" 모듈 생성 성공")
# 설정 확인
print(f" CUDA 사용: {inpainter.default_config['use_cuda']}")
print(f" TensorRT 사용: {inpainter.default_config['use_tensorrt']}")
print(f" 최대 이미지 크기: {inpainter.default_config['max_image_size']}")
except Exception as e:
print(f" 모듈 생성 실패: {e}")
def test_quick_inpaint():
"""빠른 인페인팅 함수 테스트"""
print("\n=== 빠른 인페인팅 함수 테스트 ===")
# 테스트 이미지 생성
image, mask = create_test_image_and_mask()
# 빠른 인페인팅 설정
config = {
'use_cuda': False,
'use_tensorrt': False,
'max_image_size': 512,
}
print("1. quick_migan_inpaint 함수 실행...")
try:
result = quick_migan_inpaint(image, mask, config=config)
print(f" 성공! 결과 크기: {result.shape}")
except FileNotFoundError:
print(" ⚠️ 모델 파일을 찾을 수 없습니다.")
except Exception as e:
print(f" 실패: {e}")
def test_error_handling():
"""에러 처리 테스트"""
print("\n=== 에러 처리 테스트 ===")
logger = Logger()
# 잘못된 모델 경로로 테스트
print("1. 잘못된 모델 경로 테스트...")
config = {
'onnx_path': '/nonexistent/path/model.onnx',
'use_cuda': False,
}
try:
inpainter = MIGANInpaintingModule(logger=logger, config=config)
image, mask = create_test_image_and_mask()
result = inpainter.inpaint_with_migan(image, mask)
print(" 예상과 다르게 성공함")
except Exception as e:
print(f" 예상된 에러 발생: {type(e).__name__}")
# 잘못된 입력 데이터 테스트
print("\n2. 잘못된 입력 데이터 테스트...")
try:
config = {'use_cuda': False, 'use_tensorrt': False}
inpainter = MIGANInpaintingModule(logger=logger, config=config)
# 잘못된 형태의 이미지/마스크
bad_image = np.zeros((100, 100), dtype=np.uint8) # 2D 이미지
bad_mask = np.zeros((50, 50), dtype=np.uint8) # 다른 크기
result = inpainter.inpaint_with_migan(bad_image, bad_mask)
print(" 예상과 다르게 성공함")
except Exception as e:
print(f" 예상된 에러 발생: {type(e).__name__}")
def show_usage_examples():
"""사용 예시 출력"""
print("\n=== 사용 예시 ===")
print("\n1. Celery worker에서 ROI vs MIGAN 선택:")
print("""
# ROI 기반 인페인팅 (기본)
toggle_states = {
"inpaint_method": "roi"
}
# MIGAN 인페인팅
toggle_states = {
"inpaint_method": "migan",
"migan_use_cuda": True,
"migan_trt_fp16_enable": True
}
""")
print("\n2. 직접 MIGAN 모듈 사용:")
print("""
from worker.migan_inpainting_module import MIGANInpaintingModule
# 모듈 생성
inpainter = MIGANInpaintingModule(config={
'use_cuda': True,
'max_image_size': 1600
})
# 인페인팅 실행
result = inpainter.inpaint_with_migan(image, mask)
""")
print("\n3. toggle_states로 모듈 생성:")
print("""
from worker.migan_inpainting_module import build_migan_from_toggle
toggle_states = {
"migan_use_tensorrt": True,
"migan_trt_fp16_enable": True,
"migan_max_image_size": 2048
}
inpainter = build_migan_from_toggle(toggle_states)
""")
def main():
"""메인 테스트 실행"""
print("MIGAN 인페인팅 모듈 테스트 시작")
print("=" * 50)
# 환경 정보 출력
print(f"Python 버전: {sys.version}")
print(f"작업 디렉토리: {os.getcwd()}")
print(f"NumPy 버전: {np.__version__}")
print(f"OpenCV 버전: {cv2.__version__}")
# 모델 파일 존재 확인
model_path = "/app/worker/models/migan_pipeline_v2.onnx"
if os.path.exists(model_path):
print(f"✅ 모델 파일 발견: {model_path}")
else:
print(f"⚠️ 모델 파일 없음: {model_path}")
print(" 모델 파일을 배치한 후 테스트하면 실제 인페인팅이 실행됩니다.")
# 테스트 실행
try:
test_basic_usage()
test_toggle_states_usage()
test_quick_inpaint()
test_error_handling()
show_usage_examples()
except KeyboardInterrupt:
print("\n사용자에 의해 중단됨")
except Exception as e:
print(f"\n예상치 못한 에러: {e}")
import traceback
traceback.print_exc()
print("\n" + "=" * 50)
print("테스트 완료")
if __name__ == "__main__":
main()