Gemini 를 활용한 냉장고 이미지 활용한 레시피 만들기

Posted by Albert 61Day 8Hour 45Min 19Sec ago [2025-12-07]

평소에 요리를 즐겨하는 사람으로서 쉽게 냉장고 열고 오늘은 뭘 만들지 하는 생각을 가끔 할 때가 있다.

그래서 간단히 냉장고 사진을 찍어 바로 AI한테 요청해서 원하는 레시피 만드는 앱 만들어봤다.

AI agent는 무료로 사용가능한 Gemini 1.5 Flash를 활용했다.

(심심풀이로 만드는거니깐 ^^)

기술 스택

  • Frontend: React (Vite), Vanilla CSS (VS Code 테마 스타일)
  • Backend: FastAPI (Python 3.10+), SQLAlchemy
  • Database: MariaDB
  • AI Model: Google Gemini 1.5 Flash
  • Image Source: Bing Image Search API (Thumbnail)

API 통신 내용

import os
import google.generativeai as genai
import PIL.Image
import json
from dotenv import load_dotenv


load_dotenv()


API_KEY = os.getenv("GEMINI_API_KEY")


genai.configure(api_key=API_KEY)
model = genai.GenerativeModel('gemini-flash-latest')


def analyze_fridge_image(image_paths: list[str], user_prompt: str = ""):
    imgs = []
    try:
        for path in image_paths:
            img = PIL.Image.open(path)
            ' Convert to RGB to ensure validation and supported format for Gemini
            img = img.convert('RGB')
            imgs.append(img)
    except Exception as e:
        return {"error": f"Failed to open image: {str(e)}"}


    prompt = f"""
    당신은 전문 AI 셰프입니다. 이 이미지들에 있는 재료들을 분석해주세요.
    사용자의 추가 정보: {user_prompt}
    
    1. 이미지들에서 식별된 재료들을 종합하여 나열해주세요.
    2. 그 재료들로 만들 수 있는 맛있는 요리를 추천해주세요. 사용자가 '3개', '2개' 등 구체적인 개수를 명시했다면 반드시 그 개수에 맞춰 추천하고, 명시하지 않았다면 기본적으로 3가지를 추천해주세요.
    3. 만약 사용자가 '한식', '양식' 등 특정 스타일을 요청했다면 그에 맞춰 추천해주세요.
    
    각 요리에 대해 다음 정보를 제공해주세요:
    - 요리 이름 (name)
    - 요리 이름의 영어 표기 (이미지 생성용) (english_name)
    - 재료 목록: 냉장고 재료와 기본 양념 등을 포함하며, 모든 재료(특히 소금, 설탕, 후추 등의 양념)에 대해 구체적인 계량 정보(예: 1티스푼, 10g, 1큰술 등)를 반드시 명시해주세요. (ingredients)
    - 상세 조리 순서 (1. 2. 3. 순서로 번호를 매겨서 체계적으로 작성) (instructions)
    
    결과는 반드시 다음 구조의 유효한 JSON 형식이어야 합니다:
    {{
      "detected_ingredients": ["식별된 재료1", "식별된 재료2", ...],
      "recipes": [
        {{
          "name": "요리 이름",
          "english_name": "Recipe Name in English",
          "ingredients": ["재료1", "재료2"],
          "instructions": "1. 첫 번째 단계...\\n2. 두 번째 단계..."
        }},
        ...
      ]
    }}
    
    markdown 포맷(```json 등)은 사용하지 말고 순수 JSON 문자열만 출력해주세요.
    모든 내용은 **한국어**로 작성하되, english_name 필드만 영문으로 작성해주세요.
    """
    
    try:
        import time
        
        max_retries = 3
        retry_delay = 2 ' seconds
        data = None
        
        for attempt in range(max_retries):
            try:
                ' Enforce JSON output using generation_config
                response = model.generate_content(
                    [prompt, *imgs],
                    generation_config={"response_mime_type": "application/json"}
                )
                text = response.text.strip()
                ' Handle cases where model might still wrap in markdown despite mime_type
                if text.startswith("```json"):
                    text = text.replace("```json", "", 1)
                if text.endswith("```"):
                    text = text.replace("```", "", 1)
                text = text.strip()
                
                data = json.loads(text)
                break ' Success, exit loop
            except Exception as e:
                error_str = str(e).lower()
                if attempt < max_retries - 1 and ("429" in error_str or "quota" in error_str or "limit" in error_str):
                    print(f"[AI Chef] Rate limit hit. Retrying in {retry_delay} seconds... (Attempt {attempt + 1}/{max_retries})")
                    time.sleep(retry_delay)
                    retry_delay *= 2 ' Exponential backoff
                else:
                    raise e ' Re-raise if not a rate limit issue or max retries reached
        
        if not data:
            return {"error": "Failed to generate recipes after retries."}


        ' Download images for each recipe
        import requests
        import uuid
        
        ' Ensure directory exists
        save_dir = os.path.join(os.path.dirname(__file__), "uploads", "foodImgs")
        os.makedirs(save_dir, exist_ok=True)
        
        if "recipes" in data:
            print(f"[AI Chef] Generated {len(data['recipes'])} recipes. Starting image downloads...")
            import urllib.parse
            for recipe in data["recipes"]:
                query = recipe.get("english_name", recipe["name"])
                ' Encode the ENTIRE prompt segment to handle spaces and special chars correctly
                full_prompt = f"{query} delicious food photorealistic"
                encoded_prompt = urllib.parse.quote(full_prompt)
                
                ' Use Bing Image Search (Thumbnail) for instant results
                ' "c=7" scales the image, "w" and "h" set dimensions.
                image_url = f"https://tse2.mm.bing.net/th?q={encoded_prompt}&w=800&h=500&c=7&rs=1&p=0"
                print(f"[AI Chef] Downloading image for {query}: {image_url}")
                
                try:
                    ' Short timeout (5s) is usually enough for these static assets
                    headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'}
                    img_resp = requests.get(image_url, headers=headers, timeout=5)
                    if img_resp.status_code == 200:
                        filename = f"{uuid.uuid4()}.jpg"
                        file_path = os.path.join(save_dir, filename)
                        with open(file_path, "wb") as f:
                            f.write(img_resp.content)
                        ' Store relative path for frontend
                        recipe["image_path"] = f"uploads/foodImgs/{filename}"
                        print(f"[AI Chef] Saved image to {file_path}")
                    else:
                        print(f"[AI Chef] Failed to download image. Status code: {img_resp.status_code}")
                except Exception as e:
                    print(f"[AI Chef] Failed to download image from {image_url}: {e}")
        else:
             print(f"[AI Chef] No recipes found in response data: {data.keys()}")
                    
        return data
    except Exception as e:
        print(f"Error in AI Agent: {e}")
        raw_text = response.text if 'response' in locals() else "No response"
        print(f"Raw Text: {raw_text}")
        return {"error": str(e), "raw": raw_text}


      

