cosmos0311's picture
Update README.md
367e6cc verified
metadata
license: agpl-3.0
base_model: ultralytics/yolov8n
tags:
  - yolov8
  - object-detection
  - onnx
  - onnxruntime
  - raspberry-pi5
  - edge-ai
  - fire-detection
pipeline_tag: object-detection

Edge Fire Detection AI (ํ™”์žฌ ๊ฐ์‹œ ์˜จ์  ์‹œ์Šคํ…œ)

๋ผ์ฆˆ๋ฒ ๋ฆฌํŒŒ์ด 5(Raspberry Pi 5) ์ž„๋ฒ ๋””๋“œ ์—ฃ์ง€ ๋””๋ฐ”์ด์Šค ํ™˜๊ฒฝ์—์„œ ์‹ค์‹œ๊ฐ„์œผ๋กœ ๋ถˆ๊ฝƒ(Flame)๊ณผ ์—ฐ๊ธฐ(Smoke)๋ฅผ ๊ฐ์ง€ํ•˜๊ณ  ๋ฐ”์šด๋”ฉ ๋ฐ•์Šค๋ฅผ ์ถ”์ ํ•˜๋Š” ๊ฒฝ๋Ÿ‰ ๊ฐ์ฒด ํƒ์ง€(Object Detection) ๋ชจ๋ธ์ž…๋‹ˆ๋‹ค. YOLOv8n(Nano) ์•„ํ‚คํ…์ฒ˜๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ์ „์ด ํ•™์Šต(Transfer Learning)์„ ์ง„ํ–‰ํ–ˆ์œผ๋ฉฐ, ํ•˜๋“œ์›จ์–ด ์—ฐ์‚ฐ ๋ถ€ํ•˜๋ฅผ ์ตœ์†Œํ™”ํ•˜๊ธฐ ์œ„ํ•ด ONNX ํฌ๋งท ๊ณ ์† ๋Ÿฐํƒ€์ž„ ์ตœ์ ํ™”๋ฅผ ์ ์šฉํ–ˆ์Šต๋‹ˆ๋‹ค.

1. ํ•™์Šต ๋ฐ์ดํ„ฐ (Training Data)

  • ๋ฐ์ดํ„ฐ ์ถœ์ฒ˜: AI Hub ํ™”์žฌ ๊ฐ์ง€ ์˜์ƒ ๋ฐ์ดํ„ฐ์…‹ ๋ฐ ์ž์ฒด ์ˆ˜์ง‘ ํ™”์žฌ ์ด๋ฏธ์ง€ ์…‹
  • ํด๋ž˜์Šค ์ •์˜: ์ด 2๊ฐœ ํด๋ž˜์Šค (flame: ๋ถˆ๊ฝƒ / smoke: ์—ฐ๊ธฐ)
  • ๋ฐ์ดํ„ฐ ์ •์ œ ๋ฐ ์ฆ๊ฐ• (Data Augmentation):
    • ์•ผ๊ฐ„ ๋ฐ ์˜ค์ง€ ๋“ฑ ์ €์กฐ๋„ ์ƒํ™ฉ์—์„œ์˜ ์˜ค์ž‘๋™(False Positive)์„ ์ฐจ๋‹จํ•˜๊ธฐ ์œ„ํ•ด ๋ฐ๊ธฐ ๋ณ€ํ˜•, ํ๋ฆผ(Blur), ๋ชจ์ž์ดํฌ(Mosaic) ์ฆ๊ฐ• ๊ธฐ๋ฒ•์„ ์ ์šฉํ•˜์—ฌ ๋ชจ๋ธ์˜ ๊ฐ•๊ฑด์„ฑ(Robustness) ํ™•๋ณด
    • ์—ฃ์ง€ ๋””๋ฐ”์ด์Šค์˜ I/O ๋ฒ„์Šค ๋Œ€์—ญํญ ๋ถ€ํ•˜๋ฅผ ๊ฒฝ๊ฐํ•˜๊ธฐ ์œ„ํ•ด ์ž…๋ ฅ ํ•ด์ƒ๋„๋ฅผ 320x240 ๋ฐ 640x480 ํˆฌ ํŠธ๋ž™ ๊ทœ๊ฒฉ์œผ๋กœ ์Šค์ผ€์ผ๋ง ์ „์ฒ˜๋ฆฌ ์ˆ˜ํ–‰

