【ラズパイ】リモートでカメラのプレビュー表示

いや、今回は結構ハマった。

これが半日頑張った成果だ。

まずはラズパイ側。

import base64
import cv2
import json
import os
import sys
import urllib.parse
import time
import threading

from http.server import BaseHTTPRequestHandler
from http.server import HTTPServer
from http import HTTPStatus

PORT = 8000

cap = cv2.VideoCapture(0)

def __main__():
    thread = threading.Thread(target=httpServe)
    thread.start()
    
    try:
        while cap.isOpened():
            time.sleep(1)
    except KeyboardInterrupt:
        return

def httpServe():
    handler = StubHttpRequestHandler
    httpd = HTTPServer(('',PORT),handler)
    httpd.serve_forever()

class StubHttpRequestHandler(BaseHTTPRequestHandler):
    server_version = "HTTP Stub/0.1"

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

    def do_GET(self):
        enc = sys.getfilesystemencoding()

        _, img = cap.read()
        resized_img = cv2.resize(img, (480, 320))
        _, encoded_img = cv2.imencode('.jpg', resized_img, [int(cv2.IMWRITE_JPEG_QUALITY), 30])
        dst_base64 = base64.b64encode(encoded_img).decode('utf-8')

        data = {
            'image': 'data:image/jpg;base64,' + dst_base64
        }

        encoded = json.dumps(data).encode()

        self.send_response(HTTPStatus.OK)
        self.send_header("Content-type", "text/html; charset=%s" % enc)
        self.send_header("Access-Control-Allow-Origin", "null")
        self.send_header("Content-Length", str(len(encoded)))
        self.end_headers()

        self.wfile.write(encoded)     

__main__()

そしてクライアント側。

<!DOCTYPE html>
<html>
<head>
  <title>My first Vue app</title>
  <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
  <script src="vue.min.js"></script>
  <script src="jquery-3.5.1.slim.min.js"></script>
</head>
<body>
  <div id="app">
      <image id="camera" src="" />
  </div>

  <script>
    var app = new Vue({
      el: '#app',
      data: {
        timer: null,
      },
      created: function() {
        self = this;
        this.timer = setInterval(function() {self.onLoad()}, 50)
      },
      methods: {
        onLoad: function() {
            axios.get('http://pi4.local:8000').then(function(response){
                $("#camera").attr('src', response.data.image);
            }).catch(function(error){
            });
        }
      }
    })
  </script>
</body>
</html>

まず、サーバ側の説明。

今回は、すでに使った実績のある、HTTPで通信を行います。

もっと下位層のプロトコルを使うと、もっと効率よくデータの送信ができるのですが、その分、扱いも難しくなります。

HTTPはTCP/UDPよりも、データのやりとりが多くなるので、少しモッサリ感がありますが、扱いが簡単になります。

実際、今回はブラウザで表示させているので!

カメラで撮影し、それを画像に落とすところまでは今まで通りですが、

今回はHTTPで送信するために、Base64に変換し、Jsonに載せて送信します。

Base64の頭にある「data:image/jpg;base64」というのは、このデータはJpegですよ、ということを示す文字列で、これがないと、受け手側は何のデータか判断できません。

「Access-Control-Allow-Origin」はCORS対策です。

例えばクロームなのでは、同じドメインでなければ画像が開けない、という制約がありまして、それを判断しているのが、リクエストヘッダーのOriginと、レスポンスヘッダーのAccess-Control-Allow-Origin。

この二つの値が一致しないと画像はエラーで表示されなくなります。(ただしブラウザによる)

今回はChrome側のOriginがnullだったので、それに合わせました。

FireFoxとかだったらまた話が変わってくるかもしれない。

続いて、クライアント側。

サクッと作成するために、Vue.jsを使用しました。

使用しているライブラリはaxios、jQueryです。

Laravelと同じような構成にしました。

画面には<div id=”app”>と<image id=”camera”>のみです。

やっていることは単純で、画面が作成されたらcreatedメソッドが実行されて、onLoad()を周期的にコールするようにしています。

50という数字を小さくすればヌルヌルになりますし(ただしラズパイ側の負荷が大きくなる)、大きくすればカクカクになります。

今回はラズパイにクーラーつけているから大丈夫だけど、ラズパイZeroでこの負荷はやばいと思う。

そして、レスポンスの中のBase64をimageのsrcに入れれば画像が表示されます。

これを早いサイクルで実行・画像更新することで、動画のように見せることが出来ます。

今回はWebカメラみたいに仕上がりましたが、HTTPが使えるならばクライアントは何だってできます!

「【ラズパイ】リモートでカメラのプレビュー表示」への1件のフィードバック

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください