close

使用 Flask 和 Picamera2 打造 YOLO 即時物件偵測網頁介面

前言

隨著物聯網(IoT)和人工智慧(AI)的普及,即時物件偵測已成為許多應用中的核心功能。今天,我將帶你在 Raspberry Pi 上使用 FlaskPicamera2 模組,搭配 YOLO 模型,實現即時影像偵測,並在網頁上顯示影像流和檢測資訊。

透過這篇教學,你將學會:

  • 如何安裝 Flask 與 YOLO 環境。
  • 使用 Picamera2 從 Raspberry Pi 相機捕捉影像。
  • 將即時影像與 YOLO 偵測結果透過 Flask 提供網頁串流。
  • 在網頁上加入按鈕來暫停/恢復影像流,以及顯示檢測資訊。

準備工作

硬體需求:

  • Raspberry Pi 4 或 5
  • Raspberry Pi 相機(支援 Picamera2 模組)
  • 網路連接(本地網路)

軟體需求:

  • Raspberry Pi OS(推薦最新版)
  • Python 3(已預裝)
  • Flask、OpenCV 和 YOLO 模型相關套件

步驟一:安裝必要的套件

首先,確保 Raspberry Pi 已更新系統軟體,並安裝所需的 Python 套件。

更新系統與套件:

sudo apt update
sudo apt upgrade -y

啟用虛擬環境

source venv/bin/activate

安裝 Python 套件:

pip install flask opencv-python numpy ultralytics picamera2

下載 YOLO 模型:

我們使用輕量級的 YOLO 模型 yolov8n.pt。從 Ultralytics 官方 下載,並將模型放在專案目錄中。

步驟二:撰寫 Flask 和 YOLO 應用程式

我們的 Flask 應用會:

  • 使用 Picamera2 從攝像頭捕捉影像。
  • 使用 YOLO 模型進行物件偵測。
  • 提供一個網頁介面顯示即時影像流。
  • 顯示偵測資訊並提供按鈕來控制暫停和恢復功能。

完整程式碼:

from flask import Flask, Response, render_template_string, jsonify
import cv2
import numpy as np
from ultralytics import YOLO
from picamera2 import Picamera2
import threading
import time

app = Flask(__name__)

# Load the YOLOv8 model
model = YOLO("yolov8n.pt")

# Global variables for control and information sharing
is_paused = False
lock = threading.Lock()
latest_info = []

# Function to initialize and start the camera
def initialize_camera():
    picam2 = Picamera2()
    picam2.configure(picam2.create_preview_configuration(main={"size": (640, 480)}))
    picam2.start()
    return picam2

# Generate video frames for streaming
def generate_frames():
    global is_paused, latest_info
    while True:
        with lock:
            if is_paused:
                time.sleep(0.1)  # Sleep briefly to prevent tight loop when paused
                continue
        try:
            with initialize_camera() as camera:
                frame = camera.capture_array()

                # Check if the image has 4 channels and convert to 3 channels if necessary
                if frame.shape[2] == 4:
                    frame = cv2.cvtColor(frame, cv2.COLOR_BGRA2BGR)

                results = model.predict(source=frame, stream=True)
                temp_info = []
                for result in results:
                    boxes = result.boxes.xyxy.numpy()
                    for box, cls, conf in zip(boxes, result.boxes.cls.numpy(), result.boxes.conf.numpy()):
                        x1, y1, x2, y2 = map(int, box)
                        label = f"{model.names[int(cls)]}: {conf:.2f}"
                        temp_info.append(label)
                        cv2.rectangle(frame, (x1, y1), (x2, y2), (0, 255, 0), 2)
                        cv2.putText(frame, label, (x1, y1 - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 2)
                with lock:
                    latest_info = temp_info
                _, buffer = cv2.imencode('.jpg', frame)
                yield (b'--frame\r\n' b'Content-Type: image/jpeg\r\n\r\n' + buffer.tobytes() + b'\r\n')
        except RuntimeError as e:
            print(f"RuntimeError: {e}")
            time.sleep(1)  # Wait before retrying to avoid rapid repeated failures

# Main page route
@app.route('/')
def index():
    return render_template_string('''
        <!doctype html>
        <html>
        <head>
            <title>Live YOLOv8 Object Detection</title>
            <script>
                setInterval(() => {
                    fetch('/latest_info')
                        .then(response => response.json())
                        .then(data => {
                            document.getElementById('info').innerText = "Detected: " + data.join(', ');
                        });
                }, 1000);

                function pause() {
                    fetch('/pause', { method: 'POST' }).then(() => alert('Paused'));
                }

                function resume() {
                    fetch('/resume', { method: 'POST' }).then(() => alert('Resumed'));
                }
            </script>
        </head>
        <body>
            <h1>Live YOLOv8 Object Detection</h1>
            <img src="/video_feed" width="640" height="480">
            <div id="info">Detection Info: </div>
            <button onclick="pause()">Pause</button>
            <button onclick="resume()">Resume</button>
        </body>
        </html>
    ''')

# Video feed route
@app.route('/video_feed')
def video_feed():
    return Response(generate_frames(), mimetype='multipart/x-mixed-replace; boundary=frame')

# Pause route
@app.route('/pause', methods=['POST'])
def pause():
    global is_paused
    with lock:
        is_paused = True
    return jsonify({"status": "paused"})

# Resume route
@app.route('/resume', methods=['POST'])
def resume():
    global is_paused
    with lock:
        is_paused = False
    return jsonify({"status": "resumed"})

# Latest info route
@app.route('/latest_info')
def latest_info_route():
    with lock:
        return jsonify(latest_info)

# Run the Flask app
if __name__ == "__main__":
    app.run(host="0.0.0.0", port=5000, debug=True)

步驟三:運行應用程式

運行 Flask 伺服器:

python app.py

在瀏覽器中訪問:

打開瀏覽器,輸入 Raspberry Pi 的 IP 和埠號:

http://<Raspberry_Pi_IP>:5000

結果展示

  • 影像流:網頁上顯示實時物件偵測畫面。
  • 偵測資訊:在文字框中即時顯示 YOLO 偵測到的物件類別和置信度。

結語

透過 Flask 和 Picamera2,我們實現了一個功能齊全的 YOLO 物件偵測網頁應用。不僅可以即時顯示影像流,還加入了用戶互動功能,讓你能夠暫停/恢復檢測,並即時顯示檢測資訊。

arrow
arrow
    全站熱搜
    創作者介紹
    創作者 liusming 的頭像
    liusming

    劉老師的跨域創想工坊

    liusming 發表在 痞客邦 留言(0) 人氣()