websocketでRaspberryPiからwindowsPCに画像を送る(Python)

Raspberypi3から webscketで画像をあげて、PC側で取得・表示までやります。

概要図↓

f:id:weekendproject9:20171111170611p:plain

実行画面↓

f:id:weekendproject9:20171111180301p:plain

開発環境

  • win10 64bit

 python3.5.2
 openCV3.1.0

  • Raspberrypi3

 Raspbian Debian Stretch - Version:September 2017
 python3.5.2

RaspberryPi側開発

構成

Raspberry Piのカメラモジュールで撮った映像をWebSocketでブラウザに送る!! - ami_GS's diary
ラズパイの方は、ほぼ上記参考HPのまま使っています。
今回は自分の環境でエラーが出たところを修正しました。
また、IP固定はあらかじめしておいてください。

ーーここ引用ーー
camera.py
 RPi側でブラウザからのアクセスを受け付けるwebサーバ、及びカメラから映像を撮り、ブラウザへ送る。

index.html
 WebSocketでブラウザに送られてきた画像を表示する。

ーーーーーーーー

インストール

pip3 install websocket
pip3 install tornado
camera.py
import time
import picamera
import io
import tornado
import tornado.httpserver
import tornado.websocket
import tornado.ioloop
import tornado.web
import socket
from threading import Thread

WIDTH = 480
HEIGHT = 360
FPS = 30
class HttpHandler(tornado.web.RequestHandler):
    def initialize(self):
            pass

    def get(self):
        self.render("./index.html")  #最初のHTTPアクセスを受け付け、WebSocket接続を確立させるスクリプトが入ったindex.htmlを返す

class WSHandler(tornado.websocket.WebSocketHandler):
    def initialize(self, camera):
        self.camera = camera
        self.state = True

    def open(self):
        print(self.request.remote_ip, ": connection opened")
        t = Thread(target=self.loop)    #撮影&送信スレッドの作成
        t.setDaemon(True)
        t.start()

    def loop(self):
        stream = io.BytesIO()

        for foo in self.camera.capture_continuous(stream, "jpeg"):
            stream.seek(0)
            self.write_message(stream.read(), binary=True)
            stream.seek(0)
            stream.truncate()
            if not self.state:
                break

    def on_close(self):
        self.state = False     #映像送信のループを終了させる
        self.close()     #WebSocketセッションを閉じる
        print(self.request.remote_ip, ": connection closed")

def piCamera():
    camera = picamera.PiCamera()
    camera.resolution = (WIDTH, HEIGHT)
    camera.framerate = FPS
    camera.start_preview()
    
    time.sleep(2)        #カメラ初期化
    return camera

def main():
    camera = piCamera()
    print("complete initialization")
    app = tornado.web.Application([
        (r"/", HttpHandler),                     #最初のアクセスを受け付けるHTTPハンドラ
        (r"/camera", WSHandler, dict(camera=camera)),   #WebSocket接続を待ち受けるハンドラ
    ])
    http_server = tornado.httpserver.HTTPServer(app)
    http_server.listen(8080)
    tornado.ioloop.IOLoop.instance().start()

if __name__ == "__main__":
    main()
index.html
<html>
<head>
<title>livecamera</title>
<img id="liveImg" src="" width="480" height="360">
<script type="text/javascript">
var img = document.getElementById("liveImg");  
var arrayBuffer;

//WebSocketでサーバに接続
var ws = new WebSocket("ws://192.168.1.201:8080/camera"); ws.binaryType = 'arraybuffer';   //受診するデータがバイナリであるので設定

ws.onopen = function(){console.log("connection was established");};  //接続が確立した時に呼ばれる
ws.onmessage = function(evt){
	arrayBuffer = evt.data;
        //受信したデータを復号しbase64でエンコード
	img.src = "data:image/jpeg;base64," + encode(new Uint8Array(arrayBuffer));
};

window.onbeforeunload = function(){
    //ウィンドウ(タブ)を閉じたらサーバにセッションの終了を知らせる
    ws.close(1000);
};

function encode (input) {
    var keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
    var output = "";
    var chr1, chr2, chr3, enc1, enc2, enc3, enc4;
    var i = 0;

    while (i < input.length) {
        chr1 = input[i++];
        chr2 = i < input.length ? input[i++] : Number.NaN; // Not sure if the index
        chr3 = i < input.length ? input[i++] : Number.NaN; // checks are needed here

        enc1 = chr1 >> 2;
        enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
        enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
        enc4 = chr3 & 63;

        if (isNaN(chr2)) {
            enc3 = enc4 = 64;
        } else if (isNaN(chr3)) {
            enc4 = 64;
        }
        output += keyStr.charAt(enc1) + keyStr.charAt(enc2) +
                  keyStr.charAt(enc3) + keyStr.charAt(enc4);
    }
    return output;
}
</script>
</head>
</html>

*WebSocketでサーバに接続する部分のIPアドレスを自分のラズパイのIPに書き換えてください

起動

上の2つのプログラムを同じフォルダに置いて実行します。

 python camera.py

ブラウザでwindowsPCからラズパイIPにアクセスするとリアルタイム画像が表示されます。

Windows側開発

構成

client.py
 websocketのクライアント文とOpenCVでの画像表示になります。

インストール
pip3 install websocket-client
client.py
#-*- coding:utf-8 -*-

from websocket import create_connection
import sys
import base64
from io import BytesIO 
import cv2
import numpy as np

ws = create_connection("ws://192.168.1.201:8080/camera")


# decode
while True:
    arr = np.asarray(bytearray(ws.recv()), dtype=np.uint8)
    img = cv2.imdecode(arr, -1)  # 'load it as it is'
    cv2.imshow('image', img)
    cv2.waitKey(10)
cv2.destroyAllWindows()

ws.close()

*create_connectionのアドレスをラズパイのアドレスに書き換えてください

起動

こっちを起動する前にラズパイの方を起動させておいてください。
ラズパイ起動確認後、以下で実行

python client.py

ウインドウが表示され、ラズパイが取得している画像が表示されます
 
f:id:weekendproject9:20171111180301p:plain

 
初めてwebsocketやりました。
おわり

[参考]
Raspberry Piのカメラモジュールで撮った映像をWebSocketでブラウザに送る!! - ami_GS's diary
【技術】pythonでwebsocketを試してみた - エンジニアリングとお金の話
PicameraによるRaspberry Pi 3カメラモジュールのカメラ設定 | TomoSoft
Pythonの画像読み込み: PIL, OpenCV, scikit-image - Qiita