Ostrakon-VL-8B를 활용한 음식점 소매 AI 프로그래밍 실전: 스마트 메뉴 인식 시스템 구축

Ostrakon-VL-8B를 활용한 음식점 소매 AI 프로그래밍 실전: 스마트 메뉴 인식 시스템 구축

최근 식음료 소프트웨어 개발자들과 대화를 나누며 시스템을 더욱 지능화하는 방법에 대해 논의했다. 고객이 메뉴 사진을 찍으면 시스템이 요리명, 가격, 조합 추천을 자동으로 인식하는 것이 목표였다. 이는 복잡해 보이지만, 다중 모달 대형 모델을 활용하면 간단히 구현 가능하다.

이번에는 Ostrakon-VL-8B 모델을 사용하여 스마트 메뉴 인식 시스템을 처음부터 구축하는 방법을 소개한다. AI 전문가가 아니어도 Python을 조금 알아야 하며, 몇 시간 내에 메뉴를 인식하는 AI 프로그램을 실행할 수 있다. 각 단계를 자세히 설명하며 환경 설정, 코드 작성, 일반적인 문제 해결 방법을 안내한다.

1. 환경 설정 및 빠른 배포

모델 실행 환경을 구축하는 과정부터 시작하자. 일반 소프트웨어 설치보다 복잡하지 않다.

1.1 시스템 요구사항 및 의존성 설치

Ostrakon-VL-8B는 하드웨어에 약간의 요구사항이 있다. 독립적인 그래픽 카드(선호는 NVIDIA, 메모리 8GB 이상)가 있다면 실행이 훨씬 원활할 것이다. CPU로도 가능하지만 속도가 느릴 수 있다.

먼저 Python 버전이 3.8 이상인지 확인하자. 터미널(Windows 사용자는 명령 프롬프트 또는 PowerShell)을 열고, 다른 프로젝트와 충돌하지 않도록 독립적인 Python 환경을 생성하자.

# Linux/Mac에서 가상 환경 생성 및 활성화
python -m venv ostra_env
source ostra_env/bin/activate

# Windows 사용자
# python -m venv ostra_env
# ostra_env\Scripts\activate

환경 활성화 후 필수 패키지를 설치하자. 가장 중요한 것은 PyTorch와 Transformers 라이브러리이다.

# PyTorch 설치 (CUDA 버전에 따라 선택, GPU가 없을 경우 CPU 버전)
pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu118

# Transformers 및 관련 시각 라이브러리 설치
pip install transformers pillow accelerate

pillow는 이미지를 처리하고, accelerate는 모델 실행을 가속화한다. 이 두 패키지를 설치하면 기본 환경은 완료된다.

1.2 모델 다운로드 및 로드

모델을 로컬에 다운로드하자. Ostrakon-VL-8B은 Hugging Face에서 직접 가져올 수 있다.

from transformers import AutoProcessor, AutoModelForVision2Seq
import torch

# 모델 이름 지정
model_name = "Otter-AI/Ostrakon-VL-8B"

# 프로세서 및 모델 로드
print("프로세서 로드 중...")
processor = AutoProcessor.from_pretrained(model_name)

print("모델 로드 중...")
model = AutoModelForVision2Seq.from_pretrained(
    model_name,
    torch_dtype=torch.float16,  # 반정밀도로 메모리 사용량 줄이기
    device_map="auto"  # 자동 장치 선택 (GPU 또는 CPU)
)

print("모델 로드 완료!")

이 코드를 처음 실행하면 인터넷에서 모델 파일을 다운로드하게 되며, 약 15~20GB의 디스크 공간이 필요하다. 네트워크 속도에 따라 다운로드 시간이 달라진다. 다운로드 후에는 다음부터는 로컬에서 바로 로드할 수 있다.

네트워크 문제가 있을 경우 다른 시간을 시도하거나, 국내 미러 소스를 확인해보자. 모델 로드가 성공하면 "모델 로드 완료!"와 같은 메시지가 표시된다.

2. 기본 개념 빠른 입문

