Raspberry PiベースのロボットPiCrawlerをGUIアプリケーションから操作する

  • URLをコピーしました!

今回は当ブログで作成しているRaspberry PiベースのスパイダーロボットPiCrawlerを、Pythonで記述したGUIプログラムから制御できるようにしたため、記事にまとめたいと思います。

目次

ロボットについて

使用するロボットはSunFounderのスパイダーロボット、PiCrawlerです。ロボットのコントローラーとしてRaspberry Piを搭載しています。

詳細は以下のページをご覧ください。

やりたいこと

これまでロボットの操作はターミナル上でキーボードを入力することで指令を送信し、動作させていました。しかしmultiprocessingを使用して複数のプロセスを同時実行している状態だと、ターミナル上でキーボード入力をうまく取得できなくなってしまいました。

また、ターミナルだと直感的にどのキーを押せばよいのかわかりにくいため、コントローラGUIを作成し、グラフィカルなボタン操作でロボットを操作できるようにしたいと思います。

環境

ハードウェア:Raspberry Pi 3 ModelB
OS:Raspbian 10.11
Python:3.7.3
OpenCV:4.1.0.25
カメラ:OV5647

アプリケーション構成

Python3のmultiprocessingライブラリを使用し、Raspberry Pi 3の各コアに以下のようにプロセスを割り当て、並列処理をしています。
プロセス① サーボモーター制御
プロセス② 画像処理
プロセス③ GUI処理

プロセス間通信方法

GUIプロセスとロボットのモーション制御プロセスの間のデータ通信はmultiprocessingの共有メモリ(Array)を使用しています。詳細は以下の記事で紹介しています。

作成したソースコード

実際に作成したPythonのソースコードは以下の通りです。

from picrawler import Picrawler
from time import sleep
import sys
import tty
import termios
import multiprocessing as mp
import cv2
import PySimpleGUI as psg
import ctypes
from ntpath import join

#GUIレイアウト定義
#レイアウト
layout = [
    [psg.Text("ロボットの動作を選択してください")],
    [psg.Button("前進", size=(15, 1), key="BTN_FORWARD")],
    [psg.Button("左回転", size=(7, 1), key="BTN_LEFT"),psg.Button("右回転", size=(7, 1), key="BTN_RIGHT")],
    [psg.Button("後退", size=(15, 1), key="BTN_BACKWARD")],
    [psg.Button("停止", size=(7, 1), key="BTN_STOP")],
    [psg.Button("終了", size=(7, 1), key="BTN_EXIT")]]
    
#カメラ初期化
#VideoCaptureオブジェクト取得
cap = cv2.VideoCapture(0)

#ロボット初期化
crawler = Picrawler([10,11,12,4,5,6,1,2,3,7,8,9])

#ロボットの動作速度
speed =100

#サーボモータ制御
def robot_ctl(*args):
    val = args[2] #データのアドレス
    flag = args[3] #フラグのアドレス
    print("Start control")
    while True:
        #共有メモリからGUI指令値を取得
        while True:
            if not flag.value:  # データが変わるまで待つ
                print("prosess wait")
                sleep(0.1)
                continue
            command = val[0] #共有メモリ読み出し
        
            #print("command = ",command)
            #取得したコマンドを元に制御
            if 1 == command: #前進
                crawler.do_action('forward',1,speed)
            elif 4 == command: #後退
                crawler.do_action('backward',1,speed)
            elif 2 == command: #左回転
                crawler.do_action('turn left',1,speed)
            elif 3 == command: #右回転
                crawler.do_action('turn right',1,speed)
            elif 5 == command:
                pass #停止
            elif 6 == command:# 終了
                break
        sleep(0.05)
        
    #終了メッセージ    
    print("End robot_ctl Process")
    
#カメラ制御
def cam_ctl(*args):
    while(True):
        #フレームを取得
        ret, frame = cap.read()
        
        #画面サイズを指定
        frame = cv2.resize(frame, (640, 480))
        
        #取得したフレームをウインドウ上に表示する
        cv2.imshow("frame", frame)
        
        #キーボード入力処理
        key = cv2.waitKey(1)
        if key == 13: #enterキーの場合処理を抜ける
            break
            
    #カメラデバイスクローズ
    cap.release()
    
    #ウィンドウクローズ
    cv2.destroyAllWindows()
    
    #終了メッセージ
    print("End cam_ctl Process")
    
#GUI制御
def gui_ctl(*args):
    print("gui start")
    val = args[0] #データのアドレス
    flag = args[1] #フラグのアドレス
    
    #ウインドウを表示
    win = psg.Window('コントロールパネル', layout)
    while True:
        event, values = win.read()
        if event is None: break
        
        #押下されたボタンにより処理を分岐
        if event == "BTN_FORWARD":
            command = 1
        elif event == "BTN_LEFT": 
            command = 2
        elif event == "BTN_RIGHT":
            command = 3
        elif event == "BTN_BACKWARD":
            command = 4
        elif event == "BTN_STOP":
            command = 5
        elif event == "BTN_EXIT":
            command = 6
            break
            
        #共有メモリ書き換え
        flag.value = False
        
        val[0] = command
        flag.value = True
        sleep(0.1)


if __name__ == "__main__":

    #パラメータサイズ
    size = 1
    
    #プロセス間通信用の共有メモリーを生成
    arr_img2sc = mp.Array(ctypes.c_int, [0]*size ) #画像処理→サーボ
    flag_img2sc = mp.Value(ctypes.c_bool, False) #画像処理→サーボ
    arr_gui2sc = mp.Array(ctypes.c_int, [0]*size ) #GUI→サーボ
    flag_gui2sc = mp.Value(ctypes.c_bool, False) #GUI→サーボ
    
    #初期値設定
    arr_gui2sc[0] = 5
    flag_gui2sc.value = True
    
    #プロセス生成
    ps_control = mp.Process(target=robot_ctl, args=(arr_img2sc, flag_img2sc, arr_gui2sc, flag_gui2sc))
    ps_cam = mp.Process(target=cam_ctl, args=(arr_img2sc, flag_img2sc))    
    ps_gui = mp.Process(target=gui_ctl, args=(arr_gui2sc, flag_gui2sc))
    
    #プロセスのスタート
    ps_control.start()
    ps_cam.start()
    ps_gui.start()
    
    #プロセスの終了
    ps_control.join()
    ps_cam.join()
    ps_gui.join()

実行結果

作成しているプログラムをロボット上のRaspberry Pi 3で動作させた動画です。Raspberry Piのデスクトップ上に表示されているGUIアプリケーションで、ロボットの動作を指示するボタンをマウスでクリックすると、その指令を受け取ってロボットが動作させることができました。

まとめ

いかがだったでしょうか。
マルチプロセス構成でGUIアプリケーションを実装したことで、ユーザーがリアルタイムにコントロールパネル上からロボットを操作できるようになりました。
今後はGUIを拡張し、自律制御モードと切り替えられる機能を実装していきたいと思います。

また、PythonでGUIを作成する方法についてさらに詳しく学びたい方にはUdemyの以下の講座がおすすめです。

0から始めるTkinterの使い方完全マスター講座〜Python×GUIの基礎・応用〜 icon



それでは、また次の記事でお会いしましょう。

ツール

今回の動画で公開しているRaspberry Piのリモートデスクトップ画面の録画にはBandicamを使用しています。

パソコンの画面や音をそのまま保存できる動画キャプチャーソフトBandicam
よかったらシェアしてね!
  • URLをコピーしました!
目次