๋™์•„๋ฆฌ,ํ•™ํšŒ/GDGoC

[AI ์Šคํ„ฐ๋””] Section 12 : YOLO ๋ชจ๋ธ Fast API๋กœ ๋ฐฐํฌ

egahyun 2024. 12. 27. 04:06

YOLO (You Only Look Once)

ํŠน์ง•

  1. ์†๋„๊ฐ€ ๋น ๋ฆ„ ⇒ ์ดˆ ๋‹น ์ฒ˜๋ฆฌํ•˜๋Š” ํ”„๋ ˆ์ž„์ˆ˜๊ฐ€ ๋งŽ์Œ
  2. ์‹ค์ œ ์‹œ๊ฐ„์œผ๋กœ ๋ญ”๊ฐ€๋ฅผ ํ•  ๋•Œ ๋งŽ์ด ์‚ฌ์šฉ
  3. ์ •ํ™•๋„๊ฐ€ ๋†’์Œ
  4. ๊ธฐ์กด ์ด๋ฏธ์ง€ ๋ชจ๋ธ๋ณด๋‹ค ์ข‹์•„์ง
    • ๊ธฐ์กด ์ด๋ฏธ์ง€ ๋ชจ๋ธ : ์–ด๋–ค ๊ฒƒ์— ๊ด€๋ จ๋œ ์‚ฌ์ง„์ธ์ง€ ๋ถ„๋ฅ˜
    • YOLO : ์–ด๋–ค ๋ฌผ์ฒด๊ฐ€ ์–ด๋–ค ์œ„์น˜์— ์žˆ๋Š”์ง€ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Œ → object detection

์ฃผ์š” ๊ตฌ์„ฑ ์„ฑ๋ถ„

Bounding Box Prediction

  1. ๋ฐฉ๋ฒ• : ์‚ฌ์ง„์„ 9๊ฐœ์˜ ์˜์—ญ์œผ๋กœ ๋‚˜๋ˆ„๊ณ , ๊ฐ ์˜์—ญ ํ•˜๋‚˜์— ๋ ˆ์ด๋ธ” 1๊ฐœ์”ฉ์„ ๋ถ™์ž„
    → ์‹ฑ๊ธ€์ด๋ฏธ์ง€๋„ ๋˜๊ณ , ๋ฉ€ํ‹ฐ ์ด๋ฏธ์ง€๋„ ๊ฐ€๋Šฅ
  2. \( p_c \) : ๋ฌผ์ฒด๊ฐ€ ์žˆ๋Š”์ง€, ์—†๋Š”์ง€
    → 1 : ์žˆ์Œ / 0 : ์—†์Œ
  3. ๋ฐ”์šด๋”ฉ ๋ฐ•์Šค ( \( b_x, b_y, b_h, b_w \) ) : ์ค‘์‹ฌ์ ์ด ์–ด๋””์žˆ๋Š”์ง€๋ฅผ ๋‚˜ํƒ€๋ƒ„
    → \( b_h \) : ๋ฌผ์ฒด์˜ ๋†’์ด ( ex : 0.95 ) | \( b_w \) : ๋ฌผ์ฒด์˜ ํญ (ex : 1.72 ) | \( b_x, b_y \) : ๋ฌผ์ฒด์˜ ์ค‘์‹ฌ์  ( ex : (0.45, 0.98) )
  4. ํด๋ž˜์Šค ๋ ˆ์ด๋ธ” : ๋ ˆ์ด๋ธ”๋ง ํ•œ ๊ฒƒ
    → ์›ํ•ซ ์ธ์ฝ”๋”ฉ์œผ๋กœ, ์ฐจ๋ฉด 1 0 0 ์ž์ „๊ฑฐ๋ฉด 0 0 1 ์ด๋Ÿฐ์‹์ž„

