今回はカメラなどからストリーミングした画像/動画データをDjangoを用いてWebアプリで表示する方法について書いていきます。
目次
前提
- Django、OpenCVが入っていること
- django-admin startproject 、python manage.py startappでプロジェクト作成済み
- USBカメラをつなげている
どういう仕組みでカメラストリーミングをDjangoで実装する?
今回は比較的単純な、JPEG画像を紙芝居的に切り替えることで動画っぽくする方法でやります。
あまり普及しなかったらしく一部ブラウザが対応してないが、実装が簡単で、各フレームに対して処理して送付できたりという利点があります。
multipart/x-mixed-replaceというContent-typeを指定することで、connectionを維持してサーバーからフレームが送られ続ける仕組みです。
JPEGをmultipart/x-mixed-replaceで送付して、紙芝居的に動画を作るのをMotion JPEGなどと呼びます。
処理概要
- フロント側のimgタグで画像をリクエストする
- バックエンド側はContent-typeにmultipart/x-mixed-replaceを指定して画像を送付する
- フロント側は普通のimgタグのように画像を表示する
- Connectionを維持したままバックエンド側は画像の送付を続けることで動画のように見せる
まずフロント側について。
1.3.は通常のimgタグを書いて表示するだけ。4.で画像が送られてくるがブラウザ側が勝手にやってくれる。結局フロント側は通常のimgタグを書くだけ。
次はバックエンド側について。
2.で、Content-typeを指定して返却する。4.でフレームのデータを送付し続ける。4.についてはDjangoのStreamingHttpResponseとPythonのGeneratorの仕組みを使って実現する。
フロントエンド側の実装
まずはDjangoのtemplate側。単純にimgタグを書くだけ。
指定した/video_feedにアクセスすることで画像をフレームごとに受け取って紙芝居的に表示する仕組み。
{% extends "_base.html" %}
{% load static %}
{% block extra_css %}
{% endblock %}
{% block page_title %}Top page{% endblock %}
{% block page_head_title %}Top page{% endblock %}
{% block content %}
<div>
<div id="image_area">
<img src="/video_feed" width="810" height="540"/>
</div>
</div>
<script>
</script>
{% endblock %}
全ページ共通のテンプレートとして_base.htmlを指定しています。
_base.htmlは詳しくは以下のURLを参照。
https://qiita.com/u_kan/items/fcea8bc7f338ab8770ee
Djangoバックエンド側
url.pyでルーティング設定
まずはプロジェクト全体のurls.pyの設定。
startappで指定した名前のディレクトリではなく、settings.pyと同じディレクトリの方。
startappしたアプリのルーティングをプロジェクト全体に追加する。
from django.conf.urls.static import static
from django.contrib import admin
from django.urls import path, include
from django.conf import settings
urlpatterns = [
path('admin/', admin.site.urls),
path('', include('<startappで指定した名前>.urls'))
]
次にアプリのルーティング。indexがトップページでフレームごとに画像/動画を表示するページ。
video_feed_viewの箇所は、先ほどフロント側で指定したフレームごとの画像を取得するURLだ。
from django.contrib.staticfiles.urls import staticfiles_urlpatterns
from django.conf.urls.static import static
from django.urls import path
from config import settings
from . import views
urlpatterns = [
path('index', views.IndexView.as_view()),
path('video_feed', views.video_feed_view(), name="video_feed"),
]
urlpatterns += staticfiles_urlpatterns()
views.py
IPアドレス/index でIndexViewから先ほどのindex.htmlテンプレートを使ってトップページのHTMLを生成してユーザーに返却する。
video_feed_viewが重要。アクセスされたら次々にフレーム画像をユーザーに送付する。
あとはコンソールからpython manage.py runserverを実行して、ブラウザでlocalhost:8000/indexにアクセスすれば動くはず。
import cv2
from django.http import StreamingHttpResponse
from django.shortcuts import render
from django.views import View
# ストリーミング画像・映像を表示するview
class IndexView(View):
def get(self, request):
return render(request, 'index.html', {})
# ストリーミング画像を定期的に返却するview
def video_feed_view():
return lambda _: StreamingHttpResponse(generate_frame(), content_type='multipart/x-mixed-replace; boundary=frame')
# フレーム生成・返却する処理
def generate_frame():
capture = cv2.VideoCapture(0) # USBカメラから
while True:
if not capture.isOpened():
print("Capture is not opened.")
break
# カメラからフレーム画像を取得
ret, frame = capture.read()
if not ret:
print("Failed to read frame.")
break
# フレーム画像バイナリに変換
ret, jpeg = cv2.imencode('.jpg', frame)
byte_frame = jpeg.tobytes()
# フレーム画像のバイナリデータをユーザーに送付する
yield (b'--frame\r\n'
b'Content-Type: image/jpeg\r\n\r\n' + byte_frame + b'\r\n\r\n')
capture.release()
StreamingHttpResponseでレスポンスを定期的に送付する。以下のような仕組みだ。
- video_feed_viewはStreamingHttpResponseを返却
- StreamingHttpResponseは実際はConnectionを維持している
- generate_frame()で定期的にフレーム画像を生成してそれをStreamingHttpResponse経由でユーザーに送付
Generator/yieldでフレームを次々に送付していく。
例えばDeep Learningで物体検出などの処理をフレームに加える場合は、yieldの前にframeに処理をしてその結果をyieldすればいい。
実際にカメラストリーミングWebアプリを動かしてみる
試しに今回のカメラストリーミング + 物体検出(学習途中)を使って動かしてみました。
参考URL
Motion JPEG、multipart/x-mixed-replaceについて
https://wiki.suikawiki.org/n/multipart%2Fx-mixed-replace
https://qiita.com/Yukio-Ichikawa/items/cf93c4851f871003a0f2
Django StreamingHttpResponse