LoginSignup
58
49

More than 5 years have passed since last update.

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

Last updated at Posted at 2018-12-24

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 にアクセスすると、以下のように画像がストリーミングされます。

1224.gif

これは、ブラウザをキャプチャした動画を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カメラが起動し、撮影が始まります。撮影が終わると、撮影された画像がストリーミングされます。

1224_1.gif

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

今後の課題

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

  1. 写真を撮り終わらないと、ストリーミングが始まらない(ストリーミングの意味がない)
  2. コマ撮りではなく、動画を撮影してストリーミングしたい

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

参考にした記事

  1. Video Streaming with Flask
  2. Python+OpenCVでWebカメラの画像を取り込んで処理して表示する話
58
49
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
58
49