주요 기능

1. 사용자 인증 및 프로필

  • 사용자 가입, 로그인, 로그아웃 기능
  • 프로필 이미지 업로드 및 변경 기능
  • 개인화된 설정 관리

2. AI 냉장고 분석 및 레시피 생성 (AI Chef)

  • 사용자가 업로드한 식재료 사진을 Google Gemini AI가 분석하여 식별
  • 식별된 재료를 기반으로 사용자의 요청(예: "한식", "2개 추천")에 맞춘 맞춤형 레시피 생성
  • 정확한 계량 정보(그램, 스푼 등)가 포함된 상세 조리법 제공
  • 생성된 레시피에 어울리는 이미지를 Bing 검색을 통해 자동으로 매칭하여 제공

3. 나만의 요리책 (Your Cookbook)

  • 마음에 드는 레시피를 즐겨찾기에 추가하여 보관
  • 12개 단위의 무한 스크롤 기능을 통해 저장된 수많은 레시피를 효율적으로 탐색
  • 상세 레시피 조회 및 PDF 다운로드 기능

4. 요리 기록 (Cooking History)

  • 과거에 분석했던 모든 이미지와 결과 레시피를 자동으로 기록
  • 5개 단위의 무한 스크롤 기능으로 과거 기록 조회
  • 여러 장의 이미지를 업로드했던 기록도 확인 할 수 있고, 전체 화면 미리보기 기능 제공

GitHub: https://github.com/visionboy/AIRecipeCreator




LIST

Copyright © 2014 visionboy.me All Right Reserved.