如何用 OpenCV + Flask 快速搭建影像串流?

本篇分享如何使用 Raspberry Pi 和 OpenCV + Flask 快速搭建簡易的影像串流 server,只用一份 py 檔實現土炮版的 IP cameras,就讓我們開始吧!

使用 OpenCV 擷取影像並壓縮

開一個空白檔案命名為 stream.py,初始化 OpenCV

import cv2
cap = cv2.VideoCapture(0)

準備兩個 function,一個是 get_image_bytes(),另一個是 resize_img_2_bytes()

from io import BytesIO
from PIL import Image

def resize_img_2_bytes(image, resize_factor, quality):
    bytes_io = BytesIO()
    img = Image.fromarray(image)

    w, h = img.size
    img.thumbnail((int(w * resize_factor), int(h * resize_factor)))
    img.save(bytes_io, 'jpeg', quality=quality)
    
    return bytes_io.getvalue()


def get_image_bytes():
    success, img = cap.read()
    if success:
        img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
        img_bytes = resize_img_2_bytes(img, resize_factor=0.5, quality=30)
        return img_bytes

    return None

其中做的事情如下:

  1. 首先 get_image_bytes() 從 camera 擷取一張影像
  2. 如果成功的話,先將編碼轉成 RGB(OpenCV 預設是 BGR,直接輸出成圖片顏色會很奇怪)
  3. 呼叫 resize_img_2_bytes() 壓縮圖片
  4. PIL 先將 image nparray 讀入後,使用 thumbail api 縮小圖片
  5. 透過 BytesIO 作為中介,將 PIL image 壓縮儲存成 jpeg 回傳
  6. 最後 get_image_bytes() 將結果丟出去

使用 Flask 搭建簡易 Webserver

首先初始化 Flask,給定 template folder,因為我們需要 Flask 幫忙 render template

from flask import Flask, render_template, Response

app = Flask(
    __name__,
    static_url_path='', 
    static_folder='./',
    template_folder='./',
)

寫一個 function 當進入網站時,render template

@app.route("/", methods=['GET'])
def get_stream_html():
    return render_template('stream.html')

另外實作一個 api,從剛剛寫好的 get_image_bytes() 讀取資料,不斷丟到 Response 裡,瀏覽器就能刷新 img ,實現直播的畫面

def gen_frames():
    while True:
        img_bytes = get_image_bytes()
        if img_bytes:
            yield (b'--frame\r\n'
                   b'Content-Type: image/jpeg\r\n\r\n' + img_bytes + b'\r\n')

@app.route('/api/stream')
def video_stream():
    return Response(gen_frames(), mimetype='multipart/x-mixed-replace; boundary=frame')

最後簡單使用 app.run() 啟動 webserver

if __name__ == "__main__":
    app.run(host='0.0.0.0')

別忘了準備 template html file,命名為 stream.html 並放在 py 檔同層目錄中

<!DOCTYPE html>
<html lang="en">
	<head>
		<script src="https://cdn.tailwindcss.com"></script>
	</head>
	<body>
		<div class="container">
			<div class="row">
				<div class="grid-cols-8">
					<h3 class="text-4xl text-center m-11">Live Streaming</h3>
					<img src="{{ url_for('video_stream') }}" width="100%">
				</div>
			</div>
		</div>
	</body>
</html>

啟動 Stream Server

用以下指令啟動

python stream.py

會看到 Flask 的警告,不過沒關係,只是簡單土炮,web server 還是可以正常使用

用瀏覽器開啟 http://<raspberry_pi_ip>:5000,就會看到串流的畫面了

眼尖的朋友會發現我跑在 mac 上做截圖XD,其實只要有視訊鏡頭的裝置都可以用這份 py 檔來實現即時串流!

範例原始碼在此下載:github

延伸閱讀:
用 MotionEye + Raspberry pi 做一個網路監控系統吧!
使用 Face Recognition 套件快速建立自己的人臉辨識系統!
如何在 Jetson Nano 上安裝 OpenCV?

參考資料:Video Streaming in Web Browsers with OpenCV & Flask

J
Written by J
雖然大學唸的是生物,但持著興趣與熱情自學,畢業後轉戰硬體工程師,與宅宅工程師們一起過著沒日沒夜的生活,做著台灣最薄的 intel 筆電,要與 macbook air 比拼。離開後,憑著一股傻勁與朋友創業,再度轉戰軟體工程師,一手扛起前後端、雙平台 app 開發,過程中雖跌跌撞撞,卻也累計不少經驗。可惜不是那 1% 的成功人士,於是加入其他成功人士的新創公司,專職開發後端。沒想到卻在採前人坑的過程中,拓寬了眼界,得到了深層的領悟。