이미지에서 물체지우고 배경으로 물체 있는 공간을 채우는 Image Inpainting를 위한 딥러닝 모델인 PixelHacker를 테스트해보기 위해 클로드를 사용해봤습니다. 

우선은 문서로 공개하고 준비가 되는대로 유튜브 영상으로 실행 과정과 결과를 공개하려고 합니다.

최초작성 2025. 6. 5

실행결과입니다. 왼쪽 원본 사진에 있던 고양이가 오른쪽 결과 사진에선 제거된 것을 볼 수 있습니다. 

항상 좋은 결과를 보여주는 것은 아닙니다.

이제 실행하기 위해 필요한 부분을 설명합니다. 

우분투에서 다음 과정을 거쳐 PixelHacker를 테스트하기 위해 필요한 파이썬 패키지를 설치했습니다. 

우선 다음 포스트를 사용하여 우분투에 개발환경을 만드세요. ( 윈도우에선 예상치 못한 에러가 날 수 있습니다. )

Visual Studio Code와 Miniconda를 사용한 Python 개발 환경 만들기( Windows, Ubuntu, WSL2)

https://webnautes.kr/visual-studio-codewa-minicondareul-sayonghan-python-gaebal-hwangyeong-mandeulgi-windows-ubuntu-wsl2/ 

이제 파이썬 가상환경을 생성하고 활성화합니다.

conda create -n pixelhacker python=3.10

conda activate pixelhacker

PixelHacker 깃허브 저장소를 다운로드합니다.

git clone https://github.com/hustvl/PixelHacker.git

다운로드 받은 폴더로 이동합니다.

cd PixelHacker

이제 필요한 패키지를 설치해볼 차례입니다.

우선 GPU를 사용하도록 파이토치와 CUDA를 설치합니다.

pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu118

이제 나머지 패키지를 설치합니다.

pip install accelerate diffusers einops lightning lpips  numpy omegaconf opencv-python  PyWavelets PyYAML safetensors timm tokenizers toml transformers triton

fla를 따로 설치해야 했습니다.

pip install git+https://github.com/sustcsonglin/flash-linear-attention.git 

추가로 C언어 컴파일러가 필요해서 설치해야 합니다.

sudo apt install build-essential

이제 클로드를 사용하여 작성한 다음 코드를 테스트해볼 수 있습니다. 

import cv2
import numpy as np
import torch
from PIL import Image
import os
import sys
from pathlib import Path
import requests
from tqdm import tqdm
import subprocess
import tempfile
import shutil
import datetime
import time

# =============================================================================
# 설정 변수
# =============================================================================
IMAGE_PATH = "person2.png"  # 사용할 이미지 파일 경로
MODEL_TYPE = "pretrained"  # ft_places2, ft_celebahq, ft_ffhq, pretrained 중 선택
MAX_WIDTH = 512  # 최대 이미지 너비 (픽셀) - 속도 최적화를 위해 512로 변경
FAST_MODE = True  # 빠른 모드 활성화

class PixelHackerDownloader:
    def __init__(self):
        self.base_url = "https://huggingface.co/hustvl/PixelHacker/resolve/main"
       
    def download_file(self, url, output_path, description="파일"):
        try:
            os.makedirs(os.path.dirname(output_path), exist_ok=True)
           
            if os.path.exists(output_path):
                print(f"✅ 파일이 이미 존재합니다: {output_path}")
                return True
           
            print(f"📥 {description} 다운로드 중...")
           
            response = requests.get(url, stream=True)
            response.raise_for_status()
           
            total_size = int(response.headers.get('content-length', 0))
           
            with open(output_path, 'wb') as f, tqdm(
                desc=description,
                total=total_size,
                unit='B',
                unit_scale=True,
                unit_divisor=1024,
            ) as pbar:
                for chunk in response.iter_content(chunk_size=8192):
                    if chunk:
                        f.write(chunk)
                        pbar.update(len(chunk))
           
            print(f"✅ 다운로드 완료: {output_path}")
            return True
           
        except Exception as e:
            print(f"❌ 다운로드 실패: {e}")
            return False
   
    def download_required_files(self, model_type):
        downloads = [
            (f"{self.base_url}/vae/diffusion_pytorch_model.bin", "vae/diffusion_pytorch_model.bin", "VAE 모델"),
            (f"{self.base_url}/vae/config.json", "vae/config.json", "VAE 설정"),
            (f"{self.base_url}/{model_type}/diffusion_pytorch_model.bin",
            f"weight/{model_type}/diffusion_pytorch_model.bin", f"{model_type} 모델")
        ]
       
        success_count = 0
        for url, path, desc in downloads:
            if self.download_file(url, path, desc):
                success_count += 1
       
        return success_count == len(downloads)