๋ชจ๋ธ ๋ฐฐํฌ์‹œ ํ•„์š”ํ•œ ์‚ฌ์ „ ์ง€์‹

  • deploying - prediction์— ํ•„์š”ํ•œ ๋ชจ๋“  ์†Œํ”„ํŠธ์›จ์–ด๋ฅผ 'server'์— ์„ค์น˜ํ•˜๋Š” ๊ฒƒ. ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด 'client'๊ฐ€ ์„œ๋ฒ„์— 'request'๋ฅผ ๋ณด๋‚ด ๋ชจ๋ธ๊ณผ ์ƒํ˜ธ ์ž‘์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • client๋Š” ๋ชจ๋ธ์ด ์˜ˆ์ธก์„ ์ˆ˜ํ–‰ํ•˜๋Š” ๋ฐ ํ•„์š”ํ•œ ์ •๋ณด๋ฅผ ์ œ๊ณต. server๋Š” ์ œ๊ณต๋œ ์ •๋ณด๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ํด๋ผ์ด์–ธํŠธ์—๊ฒŒ ์˜ˆ์ธก์„ ๋ฐ˜ํ™˜.
  • FastAPI ํด๋ž˜์Šค์˜ ์ธ์Šคํ„ด์Šค๋ฅผ ๋งŒ๋“ค์–ด ์‹œ์ž‘ํ•ฉ๋‹ˆ๋‹ค.
app = FastAPI()
  • ๋‹ค์Œ ๋‹จ๊ณ„๋Š” ์ด ์ธ์Šคํ„ด์Šค๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์˜ˆ์ธก ๋…ผ๋ฆฌ๋ฅผ ์ฒ˜๋ฆฌํ•  endpoint(path, route)๋ฅผ ๋งŒ๋“œ๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค.
    ์„œ๋ฒ„๋ฅผ ์‹คํ–‰ํ•˜๊ธฐ ์œ„ํ•œ ๋ชจ๋“  ์ฝ”๋“œ๊ฐ€ ์ค€๋น„๋˜๋ฉด ๋‹ค์Œ ๋ช…๋ น๋งŒ ์‚ฌ์šฉํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค.
uvicorn.run(app)
  • serving์€ uvicorn์„ ์‚ฌ์šฉํ•˜์—ฌ ์ˆ˜ํ–‰๋˜๋ฉฐ uvicorn์€ ASGI web server ์ž…๋‹ˆ๋‹ค.
    ASGI(Asynchronous Server Gateway Interface - ๋น„๋™๊ธฐ Python ์›น ์„œ๋ฒ„)

endpoints (path, route)

1. ๋™์ผํ•œ ์„œ๋ฒ„์—์„œ ์—ฌ๋Ÿฌ ๊ธฐ๊ณ„ํ•™์Šต ๋ชจ๋ธ์„ ํ˜ธ์ŠคํŒ…ํ•  ์ˆ˜ ์žˆ์Œ

-> ์ด๋ฅผ ์ž‘๋™ํ•˜๋ ค๋ฉด ๊ฐ ๋ชจ๋ธ์— ๋‹ค๋ฅธ endpoint๋ฅผ ํ• ๋‹นํ•ด์•ผํ•˜๋Š”๋ฐ, endpoint๋Š” URL ํŒจํ„ด์œผ๋กœ ํ‘œ์‹œ๋œ๋‹ค.

2. ex) imagetest.com ์˜ ์—”๋“œํฌ์ธํŠธ์— 3๊ฐ€์ง€ ๋ชจ๋ธ์„ ์ง€์ •ํ•œ ๊ฒƒ

  • imagetest.com/lenet/
  • imagetest.com/yolo/
  • imagetest.com/mobilenet/

fastAPI์—์„œ๋Š” ํ•ด๋‹น endpoint์— ๋Œ€ํ•œ ๋ชจ๋“  logic์„ ์ฒ˜๋ฆฌํ•  ํ•จ์ˆ˜๋ฅผ ๋งŒ๋“ค๊ณ  endpoint๋ฅผ decorating ํ•ฉ๋‹ˆ๋‹ค.
๋‹ค์Œ ์˜ˆ๋Š” ์—”๋“œํฌ์ธํŠธ "/my-endpoint"์— ๋Œ€ํ•œ HTTP GET ์š”์ฒญ์„ ํ—ˆ์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ๋ณด์—ฌ์ค๋‹ˆ๋‹ค.

@app.get("/my-endpoint")
def handle_endpoint():
    ...
    ...

 

HTTP Requests

  • GET -> ์„œ๋ฒ„์—์„œ ์ •๋ณด ๊ฒ€์ƒ‰
  • POST -> ์‘๋‹ต์— ํ•„์š”ํ•œ ์ •๋ณด๋ฅผ ์„œ๋ฒ„์— ์ œ๊ณต

