はじめに
前回は、HTTPでストリーミングを行って、Webブラウザで映像を見た。今回は、HTTPより低レイヤーを用いてデータの送信・受信をおこない、ローカルのアプリケーションのウィンドウに画像を表示してみる。
Socketとは
技術解説は先達にお任せ。
知ったかぶりをしていたソケット通信の基礎を改めて学んでみる
https://qiita.com/megadreams14/items/32a3eed4661e55419e1c
pythonでストリーミングを行う、Socketプログラミングの参考
こちらも先達の情報を参照・・・。
Pythonでネットワーク越しにカメラの映像をストリーミング
https://qiita.com/tokoroten-lab/items/bb27351b393f087650a9
しかし、こちらの情報をまんま書き写したところでいろいろと動作しない。まず、connection.iniの情報を自分の環境用に書き換えないといけない。そして、サンプルが書き漏らしているであろう設定情報などを自分なりにアレンジして書き込む。この設定のうえで、ストリーミングサーバーを起動させ、前回と同様、サーバーはrealsenseカメラを用いるし、IPアドレスはサーバー側の環境で自動取得のうえPrintでコンソールに出力(ターミナルに表示)させる。
できあがったファイルがこちら。
[server]
ip = AUTO
port = 12345
[packet]
# [bytes]
header_size = 4
[camera]
id = 0
fps = 15
image_width = 1280
image_height = 960
[pixels]
image_width = 640
image_height = 480
# coding:utf-8
import socket
import numpy as np
import cv2
import time
import configparser
from realsensecv import RealsenseCapture
import getIpAddress as gIA
config = configparser.ConfigParser()
config.read('./connection.ini', 'UTF-8')
# 全体の設定
FPS = int(config.get('camera','fps'))
INDENT = ' '
# カメラ設定
CAMERA_ID = int(config.get('camera','id'))
CAMERA_FPS = FPS
CAMERA_WIDTH = int(config.get('camera','image_width'))
CAMERA_HEIGHT = int(config.get('camera','image_height'))
# 画像設定
IMAGE_WIDTH = int(config.get('pixels', 'image_width'))
IMAGE_HEIGHT = int(config.get('pixels', 'image_height'))
IMAGE_QUALITY = 30
# カメラ設定適用
try:
cam = RealsenseCapture()
# プロパティの設定
cam.WIDTH = CAMERA_WIDTH
cam.HEIGHT = CAMERA_HEIGHT
cam.FPS = CAMERA_FPS
# cv2.VideoCapture()と違ってcap.start()を忘れずに
cam.start()
CAMERA_ID = "RealsenseCapture"
except:
cam = cv2.VideoCapture(CAMERA_ID, cv2.CAP_V4L)
cam.set(cv2.CAP_PROP_FRAME_WIDTH, CAMERA_WIDTH)
cam.set(cv2.CAP_PROP_FRAME_HEIGHT, CAMERA_HEIGHT)
cam.set(cv2.CAP_PROP_FPS, CAMERA_FPS)
if not cam.isOpened(): # ビデオキャプチャー可能か判断
print("Not Opened Video Camera")
exit()
# カメラ情報表示
print('Camera {')
print(INDENT + 'ID : {},'.format(CAMERA_ID))
print(INDENT + 'FPS : {},'.format(CAMERA_FPS))
print(INDENT + 'WIDTH : {},'.format(CAMERA_WIDTH))
print(INDENT + 'HEIGHT: {}'.format(CAMERA_HEIGHT))
print('}')
# サーバ設定
SERVER_IP = str(config.get('server', 'ip'))
SERVER_PORT = int(config.get('server', 'port'))
if SERVER_IP == "AUTO":
l = [d.get('name') for d in gIA.get_ip()]
print(l)
for k in gIA.get_ip():
if (k.get('name') == 'wlan0'):
HOST_wlan0 = k['address']
elif (k.get('name') == 'eth0'):
HOST_eth0 = k['address']
if HOST_wlan0 is not None:
SERVER_IP = HOST_wlan0
else:
SERVER_IP = HOST_eth0
if SERVER_IP == "AUTO" or SERVER_IP is None:
print("SERVER_IP is {}".format(SERVER_IP))
cam.release();
exit()
# パケット設定
HEADER_SIZE = int(config.get('packet', 'header_size'))
# クライアント接続 listen
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind((SERVER_IP, SERVER_PORT))
s.listen(1)
soc, addr = s.accept()
print('Server {')
print(INDENT + 'IP : {},'.format(SERVER_IP))
print(INDENT + 'PORT : {}'.format(SERVER_PORT))
print('}')
# クライアント情報表示
print('Client {')
print(INDENT + 'IP : {},'.format(addr[0]))
print(INDENT + 'PORT : {}'.format(addr[1]))
print('}')
# メインループ
while True:
loop_start_time = time.time()
# 送信用画像データ作成
flag, frames = cam.read()
color_frame = frames[0]
depth_frame = frames[1]
resized_img = cv2.resize(color_frame, (IMAGE_WIDTH, IMAGE_HEIGHT))
(status, encoded_img) = cv2.imencode('.jpg', resized_img, [int(cv2.IMWRITE_JPEG_QUALITY), IMAGE_QUALITY])
# パケット構築
packet_body = encoded_img.tostring()
packet_header = len(packet_body).to_bytes(HEADER_SIZE, 'big')
packet = packet_header + packet_body
# パケット送信
try:
soc.sendall(packet)
except socket.error as e:
print('Connection closed.')
break
# FPS制御
time.sleep(max(0, 1 / FPS - (time.time() - loop_start_time)))
s.close()
これらのファイルをを、前回も使用したrealsensecv.pyとgetIpAddress.pyのファイルと一緒に任意のフォルダに置き、python3で処理させる。
$ python3 streaming_server.py
pipline start
Camera {
ID : RealsenseCapture,
FPS : 15,
WIDTH : 1280,
HEIGHT: 960
}
['eth0', 'l4tbr0', 'wlan0']
このような表示がでて、サーバー側はclientの接続待ちになる。
client側は、別のPC、Macで実行する。connection.iniは、サーバーに置いたものと同じ。
client側のスクリプトは、先達のものの83行目を次のように、img.tostring()をimg.tobytes()に変えただけ。
82: # 画像をバイナリに変換
83: img = img.tobytes()
client側で実行成功すると、次のようにアプリケーションウィンドウが表示される。
なお、Client側のアプリケーションウィンドウを閉じると、サーバー側のプログラムも停止する。また、ブラウザでの映像参照とは異なり、このスクリプトのSocket通信では多数のクライエントからの同時アクセスは想定していないようだ。
考察
Socketプログラミングで、ブラウザではなくアプリケーションでデータを受信し、ウィンドウ表示できることがわかった。1対1の通信ならばこれで良いのだろうが、1対多、多対1のストリーミングをおこないたいときは、どのようにSocketプログラミングすればよいのだろうか。
なお、GUIの表示にkivyを使っている。初めてKivyを使ったが、Tcl/Tkのライブラリを使うよりも、ウィンドウが開くのが早いことがわかった。今後GUI開発では合わせてkivyの利用について検討したい。
まとめ
Socketプログラミングの参考情報を調べ、1対1の通信を成功させた。
今後は、1対多、多対1の通信の実現手段を調べて、機能を拡張したい。