ロボットで遊ぶ その3(映像を見ながらロボット操作)

サンプルコードのカメラ配信の仕組み

通信処理について

なぜこんな設計になっているのかわかりませんが、カメラ映像の配信はロボット(server.py)からクライアント(GUI.py)へTCPコネクションを確立していました。
※ロボットの制御はGUI.pyからserver.pyTCPコネクションを確立している

通信処理としてはロボット制御と同じく、ZMQを使ったTCP通信を実施していました。

# FPV.py抜粋
        context = zmq.Context()
        footage_socket = context.socket(zmq.PUB)
        print(IPinver)
        footage_socket.connect('tcp://%s:5555'%IPinver)
カメラ制御の生成処理

カメラ制御はロボット制御と独立してスレッドが生成されていました。
処理自体はFPV.pyで実装されていて、スレッド生成時にロボット制御で接続してきたクライアントのIPアドレスを渡しています。

## server.py抜粋
def FPV_thread():
    global fpv
    fpv=FPV.FPV()
    fpv.capture_thread(addr[0])
カメラ映像の取得

解像度は640x480指定で設定されていて、picameraクラスでオブジェクトを生成していました。
解像度を変える場合はここを触ればよさそうです。

#FPV.py抜粋
camera = picamera.PiCamera() 
camera.resolution = (640, 480)
camera.framerate = 20
rawCapture = PiRGBArray(camera, size=(640, 480))


# カメラ映像の取得、送信処理をするメソッド
def capture_thread(self,IPinver):
    <---中略--->
    for frame in camera.capture_continuous(rawCapture, format="bgr", use_video_port=True):
            # カメラ映像を画像として取得
            frame_image = frame.array
            <---中略--->
            # カメラ画像をbase64でテキストとしてフォーマット
            encoded, buffer = cv2.imencode('.jpg', frame_image)
            jpg_as_text = base64.b64encode(buffer)
            # フォーマットした画像データをクライアントへ送信
            footage_socket.send(jpg_as_text)

カメラ映像取得UI

構成

前回作成したPython+Flask+HTMLのコントローラ上にカメラ映像を描画します。
構成としては

  • ロボットからのTCP通信を受け付けるソケットを生成
  • base64でフォーマットされたjpgデータをTCPソケットで受信
  • 受け取ったデータをHTMLで表示できる形式にデコード
  • デコードしたJPG画像をFlaskで更新

TCP通信の待ち受け

server.pyGUI.pyを参考にzmqを使ったTCP通信の待ち受けを実装しました。
作成したvideo_streamingをflask起動時にスレッドとして動かすことで、TCP通信で受信したデータをoutputFrameに格納し続けます。

# カメラ映像の取得
def video_streaming():
	global vs, outputFrame, lock
	global footage_socket, font, frame_num, fps
	global frame_num

	# ZMQでTCPソケット生成
	context = zmq.Context()
	footage_socket = context.socket(zmq.SUB)
	footage_socket.bind('tcp://*:5555')
	footage_socket.setsockopt_string(zmq.SUBSCRIBE, np.unicode(''))
       # ソケットを生成したら、受信したデータをoutputFrameへ繰り返し格納する
	while True:
		frame = footage_socket.recv_string()
		with lock:
			outputFrame = frame

画像データをHTMLで表示できる形式にデコード

ロボットから受け取ったデータはbase64形式でエンコードされているので、デコードしてjpg形式に戻します。
ここで作成したgenerateメソッドは次に作成するWEBインタフェースから繰り返し呼び出されます。

# 画像更新		
def generate():
	global outputFrame, lock

	# カメラ映像が更新されるたびにデコードして画像を更新
	while True:	
		with lock:
			if outputFrame is None:
				continue
			encodedImage = base64.b64decode(outputFrame)

Flaskで画像更新

WEBブラウザから呼び出されたときに、デコードした画像を更新する

# 画像更新
@app.route("/video_feed")
def video_feed():
	return Response(generate(),
		mimetype = "multipart/x-mixed-replace; boundary=frame")

動作確認

server.pyを起動した状態で作成したプログラムを起動すると、
ブラウザでカメラ映像を受信することができました。

f:id:da-yamax:20200503145018p:plain:w400
カメラ映像をWebブラウザで表示


カメラ映像取得処理の 改良

サンプルプログラムはpicameraを使ってカメラ映像を取得していましたが、
やけに映像が重たかったのでimutilsへ変更します。
また、ライントレースなど今は使わない処理も実装されていたので、FPV.pyから不要な処理を削除しました。
※picameraで重くて、imutilsで軽くなった理由はよくわからない..
参考:imutilsについて

カメラ起動の変更

## サンプルのカメラ起動処理をコメントアウト
#camera = picamera.PiCamera() 
#camera.resolution = (640, 480)
#camera.framerate = 20
#rawCapture = PiRGBArray(camera, size=(640, 480))

# imutilsでカメラを起動
camera= VideoStream(src=0,usePiCamera=True, resolution=(640, 320), framerate=30).start()
time.sleep(2.0)

カメラ映像の取得と送信処理の変更

カメラ映像の送信スレッドを以下のようにシンプルな形に変更しました。

    ## カメラ映像の取得、送信処理のスレッド
    def capture_thread(self,IPinver):
        global frame_image,camera
        context = zmq.Context()
        footage_socket = context.socket(zmq.PUB)
        print(IPinver)
        footage_socket.connect('tcp://%s:5555'%IPinver)

        while True:
            frame = camera.read()
            (flag, encodedImage) = cv2.imencode(".jpg", frame)
            jpg_as_text = base64.b64encode(encodedImage)
            footage_socket.send(jpg_as_text)

コントローラへの組み込み

カメラ映像はJPG画像としてHTMLに渡されるので、コントローラのHTMLに以下を追加しました。

<img class="video_feed" src="{{ url_for('video_feed') }}">

まとめ

スマホから操作するUIにカメラ映像も載せられるようにしました。

f:id:da-yamax:20200503150750p:plain:w400
コントローラにカメラ映像をはめ込んでみた