CARLA X ROS: シミュレータで自動運転車にセンサをガン積みする！！！

# 自動運転車にセンサをガン積みする！！！

自動運転車といえば、カメラ、LIDAR、レーダ等、センサがガン積みされている。
どこに何を乗せればいいのか、というのは自動運転車の永遠の議題だ。

例えば、Waymoの自動運転車はこんなセンサ構成になっている

![Screenshot from 2020-03-29 15-24-28.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/388647/fb318f16-69a3-23cb-e31f-c71e164b2c13.png)
画像は[Introducing the 5th-generation Waymo Driver: Informed by experience, designed for scale, engineered to tackle more environments ](https://blog.waymo.com/2020/03/introducing-5th-generation-waymo-driver.html)より

では、センサを、何をどこにどんな角度で、つけるか検証するためには...
そうだよ、シミュレータを使うんだよ！！！
センサをガン積みして、君だけの最強を目指そうぜ！
（実際はコストとか通信とか大変だからガン積みしたらだめよ）

というわけで、センサをどこにつけるか、そんな乙女な悩みを持っている人たちのために、CARLAさんがいる訳さ...!!!
CARLAさんの扱い方を、レクチャーするぞ〜〜！

[CARLA Simulator](http://carla.org/)
![Screenshot from 2020-03-29 15-31-17.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/388647/111dfc3d-0565-d242-04a9-6184f92a224a.png)

自動運転好きだよ！って人、Like＆フォローしてね！！！

## この記事ですること
CARLAシミュレータとROSを使って車にセンサを載せまくる

* CARLAのインストール
* carla - ros_bridgeのインストール
* センサを載せまくる
* 街を爆走しながらROS＆rvizで可視化する
* CARLAの長所短所考察

## この記事の完成予想図

トヨタプリウスにセンサを乗っけるよ、自分で操縦もできる！！
![Screenshot from 2020-03-29 15-50-24.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/388647/57230fdb-eeb4-ecc9-cc2f-d6c6a60ff269.png)

LIDARを５個載せて、ROSで可視化する！！
![Screenshot from 2020-03-29 15-50-30.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/388647/8b622d53-2300-c62b-1de8-bce0604ae4ec.png)

さあ、行くぞ！！！

# CARLAのインストール

インストールの前提条件

### 環境
(GPUなしマシンでは厳しい！）
* Ubuntu 18.04 LTS, i7-9700K, RTX2080
* Python 3.6.8
* ROS melodic

```bash
pip install --user pygame numpy
sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 304F9BC29914A77D &&
sudo add-apt-repository "deb [arch=amd64 trusted=yes] http://dist.carla.org/carla-0.9.8/ all main"
sudo apt-get update
sudo apt-get install carla
```

### CARLAの起動
```bash
cd /opt/carla/bin
#CARLAシミュレータのサーバを起動
./CarlaUE4.sh
```
![Screenshot from 2020-03-29 15-59-49.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/388647/efa7ea1c-f23f-ca22-ff5c-929d687ca62e.png)

すると、こんな画面が出てきます。
CARLAはサーバとクライアントに分かれており、まずはサーバを立ち上げました。

### CARLAの街を自動車で駆け抜ける
さて、ちょっとだけ遊んでみようぜ、わくわく...!!

```bash
#サーバを立ち上げた状態で、手動操縦クライアントを立ち上げる
cd /opt/carla/PythonAPI/examples
python3 manual_control.py
```
![Screenshot from 2020-03-29 16-05-12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/388647/55d2a9ab-b648-02f7-a7b9-22cc34ee0ebc.png)

操縦方法（主要なものだけ）

* WASD 移動
* Q リバース
* space ハンドブレーキ
* backspace キャラクター変更
* P 自動操縦開始
* TAB　視点変更
* 1,2,3,4,5,6,7,8 各種センサ視点

# carla - ros_bridgeのインストール

さて、次は、carlaの結果をROSで可視化出来るようにしてみるぞ！
ROSがない人はインストールしてね！

```bash
sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 81061A1A042F527D &&
sudo add-apt-repository "deb [arch=amd64 trusted=yes] http://dist.carla.org/carla-ros-bridge-melodic/ bionic main"
sudo apt update &&
sudo apt install carla-ros-bridge-melodic

#ワークスペースの作成
mkdir -p ~/carla-ros-bridge/catkin_ws/src
cd ~/carla-ros-bridge
git clone https://github.com/carla-simulator/ros-bridge.git
cd catkin_ws/src
ln -s ../../ros-bridge
source /opt/ros/kinetic/setup.bash #Watch out, this sets ROS Kinetic. 
cd ..

rosdep update
rosdep install --from-paths src --ignore-src -r

#ビルド
catkin_make

#ソース
source /opt/carla-ros-bridge/<melodic or kinetic>/setup.bash

#ROS bridgeとrvizの同時起動
roslaunch carla_ros_bridge carla_ros_bridge_with_rviz.launch
```

さて、これでcarla-rosbridgeができた
calraのクライアント側で設定されているセンサや情報を、ros topicとして投げれるようになったぞ！

# センサを載せまくる

可視化できるようになっても、センサをガン積みできなきゃつまらないよなぁ！！！
そのためには、クライアントで使われているPythonコードを改変しないといけない
（ここからチュートリアルにないので、調べるの少し苦労したぞ...）

### キャラを固定する
センサを載せまくる前に、自分が手動操縦出来るキャラがランダムにならないように固定する

```python
#先程使ったmanual_control.pyを編集する
#def restartの中身を変える
    def restart(self):
        self.player_max_speed = 1.589
        self.player_max_speed_fast = 3.713
        # Keep same camera config if the camera manager exists.
        cam_index = self.camera_manager.index if self.camera_manager is not None else 0
        cam_pos_index = self.camera_manager.transform_index if self.camera_manager is not None else 0
        # 手動操縦キャラのランダム決定
        #blueprint = random.choice(self.world.get_blueprint_library().filter(self._actor_filter))
        # 手動操縦キャラの固定、トヨタプリウス
        blueprint = self.world.get_blueprint_library().find('vehicle.toyota.prius')
```

これで必ずトヨタプリウスになるように！
[使用可能なキャラリスト](https://carla.readthedocs.io/en/latest/bp_library/#vehicle)
![Screenshot from 2020-03-29 16-02-30.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/388647/2add44ff-1c22-427f-13c9-4d329d1d5f98.png)

### センサを追加する

トヨタプリウスにセンサをガン積みするぞ！
さあガン積みタイムだ！
今回載せるセンサは、

* 前方RGBカメラ(適当スペック）
* 前方LIDAR VLP-16
* 後方LIDAR VLP-16
* 左LIDAR VLP-16
* 右LIDAR VLP-16
* ルーフLIDAR VLS-128

載せるセンサのスペック
[VLP-16](https://www.argocorp.com/cam/special/Velodyne/VLP-16.html)
[VLS-128](https://www.argocorp.com/cam/special/Velodyne/VLS-128.html)
*完全再現は難しく、大体しか再現できない

### カメラを載せる
```python
#manual_control.pyを編集する
#def set_sensorの中身を変える
#適当なスペックのカメラを作る
    def set_sensor(self, index, notify=True, force_respawn=False):
        #CARLAシミュレータの中で登場するすべての物体の設計図を読み込む
        blueprint_library = self._parent.get_world().get_blueprint_library()
        #設計図の中からカメラを読み込む
        camera2 = blueprint_library.find('sensor.camera.rgb')
        # 画像の解像度を設定する
        IM_WIDTH = 640
        IM_HEIGHT = 480
        # カメラの解像度、FOV、カメラの名前を設定する
        camera2.set_attribute('image_size_x', f'{IM_WIDTH}')
        camera2.set_attribute('image_size_y', f'{IM_HEIGHT}')
        camera2.set_attribute('fov', '110')
        camera2.set_attribute('role_name', 'front_lookdown_camera')

        # カメラの取り付け位置を設定する、ピッチングもつける
        spawn_point1 = carla.Transform(carla.Location(x=2.5, z=0.7), carla.Rotation(pitch=-30.0, yaw=0, roll=0))
        # カメラを手動操縦の車両に取り付ける
        self.front_camera_fix = self._parent.get_world().spawn_actor(camera2, spawn_point1, attach_to=self._parent)
```

### LIDARを載せる

点数が多すぎると、シミュレータカクつくので、生成頻度や点数はお好みに合わせて変更してね★

```python
#manual_control.pyを編集する
#def set_sensorの中身を変える
#VLS-128のスペックを書き込む
        #CARLAシミュレータの中で登場するすべての物体の設計図を読み込む
        blueprint_library = self._parent.get_world().get_blueprint_library()
        #設計図の中からLIDRを読み込む
        velodyne128_roof= blueprint_library.find('sensor.lidar.ray_cast')
        #縦レイヤ数を設定する
        velodyne128_roof.set_attribute('channels', '128')
        #縦FOV最大〜最小を設定する
        velodyne128_roof.set_attribute('lower_fov', '-25')
        velodyne128_roof.set_attribute('upper_fov', '15')
        #センサの名前を設定する
        velodyne128_roof.set_attribute('role_name', 'roof_velodyne')
        #最大検出距離を設定する
        velodyne128_roof.set_attribute('range', '100')
        #レーザが一周するfrequencyを設定する
        velodyne128_roof.set_attribute('rotation_frequency', '1')
        #１秒間生成する点数を設定する
        velodyne128_roof.set_attribute('points_per_second', '240000')

        # LIDARをルーフにつける、角度はつけない
        spawn_point6 = carla.Transform(carla.Location(x=0, y=0, z=2), carla.Rotation(pitch=0, yaw=0, roll=0))
        # LIDARを手動操縦に取り付ける
        self.velodyne_right = self._parent.get_world().spawn_actor(velodyne128_roof, spawn_point6, attach_to=self._parent)
```

### センサをガン積みする
上の２つのセンサ以外にも、４つのLIDARを搭載する

```python
#manual_control.pyを編集する
#def set_sensorの中身を変える
#前方カメラ、ルーフ、前後左右のLIDARを搭載する
def set_sensor(self, index, notify=True, force_respawn=False):
        #set sensor
        blueprint_library = self._parent.get_world().get_blueprint_library()
        camera2 = blueprint_library.find('sensor.camera.rgb')
        # change the dimensions of the image
        IM_WIDTH = 640
        IM_HEIGHT = 480
        camera2.set_attribute('image_size_x', f'{IM_WIDTH}')
        camera2.set_attribute('image_size_y', f'{IM_HEIGHT}')
        camera2.set_attribute('fov', '110')
        camera2.set_attribute('role_name', 'front_lookdown_camera')

        # Adjust sensor relative to vehicle
        spawn_point1 = carla.Transform(carla.Location(x=2.5, z=0.7), carla.Rotation(pitch=-30.0, yaw=0, roll=0))
        # spawn the sensor and attach to vehicle.
        self.front_camera_fix = self._parent.get_world().spawn_actor(camera2, spawn_point1, attach_to=self._parent)

        #set sensor
        velodyne16_f = blueprint_library.find('sensor.lidar.ray_cast')
        # change the dimensions of the image
        velodyne16_f.set_attribute('channels', '16')
        velodyne16_f.set_attribute('lower_fov', '-15')
        velodyne16_f.set_attribute('upper_fov', '15')
        velodyne16_f.set_attribute('role_name', 'front_bumper_velodyne')
        velodyne16_f.set_attribute('range', '100')
        #velodyne16_f.set_attribute('rotation_frequency', '1')
        #velodyne16_f.set_attribute('points_per_second', '57600')

        # Adjust sensor relative to vehicle
        spawn_point2 = carla.Transform(carla.Location(x=2.3, z=0.3), carla.Rotation(pitch=0, yaw=0, roll=0))
        # spawn the sensor and attach to vehicle.
        self.velodyne_front = self._parent.get_world().spawn_actor(velodyne16_f, spawn_point2, attach_to=self._parent)

        #set sensor
        velodyne16_b = blueprint_library.find('sensor.lidar.ray_cast')
        # change the dimensions of the image
        velodyne16_b.set_attribute('channels', '16')
        velodyne16_b.set_attribute('lower_fov', '-15')
        velodyne16_b.set_attribute('upper_fov', '15')
        velodyne16_b.set_attribute('role_name', 'back_bumper_velodyne')
        velodyne16_b.set_attribute('range', '100')

        #velodyne16_b.set_attribute('rotation_frequency', '1')
        #velodyne16_b.set_attribute('points_per_second', '57600')

        # Adjust sensor relative to vehicle
        spawn_point3 = carla.Transform(carla.Location(x=-2.3, z=0.3), carla.Rotation(pitch=0, yaw=0, roll=0))
        # spawn the sensor and attach to vehicle.
        self.velodyne_back = self._parent.get_world().spawn_actor(velodyne16_b, spawn_point3, attach_to=self._parent)

        #set sensor
        velodyne16_l = blueprint_library.find('sensor.lidar.ray_cast')
        # change the dimensions of the image
        velodyne16_l.set_attribute('channels', '16')
        velodyne16_l.set_attribute('lower_fov', '-15')
        velodyne16_l.set_attribute('upper_fov', '15')
        velodyne16_l.set_attribute('role_name', 'left_bumper_velodyne')
        velodyne16_l.set_attribute('range', '100')
        #velodyne16_l.set_attribute('rotation_frequency', '1')
        #velodyne16_l.set_attribute('points_per_second', '57600')

        # Adjust sensor relative to vehicle
        spawn_point4 = carla.Transform(carla.Location(x=0, y=-1, z=0.3), carla.Rotation(pitch=0, yaw=0, roll=0))
        # spawn the sensor and attach to vehicle.
        self.velodyne_left = self._parent.get_world().spawn_actor(velodyne16_l, spawn_point4, attach_to=self._parent)

        #set sensor
        velodyne16_r= blueprint_library.find('sensor.lidar.ray_cast')
        # change the dimensions of the image
        velodyne16_r.set_attribute('channels', '16')
        velodyne16_r.set_attribute('lower_fov', '-15')
        velodyne16_r.set_attribute('upper_fov', '15')
        velodyne16_r.set_attribute('role_name', 'right_bumper_velodyne')
        velodyne16_r.set_attribute('range', '100')
        #velodyne16_l.set_attribute('rotation_frequency', '1')
        #velodyne16_l.set_attribute('points_per_second', '57600')

        # Adjust sensor relative to vehicle
        spawn_point5 = carla.Transform(carla.Location(x=0, y=1, z=0.3), carla.Rotation(pitch=0, yaw=0, roll=0))
        # spawn the sensor and attach to vehicle.
        self.velodyne_right = self._parent.get_world().spawn_actor(velodyne16_r, spawn_point5, attach_to=self._parent)

        #set sensor
        velodyne128_roof= blueprint_library.find('sensor.lidar.ray_cast')
        # change the dimensions of the image
        velodyne128_roof.set_attribute('channels', '128')
        velodyne128_roof.set_attribute('lower_fov', '-25')
        velodyne128_roof.set_attribute('upper_fov', '15')
        velodyne128_roof.set_attribute('role_name', 'roof_velodyne')
        velodyne128_roof.set_attribute('range', '100')
        velodyne128_roof.set_attribute('rotation_frequency', '1')
        velodyne128_roof.set_attribute('points_per_second', '240000')

        # Adjust sensor relative to vehicle
        spawn_point6 = carla.Transform(carla.Location(x=0, y=0, z=2), carla.Rotation(pitch=0, yaw=0, roll=0))
        # spawn the sensor and attach to vehicle.
        self.velodyne_right = self._parent.get_world().spawn_actor(velodyne128_roof, spawn_point6, attach_to=self._parent)
```

### センサで見たい他の交通参加者を召喚する
```bash
cd /opt/carla/PythonAPI/examples
# 交通参加者をスポーンする、５０ユニットだす（歩行者、サイクリスト、車両がランダムに配置される）
python3 spawn_npc.py -n 50
```
![Screenshot from 2020-03-29 16-39-52.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/388647/8756ad23-9235-dc93-9afd-92260fa9257b.png)

お、街が賑やかになったね、やったね！

しかし、交通参加者に動かれると困る場合がある
そういう場合は動きを止めるのだ、クロックアップ...!!

```python
#車両の動きを止める
#spawn_npc.pyを編集する
#関数mainを編集する

#　自動操縦をオフにする
batch.append(SpawnActor(blueprint, transform).then(SetAutopilot(FutureActor, False)))
```

```python
#歩行者の動きを止める
#spawn_npc.pyを編集する
#関数mainを編集する

        # 2. we spawn the walker object
        batch = []
        walker_speed = []
        for spawn_point in spawn_points:
            walker_bp = random.choice(blueprintsWalkers)
            # set as not invincible
            if walker_bp.has_attribute('is_invincible'):
                walker_bp.set_attribute('is_invincible', 'false')
            # set the max speed
            if walker_bp.has_attribute('speed'):
                if (random.random() > percentagePedestriansRunning):
                    # walking
                    #walker_speed.append(walker_bp.get_attribute('speed').recommended_values[1])
　　　　　　　　　　　　#歩行者の速度を０にする
                    walker_speed.append(0.0)
                else:
                    # running
                    #walker_speed.append(walker_bp.get_attribute('speed').recommended_values[2])
　　　　　　　　　　　　#歩行者の速度を０にする
                    walker_speed.append(0.0)
```

# 街を爆走しながらROS＆rvizで可視化する
さて、これですべて準備が整った

### ターミナル１
```bash
cd /opt/carla/bin
#CARLAシミュレータのサーバを起動
./CarlaUE4.sh
```
### ターミナル2
```bash
#carla-rosbridgeとrvizの起動
roslaunch carla_ros_bridge carla_ros_bridge_with_rviz.launch
```
### ターミナル３
```bash
#手動操縦＆センサガン積みクライアントを起動
cd /opt/carla/PythonAPI/examples
python3 manual_control.py
```
### ターミナル４
```bash
#交通参加者召喚
cd /opt/carla/PythonAPI/examples
python3 spawn_npc.py -n 50
```
操縦画面
![Screenshot from 2020-03-29 16-52-58.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/388647/b2db1cb9-6de5-3d35-cd55-8293855147cb.png)

rviz可視化結果
乗っけたすべてのカメラとLIDARを可視化する
![Screenshot from 2020-03-29 16-53-16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/388647/7807192e-8cd2-ee9a-f116-4c632e7f1213.png)

設定tips

* Global Option -> fixed_frameでheroを選択（デフォルトの自車の名前)
* PointCloud2やImage等のトピックは、自身が指定したセンサ名（各センサのrole_nameをさっき指定したよね）

# CARLAのいいところ・悪いところ

さて、一通りのことが出来たので、CARLA simulatorのいいところ、わるいところを紹介するぜ！

### いいところ

* 無料：ただで使える、しかもオープンソース
* ROSと連携できる、つまりROSで動くアルゴリズムをすぐにテストできる
* 写実性が高い：UE４ベースなので、リアリスティックな画像を求めることが出来る（リアルレンダリング機能もあるんだって、今回未使用）
* シミュレーション用アプリが揃っている：白線や、セマンティックセグメンテーション、自己位置、他者位置などを可視化、出力できる
* ドキュメンテーションが豊富：オープンソースでドキュメンテーションが揃っているため、根気はいるが便利
* 交通参加者の配置が簡単：自動操縦してくれるので、召喚スクリプトだけで交通参加者が出てきて、勝手に動き始めるので街がにぎやかになる。ただし、手動定義はゴリ面倒。
* あと名前が可愛い爆（ふざけんな

### わるいところ

* 有料サポートがない：無料、オープンソースなので、困ったら自分で解決するしかない。幸いフォーラムやgithubディスカッションはある。
* 世界のエディットが面倒：　UE４ベースで自分で街を作らなくてはいけない、超絶面倒
* ユーザーGUIがない：基本pythonAPI経由で行うので、GUIで世界を編集、動きを編集、サイズを編集なんてことができない。
* LIDARの水平角度が調整できない：　この世界のLIDARは水平３６０度LIDARしか存在しない
* LIDAR点群の当たり判定が正確ではない：　下の画像を見てほしい。車両の形状を点群が反映していないことがわかる、どうやら当たり判定はプリセットがそのまま使われているようだ。

自車（灰色）周辺の車両は、丸みを帯びた形状をしている
![Screenshot from 2020-03-29 17-03-19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/388647/8cce3090-63b1-94a0-72e9-968ba2577563.png)

点群でみると、自車も他車両も全て四角になっている。これは車だけでなく人も四角になっている。
![Screenshot from 2020-03-29 17-03-28.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/388647/08a5cc62-407d-afeb-aa41-512ebe3c0125.png)


# おわりに

というわけで、
シミュレータの車を手動操縦して、さらにその車にセンサをガン積みして、ROSで可視化する！ところまで完了しました。
これはとても楽しい。

適当にセンサを乗っけたけれど、ここからが奥深いぞ。

例えば今回のカメラ。
![Screenshot from 2020-03-29 17-07-05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/388647/eb670d66-fa0e-f5d6-32fb-e2d8814e3eac.png)

もう少し横に視界がほしいよね、下にかたむいてるけど、地面しか見えなくてぶっちゃけ無駄だよね、
そんなことがわかってくる

あとは自分でセンサのパラメータをいじって、自分だけの最強の自動運転車を作ろうぜ！！！！

それでは〜★

役に立ったなら、Like＆フォローしてね！！！

# CARLA参考資料
* [交通参加者の設計リスト](https://carla.readthedocs.io/en/latest/bp_library/#vehicle)
* [設計リスト](https://carla.readthedocs.io/en/latest/bp_library/)
* [交通参加者やセンサの召喚](https://carla.readthedocs.io/en/latest/core_actors/)
* [センサの設定(なんか古いみたいで使えなかった)](https://carla.readthedocs.io/en/stable/cameras_and_sensors/)
* [センサのリファレンス](https://carla.readthedocs.io/en/latest/ref_sensors/)
* [センサ取り付け](http://carla.org/Doxygen/html/de/da4/classcarla_1_1geom_1_1Transform.html)
* [PythonAPIリファレンス](https://carla.readthedocs.io/en/latest/python_api/)
* [交通参加者の設定変更API](https://carla.readthedocs.io/en/latest/python_api/)
* [シンプルプロジェクト](https://pythonprogramming.net/control-camera-sensor-self-driving-autonomous-cars-carla-python/)

# この話の続き

こっちもセットで見てね！
[CARLA X ROS: 自動運転シミュレータ世界で実験空間を作る！](https://qiita.com/chin_self_driving_car/items/39c65f49543d909869cd)