코드를 작성하기 전에 Ostrakon-VL-8B가 어떻게 작동하는지 간단히 알아보자. 이렇게 하면 나중에 코드를 조정할 때 어떤 작업을 하고 있는지 알 수 있다.

2.1 모델이 할 수 있는 일

Ostrakon-VL-8B는 "다중 모달" 모델로, 이미지와 텍스트를 동시에 이해할 수 있다. 이미지를 제공하고 질문을 하면 모델이 이미지 내용에 따라 답변한다.

예를 들어 메뉴 사진을 전달하고 "이 메뉴에 어떤 요리가 있습니까?"라고 묻는다면, 요리 이름 목록을 제공할 수 있다. "가장 비싼 요리는 무엇입니까?"라고 묻는다면, 메뉴에서 찾을 수 있다. 이 능력은 음식점 시나리오에서 매우 유용하다. 고객이 사진을 찍으면 AI가 해설자 역할을 한다.

2.2 입력 및 출력 형식

모델과 대화하는 주요 요소는 이미지와 텍스트 프롬프트(프롬프트)이다. 이미지는 일반적인 JPG 또는 PNG 형식이며, 텍스트 프롬프트는 질문이다.

모델 처리 과정은 다음과 같다:

  1. 이미지와 질문을 프로세서에 전달
  2. 프로세서가 이미지를 모델이 이해할 수 있는 숫자 형식으로 변환하고, 텍스트도 숫자로 인코딩
  3. 모델이 이러한 숫자를 기반으로 "생각"하고 답변 생성
  4. 프로세서가 모델의 숫자 답변을 우리가 이해할 수 있는 텍스트로 변환

이 모든 과정은 코드로 몇 줄로 이루어져 있으며, 중간의 복잡한 수학 계산은 걱정할 필요 없다. 입력 준비와 출력 해석만 알면 된다.

3. 단계별 실습

이론보다 실습이 더 효과적이다. 이제 완전한 메뉴 인식 프로그램을 작성해보자. 이미지 읽기에서 결과 얻기까지 진행한다.

3.1 메뉴 이미지 준비

먼저 메뉴 사진이 필요하다. 스마트폰으로 실제 메뉴를 촬영하거나, 인터넷에서 메뉴 이미지를 다운로드할 수 있다. 시연을 위해 간단한 메뉴 이미지를 준비했다. 위에 몇 가지 요리와 가격이 있다.

이미지를 프로젝트 폴더에 저장하자. 예를 들어 menu.jpg라고 이름을 지어라. 이미지 내용은 가능한 한 명확하게, 글자 크기가 작지 않도록, 조명이 균일하도록 하자.这样 인식 효과가 더 좋다.

3.2 기본 인식 코드 작성

핵심 코드를 작성하자. 새로운 Python 파일을 생성하자, 예를 들어 menu_analyzer.py라고 이름을 지어라.

import torch
from PIL import Image
from transformers import AutoProcessor, AutoModelForVision2Seq

class MenuAnalyzer:
    def __init__(self, model_name="Otter-AI/Ostrakon-VL-8B"):
        """분석기 초기화, 모델 로드"""
        print("메뉴 분석기 초기화 중...")
        
        # 프로세서 및 모델 로드
        self.processor = AutoProcessor.from_pretrained(model_name)
        self.model = AutoModelForVision2Seq.from_pretrained(
            model_name,
            torch_dtype=torch.float16,
            device_map="auto"
        )
        
        print("분석기 준비 완료!")

    def analyze_menu(self, img_path, query):
        """메뉴 내용 분석"""
        # 1. 이미지 로드
        image = Image.open(img_path).convert("RGB")
        
        # 2. 프롬프트 준비
        # 모델에게 메뉴 분석 보조자 역할을 명확히 전달
        prompt = f"<image>사용자가 묻습니다: {query}. 메뉴 이미지 내용에 따라 답변하십시오."
        
        # 3. 입력 처리
        inputs = self.processor(
            text=[prompt],  # 질문 텍스트
            images=[image],  # 메뉴 이미지
            return_tensors="pt"  # PyTorch 텐서 반환
        ).to(self.model.device)
        
        # 4. 모델 추론 (답변 생성)
        with torch.no_grad():  # 그래디언트 계산 안 함, 추론 속도 향상
            generated_ids = self.model.generate(
                **inputs,
                max_new_tokens=512,  # 생성 최대 길이
                do_sample=False  # 샘플링 안 함, 직접 생성
            )
        
        # 5. 출력 해독
        generated_text = self.processor.batch_decode(
            generated_ids, 
            skip_special_tokens=True
        )[0]
        
        # 6. 모델 답변 추출 (우리의 프롬프트 부분 제거)
        # 모델 출력은 전체 대화를 포함하므로 마지막 부분만 추출
        answer = generated_text.split("답변:")[-1].strip()
        
        return answer
    
    def close(self):
        """자원 정리"""
        del self.model
        torch.cuda.empty_cache()  # GPU 캐시 정리
        print("자원 해제 완료")