endpoint์— ์žˆ๋Š” ๊ธฐ๊ณ„ ํ•™์Šต ๋ชจ๋ธ๊ณผ์€ ์ผ๋ฐ˜์ ์œผ๋กœ ์˜ˆ์ธก์„ ๊ณ„์‚ฐํ•˜๋Š” ๋ฐ ํ•„์š”ํ•œ ์ •๋ณด๋ฅผ ์ œ๊ณตํ•ด์•ผ ํ•˜๊ธฐ ๋•Œ๋ฌธ์— 
POST request๋ฅผ ํ†ตํ•ด ์ˆ˜ํ–‰๋ฉ๋‹ˆ๋‹ค.

 

# ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ ๋ฌธ๋ฒ•
@app.post("/my-other-endpoint")
# ์•„๋ž˜์˜ ์šฐ๋ฆฌ๊ฐ€ ๋งŒ๋“  ํ•จ์ˆ˜๋ฅผ ํŒŒ๋ผ๋ฏธํ„ฐ ํ˜•์‹์œผ๋กœ ๋ณด๋‚ด ์ž‘๋™ํ•˜๋„๋กํ•จ
def handle_other_endpoint(param1: int, param2: str):
    ...
    ...

์ˆœ์„œ

: object detection์— ์‚ฌ์šฉ๋œ ์ด๋ฏธ์ง€ ๋ฐ์ดํ„ฐ์…‹ ๊ฒ€์‚ฌ → ๋ชจ๋ธ ์‚ดํŽด๋ณด๊ธฐ → fastAPI๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋ชจ๋ธ ๋ฐฐํฌ

์ดˆ๊ธฐ ์„ค์น˜ ์ฝ”๋“œ

!pip install opencv-python 
!pip install cvlib
!pip install uvicorn
!pip install fastapi
!pip install python-multipart

YOLOV3 ๋ฅผ ์ด์šฉํ•œ ๊ฐ์ฒด ๊ฒ€์ถœ (Object Detection)

from IPython.display import Image, display
import os

# ์ด๋ฏธ์ง€ ๋ถˆ๋Ÿฌ์„œ ์ถœ๋ ฅํ•ด๋ณด๊ธฐ
image_files = os.listdir('images')

for image_file in image_files:
    print(f"\\n{image_file}")
    display(Image(filename=f"images/{image_file}"))
# ๊ฐœ์ฒด ๊ฐ์ง€ ๋ชจ๋ธ์„ ์‚ฌ์šฉํ•˜๊ธฐ ์ „์— ๊ฒฐ๊ณผ ์ด๋ฏธ์ง€๋ฅผ ์ €์žฅํ•  ์ˆ˜ ์žˆ๋Š” ๋””๋ ‰ํ„ฐ๋ฆฌ ์ƒ์„ฑ
dir_name = "images_with_boxes"
if not os.path.exists(dir_name):
    os.mkdir(dir_name)
    
import cv2
import cvlib as cv
from cvlib.object_detection import draw_bbox

