Edited at

FlaskとOpenCVを使ってWEBカメラで撮影した画像をストリーミングする

WEBサービスで動画を配信するとき、HTMLのVideoタグを利用して、動画を再生させることがあります。

しかし、videoタグは指定された動画をそのまま読み込んで再生するだけです。そのため、データ容量の大きい動画ファイルの場合に、読み込みに時間がかかってしまうという問題があります。

そこで、WEBサーバー側で動画をストリーミングするのにどうしようかを考えていました。その過程で、FlaskとOpenCVでWEBカメラの映像をストリーミングする、ということをやったので、その方法について書きます。


ストリーミングとは

ストリーミングとは、画像や動画などの容量の大きなデータを、小さいまとまりに分けて少しずつ転送/再生することにより、ユーザーの待ち時間を少なくする技術です。

たとえば、HTML5のvideoタグでは、ブラウザで動画を開いた直後、ユーザーはすべてのデータをまとめて受け取り、その後で再生が行われます。そのため、データ量が大きい場合には、再生されるまでに待ち時間が発生します。

これによって、ユーザーが待ちきれずにサイトから離脱してしまう、といった問題が起こり得ます。

一方、ストリーミングを行うと、ユーザーはデータを少しずつ受け取りながら動画を再生できます。そのため、動画全体の読みことを待つことなく、すぐに再生を始めることができるのです。

今回は、まず、Flaskで画像のストリーミングを行います。その後、WEBカメラから映像を取得し、画像と同様にストリーミングを行います。


実行環境


  • Windows 10 home

  • Python 3.6.3

  • Flask 1.0.2

  • opencv-python 3.3.0.10


Flaskで画像をストリーミング配信する

今回は、まず簡単なHTMLテンプレートを用意し、その中にimgタグを埋め込んで、画像をストリーミング配信します。言葉で説明するよりも、まずは、コードを見て頂くと分かりやすいです。

HTMLテンプレートを用意します。


index.html

<html>

<head>
<title>Flask Streaming Test</title>
</head>
<body>
<h1>Flask Streaming Test</h1>
<img src="{{ url_for('feed') }}">
</body>
</html>

index.htmlでは、imgタグを設置して、srcをurl_for('feed')で読み込んでいます。ここで画像をストリーミングします。

次に、/feedへのアクセスに対する、レスポンスを書いていきます。


stream.py

from flask import Flask, render_template, Response

from camera import Camera

app = Flask(__name__)

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

def generate(camera):
while True:
frame = camera.get_frame()
yield (b'--frame\r\n' b'Content-Type: image/jpeg\r\n\r\n' + frame + b'\r\n')

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

if __name__ == '__main__':
app.run(host='0.0.0.0', debug=True)


feedへのアクセスは、generate(camera)メソッドを返します。generate(camera)メソッドの引数には、Cameraオブジェクトを設定します。

そしてgenerate(camera)メソッドでは、Cameraクラスのget_frame()メソッドを呼び出し、frameを取得して、それをyieldして返します。

続いて、Cameraクラスを実装します。


camera.py

from time import time

class Camera(object):
def __init__(self):
self.frames = []
self.files = ['1', '2', '3', '4', '5']

for f in self.files:
self.frames.append(open('./static/img/' + f + '.jpg', 'rb').read())

def get_frame(self):
return self.frames[int(time()) % len(self.files)]

cam = Camera()

if __name__=='__main__':
cam.open_camera()


Cameraクラスのインスタンスが生成されると、./static/img/直下にあるファイル 1.jpg, 2.jpg, ... , 5.jpgが読み込まれ、framesという名前のリストに格納されます。そして、get_frame()メソッドで、時間の変化に応じて、それらのフレームが返されます。

非常にシンプルですが、これで、ストリーミング処理は完成です。

今回は、以下の画像を 1.jpg, 2.jpg, ... , 5.jpgとして、動かしてみました。









それぞれの画像は、Windowsに標準インストールされている「ペイント」という素晴らしいお絵かきソフトで作成しました。

コードを完成させたら、python stream.pyを実行して、サーバーを起動します。 http://localhost:5000 にアクセスすると、以下のように画像がストリーミングされます。

これは、ブラウザをキャプチャした動画をgif画像に変換していますが、実際には、ちゃんとストリーミングされています。


WEBカメラの映像をストリーミングする

続いて、WEBカメラで撮影した映像をストリーミングしてみましょう。映像とはいうものの、その実態は、一定間隔で撮影した画像をストリーミングするだけです。コマ撮りです。

WEBカメラで撮影した画像を使うこと以外、上記の画像のストリーミングとなんら変わりません。WEBカメラによる撮影と画像の保存には、OpenCVを利用します。

camera.pyVideoCameraクラスを追加します。


camera.py

from time import time, sleep

import cv2

# VideoCameraを追加
class VideoCamera():
def __init__(self):
self.frames = []
self.files = []
self.MAX_FRAME_NUM = 10 # 撮影枚数を指定
self.RECORD_INTERVAL = 1 # 撮影のインターバルを指定

self.record()

def get_frame(self):
# 撮影された画像を順に返す
for file in self.files:
self.frames.append(open(file, 'rb').read())
return self.frames[int(time()) % len(self.frames)]

def record(self):
self.cap = cv2.VideoCapture(0)

cnt = 0
while True:
# 撮影したファイルの保存先を指定
path = './static/img/img' + str(cnt) + '.jpg'
ret, frame = self.cap.read()
self.files.append(path)

cv2.imwrite(path, frame)

# ESCキーで停止する
k = cv2.waitKey(1)
if k == 27:
break

# 指定した枚数を撮影したら停止する
cnt += 1
if cnt == self.MAX_FRAME_NUM:
break

# 撮影のインターバルを取る
sleep(self.RECORD_INTERVAL)

self.cap.release()
cv2.destroyAllWindows()


stream.pyは、VideoCameraクラスを利用できるように書き換えます。


stream.py

from flask import Flask, render_template, Response

# from camera import Camera
# VideoCameraに変更
from camera import VideoCamera

app = Flask(__name__)

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

def generate(camera):
while True:
frame=camera.get_frame()
yield (b'--frame\r\n' b'Content-Type: image/jpeg\r\n\r\n' + frame + b'\r\n')

@app.route('/feed')
def feed():
# generateメソッドの引数をVideoCameraに変更
return Response(generate(VideoCamera()), mimetype='multipart/x-mixed-replace; boundary=frame')

if __name__ == '__main__':
app.run(host='0.0.0.0', debug=True)


やっていることは非常に単純です。OpenCVで画像を撮影して保存します。その後、前述の画像のストリーミングと同様にストリーミングを行います。

これで、http://localhost:5000 にアクセスするとWEBカメラが起動し、撮影が始まります。撮影が終わると、撮影された画像がストリーミングされます。

特に撮るものがなかったので、JET BRAINSのヨーヨーを揺らしています。

カクカクですね。


今後の課題

課題は様々ありますが、大きなものは以下の2点です。


  1. 写真を撮り終わらないと、ストリーミングが始まらない(ストリーミングの意味がない)

  2. コマ撮りではなく、動画を撮影してストリーミングしたい

1については、マルチスレッドで書けば良さそうですね。2については、今回は力不足で実装できなかったため、何か方法を考えて、改めてやってみようと思います。


参考にした記事


  1. Video Streaming with Flask

  2. Python+OpenCVでWebカメラの画像を取り込んで処理して表示する話