API 호출 및 활용 연습

TIL Day 44

By polaris0208

API 호출 및 활용

실제 API 및 모델의 사용방법
¶ FastAPI & GPT-4o Chat 서비스
¶ YOLO Webcam 이용 객체 탐지
¶ Eleven Labs & NLLB-200 번역 서비스

FastAPI & GPT-4o Chat 서비스

  • main.py : Back-end 웹이나 앱에서 사용자의 행동을 처리하고, 정보를 저장, 관리, 전달하며, 서버와 데이터베이스를 관리
  • template - index.html : Front-end 웹사이트나 앱 등의 사용자 인터페이스(UI)

main.py

  • 서비스를 실제로 작동시키는 역할

패키지 설치 및 모듈 불러오기

패키지

  • FastAPI Form 데이터 처리를 위해 python-multipart 설치
pip install openai
pip install fastapi
pip install python-multipart # Form 데이터 처리

모듈

  • OpenAI : 핵심 프로그램 제공
  • FastAPI : API 요청과 html 응답처리
  • Jinja2Templates : html 파일과 상호작용
  • StaticFiles : 정적 파일과 상호작용
from openai import OpenAI
from fastapi import FastAPI, Request, Form
from fastapi.templating import Jinja2Templates
from fastapi.responses import HTMLResponse
from fastapi.staticfiles import StaticFiles

기본 설정

  • API 설정
  • 템플릿 설정
  • 정적 파일 설정
  • 프로그램 초기 설정
app = FastAPI() # fastapi 인스턴스르 통해 api요청-html 응답 처리

# OpenAI API 클라이언트 설정
client = OpenAI()

# Jinja2 템플릿 설정
templates = Jinja2Templates(directory="/templates")

# 정적 파일 서빙
app.mount("/static", StaticFiles(directory="/static"), name="static")


# 초기 시스템 메시지 설정
system_message = {
    "role": "system",
    "content": "너는 시인이야, 주제를 주면 5줄의 시를 지어줘"
}

# 대화 내역을 저장할 리스트 초기화
messages = [system_message]

기본 페이지 설정

  • async def 비동기 함수
    • "/" 경로(기본 페이지)에 대한 GET요청을 처리
    • response_class=HTMLResponse는 응답을 HTML 형식으로 설정(기본값 json)
  • "request": requestFastAPIJinja2 템플릿 엔진을 사용할 때 필요한 매개변수
    • 템플릿에서 요청 관련 정보에 접근할 수 있게 됨
@app.get("/", response_class=HTMLResponse)
async def get_chat_page(request: Request):
  conversation_history = [msg for msg in messages if msg["role"] != "system"]
    # 시스템 메시지 제외

    return templates.TemplateResponse("index.html", {"request": request, "conversation_history": conversation_history})

서비스 페이지 설정

async def 비동기 함수 설정

  • user_input : 사용자 입력을 받아옴
  • str : 변수 타입
  • Form : HTML 폼 데이터를 서버로 전달할 때 사용 (기본값 json)
  • ... : 필수 필드임을 나타내는 FastAPI의 방식
@app.post("/chat", response_class=HTMLResponse)
async def chat(request: Request, user_input: str = Form(...)):

프로그램 설정

  • global : 전역 변수로 설정
    • 전체 영역에서 영향을 주는 변수
    global messages # 이전 대화 내용을 저장하여 대화

    # 사용자의 메시지를 대화 내역에 추가
    messages.append({"role": "user", "content": user_input})

    # OpenAI API 호출
    completion = client.chat.completions.create(
        model="gpt-4o",
        messages=messages
    )

    # AI의 응답 가져오기
    assistant_reply = completion.choices[0].message.content

    # AI의 응답을 대화 내역에 추가
    messages.append({"role": "assistant", "content": assistant_reply})

    # 화면에 표시할 대화 내역에서 system 메시지를 제외하고 전달
    conversation_history = [msg for msg in messages if msg["role"] != "system"]

    # 결과를 HTML로 반환 (대화 내역과 함께)
    return templates.TemplateResponse("index.html", {
        "request": request,
        "conversation_history": conversation_history
    })

index.html

  • Github Pages : Jekyll 사용
    • post 내부에 html 코드가 포함되면 markdownhtml로 변환하는 과정에 오류 발생
    • 별도의 링크로 연결하여 내용 대체

결과

User : 가을에 관한 시를 지어줘

