우와한 개발자

[Spring AI] MQTT로 실시간 객체탐지 구현하기 — Mosquitto, paho-mqtt, YOLO, WebSocket

by 우와한개발자

 

MQTT는 발행/구독 모델 기반의 경량 메시지 프로토콜로, IoT 환경에서 실시간 통신에 주로 사용된다.

1. MQTT(Message Queuing Telemetry Transport)란?

  • 머신 대 머신 통신에 사용되는 표준 기반 경량 메시지 프로토콜
  • 주로 IoT(사물인터넷) 환경에서 기기들이 서로 메시지를 주고받을때 사용된다.
  • 낮은 대역폭(느린 인터넷)과 제한된 환경(IoT 센서, 임베디드 기기 등)에서도 신뢰성 있게 통신할 수 있도록 설계되었다.

 

1) HTTP vs MQTT

구분 HTTP MQTT
통신 방식 요청-응답 (클라이언트가 먼저 요청) 발행-구독 (브로커가 밀어줌)
연결 방식 요청마다 새로운 연결 한 번 연결 후 계속 유지
서버 → 클라이언트 불가 (클라이언트가 요청해야만 응답) 가능 (구독자에게 자동 전달)
수신 대상 요청한 1명만 응답 받음 구독 중인 전원이 동시에 받음
실시간성 낮음 (폴링 필요) 높음
메시지 크기 헤더가 크고 무거움 헤더 최소 2byte로 매우 가벼움
사용 상황 일반 웹 서비스, REST API IoT, 실시간 모니터링
비유 편지 (요청해야 답장) 라디오 방송 (틀면 바로 들림)

 

2) MQTT 브로커 종류

브로커 특징
Mosquitto 가볍고 널리 사용됨. MQTT 5.0 지원
HiveMQ 상용 중 가장 강력. 확장성, 고가용성, 웹 UI
EMQX 클라우드 기반, 대규모 연결 지원
VerneMQ Erlang 기반 고성능 브로커
RabbitMQ 기본은 AMQP 기반. MQTT는 플러그인으로 부분 지원
AWS IoT Core 보안·인증에 강점. MQTT 3.1.1 / 5.0 지원
Azure IoT Hub MQTT 3.1.1만 지원 (5.0 미지원)
Google Cloud IoT Core 2023년 서비스 종료
NanoMQ 경량 엣지 디바이스용. 빠르고 가벼움
Solace PubSub+ MQTT 외 SMF, REST 등 여러 프로토콜 지원

 

3) MQTT 주요 특징

  • 경량성 : 작은 데이터 패킷을 사용하여 네트워크 부하를 최소화한다.
  • 발행/구독 모델 : 클라이언트는 특정 토픽에 메시지를 발행하거나 구독할 수 있다.
  • 신뢰성 : QoS(Quality of Service) 레벨을 제공하여 메시지 전송을 보장한다.
  • 유연성 : 제한된 리소스를 가진 IoT 기기 등 다양한 환경에서 사용 가능하다.

 

4) MQTT 구성 요소

https://wikidocs.net/280624

  • 브로커 (Broker) : 발행자와 구독자 사이에서 메시지를 중계하는 서버이다. 모든 메시지는 브로커를 통해 전달된다.
  • 발행자 (Publisher) : 특정 토픽에 메시지를 발행하는 클라이언트이다. 예) 온도 센서가 home/temperature 토픽에 데이터를 전송한다.
  • 구독자 (Subscriber) : 특정 토픽을 구독하여 메시지를 수신하는 클라이언트이다. 예) 스마트폰 앱이 home/temperature 토픽을 구독해 실시간으로 온도를 표시한다.

 

2. 실시간 객체탐지 MQTT 구현 – Mosquitto 브로커

1) 브로커 설치 및 설정

(1) Windows

  • https://mosquitto.org/download 에서 다운로드한다.
  • 설치 후 C:\\Program Files\\mosquitto\\mosquitto.conf 를 아래와 같이 수정한다.
  • 이때, 해당 파일은 읽기전용 파일로 되어 있으므로 읽기 전용을 해제해야하고, 메모장을 관리자 권한으로 열어 읽어와서 수정

(2) Mac

  • Homebrew로 설치한다.
brew install mosquitto
  • 설정 파일 위치는 /opt/homebrew/etc/mosquitto/mosquitto.conf 이다.
# 파일 열기
nano /opt/homebrew/etc/mosquitto/mosquitto.conf

(3) 설정 내용 (Windows / Mac 공통)

# MQTT 포트
listener 1883
protocol mqtt

# WebSocket 포트 (브라우저 연결용)
listener 9001
protocol websockets

# 인증 없이 접속 허용
allow_anonymous true

(4) 실행

구분 Windows Mac
실행 mosquitto -c "C:\Program Files\mosquitto\mosquitto.conf" mosquitto -c /opt/homebrew/etc/mosquitto/mosquitto.conf
백그라운드 실행 서비스 등록 후 자동 실행 brew services start mosquitto
중지 서비스 중지 brew services stop mosquitto

 

2) Python 구현

(1) 라이브러리 설치

  • paho-mqtt : Python에서 MQTT 프로토콜을 사용할 수 있게 해주는 라이브러리로 MQTT 3.1, 3.1.1, 5.0 버전을 지원한다.
