LLM멀티모달 AI · 5중급

멀티모달 실전 프로젝트 — 이미지 접근성 도우미

LLM멀티모달프로젝트접근성VisionTTSFastAPI

프로젝트 개요

이미지를 업로드하면:

  1. Vision LLM이 상세하게 이미지를 설명
  2. TTS가 설명을 음성으로 변환
  3. 사용자가 음성으로 후속 질문 가능
flowchart LR
    IMG["이미지 업로드"]
    VISION["GPT-4o\n이미지 분석"]
    DESC["상세 텍스트 설명"]
    TTS["TTS\n음성 합성"]
    AUDIO["음성 재생"]
    Q["음성 질문"]
    WHISPER["Whisper\n음성 인식"]
    CHAT["GPT-4o\n후속 답변"]

    IMG --> VISION --> DESC --> TTS --> AUDIO
    Q --> WHISPER --> CHAT --> TTS

FastAPI 서버

# main.py
from fastapi import FastAPI, File, UploadFile, Form
from fastapi.responses import StreamingResponse, FileResponse
import base64
import io
from openai import OpenAI

app = FastAPI()
client = OpenAI()

# 세션별 대화 이력 (실제로는 Redis 사용)
sessions: dict[str, list] = {}

@app.post("/analyze-image")
async def analyze_image(
    file: UploadFile = File(...),
    session_id: str = Form(default="default"),
    detail_level: str = Form(default="standard"),  # standard/detailed/brief
):
    """이미지를 분석하고 텍스트 설명 + 음성 반환"""
    image_data = await file.read()
    encoded = base64.b64encode(image_data).decode("utf-8")
    mime = file.content_type or "image/jpeg"

    # 상세도에 따른 프롬프트
    prompts = {
        "brief": "이 이미지를 한 문장으로 설명해주세요.",
        "standard": """이 이미지를 시각 장애인이 이해할 수 있도록 설명해주세요.
다음을 포함하세요:
- 전체적인 장면
- 주요 오브젝트와 위치
- 색상과 분위기
- 눈에 띄는 세부사항""",
        "detailed": """이 이미지를 시각 장애인을 위해 매우 상세하게 설명해주세요.
배경, 주제, 세부 사항, 텍스트 내용(있다면), 감정적 톤까지 포함해주세요.""",
    }

    # 대화 이력 초기화/유지
    if session_id not in sessions:
        sessions[session_id] = []

    messages = [
        {"role": "system", "content": "당신은 시각 장애인을 위한 이미지 설명 전문가입니다. 명확하고 생생하게 이미지를 설명합니다."},
        *sessions[session_id],
        {
            "role": "user",
            "content": [
                {"type": "text", "text": prompts[detail_level]},
                {"type": "image_url", "image_url": {
                    "url": f"data:{mime};base64,{encoded}"
                }},
            ]
        }
    ]

    # Vision 분석
    response = client.chat.completions.create(
        model="gpt-4o",
        messages=messages,
        max_tokens=500,
    )
    description = response.choices[0].message.content

    # 대화 이력 업데이트
    sessions[session_id].append({
        "role": "user",
        "content": [{"type": "text", "text": prompts[detail_level]}]
    })
    sessions[session_id].append({
        "role": "assistant",
        "content": description
    })

    # TTS 변환
    tts_response = client.audio.speech.create(
        model="tts-1",
        voice="nova",
        input=description,
    )

    return {
        "description": description,
        "audio_base64": base64.b64encode(tts_response.read()).decode("utf-8"),
        "session_id": session_id,
    }

@app.post("/follow-up")
async def follow_up_question(
    audio: UploadFile = File(...),
    session_id: str = Form(...),
):
    """음성 후속 질문 처리"""
    audio_data = await audio.read()

    # Whisper 음성 인식
    transcript = client.audio.transcriptions.create(
        model="whisper-1",
        file=("audio.webm", io.BytesIO(audio_data), "audio/webm"),
        language="ko",
    )
    question = transcript.text

    # 이전 대화 맥락 포함 답변
    if session_id not in sessions:
        return {"error": "세션을 찾을 수 없습니다."}

    messages = [
        {"role": "system", "content": "당신은 이미지에 대한 질문에 답하는 접근성 도우미입니다."},
        *sessions[session_id],
        {"role": "user", "content": question},
    ]

    response = client.chat.completions.create(
        model="gpt-4o",
        messages=messages,
        max_tokens=300,
    )
    answer = response.choices[0].message.content

    sessions[session_id].append({"role": "user", "content": question})
    sessions[session_id].append({"role": "assistant", "content": answer})

    tts = client.audio.speech.create(model="tts-1", voice="nova", input=answer)

    return {
        "question": question,
        "answer": answer,
        "audio_base64": base64.b64encode(tts.read()).decode("utf-8"),
    }

사용 예시 (Python 클라이언트)

import httpx
import base64
from pathlib import Path

BASE_URL = "http://localhost:8000"

async def analyze(image_path: str, session_id: str = "user1") -> dict:
    async with httpx.AsyncClient() as http:
        with open(image_path, "rb") as f:
            response = await http.post(
                f"{BASE_URL}/analyze-image",
                files={"file": (Path(image_path).name, f, "image/jpeg")},
                data={"session_id": session_id, "detail_level": "standard"},
            )
    result = response.json()

    # 음성 저장
    audio_bytes = base64.b64decode(result["audio_base64"])
    Path("description.mp3").write_bytes(audio_bytes)

    print(f"설명:\n{result['description']}")
    return result

# 사용
import asyncio
asyncio.run(analyze("photo.jpg"))

멀티모달 AI 시리즈 정리

주제
1편멀티모달 개요, 모달리티 종류
2편Vision LLM 이미지 이해
3편DALL-E 이미지 생성
4편Whisper 음성인식 + TTS
5편실전 프로젝트

멀티모달 AI는 텍스트 한계를 넘어 더 풍부한 사용자 경험을 만들 수 있습니다.

다음은 Python 데이터 분석 — NumPy, Pandas로 데이터를 탐색하고 시각화하는 방법을 배웁니다.

궁금한 점이 있으신가요?

협업·의뢰는 아래로, 가벼운 소통은 인스타그램 @bluefox._.hi도 환영이에요.