# ์‹œ์Šคํ…œ์˜ ํŒŒ์ผ์„ ์ž…๋ ฅ์œผ๋กœ ์ด๋ฏธ์ง€์˜ ๊ฐ์ฒด๋ฅผ ํƒ์ง€ํ•˜๊ณ , ๊ฐœ์ฒด์™€ ํ•จ๊ป˜ bounding box๋ฅผ ํ‘œ์‹œํ•˜๋Š” ์ด๋ฏธ์ง€๋ฅผ ์ €์žฅํ•˜๋Š” ํ•จ์ˆ˜
# filename : ์›๋ณธ ํŒŒ์ผ / model : ๊ฐ€์žฅ ์ตœ์‹  yolo๋ชจ๋ธ ์‚ฌ์šฉ / confidence : ์–ผ๋งˆ๋‚˜ ํ™•์‹ ํ•˜๋Š”์ง€ -> ํ•ด๋‹น ํผ์„ผํŠธ ์ด์ƒ์ผ๋•Œ๋งŒ ๋‹ต์„ ์คŒ
def detect_and_draw_box(filename, model="yolov4", confidence=0.5): # 50% ์ด์ƒ ํ™•์‹ ์‹œ ๋‹ต์„ ์คŒ
    """์ด๋ฏธ์ง€์—์„œ ์ผ๋ฐ˜์ ์ธ object๋ฅผ ๊ฐ์ง€ํ•˜๊ณ 
         bounding box ๊ฐ€ ์žˆ๋Š” ์ƒˆ ์ด๋ฏธ์ง€ ๋งŒ๋“ค๊ธฐ. """
    
    img_filepath = f'images/{filename}'
    
    img = cv2.imread(img_filepath)  # Read the image into a numpy array
    img = cv2.resize(img, (250, 250)) # ์ด๋ฏธ 250 250 ์œผ๋กœ pretrained๋˜์–ด ์žˆ๊ธฐ๋•Œ๋ฌธ์—
    print(img.shape)
    
    # object detection ์ˆ˜ํ–‰
    bbox, label, conf = cv.detect_common_objects(img, confidence=confidence, model=model)
    
    print(f"========================\\n์ด๋ฏธ์ง€ ์ฒ˜๋ฆฌ ์™„๋ฃŒ: {filename}\\n")
    
    for l, c in zip(label, conf):
        print(f"๊ฒ€์ถœ๋œ ๊ฐ์ฒด: {l} - confidence level: {c:.2f}\\n")
    
    # bounding box๋ฅผ ํฌํ•จํ•˜๋Š” ์ƒˆ ์ด๋ฏธ์ง€ ๋งŒ๋“ค๊ธฐ
    output_image = draw_bbox(img, bbox, label, conf)

    cv2.imwrite(f'images_with_boxes/{filename}', output_image)  # ์ƒˆ๋กœ์šด ์ด๋ฏธ์ง€ ์ €์žฅ
    
    # bounding box๊ฐ€ ์ถ”๊ฐ€๋œ ์ด๋ฏธ์ง€ display
    display(Image(f'images_with_boxes/{filename}'))
# ํ•จ์ˆ˜ ์‹คํ–‰
for image_file in image_files:
    detect_and_draw_box(image_file)

๋ชจ๋ธ ๋ฐฐํฌ : fastAPI ์‚ฌ์šฉ

์„œ๋ฒ„์™€ ์ƒํ˜ธ ์ž‘์šฉํ•˜๋Š” ๋ฐ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ํด๋ผ์ด์–ธํŠธ๊ฐ€ ๋‚ด์žฅ๋˜์–ด์žˆ์Œ

# ๋ธŒ๋ผ์šฐ์ €์—์„œ ์ƒˆ๋กœ์šด ์ด๋ฏธ์ง€๋ฅผ ๋กœ๋“œํ•ด์„œ ๊ตฌ๋ถ„ํ•˜๊ธฐ ์œ„ํ•œ ๋””๋ ‰ํ† ๋ฆฌ
dir_name = "images_uploaded"
if not os.path.exists(dir_name):
    os.mkdir(dir_name)
    
import io
import uvicorn
import numpy as np
import nest_asyncio
from enum import Enum
from fastapi import FastAPI, UploadFile, File, HTTPException
from fastapi.responses import StreamingResponse

# ์ธ์Šคํ„ด์Šค ์ƒ์„ฑ
app = FastAPI(title='FastAPI๋ฅผ ์ด์šฉํ•œ ML model deploy')

#Enum์„ ์‚ฌ์šฉํ•˜์—ฌ ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ๋ชจ๋ธ์„ ๋‚˜์—ดํ•ฉ๋‹ˆ๋‹ค. ์ด๋Š” ์˜ต์…˜์ด ๋ฏธ๋ฆฌ ์ •์˜๋œ ๊ฒฝ์šฐ์— ์œ ์šฉํ•ฉ๋‹ˆ๋‹ค.
class Model(str, Enum):
  # ๋‘˜์ค‘ ํ•˜๋‚˜๋ฅผ ๊ณ ๋ฅด๋„๋ก ๋ฆฌ์ŠคํŠธ๋ฅผ ๋งŒ๋“ค์–ด์ค€๊ฒƒ
    yolov4tiny = "yolov4-tiny"
    yolov4 = "yolov4"

@app.get("/")
def home():
    return "API ๊ฐ€ ์ •์ƒ ์ž‘๋™ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. <http://localhost:8000/docs> ๋กœ ์ด๋™ํ•˜์„ธ์š”."