2. ๋ชจ๋ธ ์„ฑ๋Šฅ (Evaluation Metrics)

ํ‰๊ฐ€ ์ง€ํ‘œ ํŒŒ์ดํ† ์น˜ (.pt) ์›๋ณธ ONNX ๊ฒฝ๋Ÿ‰ ๊ฐ€์† ๊ทœ๊ฒฉ ๋น„๊ณ  (๋ผ์ฆˆ๋ฒ ๋ฆฌํŒŒ์ด 5 CPU ๊ธฐ์ค€)
mAP50 (์ „์ฒด ์ •ํ™•๋„) 84.5% 83.9% ํฌ๋งท ๋ณ€ํ™˜์— ๋”ฐ๋ฅธ ์ •ํ™•๋„ ์†์‹ค 0.6% ๋ฏธ๋งŒ ๋ฐฉ์–ด
mAP50-95 0.521 0.518 -
์ถ”๋ก  ์†๋„ (Latency) ์•ฝ 350ms ์•ฝ 45ms ONNX ๋ณ€ํ™˜ ํ›„ ์‹ค์‹œ๊ฐ„์„ฑ(20 FPS ์ด์ƒ) ์ˆ˜๋ ด

3. ๋ชจ๋ธ ํ•™์Šต ๊ณผ์ • (Model Training Process)

  • ํ•™์Šต ์ธํ”„๋ผ: ๊ณ ์„ฑ๋Šฅ ์™ธ์žฅ GPU(RTX 5070 Ti, 32GB RAM) ํ™˜๊ฒฝ ๊ธฐ๋ฐ˜ ๋กœ์ปฌ ๊ฐ€์† ๋นŒ๋“œ ๊ตฌ๋™
  • ๊ธฐ๋ฐ˜ ์•„ํ‚คํ…์ฒ˜: 3.2M ๊ฐ€๋ฒผ์šด ํŒŒ๋ผ๋ฏธํ„ฐ ๊ตฌ์กฐ๋ฅผ ๊ฐ€์ง„ YOLOv8n์„ ๋ฐฑ๋ณธ(Backbone)์œผ๋กœ ์ฑ„ํƒํ•˜์—ฌ ์ž„๋ฒ ๋””๋“œ CPU ๊ฐ€๋™ ๋Œ€์—ญํญ ์‚ฌ์ „ ํ™•๋ณด
  • ํ•˜์ดํผํŒŒ๋ผ๋ฏธํ„ฐ ์„ค์ •:
ํ•ญ๋ชฉ ์„ค์ •๊ฐ’ ๋น„๊ณ 
Epochs 50 -
Batch Size 32 ํ•˜๋“œ์›จ์–ด VRAM ์ตœ์ ํ™” ํฌ๊ธฐ
Optimizer AdamW -
Learning Rate 1e-3 ์ดˆ๊ธฐ ํ•™์Šต๋ฅ  ์ง€์ •
Augmentation Mosaic (1.0), Blur (0.2) ๊ฐ€ํ˜น ํ™˜๊ฒฝ ์˜ค์ž‘๋™ ์ฐจ๋‹จ์šฉ

4. ๊ฐœ๋ฐœ ์‹œํ–‰์ฐฉ์˜ค ๋ฐ ํŠธ๋Ÿฌ๋ธ”์ŠˆํŒ… (Trials & Errors)

์ž„๋ฒ ๋””๋“œ ์—ฃ์ง€ AI ์‹œ์Šคํ…œ ๊ตฌํ˜„ ์ค‘ ๋ฐœ์ƒํ•œ ๋ฌผ๋ฆฌ/์†Œํ”„ํŠธ์›จ์–ด ๋ ˆ์ด์–ด์˜ ๋ณ‘๋ชฉ ํ˜„์ƒ๊ณผ ํ•ด๊ฒฐ ๋ฆฌํฌํŠธ์ž…๋‹ˆ๋‹ค.

