import json import os import time import tempfile from PIL import Image import gradio as gr import logging from io import BytesIO from google import genai from google.genai import types # .env 파일에 저장된 환경변수 로드 (python-dotenv 설치 필요: pip install python-dotenv) from dotenv import load_dotenv load_dotenv() # 로깅 설정 (로그 레벨: DEBUG) logging.basicConfig(level=logging.DEBUG, format='%(asctime)s - %(levelname)s - %(message)s') logger = logging.getLogger(__name__) def save_binary_file(file_name, data): logger.debug(f"파일에 이진 데이터 저장 중: {file_name}") with open(file_name, "wb") as f: f.write(data) logger.debug(f"파일 저장 완료: {file_name}") def merge_images(person_img_path, product_img_path, background_img_path, prompt, model="gemini-2.0-flash-exp-image-generation"): logger.debug(f"merge_images 함수 시작 - 프롬프트: '{prompt}'") try: # API 키는 환경변수에서 불러옴 effective_api_key = os.environ.get("GEMINI_API_KEY") if effective_api_key: logger.debug("환경변수에서 API 키 불러옴") else: logger.error("API 키가 환경변수에 설정되지 않았습니다.") raise ValueError("API 키가 필요합니다.") client = genai.Client(api_key=effective_api_key) logger.debug("Gemini 클라이언트 초기화 완료.") # PIL 이미지 객체로 변환 person_img = Image.open(person_img_path) product_img = Image.open(product_img_path) # 컨텐츠 리스트 생성 contents = [person_img, product_img] # 배경 이미지가 있으면 추가 if background_img_path: background_img = Image.open(background_img_path) contents.append(background_img) logger.debug("배경 이미지 추가됨") # 마지막에 프롬프트 추가 contents.append(prompt) logger.debug(f"컨텐츠 객체 생성 완료: {len(contents)} 아이템") # 생성 설정 generate_content_config = types.GenerateContentConfig( temperature=1, top_p=0.95, top_k=40, max_output_tokens=8192, response_modalities=["text", "image"], ) logger.debug(f"생성 설정: {generate_content_config}") with tempfile.NamedTemporaryFile(suffix=".png", delete=False) as tmp: temp_path = tmp.name logger.debug(f"임시 파일 생성됨: {temp_path}") # 단일 요청으로 이미지 생성 response = client.models.generate_content( model=model, contents=contents, config=generate_content_config, ) logger.debug("응답 처리 시작...") # 응답에서 이미지와 텍스트 추출 image_saved = False response_text = "" for part in response.candidates[0].content.parts: if hasattr(part, 'text') and part.text: response_text += part.text logger.info(f"수신된 텍스트: {part.text}") elif hasattr(part, 'inline_data') and part.inline_data: save_binary_file(temp_path, part.inline_data.data) logger.info(f"MIME 타입 {part.inline_data.mime_type}의 파일이 저장됨: {temp_path}") image_saved = True if not image_saved: logger.warning("이미지가 생성되지 않았습니다.") return None, response_text logger.debug("이미지 생성 완료.") return temp_path, response_text except Exception as e: logger.exception("이미지 생성 중 오류 발생:") return None, str(e) # 오류 발생 시 None과 오류 메시지 반환 def process_images_and_prompt(person_pil, product_pil, background_pil, prompt): logger.debug(f"process_images_and_prompt 함수 시작 - 프롬프트: '{prompt}'") try: # 기본 프롬프트 설정 (비어있는 경우) if not prompt or not prompt.strip(): if background_pil: prompt = "이 배경에 이 사람이 이 상품을 사용하는 모습을 자연스럽게 보여주세요. 상품을 잘 보이게 해주세요. Create a natural composite image showing this person using this product in this background setting. Make sure the product is clearly visible." else: prompt = "이 사람이 이 상품을 사용하는 모습을 자연스럽게 보여주세요. 상품을 잘 보이게 해주세요. Create a natural composite image showing this person using this product. Make sure the product is clearly visible." # 프롬프트에 영어가 없으면 영어 프롬프트 추가 (더 나은 결과를 위해) if not any(ord(c) < 128 for c in prompt): if background_pil: prompt += " Create a realistic composite image of this person with this product in this background." else: prompt += " Create a realistic composite image of this person with this product." # 이미지 저장 with tempfile.NamedTemporaryFile(suffix=".png", delete=False) as tmp_person: person_path = tmp_person.name person_pil.save(person_path) logger.debug(f"사람 이미지 저장 완료: {person_path}") with tempfile.NamedTemporaryFile(suffix=".png", delete=False) as tmp_product: product_path = tmp_product.name product_pil.save(product_path) logger.debug(f"상품 이미지 저장 완료: {product_path}") # 배경 이미지 저장 (있는 경우) background_path = None if background_pil is not None: with tempfile.NamedTemporaryFile(suffix=".png", delete=False) as tmp_bg: background_path = tmp_bg.name background_pil.save(background_path) logger.debug(f"배경 이미지 저장 완료: {background_path}") # 이미지 합성 실행 result_path, response_text = merge_images( person_img_path=person_path, product_img_path=product_path, background_img_path=background_path, prompt=prompt ) # 이미지 반환 및 임시 파일 정리 if result_path: logger.debug(f"이미지 생성 완료. 경로: {result_path}") result_img = Image.open(result_path) if result_img.mode == "RGBA": result_img = result_img.convert("RGB") # 임시 파일 정리 try: os.unlink(person_path) os.unlink(product_path) if background_path: os.unlink(background_path) except Exception as e: logger.warning(f"임시 파일 삭제 중 오류: {str(e)}") return [result_img], response_text else: logger.error("merge_images 함수에서 None 반환됨.") return [], response_text # 오류 시 빈 리스트 반환 except Exception as e: logger.exception("process_images_and_prompt 함수에서 오류 발생:") return [], str(e) # 오류 시 빈 리스트와 오류 메시지 반환 # --- Gradio 인터페이스 구성 --- with gr.Blocks() as demo: gr.HTML( """

Gemini를 이용한 이미지 합성

사람, 상품, 배경 이미지를 합성하는 애플리케이션입니다.

""" ) gr.Markdown("사람 이미지, 상품 이미지, 배경 이미지를 업로드하고, 어떻게 합성할지 설명해주세요.") with gr.Row(): with gr.Column(): person_input = gr.Image(type="pil", label="사람 이미지 (필수)", image_mode="RGB") product_input = gr.Image(type="pil", label="상품 이미지 (필수)", image_mode="RGB") background_input = gr.Image(type="pil", label="배경 이미지 (선택 사항)", image_mode="RGB") prompt_input = gr.Textbox( lines=2, placeholder="합성 방법을 설명해주세요. (예: '이 배경에서 이 사람이 상품을 사용하는 모습' 또는 '이 사람이 이 상품을 들고 이 배경에 서 있는 모습')", label="합성 방법 설명" ) submit_btn = gr.Button("이미지 합성 실행") with gr.Column(): output_gallery = gr.Gallery(label="합성 결과") output_text = gr.Textbox(label="AI 응답 텍스트", visible=True) submit_btn.click( fn=process_images_and_prompt, inputs=[person_input, product_input, background_input, prompt_input], outputs=[output_gallery, output_text], ) gr.HTML("""

사용 팁:

""") # --- 실행 --- if __name__ == "__main__": demo.launch(share=True)