Assistant : 낙엽은 금빛 춤을 추며 내려앉고, 바람은 가을의 이야기를 속삭이네. 짧아진 해는 노을 속에 스며들고, 서늘한 공기에 달콤한 기운 담겨, 가을은 또 한 번 마음 깊이 물들여.

¶ Top

YOLO Webcam 이용 객체 탐지

  • PyQt5 : 유저 인터페이스를 구성하는 라이브러리

모듈 불러오기

  • 사용 모듈이 많은 경우 번거롭더라도 한번에 불러오기
    • 분리해서 불러올 경우 관리가 어려움
from ultralytics import YOLO
import cv2 # openCV 핵심 모듈
from PyQt5.QtWidgets import QApplication, QLabel, QVBoxLayout, QWidget, QPushButton # 인터페이스
from PyQt5.QtCore import QTimer
from PyQt5.QtGui import QImage, QPixmap # 이미지 처리

인터페이스 구성

  • 인터페이스의 각 부분을 설정하고 기능 함수와 연결
class VideoCaptureWidget(QWidget): 
  # Qwidget 상속
  def __init__(self):
    super().__init__()

    # YOLOv8x 모델 로드
    self.model = YOLO('yolov8x.pt')

    # UI 설정
    # 타이틀 설정
    self.setWindowTitle('실시간 객체 탐지')
    self.image_label = QLabel(self)
    self.layout = QVBoxLayout()
    self.layout.addWidget(self.image_label)

    # 시작 버튼 설정
    self.start_button = QPushButton('Start Webcam', self)
    self.start_button.clicked.connect(self.start_webcam) ##
    self.layout.addWidget(self.start_button)

    # 종료 버튼 설정
    self.stop_button = QPushButton('Stop Webcam', self)
    self.stop_button.clicked.connect(self.stop_webcam) ##
    self.layout.addWidget(self.stop_button)

    self.setLayout(self.layout)

    # 웹캠 초기화
    self.capture = None
    self.timer = QTimer(self)
    self.timer.timeout.connect(self.update_frame)
    # 주기적으로 새로운 프레임 업데이트

함수 설정

  • 객체 탐지 및 이미지 변환 함수
  • 인터페이스 작동 함수
def stop_webcam(self): 
    # 웹캠, 타이머 중지
    self.timer.stop()
    if self.capture is not None:
      self.capture.release()

  def start_webcam(self):
    # 웹캠, 타이머 시작, 주기적으로 프레임 업데이트
    self.capture = cv2.VideoCapture(0)
    self.timer.start(20) # 20ms마다 프레임 업데이트 - 50fps(초당 50 프레임)

  def update_frame(self):
    # 웹캠에서 프레임을 읽은 후 YOLO 객체 탐지 후 UI에 표시
    ret, frame = self.capture.read()
    if ret: 
      # YOLO 객체 탐지
      results = self.model(frame)
      result = results[0]
      
      # 바운딩 박스가 포함된 이미지 호출
      img_with_boxes = result.plot()

      # OpenCV 이미지를 QImage로 변환
      rgb_image = cv2.cvtColor(img_with_boxes, cv2.COLOR_BGR2RGB)
      h, w, ch = rgb_image.shape
      bytes_per_line = ch * 2
      convert_to_Qt_format = QImage(rgb_image.data, w, h, bytes_per_line, QImage.Format_RGB888)

      # QImage를 QLabel에 표시하기 위해 QPixmap으로 변환
      self.image_label.setPixmap(QPixmap.fromImage(convert_to_Qt_format))

  def closeEvent(self, event):
      # 종료 시 웹캠 해제
      if self.capture is not None:
        self.capture.release()

실행

  • if __name__ == "__main__" : 실행 파일이 main(가장 처음 시작되는 중심 파일)인 경우 실행
if __name__ == "__main__":
    app = QApplication([])
    # QApplication 실행: PyQt5 GUI 앱을 실행하고, VideoCaptureWidget 객체 호출
    window = VideoCaptureWidget()
    window.show()
    app.exec_()

결과

0: 384x640 1 person, 1 chair, 1 bed, 1 refrigerator, 531.4ms
Speed: 7.4ms preprocess, 531.4ms inference, 8.5ms postprocess per image at shape (1, 3, 384, 640)

¶ Top

Eleven Labs & NLLB-200 번역 서비스

  • NLLLB-200 모델로 번역 수행
  • Eleven Labs API로 번역 내용 음성 생성
  • PyQt5로 UI 구성

주의점

  • transformers의 최신 버전(4.38 이후)에서는 lang_code_to_id를 제공하지 않음
    • pip install transformers==4.37.0