pip install paho-mqtt ultralytics opencv-python

(2) 코드

import base64
import cv2
import numpy as np
import paho.mqtt.client as mqtt
import json
from ultralytics import YOLO

model = YOLO("yolov8n.pt")  # YOLOv8 경량 모델 로드

# MQTT 브로커 설정
broker = 'localhost'
port = 1883
topic = "/camera/objects"

client = mqtt.Client(callback_api_version=mqtt.CallbackAPIVersion.VERSION2)

def on_connect(client, userdata, flags, rc, properties=None):
    print(f"Connected with result code {rc}")

client.on_connect = on_connect
client.connect(broker, port)  # 브로커 연결

def get_colors(num_colors):
    np.random.seed(0)  # seed 고정 → 항상 같은 색상
    colors = [tuple(np.random.randint(0, 255, 3).tolist()) for _ in range(num_colors)]
    return colors

colors = get_colors(len(model.names))  # 클래스별 색상 생성

def detect_objects(image: np.array):
    results = model(image, verbose=False)  # 객체 탐지
    for result in results:
        boxes = result.boxes.xyxy      # 바운딩 박스 좌표
        confidences = result.boxes.conf  # 신뢰도
        class_ids = result.boxes.cls     # 클래스 ID

        for box, confidence, class_id in zip(boxes, confidences, class_ids):
            x1, y1, x2, y2 = map(int, box)
            label = model.names[int(class_id)]
            cv2.rectangle(image, (x1, y1), (x2, y2), colors[int(class_id)], 2)  # 박스 그리기
            cv2.putText(image, f'{label} {confidence:.2f}', (x1, y1-10),
                        cv2.FONT_HERSHEY_SIMPLEX, 0.9, colors[int(class_id)], 2)  # 라벨 표시
    return image

cap = cv2.VideoCapture(1)  # 카메라 열기 (0: 기본 카메라)

running = True

def on_message(client, userdata, msg):
    if msg.topic == 'camera/control' and msg.payload.decode() == 'stop':
        global running
        running = False  # stop 메시지 수신 시 루프 종료

client.subscribe('camera/control')  # 종료 신호 토픽 구독
client.on_message = on_message

while cap.isOpened() and running:
    ret, frame = cap.read()
    if not ret:
        print("Error: Failed to read frame")
        break

    result_image = detect_objects(frame)
    encode_param = [cv2.IMWRITE_JPEG_QUALITY, 90]
    _, buffer = cv2.imencode('.jpg', result_image, encode_param)  # jpg 인코딩 (품질 90, 낮출수록 전송 빠름)
    jpg_as_text = base64.b64encode(buffer).decode('utf-8')  # base64 문자열 변환

    payload = json.dumps({"image": jpg_as_text})
    client.publish(topic, payload)  # MQTT로 발행

cap.release()
client.disconnect()

 

3) js 구현 (camera.html)

<!DOCTYPE html>
<html>
    <head>
        <title>실시간 객체탐지</title>
        <meta charset="utf-8">
        <script src="https://unpkg.com/mqtt/dist/mqtt.min.js"></script>
    </head>
    <body>
        <h1>실시간 객체탐지</h1>
        <img id="cameraView"/>
        <script>
            const imgEl = document.getElementById("cameraView");
            const broker = "ws://localhost:9001";  // WebSocket 포트
            const topic = "/camera/objects";

            const client = mqtt.connect(broker, { protocolVersion: 5 });  // 브로커 연결

            client.on('connect', () => {
                console.log('Connected to broker');
                client.subscribe(topic, (err) => {  // 토픽 구독
                    if (!err) console.log(`Subscribed to topic: ${topic}`);
                });
            });

            client.on('message', (topic, message) => {
                try {
                    const payload = JSON.parse(message.toString());
                    imgEl.src = `data:image/jpeg;base64,${payload.image}`;  // 이미지 표시
                } catch (e) {
                    console.log('Failed to parse message:', e);
                }
            });

            client.on('error', (error) => console.log('Connection error:', error));
            client.on('close', () => console.log('Disconnected from broker'));

            // Q 키로 종료
            document.addEventListener('keydown', (e) => {
                if (e.key === 'q' || e.key === 'Q') {
                    client.publish('camera/control', 'stop');  // Python에 종료 신호 발행
                    client.disconnect();
                }
            });
        </script>
    </body>
</html>

 

 

4) 실행

(1) 브로커 실행

구분 Windows Mac
실행 mosquitto -c "C:\Program Files\mosquitto\mosquitto.conf" mosquitto -c /opt/homebrew/etc/mosquitto/mosquitto.conf
백그라운드 서비스 등록 후 자동 실행 brew services start mosquitto

(2) Python 실행

python camera.py

(3) 브라우저 실행

  • 브라우저에서 camera.html 을 직접 열거나 Spring Boot 실행 후 접속한다.
방법 주소
파일 직접 열기 camera.html 파일을 브라우저로 드래그
Spring Boot 사용 시 http://localhost:8080/camera.html

(4) 종료

  • 브라우저에서 Q 키를 누르면 Python 프로세스까지 종료된다.

블로그의 정보

우와한개발자 님의 블로그

우와한개발자

활동하기