# 사용 예시
if __name__ == "__main__":
    # 분석기 인스턴스 생성
    analyzer = MenuAnalyzer()
    
    try:
        # 메뉴 내용 분석
        img_path = "menu.jpg"  # 메뉴 이미지 경로
        query = "메뉴에 어떤 요리가 있습니까? 요리 이름과 가격을 나열해주세요."
        
        print(f"메뉴 분석 중: {img_path}")
        print(f"질문: {query}")
        print("-" * 50)
        
        answer = analyzer.analyze_menu(img_path, query)
        print(f"분석 결과: \n{answer}")
        
    finally:
        # 사용 후 자원 해제
        analyzer.close()

이 코드는 몇 가지 일을 수행한다:

  1. MenuAnalyzer 클래스를 정의하여 모델 로드 및 분석 로직을 캡슐화
  2. analyze_menu 메서드는 이미지 경로와 질문을 받아 분석 결과를 반환
  3. 프롬프트에서 모델이 메뉴 분석 보조자 역할을 수행하도록 명확히 전달
  4. 마지막 사용 예제는 이 분석기를 어떻게 호출할 수 있는지 보여준다

이 코드를 실행하면 모델이 메뉴를 인식하는 결과를 볼 수 있다. 처음 실행 시 모델이 메모리에 초기화되므로 약간 느릴 수 있다.

3.3 다양한 메뉴 질문 처리

메뉴 인식은 단순히 요리 목록을 만드는 것을 넘는다. 다음은 다른 질문 방식을 시험해보는 방법이다:

# 위의 if __name__ == "__main__": 부분에서 다음과 같이 테스트
if __name__ == "__main__":
    analyzer = MenuAnalyzer()
    
    try:
        img_path = "menu.jpg"
        
        # 다양한 질문 테스트
        queries = [
            "메뉴에 어떤 요리가 있습니까? 요리 이름과 가격을 나열해주세요.",
            "가장 비싼 요리는 무엇입니까? 가격은 얼마입니까?",
            "식물성 식사 옵션이 있습니까?",
            "추천할 만한 특별 요리는 무엇입니까?",
            "두 사람이 세 가지 요리를 주문할 경우 예상 비용은 얼마입니까?"
        ]
        
        for query in queries:
            print(f"\n질문: {query}")
            answer = analyzer.analyze_menu(img_path, query)
            print(f"답변: {answer}")
            print("-" * 50)
            
    finally:
        analyzer.close()

이렇게 하면 모델이 다양한 질문에 대한 답변을 볼 수 있다. 모델이 일부 질문에 대해 잘 대답하고, 일부는 더 명확한 프롬프트가 필요할 수 있음을 알 수 있다.

4. 빠른 시작 예제

효과를 더 빠르게 보고 싶다면, 클래스를 작성하지 않고 바로 실행할 수 있는 더 간단한 스크립트를 준비했다.

# quick_start.py - 빠른 시작 예제
import torch
from PIL import Image
from transformers import AutoProcessor, AutoModelForVision2Seq

# 1. 모델 로드 (한 번만 실행)
print("모델 로드 중...")
processor = AutoProcessor.from_pretrained("Otter-AI/Ostrakon-VL-8B")
model = AutoModelForVision2Seq.from_pretrained(
    "Otter-AI/Ostrakon-VL-8B",
    torch_dtype=torch.float16,
    device_map="auto"
)