모듈 불러오기

import os
import requests

from PyQt5 import QtWidgets
from PyQt5.QtCore import QUrl
from PyQt5.QtMultimedia import QMediaPlayer, QMediaContent
from transformers import AutoTokenizer, AutoModelForSeq2SeqLM

from pydub import AudioSegment
from pydub.playback import play

import io

API 설정

  • 환경 변수에서 불러오기
api_key = os.environ.get('Eleven_Labs_API_KEY')

# model url = "https://api.elevenlabs.io/v1/text-to-speech/{VOICE_ID}/stream"
url = "https://api.elevenlabs.io/v1/text-to-speech/ZJCNdZEjYwkOElxugmW2/stream"

class TranslatorApp(QtWidgets.QWidget):

초기화 함수 설정

  • 초기화 함수 상속
  • 모델 및 API 설정
    def __init__(self):
        super().__init__()
        self.init_ui()

        # 번역 모델 로드
        model_name = "facebook/nllb-200-distilled-600M"
        self.tokenizer = AutoTokenizer.from_pretrained(model_name)
        self.model = AutoModelForSeq2SeqLM.from_pretrained(model_name)

        # API 설정
        self.api_key = api_key
        self.url = url

        # 음성 재생기
        self.player = QMediaPlayer()

UI 설정

    def init_ui(self):
        # UI 구성
        self.text_input = QtWidgets.QLineEdit(self)
        self.text_input.setPlaceholderText("번역할 텍스트 입력")
        self.translate_button = QtWidgets.QPushButton("번역 및 음성 생성", self)
        self.output_label = QtWidgets.QLabel(self)
        self.play_button = QtWidgets.QPushButton("음성 재생", self)
        self.play_button.setEnabled(False)

        # 레이아웃 설정
        layout = QtWidgets.QVBoxLayout()
        layout.addWidget(self.text_input)
        layout.addWidget(self.translate_button)
        layout.addWidget(self.output_label)
        layout.addWidget(self.play_button)
        self.setLayout(layout)

        # 버튼 클릭 시 이벤트 핸들러 연결
        self.translate_button.clicked.connect(self.translate_and_generate_audio)
        self.play_button.clicked.connect(self.play_audio)

        # 윈도우 창 설정
        self.setWindowTitle("번역 및 음성 생성기")
        self.show()

음성 생성 기능 설정

  • 사용자 입력을 변수로 번역수행 함수 설정
  • 번역 결과를 변수로 음성 생성 함수 설정
  • 결과 저장 경로를 확인
def translate_and_generate_audio(self):
        text = self.text_input.text()

        # 번역 수행
        inputs = self.tokenizer(text, return_tensors="pt")
        generated_tokens = self.model.generate(inputs.input_ids, forced_bos_token_id=self.tokenizer.lang_code_to_id["kor_Hang"])
        translated_text = self.tokenizer.decode(generated_tokens[0], skip_special_tokens=True)

        # 음성 생성 요청
        data = {
            "text": translated_text,
            "model_id": "eleven_multilingual_v2",
            "voice_settings": {
                "stability": 0.5,
                "similarity_boost": 1,
                "style": 0.5,
                "use_speaker_boost": True
            }
        }
        headers = {
            "xi-api-key": self.api_key,
            "Content-Type": "application/json"
        }
        response = requests.post(self.url, json=data, headers=headers)

        if response.status_code == 200:
            output_audio_path = "/translator/output_audio.mp3"
            with open(output_audio_path, "wb") as f:
                f.write(response.content)

            self.output_label.setText(f"번역 결과: {translated_text}")
            self.play_button.setEnabled(True)
        else:
            self.output_label.setText("음성 생성 실패")

음성 재생 설정

  • 재생할 음성 경로 확인
    def play_audio(self):
        # 음성 파일 재생
        audio_path = "/translator/output_audio.mp3"
        if os.path.exists(audio_path):
            # Pydub을 통해 mp3 파일을 불러와서 재생
            audio = AudioSegment.from_mp3(audio_path)
            play(audio)  # Pydub의 play() 함수 사용
        else:
            self.output_label.setText("오디오 파일을 찾을 수 없습니다.")

실행

if __name__ == '__main__':
    app = QtWidgets.QApplication([])
    translator = TranslatorApp()
    app.exec_()

결과

Hello It’s me Mario 입력 결과 mp3

안녕하세요 마리오 입니다.

  • HYUK : 한국어 음성 모델 최상단

¶ Top

Tags: TIL Tools