Elsaの技術日記(徒然なるままに)

主に自分で作ったアプリとかの報告・日記を記載

MENU

カメラキャプチャ画像をバイト列で送受信

前回、Socket通信でやりたいことがあった!!と記載しました。
elsammit-beginnerblg.hatenablog.com

それが、
”カメラキャプチャ画像をバイト列に変換して送信しストリーミング配信を行ってみる”
ことです。
無駄なことだと思いますが、どうしてもやってみたくて調べてみました!!
問題なく動作させることが出来たので、方法をまとめておこうと思います。



■環境

今回こちらの構成で動作させてみました!!
f:id:Elsammit:20201220174102p:plain

前回と同様、ネットワークサーバをRaspberry Pi側に構築し、クライアントをwindows PCを用いました。
今回はpythonを用いるのですが、バージョンはこちらの通りです。
windows PC側Pythonバージョン:3.7.4
Raspberry PiPythonバージョン:3.7.3

またカメラですが、UCAM-C520FBBKを使用しました。

エレコム WEBカメラ マイク内蔵 200万画素 高精細ガラスレンズ ブラック UCAM-C520FBBK

新品価格
¥2,300から
(2020/12/20 18:19時点)


■サーバ側構築

ではまずはサーバ側の構築です。
全体のコードはこちらです。

import socketserver  
import cv2
import numpy  
import socket  
import sys  
  
class TCPHandler(socketserver.BaseRequestHandler):  
    def handle(self):
        self.data = self.request.recv(1024).strip()
        ret, frame=capture.read()
        jpegstring=cv2.imencode('.jpg', frame)[1].tostring()  
        self.request.send(jpegstring)  
  
#hostとportを設定
HOST = 'host名'
PORT = ポート番号

#カメラの設定
capture=cv2.VideoCapture(0)
capture.set(3,320)
capture.set(4,240)
if not capture:  
    print("Could not open camera")  
    sys.exit()
socketserver.TCPServer.allow_reuse_address = True
server = socketserver.TCPServer((HOST, PORT), TCPHandler)  
server.capture=capture  

try:
    server.serve_forever()  
except KeyboardInterrupt:
    pass
server.shutdown()
sys.exit()

やっていることは、画像データをバイト列に変換し前回のsocket通信を実施しているのみです。
簡単には、
①データ受信により、TCPHandlerがコール
②画像データをバイト列に変換

jpegstring=cv2.imencode('.jpg', frame)[1].tostring()  

③バイト列をレスポンス

self.request.send(jpegstring)  

です。

■クライアント側構築

次にクライアント側の構築です。
コード全体はこちら。

import socket  
import numpy  
import cv2  

def getimage():
    #IPアドレスとポート番号は環境に応じて変更
    HOST = '192.168.11.2'
    PORT = 8080
    sock=socket.socket(socket.AF_INET,socket.SOCK_STREAM)  
    sock.connect((HOST,PORT))   
    sock.send(('a').encode("utf-8"))  
    buf=b''
    recvlen=100  
    while recvlen>0:  
        receivedstr=sock.recv(1024)  
        recvlen=len(receivedstr)  
        buf +=receivedstr
    sock.close()  
    narray=numpy.fromstring(buf,dtype='uint8')  
    return cv2.imdecode(narray,1)  
  
while True:  
    img = getimage()
    cv2.imshow('Capture',img)  
    if cv2.waitKey(100) & 0xFF == ord('q'):
        break

HOST = 'ホスト名'
PORT = ポート番号
getimage()

こちらもsocket通信とバイト列から画像データ変換の組み合わせです。
具体的には、

    sock=socket.socket(socket.AF_INET,socket.SOCK_STREAM)  
    sock.connect((HOST,PORT))   
    sock.send(('a').encode("utf-8"))  

でバイト列を要求し、

    recvlen=100  
    while recvlen>0:  
        receivedstr=sock.recv(1024)  
        recvlen=len(receivedstr)  
        buf +=receivedstr

にてバイト列を全て受信。
受信したバイト列を画像データに変換。

    narray=numpy.fromstring(buf,dtype='uint8')  
    return cv2.imdecode(narray,1)  

以上です。

■動作確認

こちらを動作させてみましたが、、、
う~~ん。。。
遅延が目立ちますね。。。

コードは320×240だったのですが、画像サイズを数倍拡大させると
より遅延が目立つようになりました!!

バイトサイズが大きくなるにつれて遅延が大きくなってしまうようですね!!
しかも、jpgからbmpにencodeを変換すると、
数秒の遅延が発生してしまい、ビデオとしては、、、使い物にならないですね😅

制御が単純で分わかりやすいのですが、ちょっと使うシーンは限定されてしまう気がします。
使うとしても制限を守った使い方が必要そうですね。。

■最後に

興味があり、やってみましたが思ったより遅延があり、
gstreamerやffmpegがいかに優秀かわかりました。

時間があったらこれらの制御についても調査してみようと思いました!!