# 2. 이미지 및 질문 준비
image = Image.open("menu.jpg").convert("RGB")
query = "메뉴에 어떤 요리가 있습니까?"

# 3. 프롬프트 구성
prompt = f"<image>이것은 메뉴 이미지입니다. 사용자가 묻습니다: {query} 메뉴를 자세히 살펴보고 답변하십시오."

# 4. 처리 및 추론
inputs = processor(text=[prompt], images=[image], return_tensors="pt").to(model.device)

with torch.no_grad():
    generated_ids = model.generate(**inputs, max_new_tokens=256)
    
# 5. 결과 가져오기
result = processor.batch_decode(generated_ids, skip_special_tokens=True)[0]
answer = result.split("답변:")[-1] if "답변:" in result else result

print(f"\n질문: {query}")
print(f"답변: {answer}")

# 6. 다른 질문 시도
query2 = "가장 저렴한 요리는 무엇입니까?"
prompt2 = f"<image>사용자가 묻습니다: {query2} 메뉴 가격을 확인하고 답변하십시오."

inputs2 = processor(text=[prompt2], images=[image], return_tensors="pt").to(model.device)

with torch.no_grad():
    generated_ids2 = model.generate(**inputs2, max_new_tokens=128)
    
result2 = processor.batch_decode(generated_ids2, skip_special_tokens=True)[0]
answer2 = result2.split("답변:")[-1] if "답변:" in result2 else result2

print(f"\n질문: {query2}")
print(f"답변: {answer2}")

이 스크립트는 더 직접적이다. menu.jpg를 자신의 메뉴 이미지로 교체하고 질문 내용을 수정하면 즉시 모델의 성능을 확인할 수 있다.

5. 실용 기술 및 고급 기법

일정 기간 사용한 후에는 일부 부분을 최적화할 수 있다. 여기 몇 가지 실용 기술을 공유한다. 이 기술들은 메뉴 인식 효과를 향상시킬 수 있다.

5.1 인식 정확도 향상

모델이 때때로 "오인"하거나 "확인하지 못"하는 경우가 있다. 특히 메뉴 배치가 복잡할 때 그렇다. 다음 방법들을 시도해보자:

모델에게 더 명확한 지시 제공: "어떤 요리가 있습니까?"라고 묻기보다 구체적인 작업을 지시하라.

# 나쁜 프롬프트
query = "메뉴에 무엇이 있습니까?"

# 더 나은 프롬프트
query = """이 메뉴 이미지를 자세히 살펴보고 다음 작업을 수행하십시오:
1. 모든 요리 이름 나열
2. 각 요리 가격 나열
3. 분류가 있는 경우(예: 주식, 음료, 디저트) 분류별로 조직화
명확한 구조로 답변하십시오."""

단계별 질문: 메뉴 내용이 많은 경우, 한 번에 너무 많은 질문을 하면 효과가 나쁠 수 있다. 다음 단계를 따르자:

  1. 먼저 분류가 무엇인지 묻는다
  2. 그 후 각 분류에 있는 요리를 묻는다
  3. 마지막으로 가격 정보를 묻는다

이미지 사전 처리: 메뉴 사진이 왜곡되거나 조명이 어두울 경우, 먼저 처리하자:

from PIL import Image, ImageEnhance

def preprocess_menu_image(image_path):
    """간단한 이미지 사전 처리"""
    img = Image.open(image_path)
    
    # 1. 크기 조정 (비율 유지)
    max_size = 1024
    if max(img.size) > max_size:
        ratio = max_size / max(img.size)
        new_size = tuple(int(dim * ratio) for dim in img.size)
        img = img.resize(new_size, Image.Resampling.LANCZOS)
    
    # 2. 대비 강화 (이미지가 어두울 경우)
    enhancer = ImageEnhance.Contrast(img)
    img = enhancer.enhance(1.2)  # 20% 강화
    
    # 3. 약간의 날카로움
    enhancer = ImageEnhance.Sharpness(img)
    img = enhancer.enhance(1.1)
    
    return img

# 사전 처리된 이미지 사용
processed_image = preprocess_menu_image("menu.jpg")