โ‘  PyTorch ์›์‹œ ๋ชจ๋ธ(.pt) ๊ตฌ๋™ ์‹œ ๋ ˆ์ดํ„ด์‹œ ์ €ํ•˜

  • ๋ฌธ์ œ ์ƒํ™ฉ: ํ•™์Šต ์™„๋ฃŒ๋œ best.pt ๊ฐ€์ค‘์น˜๋ฅผ ๋ผ์ฆˆ๋ฒ ๋ฆฌํŒŒ์ด 5 CPU ๋‹จ๋… ํ™˜๊ฒฝ์—์„œ ์ถ”๋ก ํ–ˆ์„ ๋•Œ ํ”„๋ ˆ์ž„ ์ง€์—ฐ์ด ์•ฝ 350ms(3 FPS ๋ฏธ๋งŒ)๊นŒ์ง€ ๋Š˜์–ด๋‚˜ ์‹ค์‹œ๊ฐ„ ํ™”์žฌ ํƒ์ง€๊ฐ€ ๋ถˆ๊ฐ€๋Šฅํ•œ ๋ณ‘๋ชฉ ์ง๋ฉด.
  • ํ•ด๊ฒฐ ์กฐ์น˜: ์ •์  ์—ฐ์‚ฐ ๊ทธ๋ž˜ํ”„ ๊ตฌ์กฐ๋ฅผ ๊ฐ€์ง„ ONNX ํฌ๋งท์œผ๋กœ ๋ชจ๋ธ์„ ์ต์ŠคํฌํŠธ(format=onnx, imgsz=320)ํ•˜๊ณ , ํŒŒ์ด์ฌ ๋ฐฑ์—”๋“œ์— onnxruntime ๊ฐ€์† ํ”Œ๋Ÿฌ๊ทธ์ธ์„ ๊ฒฐํ•ฉํ•˜์—ฌ ์ถ”๋ก  ๋ ˆ์ดํ„ด์‹œ๋ฅผ 45ms(22 FPS ์ด์ƒ) ์ˆ˜์ค€์œผ๋กœ ๋น„์•ฝ์ ์œผ๋กœ ๋‹จ์ถ•ํ•จ.

โ‘ก Linux V4L2 ์žฅ์น˜ ์ฑ„๋„ ํƒ€์ž„์•„์›ƒ (select() timeout)

  • ๋ฌธ์ œ ์ƒํ™ฉ: ์ตœ์‹  ๋ผ์ฆˆ๋ฒ ๋ฆฌํŒŒ์ด OS(Bookworm) ์ปค๋„์—์„œ OpenCV๋กœ ์›น์บ ์„ ํ˜ธ์ถœํ•  ๋•Œ ๋ฏธ๋””์–ด ์ปจํŠธ๋กค๋Ÿฌ ๊ฐ€์† ์Šคํƒ์ด /dev/video0 ์ž์›์„ ๋…์  ์ ์œ (Lock)ํ•˜์—ฌ select() timeout๊ณผ ํ•จ๊ป˜ ํ•˜๋“œ์›จ์–ด ์ข€๋น„ ๋ฝ ํ˜„์ƒ ๋ฐœ์ƒ.
  • ํ•ด๊ฒฐ ์กฐ์น˜: OpenCV ์นด๋ฉ”๋ผ ๊ฐœ์‹œ ์ฝ”๋“œ๋‹จ์— V4L2 ๋ฐฑ์—”๋“œ๋ฅผ ๋ช…์‹œ์ ์œผ๋กœ ์ง€์ •(cv2.VideoCapture(0, cv2.CAP_V4L2))ํ•˜๊ณ , ์ฃผํ”ผํ„ฐ ์„œ๋ฒ„ ๊ตฌ๋™ ์‹œ ๋ฆฌ๋ˆ…์Šค ์—๋ฎฌ๋ ˆ์ด์…˜ ํ˜ธํ™˜ ๋ ˆ์ด์–ด ํ™˜๊ฒฝ ๋ณ€์ˆ˜์ธ LD_PRELOAD=/usr/lib/aarch64-linux-gnu/libv4l/v4l1compat.so๋ฅผ ํ”„๋ฆฌ๋กœ๋“œ ๋ž˜ํ•‘ํ•˜์—ฌ ์ปค๋„ ์ ์œ  ์ถฉ๋Œ์„ ์šฐํšŒํ•จ.

