Bye Bye Moore

PoCソルジャーな零細事業主が作業メモを残すブログ

BottleでUSBカメラの動画を配信する簡易サイトをつくる その1:MotionJPEGで出す

mjpeg-streamerやGstreamerでカメラの画像を配信する方法は何度か触りました。

確認するには便利でしたが、少し手の込んだ事をやろうとすると苦労したりするので、
今回は学習をかねてこれらの機能を簡易的に再現してみようと思います。

実際のところ

WEB鯖は
shuzo-kino.hateblo.jp
から楽に建てたいときに作って壊してしているbottleを使用。

動画の取得ははOpenCV+Pythonの構成でcv2.VideoCaptureメソッドを活用。

問題は取得した動画をどうレンダリングするかという所で、全く知識がない頃であればJavascriptで定期的にリロードする方法でゴリ押ししていたところです。
が……MIME型にmultipart/x-mixed-replaceというのがあり、これを使うとプッシュ動作ができるためJPEGを連続して送るような実装がIPカメラ業界では普及している模様。
みんな大好き「とほほ」さんがCGIな時代に取り上げているので結構歴史がある技術でした。

Bottle側ではyieldをwhile内でぶん回し、VideoCaptureからフレームを適宜返すという手法でやることに。

構成

pi@NanoPi-NEO2:~/streamTest$ tree
.
├── camera.py
├── foobar.png
├── main.py
└── templates
    └── index.html

1 directory, 4 files

camera.py

NanoPiNeo2で処理速度が怖いため、とにかく軽く。

import cv2

class VideoCamera(object):
    def __init__(self):
        self.video = cv2.VideoCapture(0)
        self.video.set(cv2.CAP_PROP_FRAME_WIDTH,  480)
        self.video.set(cv2.CAP_PROP_FRAME_HEIGHT, 360)
        self.quality = 25
        self.encode_param = [int(cv2.IMWRITE_JPEG_QUALITY), self.quality]


    def __del__(self):
        self.video.release()

    def get_frame(self):
        success, image = self.video.read()
        ret, jpeg = cv2.imencode('.jpg', image, self.encode_param)

        return jpeg.tobytes()

main.py

元記事に倣い、テンプレートエンジンをJinja2に……してみたが、結局テンプレート的な事はしなかった。

"/video_feed"の返り値の所でやや苦戦したものの、mime typeではなくcontent_typeとして指定すれば良いだけだけであった。

from bottle import route, run, response
from bottle import TEMPLATE_PATH, jinja2_template as template

from camera import VideoCamera

TEMPLATE_PATH.append("./templates/")

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

def generateJpeg(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\r\n')

@route('/video_feed')
def video_feed():
    response.content_type = 'multipart/x-mixed-replace; boundary=frame'
    return generateJpeg(VideoCamera())

if __name__ == '__main__':
    run(host='0.0.0.0', port=8080)

templates/index.html

元記事ではflaskに由来するurl_forを採用しているが、bottleにはそんな素敵なメソッドはないので直書き。
その結果、そもそもJinja2が要らないのでは疑惑が。
あとでJadeで書き直すかも。

<html>
  <head>
    <title>Video Streaming Demonstration</title>
  </head>
  <body>
    <h1>Video Streaming Demonstration</h1>
    <img src="/video_feed">
  </body>
</html>