VirtualBoxのUbuntu22.04上にROS2環境を構築し、YOLOとVOSKを動かす

# 目的

こちらの記事のロボット制御手前までをVirtualBox上に構築する

https://qiita.com/hirekatsu0523/items/83b6a411761470eb0032

ホストOS：Windows 11
ゲストOS：Ubuntu22.04 LTS

元記事と異なる点は以下2点

- カメラはRealSenseではなく、Logicoolのウェブカメラを使用
- 物体認識はLLaVAではなく、YOLOを使用

ちなみにVirtualBoxの導入がうまくいかない場合、BIOSをいじる必要があるかもしれないので、その際にはご参照ください

https://qiita.com/ck_qiita/items/c123328bcc9a996e2bd9#%E4%BB%AE%E6%83%B3%E5%8C%96%E6%94%AF%E6%8F%B4%E7%92%B0%E5%A2%83%E3%81%AE%E6%9C%89%E5%8A%B9%E5%8C%96

# VirtualBoxインストール～Ubuntu起動まで

VirtualBox公式のダウンロードページから
**Platform Packages**
**VirtualBox Extension Pack**
をダウンロード

https://www.virtualbox.org/wiki/Downloads


Ubuntu22.04.5 LTSの**Desktop image**をダウンロード

https://releases.ubuntu.com/jammy/


VirtualBoxのインストーラーを起動
インストーラーの指示のままにインストール
インストールが環境するとOracle VirtualBox マネージャーを開く
ファイル＞ツール＞機能拡張パックマネージャー と選択
画面右上部のインストールを押下し、ダウンロードしたExtension Packを選択

仮想マシン＞新規 と選択し、仮想マシンを作成する（以下変更しないものは記載なし）

- 名前とオペレーティングシステム

    - 名前：Ubuntu22（任意）

    - フォルダー：任意

    - ISOイメージ：先ほどダウンロードしたものを選択

- 自動インストール

    - ユーザー名：（任意）

    - パスワード：（任意）

- ハードウェア

    - メインメモリー：4096MB

    - プロセッサー数：6

- ハードディスク

    - ハードディスクファイルの場所とサイズ

        - 場所：任意

        - サイズ：200GB（選択した場所の空き容量を越えない範囲で任意）

以上で完了を押下すると仮想マシンが立ち上がり、Ubuntuのインストールが始まる

Ubuntuのインストールが完了したら、ウィンドウの×を押下、仮想マシンの電源オフを選択してOracle VirtualBox マネージャーに戻る

先ほど作成した仮想マシン＞設定からプラスでいくつか設定を行う

- 一般＞高度

    - クリップボードの共有：双方向

    - ドラッグ&ドロップ：双方向

- ディスプレイ＞スクリーン

    - ビデオメモリー：128MB

- USB

    - USB3.0（xHCI）コントローラー：選択

完了したら仮想マシンを起動

# Ubuntuのトラブルシュートや諸々の設定

### Terminalが起動しない現象が発生

以下の手順で解決

手順１：ログインユーザーにsudo実行権限を付与

https://qiita.com/tks_00/items/bc04bc477d9019341859

手順２：LANGの設定

https://qiita.com/towamz/items/2052f08e9e1af4068a56

### Ubuntuの画面サイズがウィンドウサイズに連動せず固定のまま

デバイス＞Guest Additions CDイメージの挿入 を押下
自動で実行されない場合は、タスクバーのVBox_GAs_7.1.0を押下し、エクスプローラー上で右クリック＞Open in Terminal から以下を実行

```bash
sudo ./VBoxLinuxAdditions.run
```

### 日本語入力の有効化

Language Support＞Install/Remove LanguagesからJapaneseを選択して再起動

画面右上の入力メソッドアイコン＞日本語＞入力モードからひらがなを選択すれば日本語入力できるようになる

# 環境構築～voice_gptの導入まで

### 仮想環境の構築とアクティベート

```bash
python3 -V  # Python 3.10.12
sudo apt install python3.10-venv
python3 -m venv venv
cd venv
source ./bin/activate
export LD_LIBRARY_PATH=~/venv/lib:$LD_LIBRARY_PATH
export PYTHONPATH=~/venv/lib/python3.10/site-packages
```

### ROS2 Humbleのインストール

https://docs.ros.org/en/humble/Installation/Ubuntu-Install-Debs.html

