파이썬으로 생성 된 이미지를 WebSocket으로 브라우저로 보내 canvas에 표시

소개



RaspberryPi의 카메라 모듈로 촬영한 이미지를 브라우저에서 표시하기 위해 WebSocket의 구조를 조사해 보았습니다.

파이썬으로 만든 이미지 데이터를 WebSocket으로 브라우저에 전송하여 canvas에 표시하는 템플릿으로 정리해 둡니다.

샘플 소스 사양


  • 편집 상자에 문자열을 입력하고 버튼을 클릭하면 WebSocket으로 문자열을 보냅니다.
  • 문자열을받은 서버는 문자열을 이미지로 변환하고 응답으로 이진 데이터를 반환합니다.
  • 이미지의 바이너리 데이터를 수신 한 javascript로 canvas에 그리기



  • 클라이언트측



    HTML 부분



    텍스트 상자, 버튼, 캔버스가 있는 HTML을 준비합니다.

    index.html
    
    <html>
      <head>
        <script src="./client.js"></script>
      </head>
      <body onload="on_load();">
        <input type="button" value="send text" onclick="on_button_send_text();">
        <input type="text" id="text_input" name="text_input" value="">
        <br>
        <div>
          <canvas id="canvas_image" width="300" height="150"></canvas>
        </div>
      </body>
    </html>
    
    

    javascript 부



    onload 및 onclick 이벤트를 javascript로 구현합니다.

    픽셀 데이터를 그리는 모드와 파일 형식의 바이너리 데이터를 그리는 모드를 준비했습니다.
    mode_pixcel의 값을 설정합니다.

    픽셀 데이터를 그리는 경우 (mode_pixcel = true)



    이미지의 픽셀 데이터 배열을 canvas로 설정하고 그립니다.
    처리는 심플하고 불필요한 변환 처리가 없기 때문에 고속으로 draw 할 수 있을 것.
    화상 사이즈 등의 메타데이터는 송신되지 않기 때문에 화면측에서 필요할 경우에는 별도의 통신이 필요하게 된다.

    파일 형식의 데이터를 그리는 경우 (mode_pixcel = false)



    data URL에서 이미지를 만들고 canvas에 그립니다.
    PNG나 jpeg등의 압축 형식으로 보낼 수 있으므로 데이터 전송량은 적습니다만, javascript측에서 data URL의 텍스트로 하기 때문에 처리는 무겁다.

    client.js
    
    var web_socket = null;
    var mode_pixcel = true; // true:ピクセルデータ形式,false=ファイル形式
    
    function on_load()
    {
      web_socket = new WebSocket('ws://localhost:60000'); // サーバーのアドレスを指定
      web_socket.binaryType = 'arraybuffer';
      web_socket.onmessage = on_message;
    };
    
    function on_button_send_text()
    {
      var text_input = document.getElementById('text_input')
      web_socket.send(text_input.value);
    }
    
    function on_message(recv_data)
    {
      var recv_image_data = new Uint8Array(recv_data.data);
    
      var canvas_image = document.getElementById('canvas_image');
      var ctx = canvas_image.getContext('2d');
    
      if(mode_pixcel){
        // ピクセルデータを受信する場合
        var imageData = ctx.createImageData(300, 150);
        for (var i=0; i < imageData.data.length; i++){
          imageData.data[i] = recv_image_data[i];
        }
        ctx.putImageData(imageData, 0, 0);
      }
      else{
        // ファイル形式のデータを受信する場合
        var image = new Image();
        image.src = 'data:image/png;base64,' + window.btoa(String.fromCharCode.apply(null, recv_image_data));
        image.onload = function() {
          ctx.drawImage(image, 0, 0);
        }
      }
    }
    
    
    
    

    큰 사이즈 이미지라면 에러가 되는 경우



    큰 이미지라면 base64에서 문자열 생성에 실패하므로 아래 기사도 참조하십시오.
    RaspberryPi로 감시 카메라(카메라 모듈+USB Audio의 데이터를 브라우저로 표시+재생)

    서버측



    패키지



    websockets, Pillow, numpy를 사용하고 있습니다.
    pip install websockets Pillow numpy
    
    

    파이썬 부분



    접속되면 recv()로 데이터의 수신 대기가 됩니다.
    클라이언트가 분리될 때까지 서버측은 연결을 끊지 않습니다.

    픽셀 데이터를 전송하는 경우와 파일 형식의 바이너리 데이터를 전송하는 코드가 있습니다.
    client.js 구현에 맞게 mode_pixcel 인수의 값을 설정하십시오.

    server.py
    
    import asyncio
    import websockets
    from PIL import Image, ImageDraw, ImageFont
    import numpy
    import io
    
    class WebSockets_Server:
    
        def __init__(self, loop, address , port, mode_pixcel):
            self.loop = loop
            self.address = address
            self.port = port
            self.mode_pixcel = mode_pixcel  # True:ピクセルデータ形式,False=ファイル形式
    
            self.font_path = "(font path)" # フォントのパスを指定
            self.font = ImageFont.truetype(font=self.font_path, size=80)
    
        async def _handler(self, websocket, path):
            while True:
                recv_data = await websocket.recv()
    
                image = Image.new("RGBA", (300, 150))
                draw = ImageDraw.Draw(image)
                draw.text((0, 0), recv_data, (0, 0, 255), font=self.font)
    
                if self.mode_pixcel:
                    # ピクセルデータを送信する場合
                    image_np = numpy.array(image)
                    await websocket.send(image_np.tobytes())
                else:
                    # ファイル形式のデータを送信する場合
                    with io.BytesIO() as image_temp:
                        image.save(image_temp, format="png")
                        await websocket.send(image_temp.getvalue())
    
        def run(self):
            self._server = websockets.serve(self._handler, self.address, self.port)
            self.loop.run_until_complete(self._server)
            self.loop.run_forever()
    
    if __name__ == '__main__':
        loop = asyncio.get_event_loop()
        wss = WebSockets_Server(loop, '0.0.0.0', 60000, mode_pixcel=True)
        wss.run()
    
    
    
    

    font_path는 내 환경이라고 다음 값을 지정했습니다.
    Windows10: "C:/WINDOWS/Fonts/MSGOTHIC.ttc"
    RaspberryPi: "/usr/share/fonts/truetype/freefont/FreeMono.ttf"

    결론



    WebSocket의 샘플이 공개되고 있는 것은 node.js가 많아, python의 샘플이 적게 실현하는데 시간이 걸렸습니다.

    카메라 모듈용으로 만들었습니다만, 구조는 여러가지 사용할 것 같았습니다. 이미지를 표시하는 툴은 Tkinter로 만들었습니다만, WebSocket+HTML5로 UI를 만드는 것도 있는 생각이 들었습니다. 기계 학습 결과를 HTML로 표시할 수 있도록 해두면, HTML을 보존하는 것만으로 보고 리포트로 할 수 있어 편리한 예감이 된다.

    참고

    좋은 웹페이지 즐겨찾기