이번에서는 OpenCV와 Yolo를 활용해서 실시간 객체 탐지 서비스 만들어볼 것이다. OpenCV는 사진이나 동영상에 대해서 굉장히 많은 기능을 지원하는 라이브러리 중 하나다. OpenCV와 YOLO 이외에도 PyQt5라는 것을 사용할 것이다. PyQt5는 파이썬에서 사용자의 인터페이스를 구성하는 라이브러리다.
먼저 필수 라이브러리를 import하자.
from ultralytics import YOLO
import cv2
from PyQt5.QtWidgets import QApplication, QLabel, QVBoxLayout, QWidget, QPushButton
from PyQt5.QtCore import QTimer
from PyQt5.QtGui import QImage, QPixmap
from ultralytics import YOLO : YOLO 모델을 사용하기 위해 불러오는 라이브러리다. 라이선스의 문제가 있을 수 있기 때문에, 사용에 주의가 필요하다.
import cv2 : OpenCV의 핵심 모듈을 가져오는 부분이다.
나머지는 PyQt5의 다양한 모듈들을 가져오고 있는데, GUI의 기본 요소, 타이머, 이미지 처리 등을 위한 모듈을 가지고 오고 있는 것이다.
이제 프로그램의 주요 기능을 담을 클래스를 만들어보도록 하자. VideoCaptureWidget 클래스를 생성하는 것이다.
class VideoCaptureWidget(QWidget):
def __init__(self):
super().__init__()
# YOLOv8x 모델 로드 (YOLOv8x)
self.model = YOLO('yolov8x.pt')
YOLO 모델은 하나의 버전 속에도 다양한 모델들이 제공되기 때문에, 본인의 컴퓨팅 리소스 그리고 환경에 맞춰서 적당한 모델을 불러와주면 된다.
이제 UI를 설정해줄 것이다.
# 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)
PyQt5는, 파이썬에서 GUI를 만들어주는 프로그램으로, UI를 설정하기 위해 다양한 요소들을 만들어주는 부분이다. 지금
이제 웹캠을 초기화하겠다.
# 웹캠 초기화
self.capture = None
self.timer = QTimer(self)
self.timer.timeout.connect(self.update_frame)
웹캠을 사용하려면 몇 가지 작업이 필요하다. 먼저, 웹캠 객체를 저장할 변수가 필요하다. 또, QTimer를 통해 업데이트 프레임 함수를 호출할건데, 이를 통해 우리는 주기적으로 새로운 프레임을 읽고 업데이트할 수 있게 된다.
이제 나오는 코드는, 웹캠을 시작하는 부분이다. cv를 사용하면, 굉장히 간단하게 비디오를 불러올 수 있다.
def start_webcam(self):
"""웹캠을 시작하고, 타이머를 시작하여 프레임을 주기적으로 읽음"""
self.capture = cv2.VideoCapture(0) # 웹캠 장치 열기
self.timer.start(20) # 20ms마다 프레임 업데이트 (50fps)
VideoCapture라는 함수를 실행하면 웹캠 장치를 열 수 있다.
이제 웹캠을 중지하는 부분이다.
def stop_webcam(self):
"""웹캠을 중지하고 타이머를 멈춤"""
self.timer.stop()
if self.capture is not None:
self.capture.release()
단순히 stop만 불러주면 된다.
마지막으로, 업데이트 프레임이라는 부분이다.
def update_frame(self):
"""웹캠에서 프레임을 읽어와서 YOLO 객체 탐지를 수행한 후 UI에 표시"""
ret, frame = self.capture.read()
if ret:
# YOLOv8 객체 탐지 수행
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 * w
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()
해당 부분은 가장 핵심이 되는 함수다. 단순히 프레임을 읽어오는 부분만 포함돼있지는 않고, YOLO 객체 탐지를 수행한 후에, 이걸 UI에 표시하는 기능까지 포함돼있다.
이제 메인 실행부인데, 해당 부분에서, 우리가 만들어놓은 기능들을 호출하게 된다.
if __name__ == "__main__":
app = QApplication([])
window = VideoCaptureWidget()
window.show()
app.exec_()
전체 코드를 입력한 다음, 코드를 실행하게 되면 작은 창이 하나 나오게 된다.
여기에 보이는 숫자나 레이블같은 것은 우리가 직접 설정한 부분이다. 위쪽에서 PyQt5를 설정할 때 보였었죠? 바로, WindowTitle과 start_button, stop_button에 적은 글들이 나오게 되는 것이다.
Start Webcam을 누르면 아객체 탐지가 실행된다.
이번에는 ChatGPT와 FastAPI를 활용해서 웹 챗봇 서비스를 만들어볼 것이다. 이를 통해 우리는 웹 페이지에서 사용자가 입력한 텍스트를 ChatGPT API에 전달하고, 그 응답을 받아 실시간으로 표시할 수 있는 챗봇 인터페이스를 구현하게 될 것이다. 특히 대화 내역을 실시간으로 반영해서 인공지능과 대화할 수 있는 서비스를 만들어볼 것이다. API를 통해 인공지능에게 입력을 전달하고, 출력을 받아오는 형태로 사용하게 된다.
from fastapi import FastAPI, Request, Form
from fastapi.templating import Jinja2Templates
from fastapi.responses import HTMLResponse
from fastapi.staticfiles import StaticFiles
from openai import OpenAI
app = FastAPI()
# OpenAI API 클라이언트 설정
client = OpenAI()
# Jinja2 템플릿 설정
templates = Jinja2Templates(directory="app/templates")
# 정적 파일 서빙
app.mount("/static", StaticFiles(directory="app/static"), name="static")
첫 번째로, FastAPI를 통해 API를 구성해서, 프론트엔드와 백엔드 간의 통신을 설정해야 한다. app이라고 돼있는 부분은, API 서버를 초기화하는 부분이다. 모든 API 요청이, 이 인스턴스를 통해 처리된다고 이해하면 된다.
client는 OpenAI API의 클라이언트를 설정하는 부분이다. 그 후에는 templates를 설정해주고 있다. 이 부분은 템플릿 엔진으로, app/templates 폴더에 있는 html 파일을 서버 응답으로 랜더링할 때 사용하게 된다. FastAPI도 굉장히 기능이 많은 라이브러리기 때문에, 우리가 필요한 부분만 가져와주는 게 좋다.
이제 실제로 대화하는 부분이 나오게 된다.
# 초기 시스템 메시지 설정
system_message = {
"role": "system",
"content": "너는 환영 인사를 하는 인공지능이야, 농담을 넣어 재미있게해줘"
}
# 대화 내역을 저장할 리스트 초기화
messages = [system_message]
@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})
첫 번째로, 초기 system_message를 설정한다. ChatGPT는 프롬프팅을 통해서 결과를 유도할 수 있다. 여기에는 system, user, assistant 3개가 있는데, system 명령은 전체 대화에 영향을 주게 된다. 아래쪽에는 대화 내역을 저장할 리스트를 만들어주었다.
채팅 페이지 랜더링같은 경우에는 경로를 지정해주고 있는 것을 볼 수 있다. 대화 페이지를 html로 반환하며, conversation_history를 통해서 리스트에 저장된 대화 내역을 templates로 랜더링해주는 부분이다.
사용자로부터 요청을 받는 부분도 있어야 한다. FastAPI는 다양한 방식으로 사용자로부터 입력을 받을 수 있다. 여기서는 Form이라는 걸 사용하고 있다.
@app.post("/chat", response_class=HTMLResponse)
async def chat(request: Request, user_input: str = Form(...)):
"""사용자 메시지를 받아 OpenAI API 호출 및 응답 반환"""
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
})
우리는 FastAPI와 ChatGPT API 통신을 통해 사용자 입력을 ChatGPT API에 전달하고 응답을 받아 대화를 만들었다.
이제 서비스를 만들었으니 실행해보자. 실행하려면 실행 명령어로 uvicorn이라는 것을 사용해야 한다. 실행하고 나면, 아래와 같이 웹에서 접근이 가능하게 된다.