5.2 복잡한 메뉴 구조 처리

실제 메뉴는 여러 열, 이미지, 특수 기호를 포함할 수 있다. 이 경우 더 현명한 질문이 필요하다:

# 다중 열 메뉴용
query = """이 메뉴는 왼쪽과 오른쪽 두 열로 구성되어 있습니다. 먼저 왼쪽 열의 내용을 설명하고, 그 후 오른쪽 열의 내용을 설명하십시오.
왼쪽 열은 주요 음식과 주요 요리로 구성되어 있고, 오른쪽 열은 음료와 디저트로 구성되어 있습니다.
왼쪽 및 오른쪽 열의 요리 및 가격을 각각 나열하십시오."""

# 이미지가 있는 메뉴용
query = """이 메뉴에는 요리 이미지와 텍스트 설명이 포함되어 있습니다.
주로 텍스트 부분에 초점을 맞추고, 다음을 나열하십시오:
1. 요리 이름
2. 가격
3. 간단한 설명이 있다면 함께 나열
이미지의 음식은 무시하고, 텍스트 정보만 사용하십시오."""

5.3 실제 시스템에 통합

이 기능을 실제 주문 시스템에 통합하려면 몇 가지 공학 문제를 고려해야 한다.

비동기 처리: 모델 추론이 몇 초 걸릴 수 있으므로, 웹 서비스에서는 비동기 방식을 사용하여 차단을 방지해야 한다.

import asyncio
from concurrent.futures import ThreadPoolExecutor

class AsyncMenuAnalyzer:
    def __init__(self):
        self.executor = ThreadPoolExecutor(max_workers=1)
        # 동기적으로 모델 로드...
    
    async def analyze_async(self, img_path, query):
        """비동기 분석"""
        loop = asyncio.get_event_loop()
        # 스레드 풀에서 동기 분석 코드 실행
        result = await loop.run_in_executor(
            self.executor,
            self.analyze_menu,  # 이전에 작성한 동기 메서드
            img_path,
            query
        )
        return result

배치 처리: 여러 메뉴를 분석할 경우, 효율성을 높이기 위해 배치 처리가 가능하다.

def batch_analyze(self, img_query_pairs):
    """여러 메뉴 배치 분석"""
    results = []
    for img_path, query in img_query_pairs:
        try:
            result = self.analyze_menu(img_path, query)
            results.append((img_path, query, result, "success"))
        except Exception as e:
            results.append((img_path, query, str(e), "error"))
    return results

결과 캐시: 동일한 메뉴 이미지와 질문은 캐시하여 중복 계산을 피할 수 있다.

import hashlib
import pickle
import os

class CachedMenuAnalyzer(MenuAnalyzer):
    def __init__(self, cache_dir=".menu_cache"):
        super().__init__()
        self.cache_dir = cache_dir
        os.makedirs(cache_dir, exist_ok=True)
    
    def analyze_with_cache(self, img_path, query):
        """캐시된 분석"""
        # 캐시 키 생성
        with open(img_path, 'rb') as f:
            img_hash = hashlib.md5(f.read()).hexdigest()
        query_hash = hashlib.md5(query.encode()).hexdigest()
        cache_key = f"{img_hash}_{query_hash}.pkl"
        cache_path = os.path.join(self.cache_dir, cache_key)
        
        # 캐시 확인
        if os.path.exists(cache_path):
            with open(cache_path, 'rb') as f:
                return pickle.load(f)
        
        # 캐시 없음, 실제 분석
        result = self.analyze_menu(img_path, query)
        
        # 캐시 저장
        with open(cache_path, 'wb') as f:
            pickle.dump(result, f)
        
        return result

6. 흔한 문제 해결

처음 사용할 때 몇 가지 문제가 발생할 수 있다. 다음은 몇 가지 흔한 문제와 해결 방법이다.

문제1: 모델 로드가 너무 느림,怎么办? 처음 로드는 모델 파일을 다운로드해야 하므로 시간이 걸린다. 다운로드 후에는 다음부터는 로컬에서 바로 로드할 수 있다. 여전히 느릴 경우:

  • 충분한 RAM 확보 (최소 16GB)
  • SSD 하드디스크 사용
  • GPU 메모리가 충분하면 전체를 GPU에 로드