基本的には上記ページの通りに進めてもらえればOKだが、以下コマンドでも可

```bash
sudo apt install software-properties-common
sudo add-apt-repository universe
sudo apt update && sudo apt install curl -y
sudo curl -sSL https://raw.githubusercontent.com/ros/rosdistro/master/ros.key -o /usr/share/keyrings/ros-archive-keyring.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/ros-archive-keyring.gpg] http://packages.ros.org/ros2/ubuntu $(. /etc/os-release && echo $UBUNTU_CODENAME) main" | sudo tee /etc/apt/sources.list.d/ros2.list > /dev/null
sudo apt update
sudo apt upgrade
sudo apt install ros-humble-desktop
source /opt/ros/humble/setup.bash
```

### BehaviorTreeのインストール

基本的には以下ページの通りに進めてもらえればOK
ModuleNotFoundError が出たら適宜 pip install しましょう

https://qiita.com/hirekatsu0523/items/0315b22d05224b3d8866

### ウェブカメラ用のROSパッケージのインストール

https://gitlab.com/boldhearts/ros2_v4l2_camera

```bash
sudo apt install python3-rosdep  # ROSにおけるパッケージ管理ツール
sudo apt install python3-colcon-common-extensions  # ROSパッケージのビルドツール

mkdir ~/venv/ros2_ws/src
cd ~/venv/ros2_ws/src
git clone --branch ${ROS_DISTRO} https://gitlab.com/boldhearts/ros2_v4l2_camera.git
rosdep install --from-paths ./v4l2_camera --ignore-src -r -y
cd ~/venv/ros2_ws
colcon build
```

### voice_gptのインストール

https://github.com/Azuma413/voice_gpt

```bash
cd ~/venv/ros2_ws/src
git clone https://github.com/Azuma413/voice_gpt.git
```

ここからいくつか準備を行う

#### ワークスペースパスの修正
voice_gpt/chat_rover/chat_rover の 以下ファイルにはパスをros2_wsのパスを直接指定している箇所がそれぞれ一か所ずつあるため、
"/home/humble/ros2_ws～"を"/home/{ユーザー名}/venv/ros2_ws～"に変更

- gpt1_node.py
- gpt2_node.py
- vosk_node.py


#### 必要パッケージのダウンロード
いくつかパッケージ不足が原因で分かりにくいエラーが発生するため、あらかじめダウンロード

```bash
pip install empy==3.3.4
pip install lark
pip install sphinx==8.0.2
```

エラーの内容としては具体的に2つ

##### ImportError: cannot import name 'OVERRIDE_OPT' from 'em'

empyのバージョンが3.3.4であれば発生しないとのこと

https://github.com/colcon/colcon-core/issues/602

##### ImportError: cannot import name 'Union' from 'types'

sphinxのバージョンが4.2以上でないと上記エラーが発生するとのこと
上では今日(2024/09/23)時点で最新のバージョンを指定している

https://github.com/sphinx-doc/sphinx/issues/9562

#### ウェブカメラ用のチューニング
ウェブカメラ用のROSパッケージros2_v4l2_cameraが出力する画像を受け取れるよう以下の通り編集する

```diff:chat_rover/chat_rover/ar_node.py
-        self.create_subscription(Image, "/camera/camera/color/image_raw", self.image_cb, 1)
+        self.create_subscription(Image, "/image_raw", self.image_cb, 1)
```

```diff:chat_rover/chat_rover/yolo_node.py
-        self.create_subscription(Image, "/camera/camera/color/image_raw", self.image_cb, 1)
+        self.create_subscription(Image, "/image_raw", self.image_cb, 1)
```

```diff:chat_rover/launch/chat_rover.launch.py
-        Node(
-        package='realsense2_camera',
-        executable='realsense2_camera_node',
-        output='log'
-        ),
+        Node(
+        package='v4l2_camera',
+        executable='v4l2_camera_node',
+        output='screen'
+        ),
```



#### YOLO用のチューニング
LLaVAではなくYOLOを使用して物体認識を行うよう以下の通り編集する

```diff:chat_rover/chat_rover/yolo_node.py
     def __init__(self):
-        self.llava_cli = self.create_client(ImageText, "/image_text")
-        flag = True
-        while not self.llava_cli.wait_for_service(timeout_sec=1.0):
-            if flag:
-                self.get_logger().info('service not available, waiting again...')
-                flag = False

    def get_object_cb(self, request, response):
         results = self.model.predict(self.color_image)
         cv_result = results[0].image
-        #name_list = results[0].class_names
-        #label_list = results[0].prediction.labels
+        name_list = results[0].class_names
+        label_list = results[0].prediction.labels
         box_list = results[0].prediction.bboxes_xyxy
-        name_list = []
-        # バウンディングボックス内の画像を切り出して、llavaに送る
-        for i in range(len(box_list)):
-            x1 = int(box_list[i][0])
-            y1 = int(box_list[i][1])
-            x2 = int(box_list[i][2])
-            y2 = int(box_list[i][3])
-            image = self.color_image[y1:y2, x1:x2]
-            # 送信する画像の画質とデータサイズを表示
-            print("image size: ", image.shape)
-            print("image quality: ", image.size)
             
-            self.req.image = self.bridge.cv2_to_imgmsg(image, encoding="bgr8").data
-            future = self.llava_cli.call_async(self.req)
-            print("send image")
-            rclpy.spin_until_future_complete(self, future)
-            name_list.append(future.result().text)
-            print("get text: ", future.result().text)
             
         response.text = "{"
         for i in range(len(box_list)):
             name = name_list[i]
+            x1 = int(box_list[i][0])
+            y1 = int(box_list[i][1])
+            x2 = int(box_list[i][2])
+            y2 = int(box_list[i][3])
             x = int((box_list[i][0] + box_list[i][2])/2)
             y = int((box_list[i][1] + box_list[i][3])/2)
             response.text += name + ":(" + str(x) + ", " + str(y) + "), "

```

#### ビルドを実施

以上の準備が完了したらビルドを行う

```bash
cd ~/venv/ros2_ws
colcon build
source ~/venv/ros2_ws/install/local_setup.bash
```



# 実行

実行にあたっては以下3点の実施が必要

#### 音声認識モデルのダウンロード
voice_gpt/chat_rover/model/readme.txt に記載の以下zipファイルをダウンロード
https://alphacephei.com/vosk/models/vosk-model-ja-0.22.zip
その後、zipファイルを解凍したら、フォルダごとvoice_gpt/chat_rover/model へ配置

#### OpenAIのAPIキーを取得
音声認識した結果を英語の指示に変換するのにOpenAIのAPIを使用しているため、環境変数にAPIキーを設定する必要がある

https://qiita.com/kurata04/items/a10bdc44cc0d1e62dad3

```bash
export OPENAI_API_KEY={取得したAPIキー}
```

#### 必要なパッケージのインストール
super-gradients以外はビルド前にインストールしていても（おそらく）大丈夫だが、super-gradientsだけはsphinxと競合するためこのタイミング

```bash
sudo apt install libportaudio2
pip install setuptools
pip install wheel
pip install openai==0.28
pip install sounddevice
pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cpu
pip install vosk
pip install super-gradients==3.5
pip uninstall matplotlib
```

#### ノードの起動

```bash:ターミナル1
# activate venv
source ~/venv/bin/activate

# setup
export LD_LIBRARY_PATH=~/venv/lib:$LD_LIBRARY_PATH
export PYTHONPATH=~/venv/lib/python3.10/site-packages
export LD_LIBRARY_PATH=/usr/local/lib:$LD_LIBRARY_PATH
source /opt/ros/humble/setup.bash
source ~/venv/ros2_ws/install/local_setup.bash
export OPENAI_API_KEY={取得したAPIキー}

# launch
ros2 launch chat_rover chat_rover.launch.py
```


```bash:ターミナル2
# activate venv
source ~/venv/bin/activate

# setup
export LD_LIBRARY_PATH=~/venv/lib:$LD_LIBRARY_PATH
export PYTHONPATH=~/venv/lib/python3.10/site-packages
export LD_LIBRARY_PATH=/usr/local/lib:$LD_LIBRARY_PATH
source /opt/ros/humble/setup.bash
source ~/venv/ros2_ws/install/local_setup.bash
export OPENAI_API_KEY={取得したAPIキー}

# launch
ros2 run chat_rover_bt main_node
```


ターミナル2のほうに"call VOSK"と表示されたら、マイクに話しかける
→話しかけた内容とGPTによって変換された結果が表示される
→YOLOによる物体検出の結果が表示される
という実行結果になった