Python Flask OpenCV 攝影機影像即時串流

本篇介紹如何用 Python 與 Flask OpenCV 來建立一個攝影機 MJPEG (Motion JPEG) 即時串流的網站,在網站上顯示 webcam 或 camera 攝影機的即時串流影像,這個應用常見於監控系統或者即時影像處理的情形上。讀取攝影機的影像在這篇使用的是 OpenCV 模組,你也可以改用其它方式來讀取攝影機的影像。你可以使用隨手可得的 webcam,也可以使用一般的 camera 攝影機就可以來試試本篇的教學囉!

學習這篇後你將可以,

  • 建立一個家庭安全監視系統
  • 建立一個嬰兒監視系統
  • 建立一個視訊串流網頁,例如:17直播、Zoom多人線上視訊會議

我陪我老婆在月子中心的時候,就透過月子中心提供的 App 來觀看嬰兒室裡寶寶的最新畫面,同時也可以從網站上觀看,之後我便對此視訊串流技術感到興趣,經過一連串的研究與了解背後的技術是怎麼實現,最後實做完變成了這篇文章。

在建立一個 Flask 串流網站前,需要先了解一個基本的網站是怎麼寫的,要學習建立一個網頁最間單的方式就是從 Hello World 開始,這部分可以參考我之前幾篇 Flask 系列教學,如果你已經學會怎麼建立一個 Flask 網站,接下來就先小試身手,用 Flask 建立一個可以顯示圖片的網站,再一步步進入本篇的主軸。

顯示圖片的 Flask 網頁伺服器

這邊就直接使用 Flask 模板的寫法來處理 index.html,在 index.html 裡放置一張圖片,而圖片這種,

templates/index.html
1
2
3
4
5
6
7
8
9
10
<!doctype html>
<html lang="en">
<head>
<title>Flask</title>
</head>
<body>
<h3>Picture</h3>
<img src="/static/lena.jpg">
</body>
</html>

Flask 裡使用 render_template 處理 templete,

flask-picture.py
1
2
3
4
5
6
7
8
9
10
11
12
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from flask import Flask, render_template

app = Flask(__name__)

@app.route('/')
def index():
return render_template('index.html')

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

Multipart/x-mixed-replace Responses

本篇使用的串流技術是 HTTP 的 multipart 類型回應,瀏覽器透過不同的 Content-Type header 定義可以做出對應的反應,例如瀏覽器遇到 Content-Type 是 application/zip 就下載檔案,遇到 Content-Type 是 application/pdf 就預覽 pdf 等等。

而本篇使用到的 Content-Type 是 multipart/x-mixed-replacex-mixed-replace 是 Server 利用 HTTP 推送串流的技術其中之一,作法是讓每一個資料區塊取代頁面中先前的一塊藉此達到更新畫面,利用這種技術你可以將圖片作為每一個資料區塊來傳送,這樣在瀏覽器上看起來就像在播放視訊或播放動畫的效果。

實作更新的關鍵在於使用 multipart 回應。multipart 回應的內容是先一個 Content-Type 為 multipart/x-mixed-replace 的 header 並指名 boundary=frame 的分割名稱(分割名稱可自行更改),之後的每一個資料區塊的是用 --frame 作為資料區塊分割的標記,並且後面接著 Content-Type 與資料,Content-Type 為 image/jpeg 表示接下來的資料是 jpeg 資料。

以下為資料傳送的示意,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
HTTP/1.1 200 OK
Content-Type: multipart/x-mixed-replace; boundary=frame

--frame
Content-Type: image/jpeg

<jpeg data here>
--frame
Content-Type: image/jpeg

<jpeg data here>
--frame
Content-Type: image/jpeg

<jpeg data here>
...

Flask 串流連續影像

在之前的範例已經可以在網頁中呈現一張靜態影像了,接下來我們要來呈現連續影像,用的就是上一節介紹的技術,這邊我們示範連續地循環串流 5 張數字影像,在 index.html 的 img 標籤裡放入 video_feed

templates/index.html
1
2
3
4
5
6
7
8
9
10
<!doctype html>
<html lang="en">
<head>
<title>Flask</title>
</head>
<body>
<h3>Pictures Streaming</h3>
<img src="{{ url_for('video_feed') }}">
</body>
</html>

/video_feed 路由處理

flask-pictures-streaming.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from flask import Flask, render_template, Response
import time

app = Flask(__name__)
frames = [open(f + '.jpg', 'rb').read() for f in ['1', '2', '3', '4', '5']]

def gen_frames():
counter = 0
while True:
n = counter % 5
print(str(n))
frame = frames[counter % 5]
counter += 1
yield (b'--frame\r\n'
b'Content-Type: image/jpeg\r\n\r\n' + frame + b'\r\n')
time.sleep(0.5)

@app.route('/video_feed')
def video_feed():
return Response(gen_frames(),
mimetype='multipart/x-mixed-replace; boundary=frame')
@app.route('/')
def index():
return render_template('index.html')

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

開啟 http://0.0.0.0:5000/ 網頁後,應該可以看到網頁上不停地輪流播放 1~5 數字影像,如下圖,

生成器

關於 之前已經介紹過了,

1
2
3
4
def gen_frames():
yield 1
yield 2
yield 3

生成器(generator functions)

Flask camera 攝影機影像串流

Response 傳入一個 genator,
multipart/x-mixed-replace:後續抵達的資料區塊會覆蓋先前的結果,藉此達成動畫效果
boundary=frame:告知後續的連續資料塊以 --frame 作為各單位資料區塊邊界
image/jpeg:告知傳送的每一塊的資料型態為 jpeg 影像

1
2
3
4
5
6
7
8
9
10
<!doctype html>
<html lang="en">
<head>
<title>Flask</title>
</head>
<body>
<h3>Camera Live Streaming</h3>
<img src="{{ url_for('video_feed') }}">
</body>
</html>
flask-camera-streaming.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from flask import Flask, render_template, Response
import cv2

app = Flask(__name__)
camera = cv2.VideoCapture(0)

def gen_frames():
while True:
success, frame = camera.read()
if not success:
break
else:
ret, buffer = cv2.imencode('.jpg', frame)
frame = buffer.tobytes()
yield (b'--frame\r\n'
b'Content-Type: image/jpeg\r\n\r\n' + frame + b'\r\n')

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

@app.route('/')
def index():
return render_template('index.html')

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

app.run() 如果沒有使用 threaded=True 的話,會發現一次只能一個使用者觀看,加上 threaded=True 後 flask 就能夠用多執行緒的方式來服務多個使用者。

執行接下來就可以直接在瀏覽器,開啟 http://127.0.0.1:5000/ 這個網址。

下一篇介紹不使用 Flask 的

其它參考
Video Streaming with Flask - miguelgrinberg.com
https://blog.miguelgrinberg.com/post/video-streaming-with-flask
OpenCV – Stream video to web browser/HTML page
https://www.pyimagesearch.com/2019/09/02/opencv-stream-video-to-web-browser-html-page/
Live Webcam Flask Opencv Python
https://medium.com/@manivannan_data/live-webcam-flask-opencv-python-26a61fee831
Video Streaming in Web Browsers with OpenCV & Flask
https://towardsdatascience.com/video-streaming-in-web-browsers-with-opencv-flask-93a38846fe00
miguelgrinberg / flask-video-streaming
https://github.com/miguelgrinberg/flask-video-streaming
akmamun / camera-live-streaming
https://github.com/akmamun/camera-live-streaming

其它相關文章推薦
Python Flask 建立簡單的網頁
Python Flask render_template 模板

Python Flask 螢幕分享