픽셀에서 칼로리까지: GPT-4o Vision으로 고정밀 식사 추적기 만들기

발행: (2026년 2월 22일 오전 10:40 GMT+9)
7 분 소요
원문: Dev.to

Source: Dev.to

wellallyTech

소개

솔직히 말하자면, 칼로리 계산은 최악이에요. 🍕 우리 모두 그런 경험이 있죠—레스토랑에서 “미스터리 파스타” 한 접시를 바라보며 파마산이 20 g인지 50 g인지 추측하는 상황. 전통적인 앱들은 “중간 사과”나 “대형 바나나” 같은 끝없는 데이터베이스를 검색하게 만들며, 완전히 분위기를 깨버립니다.

하지만 만약 당신의 휴대폰이 그냥 보는 것만으로 접시 위 상황을 정확히 파악한다면 어떨까요? 이 튜토리얼에서는 GPT‑4o Vision API, FastAPI, 그리고 React Native를 사용하여 고정밀 식단 분석 시스템을 구축할 것입니다. 우리는 멀티모달 AI와 고급 프롬프트 엔지니어링을 활용해 비구조화된 음식 사진을 구조화된 영양 데이터로 변환합니다.

만약 컴퓨터 비전, LLM 오케스트레이션, 그리고 구조화된 데이터 추출을 마스터하고 싶다면, 바로 여기입니다! 🚀

아키텍처: 이미지에서 인사이트까지

높은 정확성을 보장하기 위해 사진에 무엇이 있는지 AI에 단순히 “묻는” 것이 아니라, 우리는 부분 크기, 밀도, 숨겨진 재료(예: 오일 및 지방)를 고려한 다단계 추정 로직을 구현합니다.

graph TD
    A[React Native App] -->|Capture Image| B(FastAPI Backend)
    B -->|Image Processing| C{GPT‑4o Vision}
    C -->|Reasoning| D[Volume & Density Estimation]
    D -->|Structured JSON| E[PostgreSQL Database]
    E -->|Nutritional Summary| A
    C -.->|Reference Data| F[Nutritional DB]

사전 요구 사항

  • GPT‑4o API 키 (OpenAI)
  • FastAPI 백엔드용
  • React Native (Expo) 모바일 인터페이스용
  • PostgreSQL 지속적인 로깅용

Step 1: 비밀 소스 (프롬프트)

“추측”과 “정밀도”의 차이는 프롬프트에 있습니다. 우리는 Chain‑of‑Thought (CoT) 접근 방식을 사용합니다: 칼로리를 묻는 대신 모델에게 구성 요소를 식별하고, 밀리리터/그램 단위로 부피를 추정한 뒤 매크로 영양소를 계산하도록 요청합니다.

SYSTEM_PROMPT = """
You are a professional nutritionist. Analyze the provided image and:
1. Identify every food item.
2. Estimate the portion size (weight in grams or volume in ml).
3. Calculate Calories, Protein, Carbs, and Fats.
4. Provide a confidence score (0‑1).

Return the data strictly in JSON format.
"""

2단계: FastAPI를 이용한 백엔드 구현

우리는 Pydantic을 사용하여 엄격한 스키마를 적용합니다. 이를 통해 AI가 응답을 “창의적으로” 만들려고 할 때 모바일 앱이 충돌하지 않도록 보장합니다.

from fastapi import FastAPI, UploadFile, File
from pydantic import BaseModel
import openai
import base64

app = FastAPI()

class NutritionResult(BaseModel):
    food_name: str
    calories: int
    protein: float
    carbs: float
    fat: float
    confidence: float

@app.post("/analyze-meal", response_model=list[NutritionResult])
async def analyze_meal(file: UploadFile = File(...)):
    # Convert image to base64
    contents = await file.read()
    base64_image = base64.b64encode(contents).decode("utf-8")

    response = openai.chat.completions.create(
        model="gpt-4o",
        messages=[
            {"role": "system", "content": SYSTEM_PROMPT},
            {"role": "user", "content": [
                {"type": "text", "text": "Analyze this meal:"},
                {"type": "image_url", "image_url": {"url": f"data:image/jpeg;base64,{base64_image}"}}
            ]}
        ],
        response_format={"type": "json_object"},
    )

    return response.choices[0].message.content

Step 3: React Native를 사용한 모바일 UI

프론트엔드에서는 사진을 촬영하고 “Nutritional Breakdown” 카드를 표시할 수 있는 깔끔한 인터페이스가 필요합니다. 🥑

import React, { useState } from "react";
import { View, Button, Image, Text } from "react-native";
import * as ImagePicker from "expo-image-picker";

export default function MealTracker() {
  const [image, setImage] = useState<string | null>(null);
  const [stats, setStats] = useState<any[] | null>(null);

  const pickImage = async () => {
    const result = await ImagePicker.launchCameraAsync({
      allowsEditing: true,
      aspect: [4, 3],
      quality: 0.8,
    });

    if (!result.canceled) {
      const uri = result.assets[0].uri;
      setImage(uri);
      uploadImage({ uri });
    }
  };

  const uploadImage = async (photo: { uri: string }) => {
    const formData = new FormData();
    // @ts-ignore – React Native expects this shape
    formData.append("file", {
      uri: photo.uri,
      name: "meal.jpg",
      type: "image/jpeg",
    });

    const res = await fetch("https://your-api.com/analyze-meal", {
      method: "POST",
      body: formData,
    });
    const data = await res.json();
    setStats(data);
  };

  return (
    <View>
      <Button title="Capture Meal" onPress={pickImage} />
      {image && <Image source={{ uri: image }} style={{ width: 200, height: 200 }} />}
      {stats && (
        <Text>
          Total Calories: {stats.reduce((acc: number, cur: any) => acc + cur.calories, 0)} kcal
        </Text>
      )}
    </View>
  );
}

고급 패턴 및 모범 사례 💡

위 구현은 MVP에는 작동하지만, 프로덕션 수준의 AI 애플리케이션은 보다 견고한 오류 처리, 속도 제한 및 캐싱이 필요합니다. 모든 스캔에 GPT‑4o를 사용하는 것은 비용이 많이 들 수 있으므로, 일반적인 음식 항목에 대한 로컬 캐시를 구현하는 것이 필수입니다.

프로 팁: 프로덕션 준비된 예제(예: “흐릿한 사진”이나 “여러 접시”와 같은 에지 케이스 처리 포함)를 보려면 **WellAlly Tech**의 고급 엔지니어링 가이드를 확인하세요.

결론

우리는 방금 원시 픽셀구조화된 건강 데이터 사이의 격차를 메웠습니다. GPT‑4o의 비전 기능과 견고한 FastAPI 백엔드를 결합하여, 실제 문제를 해결하는 도구를 만들었습니다: 건강 추적을 마찰 없이 만들기.

다음 단계는?

  • Fine‑tuning: 특정 요리를 위해 PostgreSQL 데이터를 사용해 작은 모델을 미세 조정하세요.
  • AR Overlay: React Native 카메라를 사용해 실시간으로 음식에 칼로리 표시를 오버레이하세요.

멀티모달 LLM으로 무엇을 만들고 있나요? 아래에 댓글을 남겨 주세요! 👇

Blog – AI 통합의 한계를 넓히고자 하는 개발자들에게 환상적인 자료입니다.

0 조회
Back to Blog

관련 글

더 보기 »