いや、今回は結構ハマった。
これが半日頑張った成果だ。
まずはラズパイ側。
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が使えるならばクライアントは何だってできます!