【ロボット】Braitenberg Vehicleを活用した単純で直感的な障害物回避方法について


# 参考文献

**原著**
[1] [Valentino Braitenberg : (1984) VEHICLES -Expderiments in Synthetic Psychology-，The MIT Press.](https://www.amazon.co.jp/dp/0262022087)
[<img width="200" alt="image.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1822494/67de3000-e2d6-95d5-e63d-fd4c863de8b8.png">](https://www.amazon.co.jp/dp/0262022087)

**日本語訳**
[2] [V・ブライテンベルク /著，加地大介 /訳，: (1987) 模型は心を持ちうるか 人工知能・ 認知科学・脳生理学の焦点，哲学書房](https://www.amazon.co.jp/dp/4886790135)
[<img width="200" alt="image.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1822494/c46ac6b8-1587-8022-231e-baf96153fd99.png">](https://www.amazon.co.jp/dp/4886790135)

**その他参考**
[3] [R.Pfeifer, C.Scheier / 著， 石黒章夫，小林宏，細田耕 /監訳 : (2001) 知の創成 -身体性 認知科学への招待- ，共立出版株式会社.](https://www.amazon.co.jp/dp/4320120329)
[<img width="200" alt="image.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1822494/9153d461-7762-cf48-67b3-629621a5367e.png">](https://www.amazon.co.jp/dp/4320120329)


# 目次
[0.はじめに](#0-はじめに)
[1.Braitenberg Vehicleについて](#1-braitenberg-vehicleについて)
[2. Vehicle1号](#2-vehicle1号)
[3. Vehicle2号](#3-vehicle2号)
[4. Vehicle3号](#4-vehicle3号) 
[5. Vehicleを活用した障害物回避の実装](#5-vehicleを活用した障害物回避の実装)
[6. 最後に](#6-最後に)


# 0. はじめに
大学院時代に研究で使った**Braitenberg Vehicle**について紹介したいと思います。
このBraitenberg Vehicleはセンサーとモーターの結線のみで構成されており、単純で直感的なデザインにも関わらず大変興味深い振る舞いを行うことから認知科学や人工生命の文献などでよく紹介されています。
今回は、Braitenberg Vehicleの簡単な紹介を行い、この単純で複雑な振る舞いを利用し、実環境で動き回るロボットにとって欠かせない機能である「障害物回避」を単純で直感的な構成で実現したいと思います。

今回は「障害物回避」という目的に帰着する内容ですがこのBraitenberg Vehicleの内容は、センサーとモーターの結線から遺伝的アルゴリズムやニューラルネットワークに近い内容へ話が展開しますので、非常に興味深い本となっています。

また、Braitenberg Vehicleは内容的に学術的な要素が強いですが、実際に行われている処理は非常に単純でありながら、色々な可能性を感じる内容となっています。
教育現場などでも十分に使える内容ではないかと思います。


## 検証環境
Braitenberg Vehicleの振る舞いの再現には以下の環境を使用した。

**ロボット** : LEGO Mindstorm EV3
**OS** : EV3dev
**言語** : Python3.5

本来Braitenberg Vehicleはセンサとモーターの結線のみで構成されるため配線のみのノーコード？で実現できますが、今回は利便性を取ってLEGO Mindstorm EV3 を使用し簡単なコードで再現したいと思います。
(内容的にコードそのものにそれほど意味はありません)


# 1. Braitenberg Vehicleについて

Braitenberg Vehicle(以下、Vehicleと略記) は、開発者である**ドイツの神経学者 Valentino Braitenberg**にちなんで名付けられた一連のエージェント群のことであり、これらはセンサーとモーターの単純な結線により構成されています[1]。これらVehicleは1∼14号まで存在し徐々に複雑さが増加します。今回はこの中のVehicle1~3号について紹介する。


# 2. Vehicle1号
最初に最も単純な形のVehicle1号について紹介する。
Vehicle1号はセンサーとモーターが 1 つずつあり互いに結線する形で構成されており、センサーの反応に比例してモーターが回転し直進する構成となっている。
例としてセンサーを光センサーと仮定した場合、明るい場所ではセンサーが強く反応しモーターの回転数が増加、暗い場所ではセンサーの反応が弱くなることからモーターの回転数が減少する。よって、明るい場所では早く動き、暗い場所では遅く動くという振る舞いを見せる。なお、図中のセンサーとモーター間の“+”記号は、結線が比例の関係であることを表している。

<img width="500" alt="vb1.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1822494/aba22a98-8ea5-48bb-b236-d6a522533f8e.png">


# 3. Vehicle2号
Vehicle2号はセンサーとモーターがそれぞれ 2 つずつ使用されており、左右両側に設置されている。
これらのVehicleは結線が異なることから“**a**”, **b**”, “**c**” の3種類が存在する。 **a**はそれぞれのセンサーが同側のモーターに結線されており、**b**はそれぞれのセンサーが反対側のモータに結線されている。**c**は両方のセンサーが両方のモーターに結線されているため、上記Vehicle1号と本質的に同等である。

<img width="1000" alt="vb2.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1822494/d9aedaa3-7d6f-2caf-2b61-4327c71430f0.png">

## Vehicle2号a , Vehicle2号b の振る舞い
Vehicle2号のセンサーを光センサーと仮定した場合。**2号a**は結線の構成から光を避けるように振る舞い、**2号b**は光に近づく振る舞いを見せる。このことからVehicle2号は単純な構造ながら昆虫のような振る舞いを見せる。

<img width="700" alt="vehicles_2a_and_2b.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1822494/645a26e8-922b-ad6a-532e-b5a58d72f332.png">

実際に光センサを用いた**Vehicle2号a, b**の振る舞いが以下になります。

**作成したVehicle2号**
青いライトがついている左右のセンサーが光センサー
センサーを下向きにしている理由は直接光源の地面に反射する光の輝度を測定するためです。
※上部にWEBカメラが付いていますが特に関係はありません
(外すのが面倒でつけっぱなしにしているだけです....)
<img width="500" alt="bv_2.jpg" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1822494/3ebfe2bb-afc8-32b5-8361-cbf34f0dff94.jpeg">

**Vehicle2号a**
<iframe width="966" height="543" src="https://www.youtube.com/embed/eFXlNcTmrmY" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>

**Vehiicle2号b**
<iframe width="950" height="534" src="https://www.youtube.com/embed/Mez0UgaY0LA" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
  
  
光源を上に固定するとVehiicle2号bは光を好んで光源をくるくる回るような振る舞いを見せます。
<iframe width="950" height="534" src="https://www.youtube.com/embed/EpHHjPfkiVc" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>

蛇足ですがこちらの振る舞いは生物にみられる**走光性、負の走行性**(光に向かって動く、または避ける動き)が再現されているとも言えます。

https://ja.wikipedia.org/wiki/走光性


実際に使用したプログラムは以下になります。
コード内にev3devのAPIも入っており少し読みづらいかと思いますが
単純に光センサから得られた強度をモーターの出力値に充てているだけの処理で再現できます。

```python:vhicle_2.py
#!/usr/bin/env python3

# === プログラム概要 ===
# BraitenbergVhicle2号に相当するモデル
# 左右の光センサによりモーターを動作させる
# ====================

## OUTPUT_A = Left ##
## OUTPUT_D = Right ##
from ev3dev2.motor import OUTPUT_A, OUTPUT_D, MoveTank, SpeedPercent
## INPUT_1 = Left_ColorSensor ##
## INPUT_4 = Right_ColorSensor ##
from ev3dev2.sensor import INPUT_1, INPUT_4
from ev3dev2.sensor.lego import ColorSensor
from ev3dev2.button import Button


def bv_2a():
    btn = Button()
    ColorSensor_L = ColorSensor(INPUT_1)
    ColorSensor_R = ColorSensor(INPUT_4)
    TankDrive = MoveTank(OUTPUT_A, OUTPUT_D)
    
    while True:
        if btn.backspace:
                break
        color_l = ColorSensor_L.ambient_light_intensity
        color_r = ColorSensor_R.ambient_light_intensity
        print("color_l:", color_l, "color_r:", color_r)
        # vhicle_2号aの場合　
        motorpower_l = color_l
        motorpower_r = color_r
        # vhicle_2号bの場合　
        # motorpower_l = color_r
        # motorpower_r = color_l
        TankDrive.on(SpeedPercent(motorpower_l), SpeedPercent(motorpower_r))
    TankDrive.stop()


if __name__ == "__main__":
    bv_2a()

```

# 4. Vehicle3号
Vehicle3号はa、bとcで大きく構成が異なっている。まず最初にa、bは上記Vehicle2号と殆ど同じ構成だがセンサーとモーターの結線が反比例の関係となっている。つまり、センサーの反応が強い時、モーターの回転数が減少し、センサーの反応が弱い時、モーターの回転数が増加する。例としてセンサーを光センサーと仮定した場合の振る舞いの例を以下に示す。

<img width="800" alt="vb3.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1822494/16757e05-0c8a-368e-3043-d4c09e20a732.png">

## Vehicle3号a、bの振る舞い 
***a*** は光源に近づきつつ減速するため光源の方向を向いて停止する。それに対し***b***は光源とは逆方向を向いて停止、または光源から離れつつ加速を行う。なお、図中のセンサーとモーター間の“***−***”記号は、結線が反比例の関係であることを表している。
<img width="800" alt="vehicles_3a_and_3b.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1822494/14f9fcbe-2aac-98e7-6875-ab4cbf8376e0.png">

こちらは上記Vehicle2号に近い内容なので割愛

## Vehicle3号cの振る舞い 
続いてVehicle3号cは複数種類のセンサーが各モーターに結線する構成である。例として、センサーAを光センサー、センサーBを超音波センサーとし、前方に光源及び障害物があると仮定する。光センサーとモーターの結線の関係から光源に接近するが、障害物に近づいた際には超音波センサーが反応し、それに対応するモーターが回転する。そのため、障害物を避けながら光源に近づくという振る舞いを見せる。


<img width="500" alt="vb3c.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1822494/81ae9622-1a9f-ee6c-003b-37e03381cafb.png">

<img width="800" alt="vb3_behavior.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1822494/6a1db995-4834-fd2a-c5a3-f9e29db1a1e5.png">

**作成したVehicle3号c**
下向きに青い光を出している部分が光センサー、赤い目のような部分が距離を測定する超音波センサー
<img width="800" alt="bv_3c.jpg" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1822494/2265038d-deb0-2ced-6b83-9a1423a311c2.jpeg">


**Vehicle3号c**
<iframe width="950" height="534" src="https://www.youtube.com/embed/zKERsw8xXc0" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>

```python:bv_3c
#!/usr/bin/env python3

# === プログラム概要 ===
# BraitenbergVhicleの3cに相当するモデル
# 左右の超音波センサ及び光センサによりモーターを動作させる
# ====================

## OUTPUT_A = Left ##
## OUTPUT_D = Right ##
from ev3dev2.motor import OUTPUT_A, OUTPUT_D, MoveTank, SpeedPercent
## INPUT_1 = Left_Usensor ##
## INPUT_4 = Right_Usensor ##
from ev3dev2.sensor import INPUT_1, INPUT_2, INPUT_3, INPUT_4
from ev3dev2.sensor.lego import UltrasonicSensor, ColorSensor
from ev3dev2.button import Button

DRIVE_POWER = 100
UL_MAX = 50
UL = "UL"


def bv_3c():
    btn = Button()
    ColorSensor_L = ColorSensor(INPUT_1)
    ColorSensor_R = ColorSensor(INPUT_4)
    Usensor_L = UltrasonicSensor(INPUT_2)
    Usensor_R = UltrasonicSensor(INPUT_3)
    TankDrive = MoveTank(OUTPUT_A, OUTPUT_D)

    while True:
        if btn.backspace:
            break
        distance_l = dataLimit(UL_MAX, Usensor_L.distance_centimeters)
        distance_r = dataLimit(UL_MAX, Usensor_R.distance_centimeters)
        dis_power_l = conversion_dist_to_per(UL, distance_l, UL_MAX)
        dis_power_r = conversion_dist_to_per(UL, distance_r, UL_MAX)
        color_l = ColorSensor_L.ambient_light_intensity
        color_r = ColorSensor_R.ambient_light_intensity
        MotorPower_L = coef_pwr(dis_power_l, color_r)
        MotorPower_R = coef_pwr(dis_power_r, color_l)
        TankDrive.on(SpeedPercent(MotorPower_L),
                        SpeedPercent(MotorPower_R))
    TankDrive.stop()
    return 0


def coef_pwr(Power_1, Power_3):
    return round(0.3*Power_1 + 0.7*Power_3)


def conversion_dist_to_per(sensor, distance, threshold):
    if sensor == "UL":
        return round(100-(distance/threshold)*100)
    else:
        return 0


def dataLimit(threshold, data):
    if threshold < data:
        data = threshold
    return data


if __name__ == "__main__":
    bv_3c()

```


# 5. Vehicleを活用した障害物回避の実装
上記で紹介した各種Vehicleを活用し以下に障害物回避を実装する。
主な構成はVehicle2号aに超音波センサーを付けた構成であり、正面の障害物を避けるためにセンサを1つ追加しその配線を両モーターに結線する構成となる。これにより正面のセンサーが反応した際には両モーターがセンサーの反応に対して反比例の動きを見せる。なお上記で紹介したVehicle3号a,bでは減速→停止となっているが、こちらでは反回転を想定している。つまり、正面センサが反応した際には結線しているモーターは全て反回転を行う。
図中では一つのモーターに対し"**+**"と"**-**"が両方結線されてる箇所については、センサーの値に対して比例する出力と反比例する出力の和をモーターの入力としていることを表す。


<img width="500" alt="bv_original.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1822494/db162c14-6042-f61f-28e9-5942d3716ec3.png">

**作成したVehicle(Original)**
本来であれば同じ超音波センサーを3つ取り付けるのですが、手持ちの超音波センサーが2つしかないため正面のセンサーは似たように距離が測れる赤外線センサーを使用。
見た目やコード内容が若干変わりますが本質的な動作はほとんど同じです。

<img width="500" alt="bv_original_cap.jpg" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1822494/41f15daa-106d-6b43-6877-265d9e011220.jpeg">


**障害物回避**
センサーで検知した障害物に対して反射的に動作するため、急な障害物の発生に対しても問題なく対応できます。
<iframe width="950" height="534" src="https://www.youtube.com/embed/IxTaJOwHH5U" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>

```python:bv_original.py
#!/usr/bin/env python3

# //-----------------------------// #
# OUTPUT_A = Left
# OUTPUT_D = Right
# //-----------------------------// #
from ev3dev2.motor import OUTPUT_A, OUTPUT_D, MoveTank, SpeedPercent
# //-----------------------------// #
# INPUT_1 = Left_Usensor
# INPUT_2 = InfraredSensor
# INPUT_4 = Right_Usensor
# InfraredSensor 100% is 70cm
# //-----------------------------// #
from ev3dev2.sensor import INPUT_1, INPUT_2, INPUT_4
from ev3dev2.sensor.lego import UltrasonicSensor, InfraredSensor
from ev3dev2.button import Button

import ast

DRIVE_POWER = 100
UL_MAX = 50
IR_MAX = 50
UL = "UL"
IR = "IR"
CENTOR_POINT = 50


def run():
    btn = Button()
    Usensor_L = UltrasonicSensor(INPUT_1)
    Usensor_R = UltrasonicSensor(INPUT_4)
    IRsensor = InfraredSensor(INPUT_2)
    TankDrive = MoveTank(OUTPUT_A, OUTPUT_D)

    print('Start:BV_Original')

    while True:
        if btn.backspace:
            break
        distance_l = datalimit(UL_MAX, Usensor_L.distance_centimeters)
        distance_r = datalimit(UL_MAX, Usensor_R.distance_centimeters)
        distance_f = datalimit(IR_MAX, IRsensor.proximity)

        motorpwr_l = conversion_dist_to_per(UL,
                                            distance_l,
                                            UL_MAX)
        motorpwr_r = conversion_dist_to_per(UL,
                                            distance_r,
                                            UL_MAX)
        motorpwr_f = conversion_dist_to_per(IR,
                                            distance_f,
                                            IR_MAX)
        motorpwr_l, motorpwr_r = nomal_drive(motorpwr_l,
                                                motorpwr_r,
                                                motorpwr_f)
        TankDrive.on(motorpwr_l, motorpwr_r)
    TankDrive.stop()
    return 0


# 通常のbv_originalの走行(障害物回避のみ)
def nomal_drive(motorpwr_l, motorpwr_r, motorpwr_f):
    ret_motorpwr_l = SpeedPercent(coef_pwr(motorpwr_l,
                                           DRIVE_POWER,
                                           motorpwr_f))
    ret_motorpwr_r = SpeedPercent(coef_pwr(motorpwr_r,
                                           DRIVE_POWER,
                                           motorpwr_f))
    return ret_motorpwr_l, ret_motorpwr_r


def coef_pwr(Power_1, Power_2, Suppress_Power):
    return round((Power_1*0.9) + (Power_2*0.1) - Suppress_Power)


def conversion_dist_to_per(sensor, distance, threshold):
    if sensor == "IR":
        return round(100 - (distance/threshold)*100)
    elif sensor == "UL":
        return round(100 - (distance/threshold)*100)
    else:
        return 0


def datalimit(threshold, data):
    if threshold < data:
        data = threshold
    return data


if __name__ == "__main__":
    run()

```


# 6.  最後に
今回はBraitenberg Vehicleを活用して単純な障害物回避について紹介しました。
ロボットにおける障害物回避の方法は高度なもので言えばカメラやLiderセンサー、SLAMなどを活用した多彩なものがあると思いますが、それらの活用にはそれなりの知識及びマシンスペックが必要となります。
今回紹介した方法はそれほど知識が必要なく、実装も容易ですので、ちょっとしたロボットを作って障害物の回避を簡単に行いたい場合などの参考になればと思います。

また、今回は障害物回避のみのご紹介でしたが今後カメラを活用し、特定の対象に向かって障害物を回避しながら進むといった動作についても記事を書きたいと思います。

最後に、少々長い内容になってしまいましたが本記事を通してBraitenberg Vehicleに興味を持っていただければ幸いです。

以上