โ‘ข USB ๋ฒ„์Šค ๋Œ€์—ญํญ ์ดˆ๊ณผ ๋ฐ ํ”ฝ์…€ ์˜ค์—ผ (error dequeuing buf)

  • ๋ฌธ์ œ ์ƒํ™ฉ: ์••์ถ• ์ฝ”๋ฑ ๊ธฐ๋Šฅ์ด ์—†๋Š” ์ผ๋ฐ˜ ์›น์บ ์˜ ์›์‹œ ๋ฐ์ดํ„ฐ(Raw YUYV)๋ฅผ 640x480 ํ•ด์ƒ๋„๋กœ ์ˆ˜์‹  ์‹œ, ๋ผ์ฆˆ๋ฒ ๋ฆฌํŒŒ์ด USB ๋Œ€์—ญํญ ํ•œ๊ณ„๋กœ ํ”„๋ ˆ์ž„ ๋ฒ„ํผ๊ฐ€ ๋ˆ„์ˆ˜๋˜์–ด ํ™”๋ฉด์ด ์ง€์งˆ๊ฑฐ๋ฆฌ๊ฑฐ๋‚˜ ๊ฒ€์€์ƒ‰(Black Screen)์œผ๋กœ ์ฃฝ๋Š” ํ˜„์ƒ ๋ฐœ์ƒ.
  • ํ•ด๊ฒฐ ์กฐ์น˜: ์›น์บ ์„ ์ „๋ ฅ ๋ฐ ์ „์†ก ํšจ์œจ์ด ๋†’์€ ํŒŒ๋ž€์ƒ‰ USB 3.0 ํฌํŠธ๋กœ ๋ฌผ๋ฆฌ์  ์ด๋™ ์ •๋ ฌ์„ ์ˆ˜ํ–‰ํ•œ ํ›„, ์ฝ”๋“œ ๋ ˆ๋ฒจ์—์„œ ์ž…๋ ฅ ํ•ด์ƒ๋„๋ฅผ 320x240์œผ๋กœ ๋Œ€ํญ ๋‚ฎ์ถ”๊ณ  ๊ณ ์† ์••์ถ• ์ŠคํŠธ๋ฆฌ๋ฐ ํฌ๋งท(MJPG) ์ฝ”๋ฑ ์ง€์ •์„ ๊ฐ•์ œํ™”ํ•˜์—ฌ ๋ฐ์ดํ„ฐ ๋ฒ„์Šค ํ†ต๋กœ๋ฅผ ๋ณต๊ตฌํ•จ.

โ‘ฃ ์ฃผํ”ผํ„ฐ ๋„คํŠธ์›Œํฌ ์›น ์†Œ์ผ“ ๋ถ•๊ดด ํฌ๋ž˜์‹œ (ZMQError)

  • ๋ฌธ์ œ ์ƒํ™ฉ: ์ถ”๋ก  while ๋ฃจํ”„๊ฐ€ ์ดˆ๋‹น ์ˆ˜์‹ญ ์žฅ์˜ ์ด๋ฏธ์ง€๋ฅผ ์ง€์—ฐ ์—†์ด ์ฃผํ”ผํ„ฐ ์ธ๋ผ์ธ ์œ„์ ฏ(ipywidgets) ํ™”๋ฉด์œผ๋กœ ๋ฐ€์–ด ๋„ฃ์œผ๋ฉด์„œ ํ†ต์‹  ๋ฒ„ํผ ์˜ค๋ฒ„ํ”Œ๋กœ์šฐ๋กœ ์ธํ•ด ์ฃผํ”ผํ„ฐ ์ปค๋„์ด ์ •์ง€ํ•˜๊ณ  ์†Œ์ผ“์ด ํ„ฐ์ง€๋Š” ํ˜„์ƒ ๋ฐœ์ƒ.
  • ํ•ด๊ฒฐ ์กฐ์น˜: ์ด๋ฏธ์ง€ ๋ณ€ํ™˜ ๋ฐ ์ „์†ก ๋ฃจํ‹ด ์งํ›„ ๋ฏธ์„ธ ์œ ํœด ์—ฐ์‚ฐ ํƒ€์ž„์ธ time.sleep(0.04) ์ง€์—ฐ ๋ฒ„ํผ ๋กœ์ง์„ ์•ˆ์ฐฉ์‹œ์ผœ ์ดˆ๋‹น ์ „์†ก๋ฅ ์„ 20~25ํ”„๋ ˆ์ž„ ์ˆ˜์ค€์œผ๋กœ ์•ˆ์ •ํ™”ํ•จ์œผ๋กœ์จ ์—ฐ์† ๊ตฌ๋™ ์•ˆ์ „์„ฑ์„ ํ™•๋ณดํ•จ.