class OptimizedPixelHackerInteractive:
    def __init__(self, image_path, model_type="ft_places2", max_width=512, fast_mode=True):
        self.image_path = image_path
        self.model_type = model_type
        self.max_width = max_width
        self.fast_mode = fast_mode
        self.original_image = None
        self.current_image = None
        self.mask = None
        self.drawing = False
        self.start_point = None
        self.brush_size = 10
        self.selection_mode = "rectangle"  # rectangle, brush, magic_wand
        self.processing = False
        self.last_update_time = 0
        self.show_comparison = False  # 비교 창 표시 여부
        self.has_result = False  # 결과 이미지가 있는지 여부
       
        # 디바이스 설정 및 최적화
        self.device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
        print(f"🖥️  사용 디바이스: {self.device}")
        print(f"🎯 선택된 모델: {model_type}")
        if fast_mode:
            print("⚡ 고속 모드 활성화")
            self.optimize_gpu_settings()
       
        # 파일 설정
        self.setup_files()
       
        # 이미지 로드
        self.load_image()
       
        # PixelHacker 실행 가능 여부 확인
        self.check_pixelhacker_availability()
   
    def optimize_gpu_settings(self):
        """GPU 설정 최적화"""
        if torch.cuda.is_available():
            # GPU 메모리 정리
            torch.cuda.empty_cache()
           
            # cuDNN 벤치마크 모드 (입력 크기가 일정할 때 효과적)
            torch.backends.cudnn.benchmark = True
           
            # 메모리 최적화 설정
            torch.backends.cuda.matmul.allow_tf32 = True
            torch.backends.cudnn.allow_tf32 = True
           
            print("🚀 GPU 최적화 설정 완료")
        else:
            print("💻 CPU 모드에서 실행")
   
    def cleanup_memory(self):
        """메모리 정리"""
        if torch.cuda.is_available():
            torch.cuda.empty_cache()
       
        # 임시 파일 정리
        temp_dirs = ['imgs', 'masks', 'outputs']
        for temp_dir in temp_dirs:
            if os.path.exists(temp_dir):
                for file in os.listdir(temp_dir):
                    file_path = os.path.join(temp_dir, file)
                    try:
                        if os.path.isfile(file_path) and file.startswith('temp_'):
                            # 5분 이상 된 임시 파일만 삭제
                            if time.time() - os.path.getctime(file_path) > 300:
                                os.remove(file_path)
                    except:
                        pass
   
    def setup_files(self):
        """파일 설정 및 다운로드"""
        required_files = [
            f"weight/{self.model_type}/diffusion_pytorch_model.bin",
            "vae/diffusion_pytorch_model.bin",
            "vae/config.json",
            "config/PixelHacker_sdvae_f8d4.yaml"
        ]
       
        missing_files = [f for f in required_files if not Path(f).exists()]
       
        if missing_files:
            print(f"❌ {len(missing_files)}개 파일이 누락되었습니다.")
            print("자동 다운로드를 시작합니다...\n")
           
            downloader = PixelHackerDownloader()
            if downloader.download_required_files(self.model_type):
                print("✅ 모든 파일 다운로드 완료!\n")
            else:
                print("❌ 일부 파일 다운로드 실패")
        else:
            print("✅ 모든 필요 파일들이 존재합니다.\n")
   
    def check_pixelhacker_availability(self):
        """PixelHacker 사용 가능 여부 확인"""
        if Path("infer_pixelhacker.py").exists():
            print("✅ PixelHacker infer_pixelhacker.py 발견!")
            self.pixelhacker_available = True
        else:
            print("❌ infer_pixelhacker.py를 찾을 수 없습니다.")
            self.pixelhacker_available = False
   
    def load_image(self):
        """이미지 로드 및 크기 조정"""
        self.original_image = cv2.imread(self.image_path)
        if self.original_image is None:
            raise ValueError(f"이미지를 로드할 수 없습니다: {self.image_path}")
       
        # 원본 크기 기록
        original_height, original_width = self.original_image.shape[:2]
        print(f"📷 원본 이미지 크기: {original_width}x{original_height}")
       
        # 빠른 모드에서는 더 작은 크기로 제한
        if self.fast_mode and self.max_width > 512:
            self.max_width = 512
            print("⚡ 빠른 모드: 최대 너비를 512px로 제한")
       
        # 최대 너비로 제한
        if original_width > self.max_width:
            # 비율 유지하며 리사이즈
            ratio = self.max_width / original_width
            new_width = self.max_width
            new_height = int(original_height * ratio)
           
            self.original_image = cv2.resize(self.original_image, (new_width, new_height),
                                          interpolation=cv2.INTER_LANCZOS4)
            print(f"🔄 이미지 크기 조정: {new_width}x{new_height} (비율: {ratio:.2f})")
        else:
            print("✅ 이미지 크기가 적절합니다.")
       
        self.current_image = self.original_image.copy()
        height, width = self.original_image.shape[:2]
        self.mask = np.zeros((height, width), dtype=np.uint8)
        print(f"📷 최종 이미지 로드 완료: {self.image_path} ({width}x{height})")
   
    def create_comparison_view(self):
        """원본과 결과 이미지를 나란히 배치한 비교 이미지 생성"""
        if not self.has_result:
            return self.original_image
       
        # 이미지 크기 맞추기
        height, width = self.original_image.shape[:2]
       
        # 경계선 두께
        border_width = 3
       
        # 나란히 배치할 캔버스 생성
        comparison_width = width * 2 + border_width
        comparison_image = np.zeros((height, comparison_width, 3), dtype=np.uint8)
       
        # 원본 이미지 배치 (왼쪽)
        comparison_image[:, :width] = self.original_image
       
        # 경계선 그리기
        comparison_image[:, width:width+border_width] = [255, 255, 255]
       
        # 결과 이미지 배치 (오른쪽)
        comparison_image[:, width+border_width:] = self.current_image
       
        # 텍스트 추가
        font = cv2.FONT_HERSHEY_SIMPLEX
        font_scale = 0.7
        font_thickness = 2
        text_color = (255, 255, 255)
        shadow_color = (0, 0, 0)
       
        # 원본 텍스트
        original_text = "Original"
        text_size = cv2.getTextSize(original_text, font, font_scale, font_thickness)[0]
        text_x = (width - text_size[0]) // 2
        text_y = 30
       
        # 그림자 효과
        cv2.putText(comparison_image, original_text, (text_x+2, text_y+2),
                  font, font_scale, shadow_color, font_thickness+1)
        cv2.putText(comparison_image, original_text, (text_x, text_y),
                  font, font_scale, text_color, font_thickness)
       
        # 결과 텍스트
        result_text = "Result"
        text_size = cv2.getTextSize(result_text, font, font_scale, font_thickness)[0]
        text_x = width + border_width + (width - text_size[0]) // 2
       
        # 그림자 효과
        cv2.putText(comparison_image, result_text, (text_x+2, text_y+2),
                  font, font_scale, shadow_color, font_thickness+1)
        cv2.putText(comparison_image, result_text, (text_x, text_y),
                  font, font_scale, text_color, font_thickness)
       
        return comparison_image
   
    def show_original_preview(self):
        """원본 이미지를 3초간 미리 보여주기"""
        print("📷 원본 이미지를 먼저 보여드립니다...")
       
        # 원본 이미지에 텍스트 추가
        preview_image = self.original_image.copy()
        font = cv2.FONT_HERSHEY_SIMPLEX
        text = "Original Image - Loading..."
        font_scale = 0.8
        font_thickness = 2
        text_color = (255, 255, 255)
        shadow_color = (0, 0, 0)
       
        text_size = cv2.getTextSize(text, font, font_scale, font_thickness)[0]
        text_x = (preview_image.shape[1] - text_size[0]) // 2
        text_y = preview_image.shape[0] - 30
       
        # 그림자 효과
        cv2.putText(preview_image, text, (text_x+2, text_y+2),
                  font, font_scale, shadow_color, font_thickness+1)
        cv2.putText(preview_image, text, (text_x, text_y),
                  font, font_scale, text_color, font_thickness)
       
        cv2.imshow('PixelHacker Interactive', preview_image)
        start_time = time.time()
        while time.time() - start_time < 3.0:
            if cv2.waitKey(30) & 0xFF == ord('q'):
                break

        print("✅ 이제 편집을 시작할 수 있습니다!")
   
    def create_precise_mask(self, raw_mask):
        """더 정밀한 마스크 생성"""
        # 빠른 모드에서는 간단한 처리만
        if self.fast_mode:
            kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (3, 3))
            cleaned_mask = cv2.morphologyEx(raw_mask, cv2.MORPH_CLOSE, kernel)
            return cv2.GaussianBlur(cleaned_mask, (3, 3), 1)
       
        # 일반 모드에서는 정밀 처리
        kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (3, 3))
        cleaned_mask = cv2.morphologyEx(raw_mask, cv2.MORPH_CLOSE, kernel)
        cleaned_mask = cv2.morphologyEx(cleaned_mask, cv2.MORPH_OPEN, kernel)
       
        # 가장자리 부드럽게 처리
        smooth_mask = cv2.GaussianBlur(cleaned_mask, (5, 5), 1)
       
        return smooth_mask
   
    def magic_wand_select(self, x, y, tolerance=30):
        """매직 완드 스타일 선택"""
        height, width = self.original_image.shape[:2]
       
        # 시드 포인트의 색상 값
        seed_color = self.original_image[y, x]
       
        # 플러드 필 마스크 생성
        flood_mask = np.zeros((height + 2, width + 2), np.uint8)
       
        # 플러드 필 실행
        cv2.floodFill(
            self.original_image.copy(),
            flood_mask,
            (x, y),
            (255, 255, 255),
            (tolerance, tolerance, tolerance),
            (tolerance, tolerance, tolerance),
            cv2.FLOODFILL_MASK_ONLY
        )
       
        # 마스크 추출 (경계 제거)
        magic_mask = flood_mask[1:-1, 1:-1]
       
        # 기존 마스크와 결합
        self.mask = cv2.bitwise_or(self.mask, magic_mask)
       
        print(f"🪄 매직 완드 선택 완료 (허용 오차: {tolerance})")
   
    def smart_edge_select(self, x, y):
        """스마트 에지 기반 선택"""
        gray = cv2.cvtColor(self.original_image, cv2.COLOR_BGR2GRAY)
       
        # 에지 검출
        edges = cv2.Canny(gray, 50, 150)
       
        # 에지를 기반으로 한 영역 분할
        kernel = np.ones((3, 3), np.uint8)
        dilated_edges = cv2.dilate(edges, kernel, iterations=1)
       
        # 클릭 지점 주변의 닫힌 영역 찾기
        contours, _ = cv2.findContours(255 - dilated_edges, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
       
        for contour in contours:
            if cv2.pointPolygonTest(contour, (x, y), False) >= 0:
                cv2.fillPoly(self.mask, [contour], 255)
                break
       
        print("🎯 스마트 에지 선택 완료")
   
    def enhanced_mouse_callback(self, event, x, y, flags, param):
        """향상된 마우스 콜백"""
        if event == cv2.EVENT_LBUTTONDOWN:
            if flags & cv2.EVENT_FLAG_CTRLKEY:
                # Ctrl + 클릭: 매직 완드 선택
                self.magic_wand_select(x, y)
                self.update_display()
            elif flags & cv2.EVENT_FLAG_ALTKEY:
                # Alt + 클릭: 스마트 에지 선택
                self.smart_edge_select(x, y)
                self.update_display()
            else:
                self.drawing = True
                self.start_point = (x, y)
               
        elif event == cv2.EVENT_MOUSEMOVE:
            if self.drawing:
                if flags & cv2.EVENT_FLAG_SHIFTKEY or self.selection_mode == "brush":
                    # 브러시 모드
                    cv2.circle(self.mask, (x, y), self.brush_size, 255, -1)
                    self.update_display()
                elif self.selection_mode == "rectangle":
                    # 사각형 미리보기
                    temp_image = self.current_image.copy()
                    cv2.rectangle(temp_image, self.start_point, (x, y), (0, 255, 0), 2)
                    cv2.imshow('PixelHacker Interactive', temp_image)
               
        elif event == cv2.EVENT_LBUTTONUP:
            if self.drawing:
                self.drawing = False
                if self.selection_mode == "rectangle" and not (flags & cv2.EVENT_FLAG_SHIFTKEY):
                    end_point = (x, y)
                    cv2.rectangle(self.mask, self.start_point, end_point, 255, -1)
                    print("🎯 사각형 영역 선택 완료. Enter키를 눌러 처리하세요.")
                    self.update_display()
                   
                    # 즉시 inpainting 수행 부분을 제거하거나 조건부로 만들기
                    # self.perform_enhanced_inpainting()  # 이 줄을 제거하거나 주석 처리
       
        elif event == cv2.EVENT_RBUTTONDOWN:
            # 우클릭: 마스크 지우기 (지우개)
            cv2.circle(self.mask, (x, y), self.brush_size, 0, -1)
            self.update_display()
   
    def update_display(self):
        """최적화된 화면 업데이트"""
        # 화면 업데이트 빈도 제한 (20FPS)
        current_time = time.time()
        if current_time - self.last_update_time < 0.05:
            return
        self.last_update_time = current_time
       
        # 비교 모드일 때
        if self.show_comparison and self.has_result:
            comparison_image = self.create_comparison_view()
            cv2.imshow('PixelHacker Interactive', comparison_image)
        else:
            # 일반 모드
            display_image = self.current_image.copy()
           
            # 마스크가 있을 때만 오버레이 표시
            if np.any(self.mask > 0):
                # 마스크 영역을 반투명하게 표시
                mask_colored = np.zeros_like(self.current_image)
                mask_colored[self.mask > 0] = [0, 255, 0# 초록색
               
                alpha = 0.2  # 투명도 낮춤 (더 연하게)
                display_image = cv2.addWeighted(display_image, 1-alpha, mask_colored, alpha, 0)
           
            cv2.imshow('PixelHacker Interactive', display_image)
       

   
    def perform_enhanced_inpainting(self):
        """향상된 inpainting"""
        if self.processing:
            print("⚠️ 이미 처리 중입니다...")
            return
       
        if not np.any(self.mask > 0):
            print("⚠️ 마스크 영역이 없습니다.")
            return
       
        self.processing = True
        print("🎨 향상된 Inpainting 시작...")
       
        # 마스크 전처리
        processed_mask = self.create_precise_mask(self.mask)
       
        if self.pixelhacker_available:
            print("🤖 PixelHacker AI 모델 사용")
            result = self.optimized_pixelhacker_inpaint(processed_mask)
        else:
            print("⚠️  빠른 OpenCV inpainting 사용")
            result = self.fast_opencv_inpaint(processed_mask)
       
        self.current_image = result
        self.has_result = True
       
        # 마스크 초기화 (처리 완료 후)
        self.mask = np.zeros(self.original_image.shape[:2], dtype=np.uint8)
       
        print("✅ Inpainting 완료!")
       
        # 즉시 비교모드로 전환 (대기 없음)
        self.show_comparison = True
        self.update_display()
        print("👁️ 비교 모드로 표시됨 (Space키로 토글 가능)")
       
        self.cleanup_memory()
        self.processing = False
   
    def optimized_pixelhacker_inpaint(self, mask):
        """최적화된 PixelHacker 실행"""
        try:
            # 디렉토리 생성
            os.makedirs("imgs", exist_ok=True)
            os.makedirs("masks", exist_ok=True)
            os.makedirs("outputs", exist_ok=True)
           
            # 임시 파일명 생성
            timestamp = int(datetime.datetime.now().timestamp())
            base_name = f"temp_{timestamp}"
           
            # 입력 파일 준비
            input_image_path = f"imgs/{base_name}.jpg"
            input_mask_path = f"masks/{base_name}.jpg"
           
            # 빠른 모드에서는 전처리 최소화
            if self.fast_mode:
                enhanced_image = self.current_image
                quality = 85
            else:
                enhanced_image = cv2.bilateralFilter(self.current_image, 9, 75, 75)
                quality = 95
           
            # 파일 저장
            cv2.imwrite(input_image_path, enhanced_image, [cv2.IMWRITE_JPEG_QUALITY, quality])
            cv2.imwrite(input_mask_path, mask)
           
            print(f"📁 이미지 저장: {input_image_path}")
            print(f"📁 마스크 저장: {input_mask_path}")
           
            # PixelHacker 실행
            cmd = [
                sys.executable, "infer_pixelhacker.py",
                "--config", "config/PixelHacker_sdvae_f8d4.yaml",
                "--weight", f"weight/{self.model_type}/diffusion_pytorch_model.bin",
                "--image_dir", "imgs",
                "--mask_dir", "masks",
                "--output_dir", "outputs"
            ]
           
            print(f"🚀 PixelHacker 실행: {' '.join(cmd)}")
           
            # 환경 변수 설정
            env = os.environ.copy()
            if torch.cuda.is_available():
                env['CUDA_VISIBLE_DEVICES'] = '0'
           
            result = subprocess.run(
                cmd,
                capture_output=True,
                text=True,
                env=env,
                timeout=None  # 타임아웃 없음으로 변경
            )
           
            if result.returncode == 0:
                # 결과 로드
                output_path = f"outputs/{base_name}.jpg"
                if os.path.exists(output_path):
                    result_image = cv2.imread(output_path)
                    if result_image is not None:
                        # 크기 조정
                        if result_image.shape[:2] != self.current_image.shape[:2]:
                            result_image = cv2.resize(result_image,
                                                    (self.current_image.shape[1], self.current_image.shape[0]))
                       
                        # 임시 파일 정리
                        for temp_file in [input_image_path, input_mask_path, output_path]:
                            try:
                                os.remove(temp_file)
                            except:
                                pass
                       
                        return result_image
               
                # 대체 파일 찾기
                output_files = list(Path("outputs").glob("*.jpg")) + list(Path("outputs").glob("*.png"))
                if output_files:
                    latest_file = max(output_files, key=os.path.getctime)
                    result_image = cv2.imread(str(latest_file))
                    if result_image is not None:
                        if result_image.shape[:2] != self.current_image.shape[:2]:
                            result_image = cv2.resize(result_image,
                                                    (self.current_image.shape[1], self.current_image.shape[0]))
                        return result_image
           
            print("❌ PixelHacker 실행 실패, OpenCV로 대체")
            return self.fast_opencv_inpaint(mask)
               
        except Exception as e:
            print(f"❌ PixelHacker 오류: {e}")
            return self.fast_opencv_inpaint(mask)
   
    def fast_opencv_inpaint(self, mask):
        """최적화된 OpenCV inpainting"""
        try:
            # 마스크 검증
            if not np.any(mask > 0):
                print("⚠️ 마스크가 비어있습니다.")
                return self.current_image
           
            print(f"🎯 마스크 영역: {np.sum(mask > 0)} 픽셀")
           
            # 빠른 모드에서는 단일 방법만 사용
            if self.fast_mode:
                result = cv2.inpaint(self.current_image, mask, 3, cv2.INPAINT_TELEA)
                print("⚡ 빠른 모드 inpainting 완료")
            else:
                # 일반 모드에서는 더 나은 결과를 위해 두 방법 시도
                result1 = cv2.inpaint(self.current_image, mask, 3, cv2.INPAINT_TELEA)
                result2 = cv2.inpaint(self.current_image, mask, 3, cv2.INPAINT_NS)
               
                # 더 나은 결과 선택 (여기서는 NS 방법 사용)
                result = result2
                print("🎯 정밀 모드 inpainting 완료")
           
            # 후처리
            if not self.fast_mode:
                result = self.post_process_result(result, mask)
           
            return result
           
        except Exception as e:
            print(f"❌ OpenCV inpainting 오류: {e}")
            return self.current_image

    def clear_mask_after_processing(self):
        """처리 완료 후 마스크 자동 클리어"""
        if self.has_result:
            self.mask = np.zeros(self.original_image.shape[:2], dtype=np.uint8)
            print("🧹 마스크가 자동으로 클리어되었습니다.")
           
    def post_process_result(self, result, mask):
        """결과 후처리"""
        # 빠른 모드에서는 후처리 최소화
        if self.fast_mode:
            return result
       
        # 경계 블렌딩
        kernel = np.ones((5, 5), np.uint8)
        dilated_mask = cv2.dilate(mask, kernel, iterations=2)
       
        # 블렌딩을 위한 가중치 생성
        blend_mask = cv2.GaussianBlur(dilated_mask.astype(np.float32), (15, 15), 5) / 255.0
        blend_mask = np.stack([blend_mask] * 3, axis=2)
       
        # 부드러운 블렌딩
        blended = (self.current_image.astype(np.float32) * (1 - blend_mask) +
                  result.astype(np.float32) * blend_mask)
       
        return blended.astype(np.uint8)
   


    def reset_image(self):
      """이미지 리셋"""
      self.current_image = self.original_image.copy()
      self.mask = np.zeros(self.original_image.shape[:2], dtype=np.uint8)
      self.has_result = False
      self.show_comparison = False
      self.update_display()
      print("🔄 이미지가 리셋되었습니다.")
 
    def save_result(self, output_path):
        """결과 저장"""
        os.makedirs(os.path.dirname(output_path), exist_ok=True)
       
        if self.show_comparison and self.has_result:
            # 비교 이미지 저장
            comparison_image = self.create_comparison_view()
            comparison_path = output_path.replace('.jpg', '_comparison.jpg')
            cv2.imwrite(comparison_path, comparison_image, [cv2.IMWRITE_JPEG_QUALITY, 95])
            print(f"💾 비교 이미지가 저장되었습니다: {comparison_path}")
       
        # 결과 이미지만 저장
        cv2.imwrite(output_path, self.current_image, [cv2.IMWRITE_JPEG_QUALITY, 95])
        print(f"💾 결과가 저장되었습니다: {output_path}")
   
    def change_brush_size(self, delta):
        """브러시 크기 변경"""
        self.brush_size = max(1, min(50, self.brush_size + delta))
        print(f"🖌️ 브러시 크기: {self.brush_size}")
   
    def toggle_fast_mode(self):
        """빠른 모드 토글"""
        self.fast_mode = not self.fast_mode
        if self.fast_mode:
            print("⚡ 빠른 모드 활성화")
            self.optimize_gpu_settings()
        else:
            print("🎯 정밀 모드 활성화")
   
    def toggle_comparison_view(self):
        """비교 보기 토글"""
        if self.has_result:
            self.show_comparison = not self.show_comparison
            if self.show_comparison:
                print("👁️ 비교 모드: 원본 vs 결과")
            else:
                print("🎨 편집 모드: 결과 이미지만")
            self.update_display()
        else:
            print("⚠️ 아직 결과 이미지가 없습니다.")
   
    def run(self):
        """메인 실행 루프"""
        cv2.namedWindow('PixelHacker Interactive', cv2.WINDOW_AUTOSIZE)
        cv2.setMouseCallback('PixelHacker Interactive', self.enhanced_mouse_callback)
       
        # 원본 이미지 미리보기
        self.show_original_preview()
       
        self.update_display()
       
        print("="*70)
        print("🚀 최적화된 PixelHacker Interactive Inpainting")
        print("="*70)
        print("사용법:")
        print("🖱️  마우스 조작:")
        print("   • 좌클릭 + 드래그: 사각형 선택 → 즉시 처리")
        print("   • Shift + 드래그: 브러시 모드")
        print("   • Ctrl + 클릭: 매직 완드 (유사 색상 선택)")
        print("   • Alt + 클릭: 스마트 에지 선택")
        print("   • 우클릭: 지우개 (마스크 제거)")
        print()
        print("⌨️  키보드 단축키:")
        print("   'r': 이미지 리셋")
        print("   's': 결과 저장")
        print("   'c': 마스크 지우기")
        print("   'Enter': 선택된 영역 인페인팅 실행"
        print("   'Space': 비교 모드 토글 (원본 vs 결과)")
        print("   '+/-': 브러시 크기 조정")
        print("   'b': 브러시 모드 토글")
        print("   'f': 빠른/정밀 모드 토글")
        print("   't': PixelHacker 테스트")
        print("   'o': 원본 이미지 다시 보기")
        print("   'h': 도움말 표시")
        print("   'q': 종료")
        print("="*70)
       
        if self.pixelhacker_available:
            print("🤖 PixelHacker AI 모델 사용 가능!")
        else:
            print("⚠️  OpenCV 모드로만 실행")
       
        print(f"🖌️ 현재 브러시 크기: {self.brush_size}")
        print(f"⚡ 빠른 모드: {'ON' if self.fast_mode else 'OFF'}")
        print()
       
        while True:
            key = cv2.waitKey(1) & 0xFF
           
            if key == ord('q'):
                print("👋 프로그램을 종료합니다.")
                break
            elif key == 13 or key == ord('\r'):  # Enter 키
                if np.any(self.mask > 0):
                    print("🚀 인페인팅 시작...")
                    self.perform_enhanced_inpainting()
                else:
                    print("⚠️ 먼저 영역을 선택하세요.")
            elif key == ord('r'):
                self.reset_image()
            elif key == ord('s'):
                timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
                output_path = f"outputs/optimized_result_{timestamp}.jpg"
                self.save_result(output_path)
            elif key == ord('c'):
                self.mask = np.zeros(self.original_image.shape[:2], dtype=np.uint8)
                self.update_display()
                print("🧹 마스크가 지워졌습니다.")
            elif key == ord(' '):  # 스페이스바: 비교 모드 토글
                self.toggle_comparison_view()
            elif key == ord('o'):  # 원본 이미지 다시 보기
                print("📷 원본 이미지 표시")
                cv2.imshow('PixelHacker Interactive', self.original_image)
                cv2.waitKey(2000)
                self.update_display()
            elif key == ord('+') or key == ord('='):
                self.change_brush_size(2)
            elif key == ord('-'):
                self.change_brush_size(-2)
            elif key == ord('b'):
                self.selection_mode = "brush" if self.selection_mode != "brush" else "rectangle"
                print(f"🎯 선택 모드: {self.selection_mode}")
            elif key == ord('f'):
                self.toggle_fast_mode()
            elif key == ord('t'):
                print("🧪 PixelHacker 테스트 실행...")
                self.test_pixelhacker()
            elif key == ord('h'):
                self.show_help()
            elif key == ord('u'):  # 강제 업데이트
                print("🔄 화면 강제 업데이트")
                self.update_display()
               
        cv2.destroyAllWindows()
   
    def show_help(self):
        """도움말 표시"""
        help_text = """
        🚀 최적화된 PixelHacker Interactive 도움말
       
        마우스 조작:
        • 좌클릭 + 드래그: 사각형 영역 선택 → 즉시 처리
        • Shift + 드래그: 브러시로 그리기
        • Ctrl + 클릭: 매직 완드 (비슷한 색상 자동 선택)
        • Alt + 클릭: 스마트 에지 선택
        • 우클릭: 지우개 (마스크 제거)
       
        키보드 단축키:
        • 'r': 원본 이미지로 리셋
        • 's': 현재 결과 저장 (비교 모드일 때 비교 이미지도 저장)
        • 'c': 마스크 전체 지우기
        • 'Space': 비교 모드 토글 (원본 vs 결과)
        • 'o': 원본 이미지 2초간 표시
        • '+/-': 브러시 크기 증가/감소
        • 'b': 브러시/사각형 모드 전환
        • 'f': 빠른/정밀 모드 토글
        • 't': PixelHacker 연결 테스트
        • 'h': 이 도움말 표시
        • 'q': 프로그램 종료
       
        새로운 기능:
        • 🖼️  원본 이미지 미리보기: 시작 시 3초간 원본 표시
        • 👁️  비교 모드: 원본과 결과를 나란히 비교
        • 💾 비교 이미지 저장: 's' 키로 비교 이미지도 함께 저장
        • 🎯 즉시 처리: 사각형 선택 완료 시 자동으로 inpainting 실행
        • ⚡ 자동 비교 전환: 처리 완료 후 2초 뒤 비교 모드로 전환
       
        최적화 기능:
        • 빠른 모드: 처리 속도 우선 (기본값)
        • 정밀 모드: 품질 우선
        • GPU 최적화: CUDA 활용 시 자동 최적화
        • 실시간 미리보기: 20FPS 제한으로 부드러운 동작
       
        팁:
        • 빠른 모드에서는 512px 이하 권장
        • 사각형을 그리면 바로 inpainting이 시작됩니다
        • 처리 완료 후 스페이스바로 원본과 비교할 수 있습니다
        • 매직 완드로 비슷한 색상을 한 번에 선택
        • GPU 메모리 부족 시 이미지 크기 줄이기
        • 처리 중에는 다른 작업을 기다려주세요
        """
        print(help_text)
   
    def test_pixelhacker(self):
        """PixelHacker 테스트"""
        try:
            cmd = [sys.executable, "infer_pixelhacker.py", "--help"]
            result = subprocess.run(cmd, capture_output=True, text=True, timeout=10)
            print("📋 PixelHacker 상태:")
            if result.returncode == 0:
                print("✅ PixelHacker 정상 작동")
            else:
                print("❌ PixelHacker 실행 오류")
            if result.stdout:
                print("출력:", result.stdout[:200] + "..." if len(result.stdout) > 200 else result.stdout)
            if result.stderr:
                print("오류:", result.stderr[:200] + "..." if len(result.stderr) > 200 else result.stderr)
        except Exception as e:
            print(f"❌ 테스트 실패: {e}")
   
    def get_performance_stats(self):
        """성능 통계 표시"""
        stats = {
            'GPU 사용 가능': torch.cuda.is_available(),
            '현재 디바이스': str(self.device),
            '빠른 모드': self.fast_mode,
            '이미지 크기': f"{self.current_image.shape[1]}x{self.current_image.shape[0]}",
            '처리 상태': '대기 중' if not self.processing else '처리 중',
            '결과 생성됨': self.has_result,
            '비교 모드': self.show_comparison,
        }
       
        if torch.cuda.is_available():
            try:
                stats['GPU 메모리 (총)'] = f"{torch.cuda.get_device_properties(0).total_memory / 1024**3:.1f} GB"
                stats['GPU 메모리 (사용중)'] = f"{torch.cuda.memory_allocated(0) / 1024**3:.1f} GB"
                stats['GPU 이름'] = torch.cuda.get_device_name(0)
            except:
                pass
       
        print("\n📊 성능 통계:")
        for key, value in stats.items():
            print(f"   {key}: {value}")
        print()

# =============================================================================
# 메인 실행
# =============================================================================
if __name__ == "__main__":
  try:
      print("🚀 최적화된 PixelHacker Interactive Inpainting!")
      print(f"📁 이미지: {IMAGE_PATH}")
      print(f"🎯 모델: {MODEL_TYPE}")
      print(f"📏 최대 너비: {MAX_WIDTH}px")
      print(f"⚡ 빠른 모드: {FAST_MODE}\n")
     
      if not os.path.exists(IMAGE_PATH):
          print(f"❌ 이미지 파일을 찾을 수 없습니다: {IMAGE_PATH}")
          sys.exit(1)
     
      inpainter = OptimizedPixelHackerInteractive(
          IMAGE_PATH,
          MODEL_TYPE,
          MAX_WIDTH,
          FAST_MODE
      )
     
      # 성능 통계 표시
      inpainter.get_performance_stats()
     
      inpainter.run()
     
  except Exception as e:
      print(f"❌ 오류 발생: {e}")
      import traceback
      traceback.print_exc()