- Edge Fire Detection AI (ํ์ฌ ๊ฐ์ ์จ์ ์์คํ
)
- 1. ํ์ต ๋ฐ์ดํฐ (Training Data)
- 2. ๋ชจ๋ธ ์ฑ๋ฅ (Evaluation Metrics)
- 3. ๋ชจ๋ธ ํ์ต ๊ณผ์ (Model Training Process)
- 4. ๊ฐ๋ฐ ์ํ์ฐฉ์ค ๋ฐ ํธ๋ฌ๋ธ์ํ (Trials & Errors)
- 5. ์ฌ์ฉ ๋ฐฉ๋ฒ (Inference Code)
- 6. ์
์ถ๋ ฅ ํ์ (Input / Output Format)
- 7. ๋ก์ปฌ ๋๊ธฐํ ๊ฐ์ค์น ๋ค์ด๋ก๋ CLI
- 1. ํ์ต ๋ฐ์ดํฐ (Training Data)
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