@app.post("/predict") 
def prediction(model: Model, file: UploadFile = File(...)):

    # 1. INPUT FILE ํ™•์ธ
    
    filename = file.filename
    fileExtension = filename.split(".")[-1] in ("jpg", "jpeg", "png") # ์–˜๋„ค๋งŒ ์ฒ˜๋ฆฌ ๊ฐ€๋Šฅ
    if not fileExtension:
        raise HTTPException(status_code=415, detail="์ง€์›ํ•˜์ง€ ์•Š๋Š” ํŒŒ์ผ ํƒ€์ž… ์ž…๋‹ˆ๋‹ค.")
    
    # 2.  RAW IMAGE๋ฅผ CV2 image๋กœ ๋ณ€ํ™˜
    
    image_stream = io.BytesIO(file.file.read())  # stream of bytes ๋กœ ์ด๋ฏธ์ง€ ์ฝ๊ธฐ
    image_stream.seek(0)   # stream์„ ์ฒ˜์Œ(0 ์œ„์น˜)๋ถ€ํ„ฐ ์‹œ์ž‘ํ•ฉ๋‹ˆ๋‹ค.
    
    # numpy ๋ฐฐ์—ด๋กœ bytes stream ์“ฐ๊ธฐ
    file_bytes = np.asarray(bytearray(image_stream.read()), dtype=np.uint8)
    
    # numpy ๋ฐฐ์—ด์„ ์ด๋ฏธ์ง€๋กœ ๋””์ฝ”๋”ฉ
    image = cv2.imdecode(file_bytes, cv2.IMREAD_COLOR)
    
    # 3. OBJECT DETECTION MODEL ์‹คํ–‰
    
    bbox, label, conf = cv.detect_common_objects(image, model=model)
    
    # bounding box์™€ ๋ ˆ์ด๋ธ”์ด ํฌํ•จ๋œ ์ด๋ฏธ์ง€๋ฅผ ๋งŒ๋“ค์–ด ์ €์žฅ
    output_image = draw_bbox(image, bbox, label, conf)
    cv2.imwrite(f'images_uploaded/{filename}', output_image)
    
    # 4. CLIENT๋กœ Respond
    
    # ์ €์žฅ๋œ ์ด๋ฏธ์ง€๋ฅผ binary mode๋กœ ์ฝ๊ธฐ
    file_image = open(f'images_uploaded/{filename}', mode="rb")
    
    # ๋ฏธ๋””์–ด type์„ ์ง€์ •ํ•˜์—ฌ ์ŠคํŠธ๋ฆผ์œผ๋กœ ์ด๋ฏธ์ง€ ๋ฐ˜ํ™˜
    return StreamingResponse(file_image, media_type="image/jpeg")
# ์„œ๋ฒ„๊ฐ€ ๊ฐ€๋™ ์ฝ”๋“œ -> ์ปค๋„ ์ค‘๋‹จ ์ „๊นŒ์ง€ ์‹คํ–‰๋จ
# Jupyter notebook ํ™˜๊ฒฝ์—์„œ ์„œ๋ฒ„๋ฅผ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
nest_asyncio.apply()

host = "127.0.0.1"

# Spin up the server!    
uvicorn.run(app, host=host, port=8000)

http://localhost:8000/๋กœ ์ด๋™ํ•˜์—ฌ ์„œ๋ฒ„๊ฐ€ ์ž‘๋™ํ•˜๋Š” ๊ฒƒ์„ ํ™•์ธ

⇒ image๋ฅผ ์ œ์ถœํ•˜์—ฌ API๊ฐ€ ์ด๋ฏธ์ง€ ์•ˆ์˜ ๊ฐ์ฒด๋ฅผ ๊ฐ์ง€ํ•˜๊ณ  ๊ฐ์ง€๋œ ๊ฐ์ฒด์˜ ๋ ˆ์ด๋ธ”๊ณผ ํ•จ๊ป˜ bounding box๊ฐ€ ํฌํ•จ๋œ ์ƒˆ ์ด๋ฏธ์ง€๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋Š” ํ•˜๋Š” ๊ฒƒ์„ ํ™•์ธ

http://localhost:8000/docs๋ฅผ ๋ฐฉ๋ฌธํ•˜์—ฌ fastAPI์˜ ๋‚ด์žฅ client ์‚ฌ์šฉ