Arduinoと超音波センサーで作るAI障害物回避ロボット 〜Q学習による強化学習実装〜
プロジェクト概要
事前プログラミングされたルールではなく、強化学習(Q学習)により自律的に障害物回避行動を学習するロボットをArduino Pro Miniで実装しました。試行錯誤を通じて「右回避」が最適解であることを自ら発見します。
ハードウェア構成
| 部品 | 数量 | 備考 |
|---|---|---|
| Arduino Pro Mini 328 5V/16MHz | 1 | 小型・低消費電力 |
| HC-SR04 超音波距離センサー | 1 | Trig/Echoピンで距離測定 |
| L298N デュアルHブリッジドライバー | 1 | 2つのDCモーター駆動 |
| 60RPM DCギアードモーター | 2 | 左右駆動輪 |
| 車輪 | 4 | モーター軸取付 |
| 7.2V リチウムイオン電池 | 1 | モーター駆動用 |
| 9V電池+クリップ | 1 | Arduino電源用 |
| PCBWayカスタム基板 | 1 | 半田付け用ユニバーサル基板も可 |
| タクトスイッチ(電源SW) | 1 |
主要コンポーネントの動作原理
L298Nによる2モーター駆動
Hブリッジ構成で各モーターの正転・逆転・停止を制御。入力ピン(IN1〜IN4)の組み合わせで前進・後退・左旋回・右旋回・停止を実現。
HC-SR04距離測定
Trigピンに10µs以上のHIGHパルスを送信→超音波バースト発射→EchoピンがHIGHの間隔をpulseIn()で測定→距離 = (時間/2) / 29.1 [cm]。15cm未満を障害物ありと判定。
強化学習(Q学習)の設計
状態・行動・報酬の定義
| 要素 | 定義 | 値 |
|---|---|---|
| 状態 | 障害物の有無+内部カウンタ | 10状態(STATE 0〜9) |
| 行動 | モーター制御パターン | 0:前進, 1:後退, 2:停止, 3:左旋回 |
| 次状態 | 行動実行後の状態 | STATE+1(循環) |
| 報酬 | 行動の善し悪し | 前進:-10, 後退:-2, 停止:-1, 左旋回:+10 |
| Qテーブル | 状態×行動の価値 | float Q[10][4] 初期値0.0 |
設計意図:障害物あり状態で「左旋回」のみ高報酬(+10)、他は負報酬。学習が進むとQ値が左旋回で最大になり、最適行動として選択されるようになります。
ハイパーパラメータ
- 学習率 α = 0.1(新情報の重み)
- 割引率 γ = 0.5(将来報酬の重み)
- 探索率 ε = 0.90 → 0.98倍ずつ減衰、0.5未満で0.9にリセット(継続的探索)
ε-greedy方策
float prob = random(0,100)/100.0;
if (prob <= EPSILON) { // 探索:ランダム行動
action = random(0,4);
} else { // 活用:Q値最大の行動
action = argmax(Q[state]);
}
Q値更新式(ベルマン方程式・TD学習)
Q_old = Q[state][action];
Q_max = max(Q[next_state]);
Q_new = (1-ALPHA)*Q_old + ALPHA*(reward + GAMMA*Q_max);
Q[state][action] = Q_new;
学習・評価・テストの3フェーズ
Phase 1: 学習(100エピソード)
- 前進継続
- 障害物検知(<15cm)で次状態へ遷移
- ε-greedyで行動選択
- 選択行動を実行(各行動ごとに固定時間駆動)
- 報酬取得→Q値更新→状態遷移
- ε減衰、エピソード終了待機
Phase 2: 評価
学習済みQテーブルの全状態・全行動値をシリアル出力し、左旋回(action=3)のQ値が他より高くなっているか確認。
Phase 3: テスト(無限ループ)
探索なし(argmaxのみ)で実運用。障害物検知→最適行動実行→前進再開を繰り返す。
完全スケッチ
/* Q学習障害物回避ロボット - メインスケッチ */
#include <SoftwareSerial.h>
// ピン定義
const int TRIG_PIN = 7, ECHO_PIN = 8;
const int M1 = 13, M2 = 12, M3 = 11, M4 = 10;
// Q学習パラメータ
float ALPHA = 0.1, GAMMA = 0.5, EPSILON = 0.90;
const int STATES = 10, ACTIONS_NUM = 4;
float Q[10][4] = {{0}}; // 全要素0.0で初期化
int REWARDS[10][4] = {
{-10,-2,-1,10}, {-10,-2,-1,10}, {-10,-2,-1,10}, {-10,-2,-1,10},
{-10,-2,-1,10}, {-10,-2,-1,10}, {-10,-2,-1,10}, {-10,-2,-1,10},
{-10,-2,-1,10}, {-10,-2,-1,10}
};
int state = 0, action = 0, next_state = 0;
bool action_taken = false;
int episodes = 100;
void setup() {
Serial.begin(9600);
pinMode(TRIG_PIN, OUTPUT); pinMode(ECHO_PIN, INPUT);
pinMode(M1, OUTPUT); pinMode(M2, OUTPUT);
pinMode(M3, OUTPUT); pinMode(M4, OUTPUT);
randomSeed(analogRead(A0));
delay(5000); // 起動猶予
}
void loop() {
// === 学習フェーズ ===
for (int ep = 0; ep < episodes; ep++) {
action_taken = false;
while (true) {
forward();
if (obstacleDetected()) {
next_state = (state + 1) % STATES;
break;
}
}
// ε-greedy行動選択
float prob = random(0,100)/100.0;
if (prob <= EPSILON) action = random(0,4);
else action = argmax(Q, state);
// 行動実行と報酬取得
int reward = executeAction(action);
action_taken = true;
delay(500);
// Q値更新
if (action_taken) {
updateQ(state, next_state, action, reward);
state = next_state;
EPSILON *= 0.98;
if (EPSILON < 0.5) EPSILON = 0.9;
delay(7000); // エピソード間インターバル
}
}
// === 評価フェーズ ===
for (int s = 0; s < STATES; s++) {
for (int a = 0; a < ACTIONS_NUM; a++) {
Serial.print("Q["); Serial.print(s); Serial.print("][");
Serial.print(a); Serial.print("] = "); Serial.println(Q[s][a]);
delay(2000);
}
}
// === テストフェーズ(本番運用) ===
while (true) {
forward();
if (obstacleDetected()) {
state = random(0,10); // 実環境では状態推定ロジック必要
action = argmax(Q, state);
executeAction(action); // 学習済み最適行動で回避
}
}
}
// --- モーター制御関数群 ---
void forward() { digitalWrite(M1,LOW); digitalWrite(M2,HIGH); digitalWrite(M3,LOW); digitalWrite(M4,HIGH); }
void backward() { digitalWrite(M1,HIGH); digitalWrite(M2,LOW); digitalWrite(M3,HIGH); digitalWrite(M4,LOW); }
void left() { digitalWrite(M1,HIGH); digitalWrite(M2,LOW); digitalWrite(M3,LOW); digitalWrite(M4,HIGH); }
void right() { digitalWrite(M1,LOW); digitalWrite(M2,HIGH); digitalWrite(M3,HIGH); digitalWrite(M4,LOW); }
void stop() { digitalWrite(M1,LOW); digitalWrite(M2,LOW); digitalWrite(M3,LOW); digitalWrite(M4,LOW); }
bool obstacleDetected() {
digitalWrite(TRIG_PIN, HIGH);
delayMicroseconds(10);
digitalWrite(TRIG_PIN, LOW);
long dur = pulseIn(ECHO_PIN, HIGH);
float dist = (dur/2.0)/29.1;
return (dist < 15);
}
int executeAction(int act) {
int reward = REWARDS[state][act];
switch(act) {
case 0: forward(); delay(1500); stop(); break;
case 1: backward(); delay(2500); stop(); break;
case 2: stop(); break;
case 3: left(); delay(2000); stop(); break;
}
return reward;
}
int argmax(float qtable[][4], int s) {
float maxv = qtable[s][0]; int maxi = 0;
for (int i = 1; i < 4; i++) if (qtable[s][i] > maxv) { maxv = qtable[s][i]; maxi = i; }
return maxi;
}
void updateQ(int s, int ns, int a, int r) {
float qold = Q[s][a];
float qmax = Q[ns][0];
for (int i = 1; i < 4; i++) if (Q[ns][i] > qmax) qmax = Q[ns][i];
float qnew = (1-ALPHA)*qold + ALPHA*(r + GAMMA*qmax);
Q[s][a] = qnew;
Serial.print("Q updated: "); Serial.println(qnew);
}
実装上の注意点
- 状態表現の簡略化:実用化には距離の連続値を離散化する等、より豊かな状態表現が必要
- 報酬設計が鍵:目的行動に高報酬、危険行動に大幅負報酬を設定
- 探索率のリセット:終端状態がないタスクではεを完全にゼロにせず、定期的に探索を再開させる
- メモリ制約:Pro Mini(SRAM 2KB)ではQテーブル10×4=40浮動小数点数で約160バイト、余裕あり
- リアルタイム性:学習フェーズは時間がかかるため、実機では予めシミュレーションで学習済みQテーブルを書き込む運用も検討
発展の方向性
- 複数超音波センサーで前方・左右の距離を取得し、状態をベクトル化
- Deep Q-Network(DQN)への拡張(ESP32等より高性能MCU必須)
- カメラ+画像認識(TensorFlow Lite for Microcontrollers)との融合
- マルチエージェント強化学習による協調回避
製造プロセス
- Raspberry Pi で実現する画像認識ベースの障害物回避モバイルロボット
- ボッシュ、ハノーバーメッセでAI駆動製造ソリューションを発表
- AI駆動ロボティクス:最先端インテリジェントマシン
- AIが量子優位性を予測:ニューラルネットワークでシステム挙動を予見
- AIは単なるアプリではない—ビジネス成功のための強力な方法論
- Arrow Electronics、Intel/Microsoft/NvidiaとAI Experience Tourを開催
- 小売におけるAI:一過性の流行ではなく実証済みのメリット
- KubernetesがAIスタートアップに拡張可能なコンテナ化ソリューションを提供
- AIがIoTランドスケープをどう変革するか
- 人工知能は産業制御システムのサイバーセキュリティにはまだ未成熟
- AIを活用したアマゾン熱帯雨林の森林減少監視