ロボットで遊ぶ その3(映像を見ながらロボット操作)
サンプルコードのカメラ配信の仕組み
通信処理について
なぜこんな設計になっているのかわかりませんが、カメラ映像の配信はロボット(server.py)からクライアント(GUI.py)へTCPコネクションを確立していました。
※ロボットの制御はGUI.pyからserver.pyへTCPコネクションを確立している
通信処理としてはロボット制御と同じく、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通信の待ち受け
server.pyやGUI.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を起動した状態で作成したプログラムを起動すると、
ブラウザでカメラ映像を受信することができました。
カメラ映像取得処理の 改良
サンプルプログラムは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') }}">