5. ์‚ฌ์šฉ ๋ฐฉ๋ฒ• (Inference Code)

import cv2
import numpy as np
from ultralytics import YOLO
from IPython.display import display
import ipywidgets as widgets
import time

def main():
    model_path = "best.onnx"
    model = YOLO(model_path, task='detect')

    # V4L2 ๊ฐ€์† ๋ฐฑ์—”๋“œ ๋งคํ•‘
    cap = cv2.VideoCapture(0, cv2.CAP_V4L2)
    cap.set(cv2.CAP_PROP_FRAME_WIDTH, 320)
    cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 240)
    cap.set(cv2.CAP_PROP_BUFFERSIZE, 1)

    time.sleep(1) # ํ•˜๋“œ์›จ์–ด ์•ˆ์ •ํ™” ๋Œ€๊ธฐ
    if not cap.isOpened():
        print("[์˜ค๋ฅ˜] ์นด๋ฉ”๋ผ ์žฅ์น˜๋ฅผ ํ™œ์„ฑํ™”ํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.")
        return

    image_widget = widgets.Image(format='jpeg', width=320, height=240)
    display(image_widget)

    try:
        while cap.isOpened():
            success, frame = cap.read()
            if not success or frame is None:
                continue

            # ONNX ๊ฐ€์† ์ถ”๋ก  ๊ตฌ๋™ (imgsz ์ผ์น˜)
            results = model(frame, imgsz=320, stream=True)
            for r in results:
                annotated_frame = r.plot()

            # JPEG ์••์ถ• ํ›„ ์ฃผํ”ผํ„ฐ ๋ Œ๋”๋ง ์˜์—ญ ์—…๋ฐ์ดํŠธ
            ret, jpeg = cv2.imencode('.jpg', annotated_frame)
            if ret:
                image_widget.value = jpeg.tobytes()
            
            # ZMQ ๊ณผ๋ถ€ํ•˜ ๋ฐฉ์ง€์šฉ micro-delay
            time.sleep(0.04) 
            
    except KeyboardInterrupt:
        print("\n์‚ฌ์šฉ์ž์— ์˜ํ•ด ์‹œ์Šคํ…œ ๊ฐ€๋™์ด ์ค‘๋‹จ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.")
    finally:
        cap.release()
        cv2.destroyAllWindows()
        print("ํ•˜๋“œ์›จ์–ด ์ž์› ํšŒ์ˆ˜ ์™„๋ฃŒ.")

if __name__ == '__main__':
    main()

6. ์ž…์ถœ๋ ฅ ํ˜•์‹ (Input / Output Format)

์ž…๋ ฅ(Input): ์‹ค์‹œ๊ฐ„ ์ž„๋ฒ ๋””๋“œ ์นด๋ฉ”๋ผ ๋น„๋””์˜ค ํ”„๋ ˆ์ž„ (320x240 RGB Image)
์ถœ๋ ฅ(Output): 
  - ๋ถˆ๊ฝƒ ๋ฐ ์—ฐ๊ธฐ ์˜์—ญ ๋ฐ”์šด๋”ฉ ๋ฐ•์Šค(Bounding Box) ์‹œ๊ฐํ™” ํˆฌ์‚ฌ
  - ํƒ์ง€ ํด๋ž˜์Šค ๋ ˆ์ด๋ธ” ๋งคํ•‘: [flame, smoke]
  - ๊ฐ์ฒด๋ณ„ ์ถ”๋ก  ์‹ ๋ขฐ๋„ ์ˆ˜์น˜ ์ถœ๋ ฅ (Confidence Score: 0.0 ~ 1.0)

7. ๋กœ์ปฌ ๋™๊ธฐํ™” ๊ฐ€์ค‘์น˜ ๋‹ค์šด๋กœ๋“œ CLI

huggingface-cli download firedetection/fire_detection_yolov8n --local-dir ./model