LLM멀티모달 AI강의 5개 중 5번째난이도 중급

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

발행 읽는 시간 약 1 분

  • LLM
  • 멀티모달
  • 프로젝트
  • 접근성
  • Vision
  • TTS
  • FastAPI

강의 소개

멀티모달 AI 시리즈 총정리 프로젝트입니다. 이미지를 분석해 시각 장애인을 위한 상세 설명을 생성하고 음성으로 읽어주는 접근성 도우미를 만듭니다.

프로젝트 개요#

이미지를 업로드하면:

  1. Vision LLM이 상세하게 이미지를 설명
  2. TTS가 설명을 음성으로 변환
  3. 사용자가 음성으로 후속 질문 가능
Diagram · Mermaid

FastAPI 서버#

Python
# 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 클라이언트)#

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로 데이터를 탐색하고 시각화하는 방법을 배웁니다.

Follow · Ask · Work

이 글이 도움이 됐다면

짧은 작업 기록과 데모는 인스타그램에 올립니다. 만들고 싶은 것이 있다면 프로젝트 의뢰나 협업 제안도 편하게 보내 주세요.

글쓴이

파란여우

직접 만든 AI 앱·제품의 개발·배포를 공개하는 실전형 테크 크리에이터

파란여우 소개 Instagram @bluefox._.hi

이어서 보기

만들고 싶은 것이 있다면 프로젝트 의뢰로 알려 주세요.