문제2: 인식 결과가 정확하지 않음, 일부 요리가 누락됨 이는 프롬프트가 명확하지 않거나 이미지 품질이 나쁘기 때문이다. 다음 방법을 시도해보자:

  • 프롬프트에 인식할 정보를 명확히 지정 (요리 이름, 가격, 설명 등)
  • 모델에게 메뉴를 순차적으로, 부분별로 인식하도록 지시
  • 이미지 사전 처리로 명확도와 대비를 향상
  • 메뉴가 길면 영역별로 인식한 후 결과를 병합

문제3: 모델 답변이 너무 길거나 형식이 혼란스러움 모델에게 더 구체적인 형식 요구를 제공하자:

query = """다음 형식으로 답변하십시오:
요리 이름 | 가격 | 분류
--- | --- | ---
[여기서 모든 요리 나열]

각 요리는 한 줄씩, 세로선으로 정보를 분리하십시오."""

문제4: 중국어 메뉴를 어떻게 처리할까요? Ostrakon-VL-8B는 중국어를 지원하지만, 일부 중국어 글꼴 인식이 부족할 수 있다. 문제가 발생하면:

  • 이미지의 중국어 텍스트가 명확하게 보이는지 확인
  • 프롬프트에서 중국어 메뉴임을 명확히 전달
  • 모델에게 중국어로 답변하도록 요청

문제5: 손으로 쓴 메뉴를 인식하려면 어떻게 할까요? 손글씨 인식은 더 어려운데, 다음 방법을 시도해보자:

  • 이미지 해상도를 높이기
  • 프롬프트에서 손으로 쓴 메뉴임을 명시
  • 모델에게 읽을 수 있는 부분만 인식하도록 요청, 읽기 어려운 부분은 무시
  • 가능하면 인쇄된 메뉴를 제공하도록 권장

7. 결론

이 과정을 통해 Ostrakon-VL-8B를 사용하여 메뉴 인식을 어떻게 할 수 있는지 명확히 이해하게 되었다. 환경 설정부터 코드 작성까지, 전체 과정은 예상보다 복잡하지 않다. 중요한 것은 큰 문제를 작은 단계로 나누고, 하나씩 처리하는 것이다.

실제로 사용해보면, 인쇄된 메뉴의 인식 결과는 꽤 좋다. 특히 구조가 명확한 메뉴에 적합하다. 복잡한 배치나 손글씨 메뉴는 프롬프트를 더 조정하거나 이미지 사전 처리가 필요할 수 있다. 하지만 기본적으로 이 모델은 많은 실제 요구를 충족할 수 있다.

실제 프로젝트에 적용하려면, 먼저 구조가 단순한 전자 메뉴에서 시작하는 것을 권장한다. 통과한 후에는 더 복잡한 시나리오로 확장하자. 과정 중에 다양한 작은 문제가 발생할 수 있지만, 대부분 해결 가능하다. 다양한 프롬프트를 시도하고, 다양한 유형의 메뉴 이미지를 처리하면 모델의 성격을 점점 더 익숙해질 것이다.

마지막으로, 모델 추론은 일정한 컴퓨팅 자원이 필요하다. 온라인 서비스에 적용할 경우, 동시성과 응답 시간을 고려해야 한다. 적절한 캐시를 추가하거나 비동기 처리를 사용하여 경험을 더욱 매끄럽게 만들자. 잘 설명했으니, 나머지는 직접 손으로 해보는 것이 좋다.

더 많은 AI 이미지 얻기

더 많은 AI 이미지와 응용 사례를 탐색하고 싶다면, CSDN 스타맵 이미지 광장에 방문하라. 다양한 사전 설정 이미지가 제공되며, 대형 모델 추론, 이미지 생성, 동영상 생성, 모델 미세 조정 등 여러 분야를 커버하고 있으며, 한 번에 배포할 수 있다。

태그: Ostrakon-VL-8B AI 모델 메뉴 인식 다중 모달 이미지 처리

5월 28일 11:58에 게시됨